(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);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o { let exists = document.getElementById(data.key) if (exists) { exists.tag.remove() exists.remove() } let element = document.createElement('div') element.innerHTML = data.html element.id = data.key element.classList.add('root-element') let styleElement = document.createElement('style') styleElement.setAttribute('type', 'text/css') styleElement.innerHTML = data.css element.tag = styleElement document.body.appendChild(element) document.head.appendChild(styleElement) window.setTimeout(() => { element.classList.add('root-element-display') }, 50) }) socket.on('client.hide', (data) => { let exists = document.getElementById(data.key) if (exists) { exists.classList.remove('root-element-display') window.setTimeout(() => { exists.tag.remove() exists.remove() }, 1500) } }) },{"./socket":2}],2:[function(require,module,exports){ const io = require('socket.io-client') const socket = io() module.exports = socket },{"socket.io-client":36}],3:[function(require,module,exports){ module.exports = after function after(count, callback, err_cb) { var bail = false err_cb = err_cb || noop proxy.count = count return (count === 0) ? callback() : proxy function proxy(err, result) { if (proxy.count <= 0) { throw new Error('after called too many times') } --proxy.count // after first error, rest are passed to err_cb if (err) { bail = true callback(err) // future error callbacks will go to error handler callback = err_cb } else if (proxy.count === 0 && !bail) { callback(null, result) } } } function noop() {} },{}],4:[function(require,module,exports){ /** * An abstraction for slicing an arraybuffer even when * ArrayBuffer.prototype.slice is not supported * * @api public */ module.exports = function(arraybuffer, start, end) { var bytes = arraybuffer.byteLength; start = start || 0; end = end || bytes; if (arraybuffer.slice) { return arraybuffer.slice(start, end); } if (start < 0) { start += bytes; } if (end < 0) { end += bytes; } if (end > bytes) { end = bytes; } if (start >= bytes || start >= end || bytes === 0) { return new ArrayBuffer(0); } var abv = new Uint8Array(arraybuffer); var result = new Uint8Array(end - start); for (var i = start, ii = 0; i < end; i++, ii++) { result[ii] = abv[i]; } return result.buffer; }; },{}],5:[function(require,module,exports){ /** * Expose `Backoff`. */ module.exports = Backoff; /** * Initialize backoff timer with `opts`. * * - `min` initial timeout in milliseconds [100] * - `max` max timeout [10000] * - `jitter` [0] * - `factor` [2] * * @param {Object} opts * @api public */ function Backoff(opts) { opts = opts || {}; this.ms = opts.min || 100; this.max = opts.max || 10000; this.factor = opts.factor || 2; this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0; this.attempts = 0; } /** * Return the backoff duration. * * @return {Number} * @api public */ Backoff.prototype.duration = function(){ var ms = this.ms * Math.pow(this.factor, this.attempts++); if (this.jitter) { var rand = Math.random(); var deviation = Math.floor(rand * this.jitter * ms); ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation; } return Math.min(ms, this.max) | 0; }; /** * Reset the number of attempts. * * @api public */ Backoff.prototype.reset = function(){ this.attempts = 0; }; /** * Set the minimum duration * * @api public */ Backoff.prototype.setMin = function(min){ this.ms = min; }; /** * Set the maximum duration * * @api public */ Backoff.prototype.setMax = function(max){ this.max = max; }; /** * Set the jitter * * @api public */ Backoff.prototype.setJitter = function(jitter){ this.jitter = jitter; }; },{}],6:[function(require,module,exports){ /* * base64-arraybuffer * https://github.com/niklasvh/base64-arraybuffer * * Copyright (c) 2012 Niklas von Hertzen * Licensed under the MIT license. */ (function(chars){ "use strict"; exports.encode = function(arraybuffer) { var bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = ""; for (i = 0; i < len; i+=3) { base64 += chars[bytes[i] >> 2]; base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; base64 += chars[bytes[i + 2] & 63]; } if ((len % 3) === 2) { base64 = base64.substring(0, base64.length - 1) + "="; } else if (len % 3 === 1) { base64 = base64.substring(0, base64.length - 2) + "=="; } return base64; }; exports.decode = function(base64) { var bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4; if (base64[base64.length - 1] === "=") { bufferLength--; if (base64[base64.length - 2] === "=") { bufferLength--; } } var arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer); for (i = 0; i < len; i+=4) { encoded1 = chars.indexOf(base64[i]); encoded2 = chars.indexOf(base64[i+1]); encoded3 = chars.indexOf(base64[i+2]); encoded4 = chars.indexOf(base64[i+3]); bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); } return arraybuffer; }; })("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); },{}],7:[function(require,module,exports){ (function (global){ /** * Create a blob builder even when vendor prefixes exist */ var BlobBuilder = global.BlobBuilder || global.WebKitBlobBuilder || global.MSBlobBuilder || global.MozBlobBuilder; /** * Check if Blob constructor is supported */ var blobSupported = (function() { try { var a = new Blob(['hi']); return a.size === 2; } catch(e) { return false; } })(); /** * Check if Blob constructor supports ArrayBufferViews * Fails in Safari 6, so we need to map to ArrayBuffers there. */ var blobSupportsArrayBufferView = blobSupported && (function() { try { var b = new Blob([new Uint8Array([1,2])]); return b.size === 2; } catch(e) { return false; } })(); /** * Check if BlobBuilder is supported */ var blobBuilderSupported = BlobBuilder && BlobBuilder.prototype.append && BlobBuilder.prototype.getBlob; /** * Helper function that maps ArrayBufferViews to ArrayBuffers * Used by BlobBuilder constructor and old browsers that didn't * support it in the Blob constructor. */ function mapArrayBufferViews(ary) { for (var i = 0; i < ary.length; i++) { var chunk = ary[i]; if (chunk.buffer instanceof ArrayBuffer) { var buf = chunk.buffer; // if this is a subarray, make a copy so we only // include the subarray region from the underlying buffer if (chunk.byteLength !== buf.byteLength) { var copy = new Uint8Array(chunk.byteLength); copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength)); buf = copy.buffer; } ary[i] = buf; } } } function BlobBuilderConstructor(ary, options) { options = options || {}; var bb = new BlobBuilder(); mapArrayBufferViews(ary); for (var i = 0; i < ary.length; i++) { bb.append(ary[i]); } return (options.type) ? bb.getBlob(options.type) : bb.getBlob(); }; function BlobConstructor(ary, options) { mapArrayBufferViews(ary); return new Blob(ary, options || {}); }; module.exports = (function() { if (blobSupported) { return blobSupportsArrayBufferView ? global.Blob : BlobConstructor; } else if (blobBuilderSupported) { return BlobBuilderConstructor; } else { return undefined; } })(); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}],8:[function(require,module,exports){ },{}],9:[function(require,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))); } }; },{}],10:[function(require,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; }; },{}],11:[function(require,module,exports){ module.exports = function(a, b){ var fn = function(){}; fn.prototype = b.prototype; a.prototype = new fn; a.prototype.constructor = a; }; },{}],12:[function(require,module,exports){ /** * This is the web browser implementation of `debug()`. * * Expose `debug()` as the module. */ exports = module.exports = require('./debug'); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; exports.load = load; exports.useColors = useColors; exports.storage = 'undefined' != typeof chrome && 'undefined' != typeof chrome.storage ? chrome.storage.local : localstorage(); /** * Colors. */ exports.colors = [ 'lightseagreen', 'forestgreen', 'goldenrod', 'dodgerblue', 'darkorchid', 'crimson' ]; /** * Currently only WebKit-based Web Inspectors, Firefox >= v31, * and the Firebug extension (any Firefox version) are known * to support "%c" CSS customizations. * * TODO: add a `localStorage` variable to explicitly enable/disable colors */ function useColors() { // is webkit? http://stackoverflow.com/a/16459606/376773 return ('WebkitAppearance' in document.documentElement.style) || // is firebug? http://stackoverflow.com/a/398120/376773 (window.console && (console.firebug || (console.exception && console.table))) || // is firefox >= v31? // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31); } /** * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. */ exports.formatters.j = function(v) { return JSON.stringify(v); }; /** * Colorize log arguments if enabled. * * @api public */ function formatArgs() { var args = arguments; var useColors = this.useColors; args[0] = (useColors ? '%c' : '') + this.namespace + (useColors ? ' %c' : ' ') + args[0] + (useColors ? '%c ' : ' ') + '+' + exports.humanize(this.diff); if (!useColors) return args; var c = 'color: ' + this.color; args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); // the final "%c" is somewhat tricky, because there could be other // arguments passed either before or after the %c, so we need to // figure out the correct index to insert the CSS into var index = 0; var lastC = 0; args[0].replace(/%[a-z%]/g, function(match) { if ('%%' === match) return; index++; if ('%c' === match) { // we only are interested in the *last* %c // (the user may have provided their own) lastC = index; } }); args.splice(lastC, 0, c); return args; } /** * Invokes `console.log()` when available. * No-op when `console.log` is not a "function". * * @api public */ function log() { // this hackery is required for IE8/9, where // the `console.log` function doesn't have 'apply' return 'object' === typeof console && console.log && Function.prototype.apply.call(console.log, console, arguments); } /** * Save `namespaces`. * * @param {String} namespaces * @api private */ function save(namespaces) { try { if (null == namespaces) { exports.storage.removeItem('debug'); } else { exports.storage.debug = namespaces; } } catch(e) {} } /** * Load `namespaces`. * * @return {String} returns the previously persisted debug modes * @api private */ function load() { var r; try { r = exports.storage.debug; } catch(e) {} return r; } /** * Enable namespaces listed in `localStorage.debug` initially. */ exports.enable(load()); /** * Localstorage attempts to return the localstorage. * * This is necessary because safari throws * when a user disables cookies/localstorage * and you attempt to access it. * * @return {LocalStorage} * @api private */ function localstorage(){ try { return window.localStorage; } catch (e) {} } },{"./debug":13}],13:[function(require,module,exports){ /** * This is the common logic for both the Node.js and web browser * implementations of `debug()`. * * Expose `debug()` as the module. */ exports = module.exports = debug; exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; exports.humanize = require('ms'); /** * The currently active debug mode names, and names to skip. */ exports.names = []; exports.skips = []; /** * Map of special "%n" handling functions, for the debug "format" argument. * * Valid key names are a single, lowercased letter, i.e. "n". */ exports.formatters = {}; /** * Previously assigned color. */ var prevColor = 0; /** * Previous log timestamp. */ var prevTime; /** * Select a color. * * @return {Number} * @api private */ function selectColor() { return exports.colors[prevColor++ % exports.colors.length]; } /** * Create a debugger with the given `namespace`. * * @param {String} namespace * @return {Function} * @api public */ function debug(namespace) { // define the `disabled` version function disabled() { } disabled.enabled = false; // define the `enabled` version function enabled() { var self = enabled; // set `diff` timestamp var curr = +new Date(); var ms = curr - (prevTime || curr); self.diff = ms; self.prev = prevTime; self.curr = curr; prevTime = curr; // add the `color` if not set if (null == self.useColors) self.useColors = exports.useColors(); if (null == self.color && self.useColors) self.color = selectColor(); var args = Array.prototype.slice.call(arguments); args[0] = exports.coerce(args[0]); if ('string' !== typeof args[0]) { // anything else let's inspect with %o args = ['%o'].concat(args); } // apply any `formatters` transformations var index = 0; args[0] = args[0].replace(/%([a-z%])/g, function(match, format) { // if we encounter an escaped % then don't increase the array index if (match === '%%') return match; index++; var formatter = exports.formatters[format]; if ('function' === typeof formatter) { var val = args[index]; match = formatter.call(self, val); // now we need to remove `args[index]` since it's inlined in the `format` args.splice(index, 1); index--; } return match; }); if ('function' === typeof exports.formatArgs) { args = exports.formatArgs.apply(self, args); } var logFn = enabled.log || exports.log || console.log.bind(console); logFn.apply(self, args); } enabled.enabled = true; var fn = exports.enabled(namespace) ? enabled : disabled; fn.namespace = namespace; return fn; } /** * Enables a debug mode by namespaces. This can include modes * separated by a colon and wildcards. * * @param {String} namespaces * @api public */ function enable(namespaces) { exports.save(namespaces); var split = (namespaces || '').split(/[\s,]+/); var len = split.length; for (var i = 0; i < len; i++) { if (!split[i]) continue; // ignore empty strings namespaces = split[i].replace(/\*/g, '.*?'); if (namespaces[0] === '-') { exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); } else { exports.names.push(new RegExp('^' + namespaces + '$')); } } } /** * Disable debug output. * * @api public */ function disable() { exports.enable(''); } /** * Returns true if the given mode name is enabled, false otherwise. * * @param {String} name * @return {Boolean} * @api public */ function enabled(name) { var i, len; for (i = 0, len = exports.skips.length; i < len; i++) { if (exports.skips[i].test(name)) { return false; } } for (i = 0, len = exports.names.length; i < len; i++) { if (exports.names[i].test(name)) { return true; } } return false; } /** * Coerce `val`. * * @param {Mixed} val * @return {Mixed} * @api private */ function coerce(val) { if (val instanceof Error) return val.stack || val.message; return val; } },{"ms":32}],14:[function(require,module,exports){ module.exports = require('./lib/'); },{"./lib/":15}],15:[function(require,module,exports){ module.exports = require('./socket'); /** * Exports parser * * @api public * */ module.exports.parser = require('engine.io-parser'); },{"./socket":16,"engine.io-parser":24}],16:[function(require,module,exports){ (function (global){ /** * Module dependencies. */ var transports = require('./transports'); var Emitter = require('component-emitter'); var debug = require('debug')('engine.io-client:socket'); var index = require('indexof'); var parser = require('engine.io-parser'); var parseuri = require('parseuri'); var parsejson = require('parsejson'); var parseqs = require('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.hostname = uri.host; opts.secure = uri.protocol == 'https' || uri.protocol == 'wss'; opts.port = uri.port; if (uri.query) opts.query = uri.query; } else if (opts.host) { opts.hostname = parseuri(opts.host).host; } this.secure = null != opts.secure ? opts.secure : (global.location && 'https:' == location.protocol); if (opts.hostname && !opts.port) { // if no port is specified manually, use the protocol default opts.port = this.secure ? '443' : '80'; } 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.policyPort = opts.policyPort || 843; this.rememberUpgrade = opts.rememberUpgrade || false; this.binaryType = null; this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false; if (true === this.perMessageDeflate) this.perMessageDeflate = {}; if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) { this.perMessageDeflate.threshold = 1024; } // SSL options for Node.js client this.pfx = opts.pfx || null; this.key = opts.key || null; this.passphrase = opts.passphrase || null; this.cert = opts.cert || null; this.ca = opts.ca || null; this.ciphers = opts.ciphers || null; this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? null : opts.rejectUnauthorized; // other options for Node.js client var freeGlobal = typeof global == 'object' && global; if (freeGlobal.global === freeGlobal) { if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) { this.extraHeaders = opts.extraHeaders; } } this.open(); } 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 = require('./transport'); Socket.transports = require('./transports'); Socket.parser = require('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, pfx: this.pfx, key: this.key, passphrase: this.passphrase, cert: this.cert, ca: this.ca, ciphers: this.ciphers, rejectUnauthorized: this.rejectUnauthorized, perMessageDeflate: this.perMessageDeflate, extraHeaders: this.extraHeaders }); 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) 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); if (!transport) return; Socket.priorWebsocketSuccess = 'websocket' == transport.name; debug('pausing current transport "%s"', self.transport.name); self.transport.pause(function () { if (failed) return; if ('closed' == 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(); this.emit('pong'); break; case 'error': var err = new Error('server error'); err.code = packet.data; this.onError(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 private */ Socket.prototype.ping = function () { var self = this; this.sendPacket('ping', function(){ self.emit('ping'); }); }; /** * Called on `drain` event * * @api private */ Socket.prototype.onDrain = function() { this.writeBuffer.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 (0 === this.writeBuffer.length) { 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. * @param {Object} options. * @return {Socket} for chaining. * @api public */ Socket.prototype.write = Socket.prototype.send = function (msg, options, fn) { this.sendPacket('message', msg, options, fn); return this; }; /** * Sends a packet. * * @param {String} packet type. * @param {String} data. * @param {Object} options. * @param {Function} callback function. * @api private */ Socket.prototype.sendPacket = function (type, data, options, fn) { if('function' == typeof data) { fn = data; data = undefined; } if ('function' == typeof options) { fn = options; options = null; } if ('closing' == this.readyState || 'closed' == this.readyState) { return; } options = options || {}; options.compress = false !== options.compress; var packet = { type: type, data: data, options: options }; this.emit('packetCreate', packet); this.writeBuffer.push(packet); if (fn) this.once('flush', fn); this.flush(); }; /** * Closes the connection. * * @api private */ Socket.prototype.close = function () { if ('opening' == this.readyState || 'open' == this.readyState) { this.readyState = 'closing'; var self = this; if (this.writeBuffer.length) { this.once('drain', function() { if (this.upgrading) { waitForUpgrade(); } else { close(); } }); } else if (this.upgrading) { waitForUpgrade(); } else { close(); } } function close() { self.onClose('forced close'); debug('socket closing - telling transport to close'); self.transport.close(); } function cleanupAndClose() { self.removeListener('upgrade', cleanupAndClose); self.removeListener('upgradeError', cleanupAndClose); close(); } function waitForUpgrade() { // wait for upgrade to finish since we can't send packets while pausing a transport self.once('upgrade', cleanupAndClose); self.once('upgradeError', cleanupAndClose); } 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 || 'closing' == this.readyState) { debug('socket close with reason: "%s"', reason); var self = this; // clear timers clearTimeout(this.pingIntervalTimer); clearTimeout(this.pingTimeoutTimer); // 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); // clean buffers after, so users can still // grab the buffers on `close` event self.writeBuffer = []; self.prevBufferLen = 0; } }; /** * 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