socket.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. /**
  2. * Module dependencies.
  3. */
  4. var parser = require('socket.io-parser');
  5. var Emitter = require('component-emitter');
  6. var toArray = require('to-array');
  7. var on = require('./on');
  8. var bind = require('component-bind');
  9. var debug = require('debug')('socket.io-client:socket');
  10. var hasBin = require('has-binary');
  11. /**
  12. * Module exports.
  13. */
  14. module.exports = exports = Socket;
  15. /**
  16. * Internal events (blacklisted).
  17. * These events can't be emitted by the user.
  18. *
  19. * @api private
  20. */
  21. var events = {
  22. connect: 1,
  23. connect_error: 1,
  24. connect_timeout: 1,
  25. connecting: 1,
  26. disconnect: 1,
  27. error: 1,
  28. reconnect: 1,
  29. reconnect_attempt: 1,
  30. reconnect_failed: 1,
  31. reconnect_error: 1,
  32. reconnecting: 1,
  33. ping: 1,
  34. pong: 1
  35. };
  36. /**
  37. * Shortcut to `Emitter#emit`.
  38. */
  39. var emit = Emitter.prototype.emit;
  40. /**
  41. * `Socket` constructor.
  42. *
  43. * @api public
  44. */
  45. function Socket(io, nsp){
  46. this.io = io;
  47. this.nsp = nsp;
  48. this.json = this; // compat
  49. this.ids = 0;
  50. this.acks = {};
  51. this.receiveBuffer = [];
  52. this.sendBuffer = [];
  53. this.connected = false;
  54. this.disconnected = true;
  55. if (this.io.autoConnect) this.open();
  56. }
  57. /**
  58. * Mix in `Emitter`.
  59. */
  60. Emitter(Socket.prototype);
  61. /**
  62. * Subscribe to open, close and packet events
  63. *
  64. * @api private
  65. */
  66. Socket.prototype.subEvents = function() {
  67. if (this.subs) return;
  68. var io = this.io;
  69. this.subs = [
  70. on(io, 'open', bind(this, 'onopen')),
  71. on(io, 'packet', bind(this, 'onpacket')),
  72. on(io, 'close', bind(this, 'onclose'))
  73. ];
  74. };
  75. /**
  76. * "Opens" the socket.
  77. *
  78. * @api public
  79. */
  80. Socket.prototype.open =
  81. Socket.prototype.connect = function(){
  82. if (this.connected) return this;
  83. this.subEvents();
  84. this.io.open(); // ensure open
  85. if ('open' == this.io.readyState) this.onopen();
  86. this.emit('connecting');
  87. return this;
  88. };
  89. /**
  90. * Sends a `message` event.
  91. *
  92. * @return {Socket} self
  93. * @api public
  94. */
  95. Socket.prototype.send = function(){
  96. var args = toArray(arguments);
  97. args.unshift('message');
  98. this.emit.apply(this, args);
  99. return this;
  100. };
  101. /**
  102. * Override `emit`.
  103. * If the event is in `events`, it's emitted normally.
  104. *
  105. * @param {String} event name
  106. * @return {Socket} self
  107. * @api public
  108. */
  109. Socket.prototype.emit = function(ev){
  110. if (events.hasOwnProperty(ev)) {
  111. emit.apply(this, arguments);
  112. return this;
  113. }
  114. var args = toArray(arguments);
  115. var parserType = parser.EVENT; // default
  116. if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
  117. var packet = { type: parserType, data: args };
  118. packet.options = {};
  119. packet.options.compress = !this.flags || false !== this.flags.compress;
  120. // event ack callback
  121. if ('function' == typeof args[args.length - 1]) {
  122. debug('emitting packet with ack id %d', this.ids);
  123. this.acks[this.ids] = args.pop();
  124. packet.id = this.ids++;
  125. }
  126. if (this.connected) {
  127. this.packet(packet);
  128. } else {
  129. this.sendBuffer.push(packet);
  130. }
  131. delete this.flags;
  132. return this;
  133. };
  134. /**
  135. * Sends a packet.
  136. *
  137. * @param {Object} packet
  138. * @api private
  139. */
  140. Socket.prototype.packet = function(packet){
  141. packet.nsp = this.nsp;
  142. this.io.packet(packet);
  143. };
  144. /**
  145. * Called upon engine `open`.
  146. *
  147. * @api private
  148. */
  149. Socket.prototype.onopen = function(){
  150. debug('transport is open - connecting');
  151. // write connect packet if necessary
  152. if ('/' != this.nsp) {
  153. this.packet({ type: parser.CONNECT });
  154. }
  155. };
  156. /**
  157. * Called upon engine `close`.
  158. *
  159. * @param {String} reason
  160. * @api private
  161. */
  162. Socket.prototype.onclose = function(reason){
  163. debug('close (%s)', reason);
  164. this.connected = false;
  165. this.disconnected = true;
  166. delete this.id;
  167. this.emit('disconnect', reason);
  168. };
  169. /**
  170. * Called with socket packet.
  171. *
  172. * @param {Object} packet
  173. * @api private
  174. */
  175. Socket.prototype.onpacket = function(packet){
  176. if (packet.nsp != this.nsp) return;
  177. switch (packet.type) {
  178. case parser.CONNECT:
  179. this.onconnect();
  180. break;
  181. case parser.EVENT:
  182. this.onevent(packet);
  183. break;
  184. case parser.BINARY_EVENT:
  185. this.onevent(packet);
  186. break;
  187. case parser.ACK:
  188. this.onack(packet);
  189. break;
  190. case parser.BINARY_ACK:
  191. this.onack(packet);
  192. break;
  193. case parser.DISCONNECT:
  194. this.ondisconnect();
  195. break;
  196. case parser.ERROR:
  197. this.emit('error', packet.data);
  198. break;
  199. }
  200. };
  201. /**
  202. * Called upon a server event.
  203. *
  204. * @param {Object} packet
  205. * @api private
  206. */
  207. Socket.prototype.onevent = function(packet){
  208. var args = packet.data || [];
  209. debug('emitting event %j', args);
  210. if (null != packet.id) {
  211. debug('attaching ack callback to event');
  212. args.push(this.ack(packet.id));
  213. }
  214. if (this.connected) {
  215. emit.apply(this, args);
  216. } else {
  217. this.receiveBuffer.push(args);
  218. }
  219. };
  220. /**
  221. * Produces an ack callback to emit with an event.
  222. *
  223. * @api private
  224. */
  225. Socket.prototype.ack = function(id){
  226. var self = this;
  227. var sent = false;
  228. return function(){
  229. // prevent double callbacks
  230. if (sent) return;
  231. sent = true;
  232. var args = toArray(arguments);
  233. debug('sending ack %j', args);
  234. var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
  235. self.packet({
  236. type: type,
  237. id: id,
  238. data: args
  239. });
  240. };
  241. };
  242. /**
  243. * Called upon a server acknowlegement.
  244. *
  245. * @param {Object} packet
  246. * @api private
  247. */
  248. Socket.prototype.onack = function(packet){
  249. var ack = this.acks[packet.id];
  250. if ('function' == typeof ack) {
  251. debug('calling ack %s with %j', packet.id, packet.data);
  252. ack.apply(this, packet.data);
  253. delete this.acks[packet.id];
  254. } else {
  255. debug('bad ack %s', packet.id);
  256. }
  257. };
  258. /**
  259. * Called upon server connect.
  260. *
  261. * @api private
  262. */
  263. Socket.prototype.onconnect = function(){
  264. this.connected = true;
  265. this.disconnected = false;
  266. this.emit('connect');
  267. this.emitBuffered();
  268. };
  269. /**
  270. * Emit buffered events (received and emitted).
  271. *
  272. * @api private
  273. */
  274. Socket.prototype.emitBuffered = function(){
  275. var i;
  276. for (i = 0; i < this.receiveBuffer.length; i++) {
  277. emit.apply(this, this.receiveBuffer[i]);
  278. }
  279. this.receiveBuffer = [];
  280. for (i = 0; i < this.sendBuffer.length; i++) {
  281. this.packet(this.sendBuffer[i]);
  282. }
  283. this.sendBuffer = [];
  284. };
  285. /**
  286. * Called upon server disconnect.
  287. *
  288. * @api private
  289. */
  290. Socket.prototype.ondisconnect = function(){
  291. debug('server disconnect (%s)', this.nsp);
  292. this.destroy();
  293. this.onclose('io server disconnect');
  294. };
  295. /**
  296. * Called upon forced client/server side disconnections,
  297. * this method ensures the manager stops tracking us and
  298. * that reconnections don't get triggered for this.
  299. *
  300. * @api private.
  301. */
  302. Socket.prototype.destroy = function(){
  303. if (this.subs) {
  304. // clean subscriptions to avoid reconnections
  305. for (var i = 0; i < this.subs.length; i++) {
  306. this.subs[i].destroy();
  307. }
  308. this.subs = null;
  309. }
  310. this.io.destroy(this);
  311. };
  312. /**
  313. * Disconnects the socket manually.
  314. *
  315. * @return {Socket} self
  316. * @api public
  317. */
  318. Socket.prototype.close =
  319. Socket.prototype.disconnect = function(){
  320. if (this.connected) {
  321. debug('performing disconnect (%s)', this.nsp);
  322. this.packet({ type: parser.DISCONNECT });
  323. }
  324. // remove socket from pool
  325. this.destroy();
  326. if (this.connected) {
  327. // fire events
  328. this.onclose('io client disconnect');
  329. }
  330. return this;
  331. };
  332. /**
  333. * Sets the compress flag.
  334. *
  335. * @param {Boolean} if `true`, compresses the sending data
  336. * @return {Socket} self
  337. * @api public
  338. */
  339. Socket.prototype.compress = function(compress){
  340. this.flags = this.flags || {};
  341. this.flags.compress = compress;
  342. return this;
  343. };