From 67fdec20726e48ba3a934cb25bb30d47ec4a4f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20De=20La=20Pe=C3=B1a=20Smirnov?= Date: Wed, 29 Nov 2017 11:44:34 +0300 Subject: Initial commit, version 0.5.3 --- node_modules/socket.io-client/lib/manager.js | 573 +++++++++++++++++++++++++++ 1 file changed, 573 insertions(+) create mode 100644 node_modules/socket.io-client/lib/manager.js (limited to 'node_modules/socket.io-client/lib/manager.js') diff --git a/node_modules/socket.io-client/lib/manager.js b/node_modules/socket.io-client/lib/manager.js new file mode 100644 index 0000000..e44bd6c --- /dev/null +++ b/node_modules/socket.io-client/lib/manager.js @@ -0,0 +1,573 @@ + +/** + * Module dependencies. + */ + +var eio = require('engine.io-client'); +var Socket = require('./socket'); +var Emitter = require('component-emitter'); +var parser = require('socket.io-parser'); +var on = require('./on'); +var bind = require('component-bind'); +var debug = require('debug')('socket.io-client:manager'); +var indexOf = require('indexof'); +var Backoff = require('backo2'); + +/** + * IE6+ hasOwnProperty + */ + +var has = Object.prototype.hasOwnProperty; + +/** + * Module exports + */ + +module.exports = Manager; + +/** + * `Manager` constructor. + * + * @param {String} engine instance or engine uri/opts + * @param {Object} options + * @api public + */ + +function Manager (uri, opts) { + if (!(this instanceof Manager)) return new Manager(uri, opts); + if (uri && ('object' === typeof uri)) { + opts = uri; + uri = undefined; + } + opts = opts || {}; + + opts.path = opts.path || '/socket.io'; + this.nsps = {}; + this.subs = []; + this.opts = opts; + this.reconnection(opts.reconnection !== false); + this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); + this.reconnectionDelay(opts.reconnectionDelay || 1000); + this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); + this.randomizationFactor(opts.randomizationFactor || 0.5); + this.backoff = new Backoff({ + min: this.reconnectionDelay(), + max: this.reconnectionDelayMax(), + jitter: this.randomizationFactor() + }); + this.timeout(null == opts.timeout ? 20000 : opts.timeout); + this.readyState = 'closed'; + this.uri = uri; + this.connecting = []; + this.lastPing = null; + this.encoding = false; + this.packetBuffer = []; + var _parser = opts.parser || parser; + this.encoder = new _parser.Encoder(); + this.decoder = new _parser.Decoder(); + this.autoConnect = opts.autoConnect !== false; + if (this.autoConnect) this.open(); +} + +/** + * Propagate given event to sockets and emit on `this` + * + * @api private + */ + +Manager.prototype.emitAll = function () { + this.emit.apply(this, arguments); + for (var nsp in this.nsps) { + if (has.call(this.nsps, nsp)) { + this.nsps[nsp].emit.apply(this.nsps[nsp], arguments); + } + } +}; + +/** + * Update `socket.id` of all sockets + * + * @api private + */ + +Manager.prototype.updateSocketIds = function () { + for (var nsp in this.nsps) { + if (has.call(this.nsps, nsp)) { + this.nsps[nsp].id = this.generateId(nsp); + } + } +}; + +/** + * generate `socket.id` for the given `nsp` + * + * @param {String} nsp + * @return {String} + * @api private + */ + +Manager.prototype.generateId = function (nsp) { + return (nsp === '/' ? '' : (nsp + '#')) + this.engine.id; +}; + +/** + * Mix in `Emitter`. + */ + +Emitter(Manager.prototype); + +/** + * Sets the `reconnection` config. + * + * @param {Boolean} true/false if it should automatically reconnect + * @return {Manager} self or value + * @api public + */ + +Manager.prototype.reconnection = function (v) { + if (!arguments.length) return this._reconnection; + this._reconnection = !!v; + return this; +}; + +/** + * Sets the reconnection attempts config. + * + * @param {Number} max reconnection attempts before giving up + * @return {Manager} self or value + * @api public + */ + +Manager.prototype.reconnectionAttempts = function (v) { + if (!arguments.length) return this._reconnectionAttempts; + this._reconnectionAttempts = v; + return this; +}; + +/** + * Sets the delay between reconnections. + * + * @param {Number} delay + * @return {Manager} self or value + * @api public + */ + +Manager.prototype.reconnectionDelay = function (v) { + if (!arguments.length) return this._reconnectionDelay; + this._reconnectionDelay = v; + this.backoff && this.backoff.setMin(v); + return this; +}; + +Manager.prototype.randomizationFactor = function (v) { + if (!arguments.length) return this._randomizationFactor; + this._randomizationFactor = v; + this.backoff && this.backoff.setJitter(v); + return this; +}; + +/** + * Sets the maximum delay between reconnections. + * + * @param {Number} delay + * @return {Manager} self or value + * @api public + */ + +Manager.prototype.reconnectionDelayMax = function (v) { + if (!arguments.length) return this._reconnectionDelayMax; + this._reconnectionDelayMax = v; + this.backoff && this.backoff.setMax(v); + return this; +}; + +/** + * Sets the connection timeout. `false` to disable + * + * @return {Manager} self or value + * @api public + */ + +Manager.prototype.timeout = function (v) { + if (!arguments.length) return this._timeout; + this._timeout = v; + return this; +}; + +/** + * Starts trying to reconnect if reconnection is enabled and we have not + * started reconnecting yet + * + * @api private + */ + +Manager.prototype.maybeReconnectOnOpen = function () { + // Only try to reconnect if it's the first time we're connecting + if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) { + // keeps reconnection from firing twice for the same reconnection loop + this.reconnect(); + } +}; + +/** + * Sets the current transport `socket`. + * + * @param {Function} optional, callback + * @return {Manager} self + * @api public + */ + +Manager.prototype.open = +Manager.prototype.connect = function (fn, opts) { + debug('readyState %s', this.readyState); + if (~this.readyState.indexOf('open')) return this; + + debug('opening %s', this.uri); + this.engine = eio(this.uri, this.opts); + var socket = this.engine; + var self = this; + this.readyState = 'opening'; + this.skipReconnect = false; + + // emit `open` + var openSub = on(socket, 'open', function () { + self.onopen(); + fn && fn(); + }); + + // emit `connect_error` + var errorSub = on(socket, 'error', function (data) { + debug('connect_error'); + self.cleanup(); + self.readyState = 'closed'; + self.emitAll('connect_error', data); + if (fn) { + var err = new Error('Connection error'); + err.data = data; + fn(err); + } else { + // Only do this if there is no fn to handle the error + self.maybeReconnectOnOpen(); + } + }); + + // emit `connect_timeout` + if (false !== this._timeout) { + var timeout = this._timeout; + debug('connect attempt will timeout after %d', timeout); + + // set timer + var timer = setTimeout(function () { + debug('connect attempt timed out after %d', timeout); + openSub.destroy(); + socket.close(); + socket.emit('error', 'timeout'); + self.emitAll('connect_timeout', timeout); + }, timeout); + + this.subs.push({ + destroy: function () { + clearTimeout(timer); + } + }); + } + + this.subs.push(openSub); + this.subs.push(errorSub); + + return this; +}; + +/** + * Called upon transport open. + * + * @api private + */ + +Manager.prototype.onopen = function () { + debug('open'); + + // clear old subs + this.cleanup(); + + // mark as open + this.readyState = 'open'; + this.emit('open'); + + // add new subs + var socket = this.engine; + this.subs.push(on(socket, 'data', bind(this, 'ondata'))); + this.subs.push(on(socket, 'ping', bind(this, 'onping'))); + this.subs.push(on(socket, 'pong', bind(this, 'onpong'))); + this.subs.push(on(socket, 'error', bind(this, 'onerror'))); + this.subs.push(on(socket, 'close', bind(this, 'onclose'))); + this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded'))); +}; + +/** + * Called upon a ping. + * + * @api private + */ + +Manager.prototype.onping = function () { + this.lastPing = new Date(); + this.emitAll('ping'); +}; + +/** + * Called upon a packet. + * + * @api private + */ + +Manager.prototype.onpong = function () { + this.emitAll('pong', new Date() - this.lastPing); +}; + +/** + * Called with data. + * + * @api private + */ + +Manager.prototype.ondata = function (data) { + this.decoder.add(data); +}; + +/** + * Called when parser fully decodes a packet. + * + * @api private + */ + +Manager.prototype.ondecoded = function (packet) { + this.emit('packet', packet); +}; + +/** + * Called upon socket error. + * + * @api private + */ + +Manager.prototype.onerror = function (err) { + debug('error', err); + this.emitAll('error', err); +}; + +/** + * Creates a new socket for the given `nsp`. + * + * @return {Socket} + * @api public + */ + +Manager.prototype.socket = function (nsp, opts) { + var socket = this.nsps[nsp]; + if (!socket) { + socket = new Socket(this, nsp, opts); + this.nsps[nsp] = socket; + var self = this; + socket.on('connecting', onConnecting); + socket.on('connect', function () { + socket.id = self.generateId(nsp); + }); + + if (this.autoConnect) { + // manually call here since connecting event is fired before listening + onConnecting(); + } + } + + function onConnecting () { + if (!~indexOf(self.connecting, socket)) { + self.connecting.push(socket); + } + } + + return socket; +}; + +/** + * Called upon a socket close. + * + * @param {Socket} socket + */ + +Manager.prototype.destroy = function (socket) { + var index = indexOf(this.connecting, socket); + if (~index) this.connecting.splice(index, 1); + if (this.connecting.length) return; + + this.close(); +}; + +/** + * Writes a packet. + * + * @param {Object} packet + * @api private + */ + +Manager.prototype.packet = function (packet) { + debug('writing packet %j', packet); + var self = this; + if (packet.query && packet.type === 0) packet.nsp += '?' + packet.query; + + if (!self.encoding) { + // encode, then write to engine with result + self.encoding = true; + this.encoder.encode(packet, function (encodedPackets) { + for (var i = 0; i < encodedPackets.length; i++) { + self.engine.write(encodedPackets[i], packet.options); + } + self.encoding = false; + self.processPacketQueue(); + }); + } else { // add packet to the queue + self.packetBuffer.push(packet); + } +}; + +/** + * If packet buffer is non-empty, begins encoding the + * next packet in line. + * + * @api private + */ + +Manager.prototype.processPacketQueue = function () { + if (this.packetBuffer.length > 0 && !this.encoding) { + var pack = this.packetBuffer.shift(); + this.packet(pack); + } +}; + +/** + * Clean up transport subscriptions and packet buffer. + * + * @api private + */ + +Manager.prototype.cleanup = function () { + debug('cleanup'); + + var subsLength = this.subs.length; + for (var i = 0; i < subsLength; i++) { + var sub = this.subs.shift(); + sub.destroy(); + } + + this.packetBuffer = []; + this.encoding = false; + this.lastPing = null; + + this.decoder.destroy(); +}; + +/** + * Close the current socket. + * + * @api private + */ + +Manager.prototype.close = +Manager.prototype.disconnect = function () { + debug('disconnect'); + this.skipReconnect = true; + this.reconnecting = false; + if ('opening' === this.readyState) { + // `onclose` will not fire because + // an open event never happened + this.cleanup(); + } + this.backoff.reset(); + this.readyState = 'closed'; + if (this.engine) this.engine.close(); +}; + +/** + * Called upon engine close. + * + * @api private + */ + +Manager.prototype.onclose = function (reason) { + debug('onclose'); + + this.cleanup(); + this.backoff.reset(); + this.readyState = 'closed'; + this.emit('close', reason); + + if (this._reconnection && !this.skipReconnect) { + this.reconnect(); + } +}; + +/** + * Attempt a reconnection. + * + * @api private + */ + +Manager.prototype.reconnect = function () { + if (this.reconnecting || this.skipReconnect) return this; + + var self = this; + + if (this.backoff.attempts >= this._reconnectionAttempts) { + debug('reconnect failed'); + this.backoff.reset(); + this.emitAll('reconnect_failed'); + this.reconnecting = false; + } else { + var delay = this.backoff.duration(); + debug('will wait %dms before reconnect attempt', delay); + + this.reconnecting = true; + var timer = setTimeout(function () { + if (self.skipReconnect) return; + + debug('attempting reconnect'); + self.emitAll('reconnect_attempt', self.backoff.attempts); + self.emitAll('reconnecting', self.backoff.attempts); + + // check again for the case socket closed in above events + if (self.skipReconnect) return; + + self.open(function (err) { + if (err) { + debug('reconnect attempt error'); + self.reconnecting = false; + self.reconnect(); + self.emitAll('reconnect_error', err.data); + } else { + debug('reconnect success'); + self.onreconnect(); + } + }); + }, delay); + + this.subs.push({ + destroy: function () { + clearTimeout(timer); + } + }); + } +}; + +/** + * Called upon successful reconnect. + * + * @api private + */ + +Manager.prototype.onreconnect = function () { + var attempt = this.backoff.attempts; + this.reconnecting = false; + this.backoff.reset(); + this.updateSocketIds(); + this.emitAll('reconnect', attempt); +}; -- cgit v1.2.3