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