arnaucube
arnaucube/socketioMEANseedPublic Self-hosted repository
socketioMEANseed/server/node_modules/engine.io-client/lib/socket.js
server/node_modules/engine.io-client/lib/socket.jsRaw
  1/**
  2 * Module dependencies.
  3 */
  4
  5var transports = require('./transports/index');
  6var Emitter = require('component-emitter');
  7var debug = require('debug')('engine.io-client:socket');
  8var index = require('indexof');
  9var parser = require('engine.io-parser');
 10var parseuri = require('parseuri');
 11var parsejson = require('parsejson');
 12var parseqs = require('parseqs');
 13
 14/**
 15 * Module exports.
 16 */
 17
 18module.exports = Socket;
 19
 20/**
 21 * Socket constructor.
 22 *
 23 * @param {String|Object} uri or options
 24 * @param {Object} options
 25 * @api public
 26 */
 27
 28function Socket (uri, opts) {
 29  if (!(this instanceof Socket)) return new Socket(uri, opts);
 30
 31  opts = opts || {};
 32
 33  if (uri && 'object' === typeof uri) {
 34    opts = uri;
 35    uri = null;
 36  }
 37
 38  if (uri) {
 39    uri = parseuri(uri);
 40    opts.hostname = uri.host;
 41    opts.secure = uri.protocol === 'https' || uri.protocol === 'wss';
 42    opts.port = uri.port;
 43    if (uri.query) opts.query = uri.query;
 44  } else if (opts.host) {
 45    opts.hostname = parseuri(opts.host).host;
 46  }
 47
 48  this.secure = null != opts.secure ? opts.secure
 49    : (global.location && 'https:' === location.protocol);
 50
 51  if (opts.hostname && !opts.port) {
 52    // if no port is specified manually, use the protocol default
 53    opts.port = this.secure ? '443' : '80';
 54  }
 55
 56  this.agent = opts.agent || false;
 57  this.hostname = opts.hostname ||
 58    (global.location ? location.hostname : 'localhost');
 59  this.port = opts.port || (global.location && location.port
 60      ? location.port
 61      : (this.secure ? 443 : 80));
 62  this.query = opts.query || {};
 63  if ('string' === typeof this.query) this.query = parseqs.decode(this.query);
 64  this.upgrade = false !== opts.upgrade;
 65  this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/';
 66  this.forceJSONP = !!opts.forceJSONP;
 67  this.jsonp = false !== opts.jsonp;
 68  this.forceBase64 = !!opts.forceBase64;
 69  this.enablesXDR = !!opts.enablesXDR;
 70  this.timestampParam = opts.timestampParam || 't';
 71  this.timestampRequests = opts.timestampRequests;
 72  this.transports = opts.transports || ['polling', 'websocket'];
 73  this.readyState = '';
 74  this.writeBuffer = [];
 75  this.prevBufferLen = 0;
 76  this.policyPort = opts.policyPort || 843;
 77  this.rememberUpgrade = opts.rememberUpgrade || false;
 78  this.binaryType = null;
 79  this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades;
 80  this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false;
 81
 82  if (true === this.perMessageDeflate) this.perMessageDeflate = {};
 83  if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) {
 84    this.perMessageDeflate.threshold = 1024;
 85  }
 86
 87  // SSL options for Node.js client
 88  this.pfx = opts.pfx || null;
 89  this.key = opts.key || null;
 90  this.passphrase = opts.passphrase || null;
 91  this.cert = opts.cert || null;
 92  this.ca = opts.ca || null;
 93  this.ciphers = opts.ciphers || null;
 94  this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? null : opts.rejectUnauthorized;
 95  this.forceNode = !!opts.forceNode;
 96
 97  // other options for Node.js client
 98  var freeGlobal = typeof global === 'object' && global;
 99  if (freeGlobal.global === freeGlobal) {
100    if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) {
101      this.extraHeaders = opts.extraHeaders;
102    }
103
104    if (opts.localAddress) {
105      this.localAddress = opts.localAddress;
106    }
107  }
108
109  // set on handshake
110  this.id = null;
111  this.upgrades = null;
112  this.pingInterval = null;
113  this.pingTimeout = null;
114
115  // set on heartbeat
116  this.pingIntervalTimer = null;
117  this.pingTimeoutTimer = null;
118
119  this.open();
120}
121
122Socket.priorWebsocketSuccess = false;
123
124/**
125 * Mix in `Emitter`.
126 */
127
128Emitter(Socket.prototype);
129
130/**
131 * Protocol version.
132 *
133 * @api public
134 */
135
136Socket.protocol = parser.protocol; // this is an int
137
138/**
139 * Expose deps for legacy compatibility
140 * and standalone browser access.
141 */
142
143Socket.Socket = Socket;
144Socket.Transport = require('./transport');
145Socket.transports = require('./transports/index');
146Socket.parser = require('engine.io-parser');
147
148/**
149 * Creates transport of the given type.
150 *
151 * @param {String} transport name
152 * @return {Transport}
153 * @api private
154 */
155
156Socket.prototype.createTransport = function (name) {
157  debug('creating transport "%s"', name);
158  var query = clone(this.query);
159
160  // append engine.io protocol identifier
161  query.EIO = parser.protocol;
162
163  // transport name
164  query.transport = name;
165
166  // session id if we already have one
167  if (this.id) query.sid = this.id;
168
169  var transport = new transports[name]({
170    agent: this.agent,
171    hostname: this.hostname,
172    port: this.port,
173    secure: this.secure,
174    path: this.path,
175    query: query,
176    forceJSONP: this.forceJSONP,
177    jsonp: this.jsonp,
178    forceBase64: this.forceBase64,
179    enablesXDR: this.enablesXDR,
180    timestampRequests: this.timestampRequests,
181    timestampParam: this.timestampParam,
182    policyPort: this.policyPort,
183    socket: this,
184    pfx: this.pfx,
185    key: this.key,
186    passphrase: this.passphrase,
187    cert: this.cert,
188    ca: this.ca,
189    ciphers: this.ciphers,
190    rejectUnauthorized: this.rejectUnauthorized,
191    perMessageDeflate: this.perMessageDeflate,
192    extraHeaders: this.extraHeaders,
193    forceNode: this.forceNode,
194    localAddress: this.localAddress
195  });
196
197  return transport;
198};
199
200function clone (obj) {
201  var o = {};
202  for (var i in obj) {
203    if (obj.hasOwnProperty(i)) {
204      o[i] = obj[i];
205    }
206  }
207  return o;
208}
209
210/**
211 * Initializes transport to use and starts probe.
212 *
213 * @api private
214 */
215Socket.prototype.open = function () {
216  var transport;
217  if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') !== -1) {
218    transport = 'websocket';
219  } else if (0 === this.transports.length) {
220    // Emit error on next tick so it can be listened to
221    var self = this;
222    setTimeout(function () {
223      self.emit('error', 'No transports available');
224    }, 0);
225    return;
226  } else {
227    transport = this.transports[0];
228  }
229  this.readyState = 'opening';
230
231  // Retry with the next transport if the transport is disabled (jsonp: false)
232  try {
233    transport = this.createTransport(transport);
234  } catch (e) {
235    this.transports.shift();
236    this.open();
237    return;
238  }
239
240  transport.open();
241  this.setTransport(transport);
242};
243
244/**
245 * Sets the current transport. Disables the existing one (if any).
246 *
247 * @api private
248 */
249
250Socket.prototype.setTransport = function (transport) {
251  debug('setting transport %s', transport.name);
252  var self = this;
253
254  if (this.transport) {
255    debug('clearing existing transport %s', this.transport.name);
256    this.transport.removeAllListeners();
257  }
258
259  // set up transport
260  this.transport = transport;
261
262  // set up transport listeners
263  transport
264  .on('drain', function () {
265    self.onDrain();
266  })
267  .on('packet', function (packet) {
268    self.onPacket(packet);
269  })
270  .on('error', function (e) {
271    self.onError(e);
272  })
273  .on('close', function () {
274    self.onClose('transport close');
275  });
276};
277
278/**
279 * Probes a transport.
280 *
281 * @param {String} transport name
282 * @api private
283 */
284
285Socket.prototype.probe = function (name) {
286  debug('probing transport "%s"', name);
287  var transport = this.createTransport(name, { probe: 1 });
288  var failed = false;
289  var self = this;
290
291  Socket.priorWebsocketSuccess = false;
292
293  function onTransportOpen () {
294    if (self.onlyBinaryUpgrades) {
295      var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary;
296      failed = failed || upgradeLosesBinary;
297    }
298    if (failed) return;
299
300    debug('probe transport "%s" opened', name);
301    transport.send([{ type: 'ping', data: 'probe' }]);
302    transport.once('packet', function (msg) {
303      if (failed) return;
304      if ('pong' === msg.type && 'probe' === msg.data) {
305        debug('probe transport "%s" pong', name);
306        self.upgrading = true;
307        self.emit('upgrading', transport);
308        if (!transport) return;
309        Socket.priorWebsocketSuccess = 'websocket' === transport.name;
310
311        debug('pausing current transport "%s"', self.transport.name);
312        self.transport.pause(function () {
313          if (failed) return;
314          if ('closed' === self.readyState) return;
315          debug('changing transport and sending upgrade packet');
316
317          cleanup();
318
319          self.setTransport(transport);
320          transport.send([{ type: 'upgrade' }]);
321          self.emit('upgrade', transport);
322          transport = null;
323          self.upgrading = false;
324          self.flush();
325        });
326      } else {
327        debug('probe transport "%s" failed', name);
328        var err = new Error('probe error');
329        err.transport = transport.name;
330        self.emit('upgradeError', err);
331      }
332    });
333  }
334
335  function freezeTransport () {
336    if (failed) return;
337
338    // Any callback called by transport should be ignored since now
339    failed = true;
340
341    cleanup();
342
343    transport.close();
344    transport = null;
345  }
346
347  // Handle any error that happens while probing
348  function onerror (err) {
349    var error = new Error('probe error: ' + err);
350    error.transport = transport.name;
351
352    freezeTransport();
353
354    debug('probe transport "%s" failed because of error: %s', name, err);
355
356    self.emit('upgradeError', error);
357  }
358
359  function onTransportClose () {
360    onerror('transport closed');
361  }
362
363  // When the socket is closed while we're probing
364  function onclose () {
365    onerror('socket closed');
366  }
367
368  // When the socket is upgraded while we're probing
369  function onupgrade (to) {
370    if (transport && to.name !== transport.name) {
371      debug('"%s" works - aborting "%s"', to.name, transport.name);
372      freezeTransport();
373    }
374  }
375
376  // Remove all listeners on the transport and on self
377  function cleanup () {
378    transport.removeListener('open', onTransportOpen);
379    transport.removeListener('error', onerror);
380    transport.removeListener('close', onTransportClose);
381    self.removeListener('close', onclose);
382    self.removeListener('upgrading', onupgrade);
383  }
384
385  transport.once('open', onTransportOpen);
386  transport.once('error', onerror);
387  transport.once('close', onTransportClose);
388
389  this.once('close', onclose);
390  this.once('upgrading', onupgrade);
391
392  transport.open();
393};
394
395/**
396 * Called when connection is deemed open.
397 *
398 * @api public
399 */
400
401Socket.prototype.onOpen = function () {
402  debug('socket open');
403  this.readyState = 'open';
404  Socket.priorWebsocketSuccess = 'websocket' === this.transport.name;
405  this.emit('open');
406  this.flush();
407
408  // we check for `readyState` in case an `open`
409  // listener already closed the socket
410  if ('open' === this.readyState && this.upgrade && this.transport.pause) {
411    debug('starting upgrade probes');
412    for (var i = 0, l = this.upgrades.length; i < l; i++) {
413      this.probe(this.upgrades[i]);
414    }
415  }
416};
417
418/**
419 * Handles a packet.
420 *
421 * @api private
422 */
423
424Socket.prototype.onPacket = function (packet) {
425  if ('opening' === this.readyState || 'open' === this.readyState ||
426      'closing' === this.readyState) {
427    debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
428
429    this.emit('packet', packet);
430
431    // Socket is live - any packet counts
432    this.emit('heartbeat');
433
434    switch (packet.type) {
435      case 'open':
436        this.onHandshake(parsejson(packet.data));
437        break;
438
439      case 'pong':
440        this.setPing();
441        this.emit('pong');
442        break;
443
444      case 'error':
445        var err = new Error('server error');
446        err.code = packet.data;
447        this.onError(err);
448        break;
449
450      case 'message':
451        this.emit('data', packet.data);
452        this.emit('message', packet.data);
453        break;
454    }
455  } else {
456    debug('packet received with socket readyState "%s"', this.readyState);
457  }
458};
459
460/**
461 * Called upon handshake completion.
462 *
463 * @param {Object} handshake obj
464 * @api private
465 */
466
467Socket.prototype.onHandshake = function (data) {
468  this.emit('handshake', data);
469  this.id = data.sid;
470  this.transport.query.sid = data.sid;
471  this.upgrades = this.filterUpgrades(data.upgrades);
472  this.pingInterval = data.pingInterval;
473  this.pingTimeout = data.pingTimeout;
474  this.onOpen();
475  // In case open handler closes socket
476  if ('closed' === this.readyState) return;
477  this.setPing();
478
479  // Prolong liveness of socket on heartbeat
480  this.removeListener('heartbeat', this.onHeartbeat);
481  this.on('heartbeat', this.onHeartbeat);
482};
483
484/**
485 * Resets ping timeout.
486 *
487 * @api private
488 */
489
490Socket.prototype.onHeartbeat = function (timeout) {
491  clearTimeout(this.pingTimeoutTimer);
492  var self = this;
493  self.pingTimeoutTimer = setTimeout(function () {
494    if ('closed' === self.readyState) return;
495    self.onClose('ping timeout');
496  }, timeout || (self.pingInterval + self.pingTimeout));
497};
498
499/**
500 * Pings server every `this.pingInterval` and expects response
501 * within `this.pingTimeout` or closes connection.
502 *
503 * @api private
504 */
505
506Socket.prototype.setPing = function () {
507  var self = this;
508  clearTimeout(self.pingIntervalTimer);
509  self.pingIntervalTimer = setTimeout(function () {
510    debug('writing ping packet - expecting pong within %sms', self.pingTimeout);
511    self.ping();
512    self.onHeartbeat(self.pingTimeout);
513  }, self.pingInterval);
514};
515
516/**
517* Sends a ping packet.
518*
519* @api private
520*/
521
522Socket.prototype.ping = function () {
523  var self = this;
524  this.sendPacket('ping', function () {
525    self.emit('ping');
526  });
527};
528
529/**
530 * Called on `drain` event
531 *
532 * @api private
533 */
534
535Socket.prototype.onDrain = function () {
536  this.writeBuffer.splice(0, this.prevBufferLen);
537
538  // setting prevBufferLen = 0 is very important
539  // for example, when upgrading, upgrade packet is sent over,
540  // and a nonzero prevBufferLen could cause problems on `drain`
541  this.prevBufferLen = 0;
542
543  if (0 === this.writeBuffer.length) {
544    this.emit('drain');
545  } else {
546    this.flush();
547  }
548};
549
550/**
551 * Flush write buffers.
552 *
553 * @api private
554 */
555
556Socket.prototype.flush = function () {
557  if ('closed' !== this.readyState && this.transport.writable &&
558    !this.upgrading && this.writeBuffer.length) {
559    debug('flushing %d packets in socket', this.writeBuffer.length);
560    this.transport.send(this.writeBuffer);
561    // keep track of current length of writeBuffer
562    // splice writeBuffer and callbackBuffer on `drain`
563    this.prevBufferLen = this.writeBuffer.length;
564    this.emit('flush');
565  }
566};
567
568/**
569 * Sends a message.
570 *
571 * @param {String} message.
572 * @param {Function} callback function.
573 * @param {Object} options.
574 * @return {Socket} for chaining.
575 * @api public
576 */
577
578Socket.prototype.write =
579Socket.prototype.send = function (msg, options, fn) {
580  this.sendPacket('message', msg, options, fn);
581  return this;
582};
583
584/**
585 * Sends a packet.
586 *
587 * @param {String} packet type.
588 * @param {String} data.
589 * @param {Object} options.
590 * @param {Function} callback function.
591 * @api private
592 */
593
594Socket.prototype.sendPacket = function (type, data, options, fn) {
595  if ('function' === typeof data) {
596    fn = data;
597    data = undefined;
598  }
599
600  if ('function' === typeof options) {
601    fn = options;
602    options = null;
603  }
604
605  if ('closing' === this.readyState || 'closed' === this.readyState) {
606    return;
607  }
608
609  options = options || {};
610  options.compress = false !== options.compress;
611
612  var packet = {
613    type: type,
614    data: data,
615    options: options
616  };
617  this.emit('packetCreate', packet);
618  this.writeBuffer.push(packet);
619  if (fn) this.once('flush', fn);
620  this.flush();
621};
622
623/**
624 * Closes the connection.
625 *
626 * @api private
627 */
628
629Socket.prototype.close = function () {
630  if ('opening' === this.readyState || 'open' === this.readyState) {
631    this.readyState = 'closing';
632
633    var self = this;
634
635    if (this.writeBuffer.length) {
636      this.once('drain', function () {
637        if (this.upgrading) {
638          waitForUpgrade();
639        } else {
640          close();
641        }
642      });
643    } else if (this.upgrading) {
644      waitForUpgrade();
645    } else {
646      close();
647    }
648  }
649
650  function close () {
651    self.onClose('forced close');
652    debug('socket closing - telling transport to close');
653    self.transport.close();
654  }
655
656  function cleanupAndClose () {
657    self.removeListener('upgrade', cleanupAndClose);
658    self.removeListener('upgradeError', cleanupAndClose);
659    close();
660  }
661
662  function waitForUpgrade () {
663    // wait for upgrade to finish since we can't send packets while pausing a transport
664    self.once('upgrade', cleanupAndClose);
665    self.once('upgradeError', cleanupAndClose);
666  }
667
668  return this;
669};
670
671/**
672 * Called upon transport error
673 *
674 * @api private
675 */
676
677Socket.prototype.onError = function (err) {
678  debug('socket error %j', err);
679  Socket.priorWebsocketSuccess = false;
680  this.emit('error', err);
681  this.onClose('transport error', err);
682};
683
684/**
685 * Called upon transport close.
686 *
687 * @api private
688 */
689
690Socket.prototype.onClose = function (reason, desc) {
691  if ('opening' === this.readyState || 'open' === this.readyState || 'closing' === this.readyState) {
692    debug('socket close with reason: "%s"', reason);
693    var self = this;
694
695    // clear timers
696    clearTimeout(this.pingIntervalTimer);
697    clearTimeout(this.pingTimeoutTimer);
698
699    // stop event from firing again for transport
700    this.transport.removeAllListeners('close');
701
702    // ensure transport won't stay open
703    this.transport.close();
704
705    // ignore further transport communication
706    this.transport.removeAllListeners();
707
708    // set ready state
709    this.readyState = 'closed';
710
711    // clear session id
712    this.id = null;
713
714    // emit close event
715    this.emit('close', reason, desc);
716
717    // clean buffers after, so users can still
718    // grab the buffers on `close` event
719    self.writeBuffer = [];
720    self.prevBufferLen = 0;
721  }
722};
723
724/**
725 * Filters upgrades, returning only those matching client transports.
726 *
727 * @param {Array} server upgrades
728 * @api private
729 *
730 */
731
732Socket.prototype.filterUpgrades = function (upgrades) {
733  var filteredUpgrades = [];
734  for (var i = 0, j = upgrades.length; i < j; i++) {
735    if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]);
736  }
737  return filteredUpgrades;
738};