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.

384 lines
6.8 KiB

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