!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.io=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && !this.encoding) { var pack = this.packetBuffer.shift(); this.packet(pack); } }; /** * Clean up transport subscriptions and packet buffer. * * @api private */ Manager.prototype.cleanup = function(){ var sub; while (sub = this.subs.shift()) sub.destroy(); this.packetBuffer = []; this.encoding = false; this.decoder.destroy(); }; /** * Close the current socket. * * @api private */ Manager.prototype.close = Manager.prototype.disconnect = function(){ this.skipReconnect = true; this.engine.close(); }; /** * Called upon engine close. * * @api private */ Manager.prototype.onclose = function(reason){ debug('close'); this.cleanup(); 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) return this; var self = this; this.attempts++; if (this.attempts > this._reconnectionAttempts) { debug('reconnect failed'); this.emitAll('reconnect_failed'); this.reconnecting = false; } else { var delay = this.attempts * this.reconnectionDelay(); delay = Math.min(delay, this.reconnectionDelayMax()); debug('will wait %dms before reconnect attempt', delay); this.reconnecting = true; var timer = setTimeout(function(){ debug('attempting reconnect'); self.emitAll('reconnect_attempt', self.attempts); self.emitAll('reconnecting', self.attempts); 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.attempts; this.attempts = 0; this.reconnecting = false; this.emitAll('reconnect', attempt); }; },{"./on":4,"./socket":5,"./url":6,"component-bind":7,"component-emitter":8,"debug":9,"engine.io-client":10,"object-component":37,"socket.io-parser":40}],4:[function(_dereq_,module,exports){ /** * Module exports. */ module.exports = on; /** * Helper for subscriptions. * * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter` * @param {String} event name * @param {Function} callback * @api public */ function on(obj, ev, fn) { obj.on(ev, fn); return { destroy: function(){ obj.removeListener(ev, fn); } }; } },{}],5:[function(_dereq_,module,exports){ /** * Module dependencies. */ var parser = _dereq_('socket.io-parser'); var Emitter = _dereq_('component-emitter'); var toArray = _dereq_('to-array'); var on = _dereq_('./on'); var bind = _dereq_('component-bind'); var debug = _dereq_('debug')('socket.io-client:socket'); var hasBin = _dereq_('has-binary'); var indexOf = _dereq_('indexof'); /** * Module exports. */ module.exports = exports = Socket; /** * Internal events (blacklisted). * These events can't be emitted by the user. * * @api private */ var events = { connect: 1, connect_error: 1, connect_timeout: 1, disconnect: 1, error: 1, reconnect: 1, reconnect_attempt: 1, reconnect_failed: 1, reconnect_error: 1, reconnecting: 1 }; /** * Shortcut to `Emitter#emit`. */ var emit = Emitter.prototype.emit; /** * `Socket` constructor. * * @api public */ function Socket(io, nsp){ this.io = io; this.nsp = nsp; this.json = this; // compat this.ids = 0; this.acks = {}; if (this.io.autoConnect) this.open(); this.receiveBuffer = []; this.sendBuffer = []; this.connected = false; this.disconnected = true; this.subEvents(); } /** * Mix in `Emitter`. */ Emitter(Socket.prototype); /** * Subscribe to open, close and packet events * * @api private */ Socket.prototype.subEvents = function() { var io = this.io; this.subs = [ on(io, 'open', bind(this, 'onopen')), on(io, 'packet', bind(this, 'onpacket')), on(io, 'close', bind(this, 'onclose')) ]; }; /** * Called upon engine `open`. * * @api private */ Socket.prototype.open = Socket.prototype.connect = function(){ if (this.connected) return this; this.io.open(); // ensure open if ('open' == this.io.readyState) this.onopen(); return this; }; /** * Sends a `message` event. * * @return {Socket} self * @api public */ Socket.prototype.send = function(){ var args = toArray(arguments); args.unshift('message'); this.emit.apply(this, args); return this; }; /** * Override `emit`. * If the event is in `events`, it's emitted normally. * * @param {String} event name * @return {Socket} self * @api public */ Socket.prototype.emit = function(ev){ if (events.hasOwnProperty(ev)) { emit.apply(this, arguments); return this; } var args = toArray(arguments); var parserType = parser.EVENT; // default if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary var packet = { type: parserType, data: args }; // event ack callback if ('function' == typeof args[args.length - 1]) { debug('emitting packet with ack id %d', this.ids); this.acks[this.ids] = args.pop(); packet.id = this.ids++; } if (this.connected) { this.packet(packet); } else { this.sendBuffer.push(packet); } return this; }; /** * Sends a packet. * * @param {Object} packet * @api private */ Socket.prototype.packet = function(packet){ packet.nsp = this.nsp; this.io.packet(packet); }; /** * "Opens" the socket. * * @api private */ Socket.prototype.onopen = function(){ debug('transport is open - connecting'); // write connect packet if necessary if ('/' != this.nsp) { this.packet({ type: parser.CONNECT }); } }; /** * Called upon engine `close`. * * @param {String} reason * @api private */ Socket.prototype.onclose = function(reason){ debug('close (%s)', reason); this.connected = false; this.disconnected = true; this.emit('disconnect', reason); }; /** * Called with socket packet. * * @param {Object} packet * @api private */ Socket.prototype.onpacket = function(packet){ if (packet.nsp != this.nsp) return; switch (packet.type) { case parser.CONNECT: this.onconnect(); break; case parser.EVENT: this.onevent(packet); break; case parser.BINARY_EVENT: this.onevent(packet); break; case parser.ACK: this.onack(packet); break; case parser.BINARY_ACK: this.onack(packet); break; case parser.DISCONNECT: this.ondisconnect(); break; case parser.ERROR: this.emit('error', packet.data); break; } }; /** * Called upon a server event. * * @param {Object} packet * @api private */ Socket.prototype.onevent = function(packet){ var args = packet.data || []; debug('emitting event %j', args); if (null != packet.id) { debug('attaching ack callback to event'); args.push(this.ack(packet.id)); } if (this.connected) { emit.apply(this, args); } else { this.receiveBuffer.push(args); } }; /** * Produces an ack callback to emit with an event. * * @api private */ Socket.prototype.ack = function(id){ var self = this; var sent = false; return function(){ // prevent double callbacks if (sent) return; sent = true; var args = toArray(arguments); debug('sending ack %j', args); var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK; self.packet({ type: type, id: id, data: args }); }; }; /** * Called upon a server acknowlegement. * * @param {Object} packet * @api private */ Socket.prototype.onack = function(packet){ debug('calling ack %s with %j', packet.id, packet.data); var fn = this.acks[packet.id]; fn.apply(this, packet.data); delete this.acks[packet.id]; }; /** * Called upon server connect. * * @api private */ Socket.prototype.onconnect = function(){ this.connected = true; this.disconnected = false; this.emit('connect'); this.emitBuffered(); }; /** * Emit buffered events (received and emitted). * * @api private */ Socket.prototype.emitBuffered = function(){ var i; for (i = 0; i < this.receiveBuffer.length; i++) { emit.apply(this, this.receiveBuffer[i]); } this.receiveBuffer = []; for (i = 0; i < this.sendBuffer.length; i++) { this.packet(this.sendBuffer[i]); } this.sendBuffer = []; }; /** * Called upon server disconnect. * * @api private */ Socket.prototype.ondisconnect = function(){ debug('server disconnect (%s)', this.nsp); this.destroy(); this.onclose('io server disconnect'); }; /** * Called upon forced client/server side disconnections, * this method ensures the manager stops tracking us and * that reconnections don't get triggered for this. * * @api private. */ Socket.prototype.destroy = function(){ // clean subscriptions to avoid reconnections for (var i = 0; i < this.subs.length; i++) { this.subs[i].destroy(); } this.io.destroy(this); }; /** * Disconnects the socket manually. * * @return {Socket} self * @api public */ Socket.prototype.close = Socket.prototype.disconnect = function(){ if (!this.connected) return this; debug('performing disconnect (%s)', this.nsp); this.packet({ type: parser.DISCONNECT }); // remove socket from pool this.destroy(); // fire events this.onclose('io client disconnect'); return this; }; },{"./on":4,"component-bind":7,"component-emitter":8,"debug":9,"has-binary":32,"indexof":36,"socket.io-parser":40,"to-array":44}],6:[function(_dereq_,module,exports){ (function (global){ /** * Module dependencies. */ var parseuri = _dereq_('parseuri'); var debug = _dereq_('debug')('socket.io-client:url'); /** * Module exports. */ module.exports = url; /** * URL parser. * * @param {String} url * @param {Object} An object meant to mimic window.location. * Defaults to window.location. * @api public */ function url(uri, loc){ var obj = uri; // default to window.location var loc = loc || global.location; if (null == uri) uri = loc.protocol + '//' + loc.hostname; // relative path support if ('string' == typeof uri) { if ('/' == uri.charAt(0)) { if ('undefined' != typeof loc) { uri = loc.hostname + uri; } } if (!/^(https?|wss?):\/\//.test(uri)) { debug('protocol-less url %s', uri); if ('undefined' != typeof loc) { uri = loc.protocol + '//' + uri; } else { uri = 'https://' + uri; } } // parse debug('parse %s', uri); obj = parseuri(uri); } // make sure we treat `localhost:80` and `localhost` equally if (!obj.port) { if (/^(http|ws)$/.test(obj.protocol)) { obj.port = '80'; } else if (/^(http|ws)s$/.test(obj.protocol)) { obj.port = '443'; } } obj.path = obj.path || '/'; // define unique id obj.id = obj.protocol + '://' + obj.host + ':' + obj.port; // define href obj.href = obj.protocol + '://' + obj.host + (loc && loc.port == obj.port ? '' : (':' + obj.port)); return obj; } }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"debug":9,"parseuri":38}],7:[function(_dereq_,module,exports){ /** * Slice reference. */ var slice = [].slice; /** * Bind `obj` to `fn`. * * @param {Object} obj * @param {Function|String} fn or string * @return {Function} * @api public */ module.exports = function(obj, fn){ if ('string' == typeof fn) fn = obj[fn]; if ('function' != typeof fn) throw new Error('bind() requires a function'); var args = slice.call(arguments, 2); return function(){ return fn.apply(obj, args.concat(slice.call(arguments))); } }; },{}],8:[function(_dereq_,module,exports){ /** * Expose `Emitter`. */ module.exports = Emitter; /** * Initialize a new `Emitter`. * * @api public */ function Emitter(obj) { if (obj) return mixin(obj); }; /** * Mixin the emitter properties. * * @param {Object} obj * @return {Object} * @api private */ function mixin(obj) { for (var key in Emitter.prototype) { obj[key] = Emitter.prototype[key]; } return obj; } /** * Listen on the given `event` with `fn`. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.on = Emitter.prototype.addEventListener = function(event, fn){ this._callbacks = this._callbacks || {}; (this._callbacks[event] = this._callbacks[event] || []) .push(fn); return this; }; /** * Adds an `event` listener that will be invoked a single * time then automatically removed. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.once = function(event, fn){ var self = this; this._callbacks = this._callbacks || {}; function on() { self.off(event, on); fn.apply(this, arguments); } on.fn = fn; this.on(event, on); return this; }; /** * Remove the given callback for `event` or all * registered callbacks. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.off = Emitter.prototype.removeListener = Emitter.prototype.removeAllListeners = Emitter.prototype.removeEventListener = function(event, fn){ this._callbacks = this._callbacks || {}; // all if (0 == arguments.length) { this._callbacks = {}; return this; } // specific event var callbacks = this._callbacks[event]; if (!callbacks) return this; // remove all handlers if (1 == arguments.length) { delete this._callbacks[event]; return this; } // remove specific handler var cb; for (var i = 0; i < callbacks.length; i++) { cb = callbacks[i]; if (cb === fn || cb.fn === fn) { callbacks.splice(i, 1); break; } } return this; }; /** * Emit `event` with the given args. * * @param {String} event * @param {Mixed} ... * @return {Emitter} */ Emitter.prototype.emit = function(event){ this._callbacks = this._callbacks || {}; var args = [].slice.call(arguments, 1) , callbacks = this._callbacks[event]; if (callbacks) { callbacks = callbacks.slice(0); for (var i = 0, len = callbacks.length; i < len; ++i) { callbacks[i].apply(this, args); } } return this; }; /** * Return array of callbacks for `event`. * * @param {String} event * @return {Array} * @api public */ Emitter.prototype.listeners = function(event){ this._callbacks = this._callbacks || {}; return this._callbacks[event] || []; }; /** * Check if this emitter has `event` handlers. * * @param {String} event * @return {Boolean} * @api public */ Emitter.prototype.hasListeners = function(event){ return !! this.listeners(event).length; }; },{}],9:[function(_dereq_,module,exports){ /** * Expose `debug()` as the module. */ module.exports = debug; /** * Create a debugger with the given `name`. * * @param {String} name * @return {Type} * @api public */ function debug(name) { if (!debug.enabled(name)) return function(){}; return function(fmt){ fmt = coerce(fmt); var curr = new Date; var ms = curr - (debug[name] || curr); debug[name] = curr; fmt = name + ' ' + fmt + ' +' + debug.humanize(ms); // This hackery is required for IE8 // where `console.log` doesn't have 'apply' window.console && console.log && Function.prototype.apply.call(console.log, console, arguments); } } /** * The currently active debug mode names. */ debug.names = []; debug.skips = []; /** * Enables a debug mode by name. This can include modes * separated by a colon and wildcards. * * @param {String} name * @api public */ debug.enable = function(name) { try { localStorage.debug = name; } catch(e){} var split = (name || '').split(/[\s,]+/) , len = split.length; for (var i = 0; i < len; i++) { name = split[i].replace('*', '.*?'); if (name[0] === '-') { debug.skips.push(new RegExp('^' + name.substr(1) + '$')); } else { debug.names.push(new RegExp('^' + name + '$')); } } }; /** * Disable debug output. * * @api public */ debug.disable = function(){ debug.enable(''); }; /** * Humanize the given `ms`. * * @param {Number} m * @return {String} * @api private */ debug.humanize = function(ms) { var sec = 1000 , min = 60 * 1000 , hour = 60 * min; if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; if (ms >= min) return (ms / min).toFixed(1) + 'm'; if (ms >= sec) return (ms / sec | 0) + 's'; return ms + 'ms'; }; /** * Returns true if the given mode name is enabled, false otherwise. * * @param {String} name * @return {Boolean} * @api public */ debug.enabled = function(name) { for (var i = 0, len = debug.skips.length; i < len; i++) { if (debug.skips[i].test(name)) { return false; } } for (var i = 0, len = debug.names.length; i < len; i++) { if (debug.names[i].test(name)) { return true; } } return false; }; /** * Coerce `val`. */ function coerce(val) { if (val instanceof Error) return val.stack || val.message; return val; } // persist try { if (window.localStorage) debug.enable(localStorage.debug); } catch(e){} },{}],10:[function(_dereq_,module,exports){ module.exports = _dereq_('./lib/'); },{"./lib/":11}],11:[function(_dereq_,module,exports){ module.exports = _dereq_('./socket'); /** * Exports parser * * @api public * */ module.exports.parser = _dereq_('engine.io-parser'); },{"./socket":12,"engine.io-parser":21}],12:[function(_dereq_,module,exports){ (function (global){ /** * Module dependencies. */ var transports = _dereq_('./transports'); var Emitter = _dereq_('component-emitter'); var debug = _dereq_('debug')('engine.io-client:socket'); var index = _dereq_('indexof'); var parser = _dereq_('engine.io-parser'); var parseuri = _dereq_('parseuri'); var parsejson = _dereq_('parsejson'); var parseqs = _dereq_('parseqs'); /** * Module exports. */ module.exports = Socket; /** * Noop function. * * @api private */ function noop(){} /** * Socket constructor. * * @param {String|Object} uri or options * @param {Object} options * @api public */ function Socket(uri, opts){ if (!(this instanceof Socket)) return new Socket(uri, opts); opts = opts || {}; if (uri && 'object' == typeof uri) { opts = uri; uri = null; } if (uri) { uri = parseuri(uri); opts.host = uri.host; opts.secure = uri.protocol == 'https' || uri.protocol == 'wss'; opts.port = uri.port; if (uri.query) opts.query = uri.query; } this.secure = null != opts.secure ? opts.secure : (global.location && 'https:' == location.protocol); if (opts.host) { var pieces = opts.host.split(':'); opts.hostname = pieces.shift(); if (pieces.length) opts.port = pieces.pop(); } this.agent = opts.agent || false; this.hostname = opts.hostname || (global.location ? location.hostname : 'localhost'); this.port = opts.port || (global.location && location.port ? location.port : (this.secure ? 443 : 80)); this.query = opts.query || {}; if ('string' == typeof this.query) this.query = parseqs.decode(this.query); this.upgrade = false !== opts.upgrade; this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/'; this.forceJSONP = !!opts.forceJSONP; this.jsonp = false !== opts.jsonp; this.forceBase64 = !!opts.forceBase64; this.enablesXDR = !!opts.enablesXDR; this.timestampParam = opts.timestampParam || 't'; this.timestampRequests = opts.timestampRequests; this.transports = opts.transports || ['polling', 'websocket']; this.readyState = ''; this.writeBuffer = []; this.callbackBuffer = []; this.policyPort = opts.policyPort || 843; this.rememberUpgrade = opts.rememberUpgrade || false; this.open(); this.binaryType = null; this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; } Socket.priorWebsocketSuccess = false; /** * Mix in `Emitter`. */ Emitter(Socket.prototype); /** * Protocol version. * * @api public */ Socket.protocol = parser.protocol; // this is an int /** * Expose deps for legacy compatibility * and standalone browser access. */ Socket.Socket = Socket; Socket.Transport = _dereq_('./transport'); Socket.transports = _dereq_('./transports'); Socket.parser = _dereq_('engine.io-parser'); /** * Creates transport of the given type. * * @param {String} transport name * @return {Transport} * @api private */ Socket.prototype.createTransport = function (name) { debug('creating transport "%s"', name); var query = clone(this.query); // append engine.io protocol identifier query.EIO = parser.protocol; // transport name query.transport = name; // session id if we already have one if (this.id) query.sid = this.id; var transport = new transports[name]({ agent: this.agent, hostname: this.hostname, port: this.port, secure: this.secure, path: this.path, query: query, forceJSONP: this.forceJSONP, jsonp: this.jsonp, forceBase64: this.forceBase64, enablesXDR: this.enablesXDR, timestampRequests: this.timestampRequests, timestampParam: this.timestampParam, policyPort: this.policyPort, socket: this }); return transport; }; function clone (obj) { var o = {}; for (var i in obj) { if (obj.hasOwnProperty(i)) { o[i] = obj[i]; } } return o; } /** * Initializes transport to use and starts probe. * * @api private */ Socket.prototype.open = function () { var transport; if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) { transport = 'websocket'; } else if (0 == this.transports.length) { // Emit error on next tick so it can be listened to var self = this; setTimeout(function() { self.emit('error', 'No transports available'); }, 0); return; } else { transport = this.transports[0]; } this.readyState = 'opening'; // Retry with the next transport if the transport is disabled (jsonp: false) var transport; try { transport = this.createTransport(transport); } catch (e) { this.transports.shift(); this.open(); return; } transport.open(); this.setTransport(transport); }; /** * Sets the current transport. Disables the existing one (if any). * * @api private */ Socket.prototype.setTransport = function(transport){ debug('setting transport %s', transport.name); var self = this; if (this.transport) { debug('clearing existing transport %s', this.transport.name); this.transport.removeAllListeners(); } // set up transport this.transport = transport; // set up transport listeners transport .on('drain', function(){ self.onDrain(); }) .on('packet', function(packet){ self.onPacket(packet); }) .on('error', function(e){ self.onError(e); }) .on('close', function(){ self.onClose('transport close'); }); }; /** * Probes a transport. * * @param {String} transport name * @api private */ Socket.prototype.probe = function (name) { debug('probing transport "%s"', name); var transport = this.createTransport(name, { probe: 1 }) , failed = false , self = this; Socket.priorWebsocketSuccess = false; function onTransportOpen(){ if (self.onlyBinaryUpgrades) { var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; failed = failed || upgradeLosesBinary; } if (failed) return; debug('probe transport "%s" opened', name); transport.send([{ type: 'ping', data: 'probe' }]); transport.once('packet', function (msg) { if (failed) return; if ('pong' == msg.type && 'probe' == msg.data) { debug('probe transport "%s" pong', name); self.upgrading = true; self.emit('upgrading', transport); Socket.priorWebsocketSuccess = 'websocket' == transport.name; debug('pausing current transport "%s"', self.transport.name); self.transport.pause(function () { if (failed) return; if ('closed' == self.readyState || 'closing' == self.readyState) { return; } debug('changing transport and sending upgrade packet'); cleanup(); self.setTransport(transport); transport.send([{ type: 'upgrade' }]); self.emit('upgrade', transport); transport = null; self.upgrading = false; self.flush(); }); } else { debug('probe transport "%s" failed', name); var err = new Error('probe error'); err.transport = transport.name; self.emit('upgradeError', err); } }); } function freezeTransport() { if (failed) return; // Any callback called by transport should be ignored since now failed = true; cleanup(); transport.close(); transport = null; } //Handle any error that happens while probing function onerror(err) { var error = new Error('probe error: ' + err); error.transport = transport.name; freezeTransport(); debug('probe transport "%s" failed because of error: %s', name, err); self.emit('upgradeError', error); } function onTransportClose(){ onerror("transport closed"); } //When the socket is closed while we're probing function onclose(){ onerror("socket closed"); } //When the socket is upgraded while we're probing function onupgrade(to){ if (transport && to.name != transport.name) { debug('"%s" works - aborting "%s"', to.name, transport.name); freezeTransport(); } } //Remove all listeners on the transport and on self function cleanup(){ transport.removeListener('open', onTransportOpen); transport.removeListener('error', onerror); transport.removeListener('close', onTransportClose); self.removeListener('close', onclose); self.removeListener('upgrading', onupgrade); } transport.once('open', onTransportOpen); transport.once('error', onerror); transport.once('close', onTransportClose); this.once('close', onclose); this.once('upgrading', onupgrade); transport.open(); }; /** * Called when connection is deemed open. * * @api public */ Socket.prototype.onOpen = function () { debug('socket open'); this.readyState = 'open'; Socket.priorWebsocketSuccess = 'websocket' == this.transport.name; this.emit('open'); this.flush(); // we check for `readyState` in case an `open` // listener already closed the socket if ('open' == this.readyState && this.upgrade && this.transport.pause) { debug('starting upgrade probes'); for (var i = 0, l = this.upgrades.length; i < l; i++) { this.probe(this.upgrades[i]); } } }; /** * Handles a packet. * * @api private */ Socket.prototype.onPacket = function (packet) { if ('opening' == this.readyState || 'open' == this.readyState) { debug('socket receive: type "%s", data "%s"', packet.type, packet.data); this.emit('packet', packet); // Socket is live - any packet counts this.emit('heartbeat'); switch (packet.type) { case 'open': this.onHandshake(parsejson(packet.data)); break; case 'pong': this.setPing(); break; case 'error': var err = new Error('server error'); err.code = packet.data; this.emit('error', err); break; case 'message': this.emit('data', packet.data); this.emit('message', packet.data); break; } } else { debug('packet received with socket readyState "%s"', this.readyState); } }; /** * Called upon handshake completion. * * @param {Object} handshake obj * @api private */ Socket.prototype.onHandshake = function (data) { this.emit('handshake', data); this.id = data.sid; this.transport.query.sid = data.sid; this.upgrades = this.filterUpgrades(data.upgrades); this.pingInterval = data.pingInterval; this.pingTimeout = data.pingTimeout; this.onOpen(); // In case open handler closes socket if ('closed' == this.readyState) return; this.setPing(); // Prolong liveness of socket on heartbeat this.removeListener('heartbeat', this.onHeartbeat); this.on('heartbeat', this.onHeartbeat); }; /** * Resets ping timeout. * * @api private */ Socket.prototype.onHeartbeat = function (timeout) { clearTimeout(this.pingTimeoutTimer); var self = this; self.pingTimeoutTimer = setTimeout(function () { if ('closed' == self.readyState) return; self.onClose('ping timeout'); }, timeout || (self.pingInterval + self.pingTimeout)); }; /** * Pings server every `this.pingInterval` and expects response * within `this.pingTimeout` or closes connection. * * @api private */ Socket.prototype.setPing = function () { var self = this; clearTimeout(self.pingIntervalTimer); self.pingIntervalTimer = setTimeout(function () { debug('writing ping packet - expecting pong within %sms', self.pingTimeout); self.ping(); self.onHeartbeat(self.pingTimeout); }, self.pingInterval); }; /** * Sends a ping packet. * * @api public */ Socket.prototype.ping = function () { this.sendPacket('ping'); }; /** * Called on `drain` event * * @api private */ Socket.prototype.onDrain = function() { for (var i = 0; i < this.prevBufferLen; i++) { if (this.callbackBuffer[i]) { this.callbackBuffer[i](); } } this.writeBuffer.splice(0, this.prevBufferLen); this.callbackBuffer.splice(0, this.prevBufferLen); // setting prevBufferLen = 0 is very important // for example, when upgrading, upgrade packet is sent over, // and a nonzero prevBufferLen could cause problems on `drain` this.prevBufferLen = 0; if (this.writeBuffer.length == 0) { this.emit('drain'); } else { this.flush(); } }; /** * Flush write buffers. * * @api private */ Socket.prototype.flush = function () { if ('closed' != this.readyState && this.transport.writable && !this.upgrading && this.writeBuffer.length) { debug('flushing %d packets in socket', this.writeBuffer.length); this.transport.send(this.writeBuffer); // keep track of current length of writeBuffer // splice writeBuffer and callbackBuffer on `drain` this.prevBufferLen = this.writeBuffer.length; this.emit('flush'); } }; /** * Sends a message. * * @param {String} message. * @param {Function} callback function. * @return {Socket} for chaining. * @api public */ Socket.prototype.write = Socket.prototype.send = function (msg, fn) { this.sendPacket('message', msg, fn); return this; }; /** * Sends a packet. * * @param {String} packet type. * @param {String} data. * @param {Function} callback function. * @api private */ Socket.prototype.sendPacket = function (type, data, fn) { var packet = { type: type, data: data }; this.emit('packetCreate', packet); this.writeBuffer.push(packet); this.callbackBuffer.push(fn); this.flush(); }; /** * Closes the connection. * * @api private */ Socket.prototype.close = function () { if ('opening' == this.readyState || 'open' == this.readyState) { this.onClose('forced close'); debug('socket closing - telling transport to close'); this.transport.close(); } return this; }; /** * Called upon transport error * * @api private */ Socket.prototype.onError = function (err) { debug('socket error %j', err); Socket.priorWebsocketSuccess = false; this.emit('error', err); this.onClose('transport error', err); }; /** * Called upon transport close. * * @api private */ Socket.prototype.onClose = function (reason, desc) { if ('opening' == this.readyState || 'open' == this.readyState) { debug('socket close with reason: "%s"', reason); var self = this; // clear timers clearTimeout(this.pingIntervalTimer); clearTimeout(this.pingTimeoutTimer); // clean buffers in next tick, so developers can still // grab the buffers on `close` event setTimeout(function() { self.writeBuffer = []; self.callbackBuffer = []; self.prevBufferLen = 0; }, 0); // stop event from firing again for transport this.transport.removeAllListeners('close'); // ensure transport won't stay open this.transport.close(); // ignore further transport communication this.transport.removeAllListeners(); // set ready state this.readyState = 'closed'; // clear session id this.id = null; // emit close event this.emit('close', reason, desc); } }; /** * Filters upgrades, returning only those matching client transports. * * @param {Array} server upgrades * @api private * */ Socket.prototype.filterUpgrades = function (upgrades) { var filteredUpgrades = []; for (var i = 0, j = upgrades.length; i