You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

419 lines
7.6 KiB

8 years ago
  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, 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 parserType = parser.EVENT; // default
  119. if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
  120. var packet = { type: parserType, data: args };
  121. packet.options = {};
  122. packet.options.compress = !this.flags || false !== this.flags.compress;
  123. // event ack callback
  124. if ('function' === typeof args[args.length - 1]) {
  125. debug('emitting packet with ack id %d', this.ids);
  126. this.acks[this.ids] = args.pop();
  127. packet.id = this.ids++;
  128. }
  129. if (this.connected) {
  130. this.packet(packet);
  131. } else {
  132. this.sendBuffer.push(packet);
  133. }
  134. delete this.flags;
  135. return this;
  136. };
  137. /**
  138. * Sends a packet.
  139. *
  140. * @param {Object} packet
  141. * @api private
  142. */
  143. Socket.prototype.packet = function (packet) {
  144. packet.nsp = this.nsp;
  145. this.io.packet(packet);
  146. };
  147. /**
  148. * Called upon engine `open`.
  149. *
  150. * @api private
  151. */
  152. Socket.prototype.onopen = function () {
  153. debug('transport is open - connecting');
  154. // write connect packet if necessary
  155. if ('/' !== this.nsp) {
  156. if (this.query) {
  157. this.packet({type: parser.CONNECT, query: this.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. var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
  242. self.packet({
  243. type: type,
  244. id: id,
  245. data: args
  246. });
  247. };
  248. };
  249. /**
  250. * Called upon a server acknowlegement.
  251. *
  252. * @param {Object} packet
  253. * @api private
  254. */
  255. Socket.prototype.onack = function (packet) {
  256. var ack = this.acks[packet.id];
  257. if ('function' === typeof ack) {
  258. debug('calling ack %s with %j', packet.id, packet.data);
  259. ack.apply(this, packet.data);
  260. delete this.acks[packet.id];
  261. } else {
  262. debug('bad ack %s', packet.id);
  263. }
  264. };
  265. /**
  266. * Called upon server connect.
  267. *
  268. * @api private
  269. */
  270. Socket.prototype.onconnect = function () {
  271. this.connected = true;
  272. this.disconnected = false;
  273. this.emit('connect');
  274. this.emitBuffered();
  275. };
  276. /**
  277. * Emit buffered events (received and emitted).
  278. *
  279. * @api private
  280. */
  281. Socket.prototype.emitBuffered = function () {
  282. var i;
  283. for (i = 0; i < this.receiveBuffer.length; i++) {
  284. emit.apply(this, this.receiveBuffer[i]);
  285. }
  286. this.receiveBuffer = [];
  287. for (i = 0; i < this.sendBuffer.length; i++) {
  288. this.packet(this.sendBuffer[i]);
  289. }
  290. this.sendBuffer = [];
  291. };
  292. /**
  293. * Called upon server disconnect.
  294. *
  295. * @api private
  296. */
  297. Socket.prototype.ondisconnect = function () {
  298. debug('server disconnect (%s)', this.nsp);
  299. this.destroy();
  300. this.onclose('io server disconnect');
  301. };
  302. /**
  303. * Called upon forced client/server side disconnections,
  304. * this method ensures the manager stops tracking us and
  305. * that reconnections don't get triggered for this.
  306. *
  307. * @api private.
  308. */
  309. Socket.prototype.destroy = function () {
  310. if (this.subs) {
  311. // clean subscriptions to avoid reconnections
  312. for (var i = 0; i < this.subs.length; i++) {
  313. this.subs[i].destroy();
  314. }
  315. this.subs = null;
  316. }
  317. this.io.destroy(this);
  318. };
  319. /**
  320. * Disconnects the socket manually.
  321. *
  322. * @return {Socket} self
  323. * @api public
  324. */
  325. Socket.prototype.close =
  326. Socket.prototype.disconnect = function () {
  327. if (this.connected) {
  328. debug('performing disconnect (%s)', this.nsp);
  329. this.packet({ type: parser.DISCONNECT });
  330. }
  331. // remove socket from pool
  332. this.destroy();
  333. if (this.connected) {
  334. // fire events
  335. this.onclose('io client disconnect');
  336. }
  337. return this;
  338. };
  339. /**
  340. * Sets the compress flag.
  341. *
  342. * @param {Boolean} if `true`, compresses the sending data
  343. * @return {Socket} self
  344. * @api public
  345. */
  346. Socket.prototype.compress = function (compress) {
  347. this.flags = this.flags || {};
  348. this.flags.compress = compress;
  349. return this;
  350. };