socket.js 7.6 KB

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