Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

manager.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. /**
  2. * Module dependencies.
  3. */
  4. var eio = require('engine.io-client');
  5. var Socket = require('./socket');
  6. var Emitter = require('component-emitter');
  7. var parser = require('socket.io-parser');
  8. var on = require('./on');
  9. var bind = require('component-bind');
  10. var debug = require('debug')('socket.io-client:manager');
  11. var indexOf = require('indexof');
  12. var Backoff = require('backo2');
  13. /**
  14. * IE6+ hasOwnProperty
  15. */
  16. var has = Object.prototype.hasOwnProperty;
  17. /**
  18. * Module exports
  19. */
  20. module.exports = Manager;
  21. /**
  22. * `Manager` constructor.
  23. *
  24. * @param {String} engine instance or engine uri/opts
  25. * @param {Object} options
  26. * @api public
  27. */
  28. function Manager (uri, opts) {
  29. if (!(this instanceof Manager)) return new Manager(uri, opts);
  30. if (uri && ('object' === typeof uri)) {
  31. opts = uri;
  32. uri = undefined;
  33. }
  34. opts = opts || {};
  35. opts.path = opts.path || '/socket.io';
  36. this.nsps = {};
  37. this.subs = [];
  38. this.opts = opts;
  39. this.reconnection(opts.reconnection !== false);
  40. this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);
  41. this.reconnectionDelay(opts.reconnectionDelay || 1000);
  42. this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);
  43. this.randomizationFactor(opts.randomizationFactor || 0.5);
  44. this.backoff = new Backoff({
  45. min: this.reconnectionDelay(),
  46. max: this.reconnectionDelayMax(),
  47. jitter: this.randomizationFactor()
  48. });
  49. this.timeout(null == opts.timeout ? 20000 : opts.timeout);
  50. this.readyState = 'closed';
  51. this.uri = uri;
  52. this.connecting = [];
  53. this.lastPing = null;
  54. this.encoding = false;
  55. this.packetBuffer = [];
  56. var _parser = opts.parser || parser;
  57. this.encoder = new _parser.Encoder();
  58. this.decoder = new _parser.Decoder();
  59. this.autoConnect = opts.autoConnect !== false;
  60. if (this.autoConnect) this.open();
  61. }
  62. /**
  63. * Propagate given event to sockets and emit on `this`
  64. *
  65. * @api private
  66. */
  67. Manager.prototype.emitAll = function () {
  68. this.emit.apply(this, arguments);
  69. for (var nsp in this.nsps) {
  70. if (has.call(this.nsps, nsp)) {
  71. this.nsps[nsp].emit.apply(this.nsps[nsp], arguments);
  72. }
  73. }
  74. };
  75. /**
  76. * Update `socket.id` of all sockets
  77. *
  78. * @api private
  79. */
  80. Manager.prototype.updateSocketIds = function () {
  81. for (var nsp in this.nsps) {
  82. if (has.call(this.nsps, nsp)) {
  83. this.nsps[nsp].id = this.generateId(nsp);
  84. }
  85. }
  86. };
  87. /**
  88. * generate `socket.id` for the given `nsp`
  89. *
  90. * @param {String} nsp
  91. * @return {String}
  92. * @api private
  93. */
  94. Manager.prototype.generateId = function (nsp) {
  95. return (nsp === '/' ? '' : (nsp + '#')) + this.engine.id;
  96. };
  97. /**
  98. * Mix in `Emitter`.
  99. */
  100. Emitter(Manager.prototype);
  101. /**
  102. * Sets the `reconnection` config.
  103. *
  104. * @param {Boolean} true/false if it should automatically reconnect
  105. * @return {Manager} self or value
  106. * @api public
  107. */
  108. Manager.prototype.reconnection = function (v) {
  109. if (!arguments.length) return this._reconnection;
  110. this._reconnection = !!v;
  111. return this;
  112. };
  113. /**
  114. * Sets the reconnection attempts config.
  115. *
  116. * @param {Number} max reconnection attempts before giving up
  117. * @return {Manager} self or value
  118. * @api public
  119. */
  120. Manager.prototype.reconnectionAttempts = function (v) {
  121. if (!arguments.length) return this._reconnectionAttempts;
  122. this._reconnectionAttempts = v;
  123. return this;
  124. };
  125. /**
  126. * Sets the delay between reconnections.
  127. *
  128. * @param {Number} delay
  129. * @return {Manager} self or value
  130. * @api public
  131. */
  132. Manager.prototype.reconnectionDelay = function (v) {
  133. if (!arguments.length) return this._reconnectionDelay;
  134. this._reconnectionDelay = v;
  135. this.backoff && this.backoff.setMin(v);
  136. return this;
  137. };
  138. Manager.prototype.randomizationFactor = function (v) {
  139. if (!arguments.length) return this._randomizationFactor;
  140. this._randomizationFactor = v;
  141. this.backoff && this.backoff.setJitter(v);
  142. return this;
  143. };
  144. /**
  145. * Sets the maximum delay between reconnections.
  146. *
  147. * @param {Number} delay
  148. * @return {Manager} self or value
  149. * @api public
  150. */
  151. Manager.prototype.reconnectionDelayMax = function (v) {
  152. if (!arguments.length) return this._reconnectionDelayMax;
  153. this._reconnectionDelayMax = v;
  154. this.backoff && this.backoff.setMax(v);
  155. return this;
  156. };
  157. /**
  158. * Sets the connection timeout. `false` to disable
  159. *
  160. * @return {Manager} self or value
  161. * @api public
  162. */
  163. Manager.prototype.timeout = function (v) {
  164. if (!arguments.length) return this._timeout;
  165. this._timeout = v;
  166. return this;
  167. };
  168. /**
  169. * Starts trying to reconnect if reconnection is enabled and we have not
  170. * started reconnecting yet
  171. *
  172. * @api private
  173. */
  174. Manager.prototype.maybeReconnectOnOpen = function () {
  175. // Only try to reconnect if it's the first time we're connecting
  176. if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) {
  177. // keeps reconnection from firing twice for the same reconnection loop
  178. this.reconnect();
  179. }
  180. };
  181. /**
  182. * Sets the current transport `socket`.
  183. *
  184. * @param {Function} optional, callback
  185. * @return {Manager} self
  186. * @api public
  187. */
  188. Manager.prototype.open =
  189. Manager.prototype.connect = function (fn, opts) {
  190. debug('readyState %s', this.readyState);
  191. if (~this.readyState.indexOf('open')) return this;
  192. debug('opening %s', this.uri);
  193. this.engine = eio(this.uri, this.opts);
  194. var socket = this.engine;
  195. var self = this;
  196. this.readyState = 'opening';
  197. this.skipReconnect = false;
  198. // emit `open`
  199. var openSub = on(socket, 'open', function () {
  200. self.onopen();
  201. fn && fn();
  202. });
  203. // emit `connect_error`
  204. var errorSub = on(socket, 'error', function (data) {
  205. debug('connect_error');
  206. self.cleanup();
  207. self.readyState = 'closed';
  208. self.emitAll('connect_error', data);
  209. if (fn) {
  210. var err = new Error('Connection error');
  211. err.data = data;
  212. fn(err);
  213. } else {
  214. // Only do this if there is no fn to handle the error
  215. self.maybeReconnectOnOpen();
  216. }
  217. });
  218. // emit `connect_timeout`
  219. if (false !== this._timeout) {
  220. var timeout = this._timeout;
  221. debug('connect attempt will timeout after %d', timeout);
  222. // set timer
  223. var timer = setTimeout(function () {
  224. debug('connect attempt timed out after %d', timeout);
  225. openSub.destroy();
  226. socket.close();
  227. socket.emit('error', 'timeout');
  228. self.emitAll('connect_timeout', timeout);
  229. }, timeout);
  230. this.subs.push({
  231. destroy: function () {
  232. clearTimeout(timer);
  233. }
  234. });
  235. }
  236. this.subs.push(openSub);
  237. this.subs.push(errorSub);
  238. return this;
  239. };
  240. /**
  241. * Called upon transport open.
  242. *
  243. * @api private
  244. */
  245. Manager.prototype.onopen = function () {
  246. debug('open');
  247. // clear old subs
  248. this.cleanup();
  249. // mark as open
  250. this.readyState = 'open';
  251. this.emit('open');
  252. // add new subs
  253. var socket = this.engine;
  254. this.subs.push(on(socket, 'data', bind(this, 'ondata')));
  255. this.subs.push(on(socket, 'ping', bind(this, 'onping')));
  256. this.subs.push(on(socket, 'pong', bind(this, 'onpong')));
  257. this.subs.push(on(socket, 'error', bind(this, 'onerror')));
  258. this.subs.push(on(socket, 'close', bind(this, 'onclose')));
  259. this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded')));
  260. };
  261. /**
  262. * Called upon a ping.
  263. *
  264. * @api private
  265. */
  266. Manager.prototype.onping = function () {
  267. this.lastPing = new Date();
  268. this.emitAll('ping');
  269. };
  270. /**
  271. * Called upon a packet.
  272. *
  273. * @api private
  274. */
  275. Manager.prototype.onpong = function () {
  276. this.emitAll('pong', new Date() - this.lastPing);
  277. };
  278. /**
  279. * Called with data.
  280. *
  281. * @api private
  282. */
  283. Manager.prototype.ondata = function (data) {
  284. this.decoder.add(data);
  285. };
  286. /**
  287. * Called when parser fully decodes a packet.
  288. *
  289. * @api private
  290. */
  291. Manager.prototype.ondecoded = function (packet) {
  292. this.emit('packet', packet);
  293. };
  294. /**
  295. * Called upon socket error.
  296. *
  297. * @api private
  298. */
  299. Manager.prototype.onerror = function (err) {
  300. debug('error', err);
  301. this.emitAll('error', err);
  302. };
  303. /**
  304. * Creates a new socket for the given `nsp`.
  305. *
  306. * @return {Socket}
  307. * @api public
  308. */
  309. Manager.prototype.socket = function (nsp, opts) {
  310. var socket = this.nsps[nsp];
  311. if (!socket) {
  312. socket = new Socket(this, nsp, opts);
  313. this.nsps[nsp] = socket;
  314. var self = this;
  315. socket.on('connecting', onConnecting);
  316. socket.on('connect', function () {
  317. socket.id = self.generateId(nsp);
  318. });
  319. if (this.autoConnect) {
  320. // manually call here since connecting event is fired before listening
  321. onConnecting();
  322. }
  323. }
  324. function onConnecting () {
  325. if (!~indexOf(self.connecting, socket)) {
  326. self.connecting.push(socket);
  327. }
  328. }
  329. return socket;
  330. };
  331. /**
  332. * Called upon a socket close.
  333. *
  334. * @param {Socket} socket
  335. */
  336. Manager.prototype.destroy = function (socket) {
  337. var index = indexOf(this.connecting, socket);
  338. if (~index) this.connecting.splice(index, 1);
  339. if (this.connecting.length) return;
  340. this.close();
  341. };
  342. /**
  343. * Writes a packet.
  344. *
  345. * @param {Object} packet
  346. * @api private
  347. */
  348. Manager.prototype.packet = function (packet) {
  349. debug('writing packet %j', packet);
  350. var self = this;
  351. if (packet.query && packet.type === 0) packet.nsp += '?' + packet.query;
  352. if (!self.encoding) {
  353. // encode, then write to engine with result
  354. self.encoding = true;
  355. this.encoder.encode(packet, function (encodedPackets) {
  356. for (var i = 0; i < encodedPackets.length; i++) {
  357. self.engine.write(encodedPackets[i], packet.options);
  358. }
  359. self.encoding = false;
  360. self.processPacketQueue();
  361. });
  362. } else { // add packet to the queue
  363. self.packetBuffer.push(packet);
  364. }
  365. };
  366. /**
  367. * If packet buffer is non-empty, begins encoding the
  368. * next packet in line.
  369. *
  370. * @api private
  371. */
  372. Manager.prototype.processPacketQueue = function () {
  373. if (this.packetBuffer.length > 0 && !this.encoding) {
  374. var pack = this.packetBuffer.shift();
  375. this.packet(pack);
  376. }
  377. };
  378. /**
  379. * Clean up transport subscriptions and packet buffer.
  380. *
  381. * @api private
  382. */
  383. Manager.prototype.cleanup = function () {
  384. debug('cleanup');
  385. var subsLength = this.subs.length;
  386. for (var i = 0; i < subsLength; i++) {
  387. var sub = this.subs.shift();
  388. sub.destroy();
  389. }
  390. this.packetBuffer = [];
  391. this.encoding = false;
  392. this.lastPing = null;
  393. this.decoder.destroy();
  394. };
  395. /**
  396. * Close the current socket.
  397. *
  398. * @api private
  399. */
  400. Manager.prototype.close =
  401. Manager.prototype.disconnect = function () {
  402. debug('disconnect');
  403. this.skipReconnect = true;
  404. this.reconnecting = false;
  405. if ('opening' === this.readyState) {
  406. // `onclose` will not fire because
  407. // an open event never happened
  408. this.cleanup();
  409. }
  410. this.backoff.reset();
  411. this.readyState = 'closed';
  412. if (this.engine) this.engine.close();
  413. };
  414. /**
  415. * Called upon engine close.
  416. *
  417. * @api private
  418. */
  419. Manager.prototype.onclose = function (reason) {
  420. debug('onclose');
  421. this.cleanup();
  422. this.backoff.reset();
  423. this.readyState = 'closed';
  424. this.emit('close', reason);
  425. if (this._reconnection && !this.skipReconnect) {
  426. this.reconnect();
  427. }
  428. };
  429. /**
  430. * Attempt a reconnection.
  431. *
  432. * @api private
  433. */
  434. Manager.prototype.reconnect = function () {
  435. if (this.reconnecting || this.skipReconnect) return this;
  436. var self = this;
  437. if (this.backoff.attempts >= this._reconnectionAttempts) {
  438. debug('reconnect failed');
  439. this.backoff.reset();
  440. this.emitAll('reconnect_failed');
  441. this.reconnecting = false;
  442. } else {
  443. var delay = this.backoff.duration();
  444. debug('will wait %dms before reconnect attempt', delay);
  445. this.reconnecting = true;
  446. var timer = setTimeout(function () {
  447. if (self.skipReconnect) return;
  448. debug('attempting reconnect');
  449. self.emitAll('reconnect_attempt', self.backoff.attempts);
  450. self.emitAll('reconnecting', self.backoff.attempts);
  451. // check again for the case socket closed in above events
  452. if (self.skipReconnect) return;
  453. self.open(function (err) {
  454. if (err) {
  455. debug('reconnect attempt error');
  456. self.reconnecting = false;
  457. self.reconnect();
  458. self.emitAll('reconnect_error', err.data);
  459. } else {
  460. debug('reconnect success');
  461. self.onreconnect();
  462. }
  463. });
  464. }, delay);
  465. this.subs.push({
  466. destroy: function () {
  467. clearTimeout(timer);
  468. }
  469. });
  470. }
  471. };
  472. /**
  473. * Called upon successful reconnect.
  474. *
  475. * @api private
  476. */
  477. Manager.prototype.onreconnect = function () {
  478. var attempt = this.backoff.attempts;
  479. this.reconnecting = false;
  480. this.backoff.reset();
  481. this.updateSocketIds();
  482. this.emitAll('reconnect', attempt);
  483. };