diff options
Diffstat (limited to 'node_modules/engine.io-client/lib')
-rw-r--r-- | node_modules/engine.io-client/lib/index.js | 10 | ||||
-rw-r--r-- | node_modules/engine.io-client/lib/socket.js | 743 | ||||
-rw-r--r-- | node_modules/engine.io-client/lib/transport.js | 157 | ||||
-rw-r--r-- | node_modules/engine.io-client/lib/transports/index.js | 53 | ||||
-rw-r--r-- | node_modules/engine.io-client/lib/transports/polling-jsonp.js | 231 | ||||
-rw-r--r-- | node_modules/engine.io-client/lib/transports/polling-xhr.js | 413 | ||||
-rw-r--r-- | node_modules/engine.io-client/lib/transports/polling.js | 245 | ||||
-rw-r--r-- | node_modules/engine.io-client/lib/transports/websocket.js | 286 | ||||
-rw-r--r-- | node_modules/engine.io-client/lib/xmlhttprequest.js | 37 |
9 files changed, 2175 insertions, 0 deletions
diff --git a/node_modules/engine.io-client/lib/index.js b/node_modules/engine.io-client/lib/index.js new file mode 100644 index 0000000..0bf4058 --- /dev/null +++ b/node_modules/engine.io-client/lib/index.js @@ -0,0 +1,10 @@ + +module.exports = require('./socket'); + +/** + * Exports parser + * + * @api public + * + */ +module.exports.parser = require('engine.io-parser'); diff --git a/node_modules/engine.io-client/lib/socket.js b/node_modules/engine.io-client/lib/socket.js new file mode 100644 index 0000000..d2eb44c --- /dev/null +++ b/node_modules/engine.io-client/lib/socket.js @@ -0,0 +1,743 @@ +/** + * Module dependencies. + */ + +var transports = require('./transports/index'); +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 parseqs = require('parseqs'); + +/** + * Module exports. + */ + +module.exports = Socket; + +/** + * 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.transportOptions = opts.transportOptions || {}; + this.readyState = ''; + this.writeBuffer = []; + this.prevBufferLen = 0; + 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 ? true : opts.rejectUnauthorized; + this.forceNode = !!opts.forceNode; + + // 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; + } + + if (opts.localAddress) { + this.localAddress = opts.localAddress; + } + } + + // set on handshake + this.id = null; + this.upgrades = null; + this.pingInterval = null; + this.pingTimeout = null; + + // set on heartbeat + this.pingIntervalTimer = null; + this.pingTimeoutTimer = null; + + 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/index'); +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; + + // per-transport options + var options = this.transportOptions[name] || {}; + + // session id if we already have one + if (this.id) query.sid = this.id; + + var transport = new transports[name]({ + query: query, + socket: this, + agent: options.agent || this.agent, + hostname: options.hostname || this.hostname, + port: options.port || this.port, + secure: options.secure || this.secure, + path: options.path || this.path, + forceJSONP: options.forceJSONP || this.forceJSONP, + jsonp: options.jsonp || this.jsonp, + forceBase64: options.forceBase64 || this.forceBase64, + enablesXDR: options.enablesXDR || this.enablesXDR, + timestampRequests: options.timestampRequests || this.timestampRequests, + timestampParam: options.timestampParam || this.timestampParam, + policyPort: options.policyPort || this.policyPort, + pfx: options.pfx || this.pfx, + key: options.key || this.key, + passphrase: options.passphrase || this.passphrase, + cert: options.cert || this.cert, + ca: options.ca || this.ca, + ciphers: options.ciphers || this.ciphers, + rejectUnauthorized: options.rejectUnauthorized || this.rejectUnauthorized, + perMessageDeflate: options.perMessageDeflate || this.perMessageDeflate, + extraHeaders: options.extraHeaders || this.extraHeaders, + forceNode: options.forceNode || this.forceNode, + localAddress: options.localAddress || this.localAddress, + requestTimeout: options.requestTimeout || this.requestTimeout, + protocols: options.protocols || void (0) + }); + + 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 }); + var failed = false; + var 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 || + 'closing' === 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(JSON.parse(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 < j; i++) { + if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]); + } + return filteredUpgrades; +}; diff --git a/node_modules/engine.io-client/lib/transport.js b/node_modules/engine.io-client/lib/transport.js new file mode 100644 index 0000000..f2a90a4 --- /dev/null +++ b/node_modules/engine.io-client/lib/transport.js @@ -0,0 +1,157 @@ +/** + * Module dependencies. + */ + +var parser = require('engine.io-parser'); +var Emitter = require('component-emitter'); + +/** + * Module exports. + */ + +module.exports = Transport; + +/** + * Transport abstract constructor. + * + * @param {Object} options. + * @api private + */ + +function Transport (opts) { + this.path = opts.path; + this.hostname = opts.hostname; + this.port = opts.port; + this.secure = opts.secure; + this.query = opts.query; + this.timestampParam = opts.timestampParam; + this.timestampRequests = opts.timestampRequests; + this.readyState = ''; + this.agent = opts.agent || false; + this.socket = opts.socket; + this.enablesXDR = opts.enablesXDR; + + // SSL options for Node.js client + this.pfx = opts.pfx; + this.key = opts.key; + this.passphrase = opts.passphrase; + this.cert = opts.cert; + this.ca = opts.ca; + this.ciphers = opts.ciphers; + this.rejectUnauthorized = opts.rejectUnauthorized; + this.forceNode = opts.forceNode; + + // other options for Node.js client + this.extraHeaders = opts.extraHeaders; + this.localAddress = opts.localAddress; +} + +/** + * Mix in `Emitter`. + */ + +Emitter(Transport.prototype); + +/** + * Emits an error. + * + * @param {String} str + * @return {Transport} for chaining + * @api public + */ + +Transport.prototype.onError = function (msg, desc) { + var err = new Error(msg); + err.type = 'TransportError'; + err.description = desc; + this.emit('error', err); + return this; +}; + +/** + * Opens the transport. + * + * @api public + */ + +Transport.prototype.open = function () { + if ('closed' === this.readyState || '' === this.readyState) { + this.readyState = 'opening'; + this.doOpen(); + } + + return this; +}; + +/** + * Closes the transport. + * + * @api private + */ + +Transport.prototype.close = function () { + if ('opening' === this.readyState || 'open' === this.readyState) { + this.doClose(); + this.onClose(); + } + + return this; +}; + +/** + * Sends multiple packets. + * + * @param {Array} packets + * @api private + */ + +Transport.prototype.send = function (packets) { + if ('open' === this.readyState) { + this.write(packets); + } else { + throw new Error('Transport not open'); + } +}; + +/** + * Called upon open + * + * @api private + */ + +Transport.prototype.onOpen = function () { + this.readyState = 'open'; + this.writable = true; + this.emit('open'); +}; + +/** + * Called with data. + * + * @param {String} data + * @api private + */ + +Transport.prototype.onData = function (data) { + var packet = parser.decodePacket(data, this.socket.binaryType); + this.onPacket(packet); +}; + +/** + * Called with a decoded packet. + */ + +Transport.prototype.onPacket = function (packet) { + this.emit('packet', packet); +}; + +/** + * Called upon close. + * + * @api private + */ + +Transport.prototype.onClose = function () { + this.readyState = 'closed'; + this.emit('close'); +}; diff --git a/node_modules/engine.io-client/lib/transports/index.js b/node_modules/engine.io-client/lib/transports/index.js new file mode 100644 index 0000000..df68fb9 --- /dev/null +++ b/node_modules/engine.io-client/lib/transports/index.js @@ -0,0 +1,53 @@ +/** + * Module dependencies + */ + +var XMLHttpRequest = require('xmlhttprequest-ssl'); +var XHR = require('./polling-xhr'); +var JSONP = require('./polling-jsonp'); +var websocket = require('./websocket'); + +/** + * Export transports. + */ + +exports.polling = polling; +exports.websocket = websocket; + +/** + * Polling transport polymorphic constructor. + * Decides on xhr vs jsonp based on feature detection. + * + * @api private + */ + +function polling (opts) { + var xhr; + var xd = false; + var xs = false; + var jsonp = false !== opts.jsonp; + + if (global.location) { + var isSSL = 'https:' === location.protocol; + var port = location.port; + + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? 443 : 80; + } + + xd = opts.hostname !== location.hostname || port !== opts.port; + xs = opts.secure !== isSSL; + } + + opts.xdomain = xd; + opts.xscheme = xs; + xhr = new XMLHttpRequest(opts); + + if ('open' in xhr && !opts.forceJSONP) { + return new XHR(opts); + } else { + if (!jsonp) throw new Error('JSONP disabled'); + return new JSONP(opts); + } +} diff --git a/node_modules/engine.io-client/lib/transports/polling-jsonp.js b/node_modules/engine.io-client/lib/transports/polling-jsonp.js new file mode 100644 index 0000000..8ba4833 --- /dev/null +++ b/node_modules/engine.io-client/lib/transports/polling-jsonp.js @@ -0,0 +1,231 @@ + +/** + * Module requirements. + */ + +var Polling = require('./polling'); +var inherit = require('component-inherit'); + +/** + * Module exports. + */ + +module.exports = JSONPPolling; + +/** + * Cached regular expressions. + */ + +var rNewline = /\n/g; +var rEscapedNewline = /\\n/g; + +/** + * Global JSONP callbacks. + */ + +var callbacks; + +/** + * Noop. + */ + +function empty () { } + +/** + * JSONP Polling constructor. + * + * @param {Object} opts. + * @api public + */ + +function JSONPPolling (opts) { + Polling.call(this, opts); + + this.query = this.query || {}; + + // define global callbacks array if not present + // we do this here (lazily) to avoid unneeded global pollution + if (!callbacks) { + // we need to consider multiple engines in the same page + if (!global.___eio) global.___eio = []; + callbacks = global.___eio; + } + + // callback identifier + this.index = callbacks.length; + + // add callback to jsonp global + var self = this; + callbacks.push(function (msg) { + self.onData(msg); + }); + + // append to query string + this.query.j = this.index; + + // prevent spurious errors from being emitted when the window is unloaded + if (global.document && global.addEventListener) { + global.addEventListener('beforeunload', function () { + if (self.script) self.script.onerror = empty; + }, false); + } +} + +/** + * Inherits from Polling. + */ + +inherit(JSONPPolling, Polling); + +/* + * JSONP only supports binary as base64 encoded strings + */ + +JSONPPolling.prototype.supportsBinary = false; + +/** + * Closes the socket. + * + * @api private + */ + +JSONPPolling.prototype.doClose = function () { + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + if (this.form) { + this.form.parentNode.removeChild(this.form); + this.form = null; + this.iframe = null; + } + + Polling.prototype.doClose.call(this); +}; + +/** + * Starts a poll cycle. + * + * @api private + */ + +JSONPPolling.prototype.doPoll = function () { + var self = this; + var script = document.createElement('script'); + + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + script.async = true; + script.src = this.uri(); + script.onerror = function (e) { + self.onError('jsonp poll error', e); + }; + + var insertAt = document.getElementsByTagName('script')[0]; + if (insertAt) { + insertAt.parentNode.insertBefore(script, insertAt); + } else { + (document.head || document.body).appendChild(script); + } + this.script = script; + + var isUAgecko = 'undefined' !== typeof navigator && /gecko/i.test(navigator.userAgent); + + if (isUAgecko) { + setTimeout(function () { + var iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + document.body.removeChild(iframe); + }, 100); + } +}; + +/** + * Writes with a hidden iframe. + * + * @param {String} data to send + * @param {Function} called upon flush. + * @api private + */ + +JSONPPolling.prototype.doWrite = function (data, fn) { + var self = this; + + if (!this.form) { + var form = document.createElement('form'); + var area = document.createElement('textarea'); + var id = this.iframeId = 'eio_iframe_' + this.index; + var iframe; + + form.className = 'socketio'; + form.style.position = 'absolute'; + form.style.top = '-1000px'; + form.style.left = '-1000px'; + form.target = id; + form.method = 'POST'; + form.setAttribute('accept-charset', 'utf-8'); + area.name = 'd'; + form.appendChild(area); + document.body.appendChild(form); + + this.form = form; + this.area = area; + } + + this.form.action = this.uri(); + + function complete () { + initIframe(); + fn(); + } + + function initIframe () { + if (self.iframe) { + try { + self.form.removeChild(self.iframe); + } catch (e) { + self.onError('jsonp polling iframe removal error', e); + } + } + + try { + // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) + var html = '<iframe src="javascript:0" name="' + self.iframeId + '">'; + iframe = document.createElement(html); + } catch (e) { + iframe = document.createElement('iframe'); + iframe.name = self.iframeId; + iframe.src = 'javascript:0'; + } + + iframe.id = self.iframeId; + + self.form.appendChild(iframe); + self.iframe = iframe; + } + + initIframe(); + + // escape \n to prevent it from being converted into \r\n by some UAs + // double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side + data = data.replace(rEscapedNewline, '\\\n'); + this.area.value = data.replace(rNewline, '\\n'); + + try { + this.form.submit(); + } catch (e) {} + + if (this.iframe.attachEvent) { + this.iframe.onreadystatechange = function () { + if (self.iframe.readyState === 'complete') { + complete(); + } + }; + } else { + this.iframe.onload = complete; + } +}; diff --git a/node_modules/engine.io-client/lib/transports/polling-xhr.js b/node_modules/engine.io-client/lib/transports/polling-xhr.js new file mode 100644 index 0000000..297bac5 --- /dev/null +++ b/node_modules/engine.io-client/lib/transports/polling-xhr.js @@ -0,0 +1,413 @@ +/** + * Module requirements. + */ + +var XMLHttpRequest = require('xmlhttprequest-ssl'); +var Polling = require('./polling'); +var Emitter = require('component-emitter'); +var inherit = require('component-inherit'); +var debug = require('debug')('engine.io-client:polling-xhr'); + +/** + * Module exports. + */ + +module.exports = XHR; +module.exports.Request = Request; + +/** + * Empty function + */ + +function empty () {} + +/** + * XHR Polling constructor. + * + * @param {Object} opts + * @api public + */ + +function XHR (opts) { + Polling.call(this, opts); + this.requestTimeout = opts.requestTimeout; + this.extraHeaders = opts.extraHeaders; + + if (global.location) { + var isSSL = 'https:' === location.protocol; + var port = location.port; + + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? 443 : 80; + } + + this.xd = opts.hostname !== global.location.hostname || + port !== opts.port; + this.xs = opts.secure !== isSSL; + } +} + +/** + * Inherits from Polling. + */ + +inherit(XHR, Polling); + +/** + * XHR supports binary + */ + +XHR.prototype.supportsBinary = true; + +/** + * Creates a request. + * + * @param {String} method + * @api private + */ + +XHR.prototype.request = function (opts) { + opts = opts || {}; + opts.uri = this.uri(); + opts.xd = this.xd; + opts.xs = this.xs; + opts.agent = this.agent || false; + opts.supportsBinary = this.supportsBinary; + opts.enablesXDR = this.enablesXDR; + + // SSL options for Node.js client + opts.pfx = this.pfx; + opts.key = this.key; + opts.passphrase = this.passphrase; + opts.cert = this.cert; + opts.ca = this.ca; + opts.ciphers = this.ciphers; + opts.rejectUnauthorized = this.rejectUnauthorized; + opts.requestTimeout = this.requestTimeout; + + // other options for Node.js client + opts.extraHeaders = this.extraHeaders; + + return new Request(opts); +}; + +/** + * Sends data. + * + * @param {String} data to send. + * @param {Function} called upon flush. + * @api private + */ + +XHR.prototype.doWrite = function (data, fn) { + var isBinary = typeof data !== 'string' && data !== undefined; + var req = this.request({ method: 'POST', data: data, isBinary: isBinary }); + var self = this; + req.on('success', fn); + req.on('error', function (err) { + self.onError('xhr post error', err); + }); + this.sendXhr = req; +}; + +/** + * Starts a poll cycle. + * + * @api private + */ + +XHR.prototype.doPoll = function () { + debug('xhr poll'); + var req = this.request(); + var self = this; + req.on('data', function (data) { + self.onData(data); + }); + req.on('error', function (err) { + self.onError('xhr poll error', err); + }); + this.pollXhr = req; +}; + +/** + * Request constructor + * + * @param {Object} options + * @api public + */ + +function Request (opts) { + this.method = opts.method || 'GET'; + this.uri = opts.uri; + this.xd = !!opts.xd; + this.xs = !!opts.xs; + this.async = false !== opts.async; + this.data = undefined !== opts.data ? opts.data : null; + this.agent = opts.agent; + this.isBinary = opts.isBinary; + this.supportsBinary = opts.supportsBinary; + this.enablesXDR = opts.enablesXDR; + this.requestTimeout = opts.requestTimeout; + + // SSL options for Node.js client + this.pfx = opts.pfx; + this.key = opts.key; + this.passphrase = opts.passphrase; + this.cert = opts.cert; + this.ca = opts.ca; + this.ciphers = opts.ciphers; + this.rejectUnauthorized = opts.rejectUnauthorized; + + // other options for Node.js client + this.extraHeaders = opts.extraHeaders; + + this.create(); +} + +/** + * Mix in `Emitter`. + */ + +Emitter(Request.prototype); + +/** + * Creates the XHR object and sends the request. + * + * @api private + */ + +Request.prototype.create = function () { + var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR }; + + // SSL options for Node.js client + opts.pfx = this.pfx; + opts.key = this.key; + opts.passphrase = this.passphrase; + opts.cert = this.cert; + opts.ca = this.ca; + opts.ciphers = this.ciphers; + opts.rejectUnauthorized = this.rejectUnauthorized; + + var xhr = this.xhr = new XMLHttpRequest(opts); + var self = this; + + try { + debug('xhr open %s: %s', this.method, this.uri); + xhr.open(this.method, this.uri, this.async); + try { + if (this.extraHeaders) { + xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true); + for (var i in this.extraHeaders) { + if (this.extraHeaders.hasOwnProperty(i)) { + xhr.setRequestHeader(i, this.extraHeaders[i]); + } + } + } + } catch (e) {} + + if ('POST' === this.method) { + try { + if (this.isBinary) { + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + } else { + xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); + } + } catch (e) {} + } + + try { + xhr.setRequestHeader('Accept', '*/*'); + } catch (e) {} + + // ie6 check + if ('withCredentials' in xhr) { + xhr.withCredentials = true; + } + + if (this.requestTimeout) { + xhr.timeout = this.requestTimeout; + } + + if (this.hasXDR()) { + xhr.onload = function () { + self.onLoad(); + }; + xhr.onerror = function () { + self.onError(xhr.responseText); + }; + } else { + xhr.onreadystatechange = function () { + if (xhr.readyState === 2) { + var contentType; + try { + contentType = xhr.getResponseHeader('Content-Type'); + } catch (e) {} + if (contentType === 'application/octet-stream') { + xhr.responseType = 'arraybuffer'; + } + } + if (4 !== xhr.readyState) return; + if (200 === xhr.status || 1223 === xhr.status) { + self.onLoad(); + } else { + // make sure the `error` event handler that's user-set + // does not throw in the same tick and gets caught here + setTimeout(function () { + self.onError(xhr.status); + }, 0); + } + }; + } + + debug('xhr data %s', this.data); + xhr.send(this.data); + } catch (e) { + // Need to defer since .create() is called directly fhrom the constructor + // and thus the 'error' event can only be only bound *after* this exception + // occurs. Therefore, also, we cannot throw here at all. + setTimeout(function () { + self.onError(e); + }, 0); + return; + } + + if (global.document) { + this.index = Request.requestsCount++; + Request.requests[this.index] = this; + } +}; + +/** + * Called upon successful response. + * + * @api private + */ + +Request.prototype.onSuccess = function () { + this.emit('success'); + this.cleanup(); +}; + +/** + * Called if we have data. + * + * @api private + */ + +Request.prototype.onData = function (data) { + this.emit('data', data); + this.onSuccess(); +}; + +/** + * Called upon error. + * + * @api private + */ + +Request.prototype.onError = function (err) { + this.emit('error', err); + this.cleanup(true); +}; + +/** + * Cleans up house. + * + * @api private + */ + +Request.prototype.cleanup = function (fromError) { + if ('undefined' === typeof this.xhr || null === this.xhr) { + return; + } + // xmlhttprequest + if (this.hasXDR()) { + this.xhr.onload = this.xhr.onerror = empty; + } else { + this.xhr.onreadystatechange = empty; + } + + if (fromError) { + try { + this.xhr.abort(); + } catch (e) {} + } + + if (global.document) { + delete Request.requests[this.index]; + } + + this.xhr = null; +}; + +/** + * Called upon load. + * + * @api private + */ + +Request.prototype.onLoad = function () { + var data; + try { + var contentType; + try { + contentType = this.xhr.getResponseHeader('Content-Type'); + } catch (e) {} + if (contentType === 'application/octet-stream') { + data = this.xhr.response || this.xhr.responseText; + } else { + data = this.xhr.responseText; + } + } catch (e) { + this.onError(e); + } + if (null != data) { + this.onData(data); + } +}; + +/** + * Check if it has XDomainRequest. + * + * @api private + */ + +Request.prototype.hasXDR = function () { + return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR; +}; + +/** + * Aborts the request. + * + * @api public + */ + +Request.prototype.abort = function () { + this.cleanup(); +}; + +/** + * Aborts pending requests when unloading the window. This is needed to prevent + * memory leaks (e.g. when using IE) and to ensure that no spurious error is + * emitted. + */ + +Request.requestsCount = 0; +Request.requests = {}; + +if (global.document) { + if (global.attachEvent) { + global.attachEvent('onunload', unloadHandler); + } else if (global.addEventListener) { + global.addEventListener('beforeunload', unloadHandler, false); + } +} + +function unloadHandler () { + for (var i in Request.requests) { + if (Request.requests.hasOwnProperty(i)) { + Request.requests[i].abort(); + } + } +} diff --git a/node_modules/engine.io-client/lib/transports/polling.js b/node_modules/engine.io-client/lib/transports/polling.js new file mode 100644 index 0000000..970313e --- /dev/null +++ b/node_modules/engine.io-client/lib/transports/polling.js @@ -0,0 +1,245 @@ +/** + * Module dependencies. + */ + +var Transport = require('../transport'); +var parseqs = require('parseqs'); +var parser = require('engine.io-parser'); +var inherit = require('component-inherit'); +var yeast = require('yeast'); +var debug = require('debug')('engine.io-client:polling'); + +/** + * Module exports. + */ + +module.exports = Polling; + +/** + * Is XHR2 supported? + */ + +var hasXHR2 = (function () { + var XMLHttpRequest = require('xmlhttprequest-ssl'); + var xhr = new XMLHttpRequest({ xdomain: false }); + return null != xhr.responseType; +})(); + +/** + * Polling interface. + * + * @param {Object} opts + * @api private + */ + +function Polling (opts) { + var forceBase64 = (opts && opts.forceBase64); + if (!hasXHR2 || forceBase64) { + this.supportsBinary = false; + } + Transport.call(this, opts); +} + +/** + * Inherits from Transport. + */ + +inherit(Polling, Transport); + +/** + * Transport name. + */ + +Polling.prototype.name = 'polling'; + +/** + * Opens the socket (triggers polling). We write a PING message to determine + * when the transport is open. + * + * @api private + */ + +Polling.prototype.doOpen = function () { + this.poll(); +}; + +/** + * Pauses polling. + * + * @param {Function} callback upon buffers are flushed and transport is paused + * @api private + */ + +Polling.prototype.pause = function (onPause) { + var self = this; + + this.readyState = 'pausing'; + + function pause () { + debug('paused'); + self.readyState = 'paused'; + onPause(); + } + + if (this.polling || !this.writable) { + var total = 0; + + if (this.polling) { + debug('we are currently polling - waiting to pause'); + total++; + this.once('pollComplete', function () { + debug('pre-pause polling complete'); + --total || pause(); + }); + } + + if (!this.writable) { + debug('we are currently writing - waiting to pause'); + total++; + this.once('drain', function () { + debug('pre-pause writing complete'); + --total || pause(); + }); + } + } else { + pause(); + } +}; + +/** + * Starts polling cycle. + * + * @api public + */ + +Polling.prototype.poll = function () { + debug('polling'); + this.polling = true; + this.doPoll(); + this.emit('poll'); +}; + +/** + * Overloads onData to detect payloads. + * + * @api private + */ + +Polling.prototype.onData = function (data) { + var self = this; + debug('polling got data %s', data); + var callback = function (packet, index, total) { + // if its the first message we consider the transport open + if ('opening' === self.readyState) { + self.onOpen(); + } + + // if its a close packet, we close the ongoing requests + if ('close' === packet.type) { + self.onClose(); + return false; + } + + // otherwise bypass onData and handle the message + self.onPacket(packet); + }; + + // decode payload + parser.decodePayload(data, this.socket.binaryType, callback); + + // if an event did not trigger closing + if ('closed' !== this.readyState) { + // if we got data we're not polling + this.polling = false; + this.emit('pollComplete'); + + if ('open' === this.readyState) { + this.poll(); + } else { + debug('ignoring poll - transport state "%s"', this.readyState); + } + } +}; + +/** + * For polling, send a close packet. + * + * @api private + */ + +Polling.prototype.doClose = function () { + var self = this; + + function close () { + debug('writing close packet'); + self.write([{ type: 'close' }]); + } + + if ('open' === this.readyState) { + debug('transport open - closing'); + close(); + } else { + // in case we're trying to close while + // handshaking is in progress (GH-164) + debug('transport not open - deferring close'); + this.once('open', close); + } +}; + +/** + * Writes a packets payload. + * + * @param {Array} data packets + * @param {Function} drain callback + * @api private + */ + +Polling.prototype.write = function (packets) { + var self = this; + this.writable = false; + var callbackfn = function () { + self.writable = true; + self.emit('drain'); + }; + + parser.encodePayload(packets, this.supportsBinary, function (data) { + self.doWrite(data, callbackfn); + }); +}; + +/** + * Generates uri for connection. + * + * @api private + */ + +Polling.prototype.uri = function () { + var query = this.query || {}; + var schema = this.secure ? 'https' : 'http'; + var port = ''; + + // cache busting is forced + if (false !== this.timestampRequests) { + query[this.timestampParam] = yeast(); + } + + if (!this.supportsBinary && !query.sid) { + query.b64 = 1; + } + + query = parseqs.encode(query); + + // avoid port if default for schema + if (this.port && (('https' === schema && Number(this.port) !== 443) || + ('http' === schema && Number(this.port) !== 80))) { + port = ':' + this.port; + } + + // prepend ? to query + if (query.length) { + query = '?' + query; + } + + var ipv6 = this.hostname.indexOf(':') !== -1; + return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query; +}; diff --git a/node_modules/engine.io-client/lib/transports/websocket.js b/node_modules/engine.io-client/lib/transports/websocket.js new file mode 100644 index 0000000..de14ba0 --- /dev/null +++ b/node_modules/engine.io-client/lib/transports/websocket.js @@ -0,0 +1,286 @@ +/** + * Module dependencies. + */ + +var Transport = require('../transport'); +var parser = require('engine.io-parser'); +var parseqs = require('parseqs'); +var inherit = require('component-inherit'); +var yeast = require('yeast'); +var debug = require('debug')('engine.io-client:websocket'); +var BrowserWebSocket = global.WebSocket || global.MozWebSocket; +var NodeWebSocket; +if (typeof window === 'undefined') { + try { + NodeWebSocket = require('ws'); + } catch (e) { } +} + +/** + * Get either the `WebSocket` or `MozWebSocket` globals + * in the browser or try to resolve WebSocket-compatible + * interface exposed by `ws` for Node-like environment. + */ + +var WebSocket = BrowserWebSocket; +if (!WebSocket && typeof window === 'undefined') { + WebSocket = NodeWebSocket; +} + +/** + * Module exports. + */ + +module.exports = WS; + +/** + * WebSocket transport constructor. + * + * @api {Object} connection options + * @api public + */ + +function WS (opts) { + var forceBase64 = (opts && opts.forceBase64); + if (forceBase64) { + this.supportsBinary = false; + } + this.perMessageDeflate = opts.perMessageDeflate; + this.usingBrowserWebSocket = BrowserWebSocket && !opts.forceNode; + this.protocols = opts.protocols; + if (!this.usingBrowserWebSocket) { + WebSocket = NodeWebSocket; + } + Transport.call(this, opts); +} + +/** + * Inherits from Transport. + */ + +inherit(WS, Transport); + +/** + * Transport name. + * + * @api public + */ + +WS.prototype.name = 'websocket'; + +/* + * WebSockets support binary + */ + +WS.prototype.supportsBinary = true; + +/** + * Opens socket. + * + * @api private + */ + +WS.prototype.doOpen = function () { + if (!this.check()) { + // let probe timeout + return; + } + + var uri = this.uri(); + var protocols = this.protocols; + var opts = { + agent: this.agent, + perMessageDeflate: this.perMessageDeflate + }; + + // SSL options for Node.js client + opts.pfx = this.pfx; + opts.key = this.key; + opts.passphrase = this.passphrase; + opts.cert = this.cert; + opts.ca = this.ca; + opts.ciphers = this.ciphers; + opts.rejectUnauthorized = this.rejectUnauthorized; + if (this.extraHeaders) { + opts.headers = this.extraHeaders; + } + if (this.localAddress) { + opts.localAddress = this.localAddress; + } + + try { + this.ws = this.usingBrowserWebSocket ? (protocols ? new WebSocket(uri, protocols) : new WebSocket(uri)) : new WebSocket(uri, protocols, opts); + } catch (err) { + return this.emit('error', err); + } + + if (this.ws.binaryType === undefined) { + this.supportsBinary = false; + } + + if (this.ws.supports && this.ws.supports.binary) { + this.supportsBinary = true; + this.ws.binaryType = 'nodebuffer'; + } else { + this.ws.binaryType = 'arraybuffer'; + } + + this.addEventListeners(); +}; + +/** + * Adds event listeners to the socket + * + * @api private + */ + +WS.prototype.addEventListeners = function () { + var self = this; + + this.ws.onopen = function () { + self.onOpen(); + }; + this.ws.onclose = function () { + self.onClose(); + }; + this.ws.onmessage = function (ev) { + self.onData(ev.data); + }; + this.ws.onerror = function (e) { + self.onError('websocket error', e); + }; +}; + +/** + * Writes data to socket. + * + * @param {Array} array of packets. + * @api private + */ + +WS.prototype.write = function (packets) { + var self = this; + this.writable = false; + + // encodePacket efficient as it uses WS framing + // no need for encodePayload + var total = packets.length; + for (var i = 0, l = total; i < l; i++) { + (function (packet) { + parser.encodePacket(packet, self.supportsBinary, function (data) { + if (!self.usingBrowserWebSocket) { + // always create a new object (GH-437) + var opts = {}; + if (packet.options) { + opts.compress = packet.options.compress; + } + + if (self.perMessageDeflate) { + var len = 'string' === typeof data ? global.Buffer.byteLength(data) : data.length; + if (len < self.perMessageDeflate.threshold) { + opts.compress = false; + } + } + } + + // Sometimes the websocket has already been closed but the browser didn't + // have a chance of informing us about it yet, in that case send will + // throw an error + try { + if (self.usingBrowserWebSocket) { + // TypeError is thrown when passing the second argument on Safari + self.ws.send(data); + } else { + self.ws.send(data, opts); + } + } catch (e) { + debug('websocket closed before onclose event'); + } + + --total || done(); + }); + })(packets[i]); + } + + function done () { + self.emit('flush'); + + // fake drain + // defer to next tick to allow Socket to clear writeBuffer + setTimeout(function () { + self.writable = true; + self.emit('drain'); + }, 0); + } +}; + +/** + * Called upon close + * + * @api private + */ + +WS.prototype.onClose = function () { + Transport.prototype.onClose.call(this); +}; + +/** + * Closes socket. + * + * @api private + */ + +WS.prototype.doClose = function () { + if (typeof this.ws !== 'undefined') { + this.ws.close(); + } +}; + +/** + * Generates uri for connection. + * + * @api private + */ + +WS.prototype.uri = function () { + var query = this.query || {}; + var schema = this.secure ? 'wss' : 'ws'; + var port = ''; + + // avoid port if default for schema + if (this.port && (('wss' === schema && Number(this.port) !== 443) || + ('ws' === schema && Number(this.port) !== 80))) { + port = ':' + this.port; + } + + // append timestamp to URI + if (this.timestampRequests) { + query[this.timestampParam] = yeast(); + } + + // communicate binary support capabilities + if (!this.supportsBinary) { + query.b64 = 1; + } + + query = parseqs.encode(query); + + // prepend ? to query + if (query.length) { + query = '?' + query; + } + + var ipv6 = this.hostname.indexOf(':') !== -1; + return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query; +}; + +/** + * Feature detection for WebSocket. + * + * @return {Boolean} whether this transport is available. + * @api public + */ + +WS.prototype.check = function () { + return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name); +}; diff --git a/node_modules/engine.io-client/lib/xmlhttprequest.js b/node_modules/engine.io-client/lib/xmlhttprequest.js new file mode 100644 index 0000000..d76d9c7 --- /dev/null +++ b/node_modules/engine.io-client/lib/xmlhttprequest.js @@ -0,0 +1,37 @@ +// browser shim for xmlhttprequest module + +var hasCORS = require('has-cors'); + +module.exports = function (opts) { + var xdomain = opts.xdomain; + + // scheme must be same when usign XDomainRequest + // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx + var xscheme = opts.xscheme; + + // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default. + // https://github.com/Automattic/engine.io-client/pull/217 + var enablesXDR = opts.enablesXDR; + + // XMLHttpRequest can be disabled on IE + try { + if ('undefined' !== typeof XMLHttpRequest && (!xdomain || hasCORS)) { + return new XMLHttpRequest(); + } + } catch (e) { } + + // Use XDomainRequest for IE8 if enablesXDR is true + // because loading bar keeps flashing when using jsonp-polling + // https://github.com/yujiosaka/socke.io-ie8-loading-example + try { + if ('undefined' !== typeof XDomainRequest && !xscheme && enablesXDR) { + return new XDomainRequest(); + } + } catch (e) { } + + if (!xdomain) { + try { + return new global[['Active'].concat('Object').join('X')]('Microsoft.XMLHTTP'); + } catch (e) { } + } +}; |