engine.io-client
Advanced tools
Comparing version 3.4.0 to 4.0.0-alpha.0
@@ -0,10 +1,14 @@ | ||
const Socket = require("./socket"); | ||
module.exports = require('./socket'); | ||
module.exports = (uri, opts) => new Socket(uri, opts); | ||
/** | ||
* Exports parser | ||
* | ||
* @api public | ||
* | ||
* Expose deps for legacy compatibility | ||
* and standalone browser access. | ||
*/ | ||
module.exports.parser = require('engine.io-parser'); | ||
module.exports.Socket = Socket; | ||
module.exports.protocol = Socket.protocol; // this is an int | ||
module.exports.Transport = require("./transport"); | ||
module.exports.transports = require("./transports/index"); | ||
module.exports.parser = require("engine.io-parser"); |
1191
lib/socket.js
@@ -1,748 +0,673 @@ | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const transports = require("./transports/index"); | ||
const Emitter = require("component-emitter"); | ||
const debug = require("debug")("engine.io-client:socket"); | ||
const index = require("indexof"); | ||
const parser = require("engine.io-parser"); | ||
const parseuri = require("parseuri"); | ||
const parseqs = require("parseqs"); | ||
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'); | ||
class Socket extends Emitter { | ||
/** | ||
* Socket constructor. | ||
* | ||
* @param {String|Object} uri or options | ||
* @param {Object} options | ||
* @api public | ||
*/ | ||
constructor(uri, opts = {}) { | ||
super(); | ||
/** | ||
* Module exports. | ||
*/ | ||
if (uri && "object" === typeof uri) { | ||
opts = uri; | ||
uri = null; | ||
} | ||
module.exports = Socket; | ||
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; | ||
} | ||
/** | ||
* Socket constructor. | ||
* | ||
* @param {String|Object} uri or options | ||
* @param {Object} options | ||
* @api public | ||
*/ | ||
this.secure = | ||
null != opts.secure | ||
? opts.secure | ||
: typeof location !== "undefined" && "https:" === location.protocol; | ||
function Socket (uri, opts) { | ||
if (!(this instanceof Socket)) return new Socket(uri, opts); | ||
if (opts.hostname && !opts.port) { | ||
// if no port is specified manually, use the protocol default | ||
opts.port = this.secure ? "443" : "80"; | ||
} | ||
opts = opts || {}; | ||
this.hostname = | ||
opts.hostname || | ||
(typeof location !== "undefined" ? location.hostname : "localhost"); | ||
this.port = | ||
opts.port || | ||
(typeof location !== "undefined" && location.port | ||
? location.port | ||
: this.secure | ||
? 443 | ||
: 80); | ||
if (uri && 'object' === typeof uri) { | ||
opts = uri; | ||
uri = null; | ||
} | ||
this.transports = opts.transports || ["polling", "websocket"]; | ||
this.readyState = ""; | ||
this.writeBuffer = []; | ||
this.prevBufferLen = 0; | ||
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.opts = Object.assign( | ||
{ | ||
path: "/engine.io", | ||
agent: false, | ||
upgrade: true, | ||
jsonp: true, | ||
timestampParam: "t", | ||
policyPort: 843, | ||
rememberUpgrade: false, | ||
rejectUnauthorized: true, | ||
perMessageDeflate: { | ||
threshold: 1024 | ||
}, | ||
transportOptions: {} | ||
}, | ||
opts | ||
); | ||
this.secure = null != opts.secure ? opts.secure | ||
: (typeof location !== 'undefined' && 'https:' === location.protocol); | ||
this.opts.path = this.opts.path.replace(/\/$/, "") + "/"; | ||
if (opts.hostname && !opts.port) { | ||
// if no port is specified manually, use the protocol default | ||
opts.port = this.secure ? '443' : '80'; | ||
} | ||
if (typeof this.opts.query === "string") { | ||
this.opts.query = parseqs.decode(this.opts.query); | ||
} | ||
this.agent = opts.agent || false; | ||
this.hostname = opts.hostname || | ||
(typeof location !== 'undefined' ? location.hostname : 'localhost'); | ||
this.port = opts.port || (typeof location !== 'undefined' && 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.withCredentials = false !== opts.withCredentials; | ||
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; | ||
// detect ReactNative environment | ||
this.opts.isReactNative = | ||
typeof navigator !== "undefined" && | ||
typeof navigator.product === "string" && | ||
navigator.product.toLowerCase() === "reactnative"; | ||
if (true === this.perMessageDeflate) this.perMessageDeflate = {}; | ||
if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) { | ||
this.perMessageDeflate.threshold = 1024; | ||
} | ||
// set on handshake | ||
this.id = null; | ||
this.upgrades = null; | ||
this.pingInterval = null; | ||
this.pingTimeout = null; | ||
// 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; | ||
// set on heartbeat | ||
this.pingTimeoutTimer = null; | ||
// detect ReactNative environment | ||
this.isReactNative = (typeof navigator !== 'undefined' && typeof navigator.product === 'string' && navigator.product.toLowerCase() === 'reactnative'); | ||
this.open(); | ||
} | ||
// other options for Node.js or ReactNative client | ||
if (typeof self === 'undefined' || this.isReactNative) { | ||
if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) { | ||
this.extraHeaders = opts.extraHeaders; | ||
} | ||
/** | ||
* Creates transport of the given type. | ||
* | ||
* @param {String} transport name | ||
* @return {Transport} | ||
* @api private | ||
*/ | ||
createTransport(name) { | ||
debug('creating transport "%s"', name); | ||
const query = clone(this.opts.query); | ||
if (opts.localAddress) { | ||
this.localAddress = opts.localAddress; | ||
} | ||
} | ||
// append engine.io protocol identifier | ||
query.EIO = parser.protocol; | ||
// set on handshake | ||
this.id = null; | ||
this.upgrades = null; | ||
this.pingInterval = null; | ||
this.pingTimeout = null; | ||
// transport name | ||
query.transport = name; | ||
// set on heartbeat | ||
this.pingIntervalTimer = null; | ||
this.pingTimeoutTimer = null; | ||
// session id if we already have one | ||
if (this.id) query.sid = this.id; | ||
this.open(); | ||
} | ||
const opts = Object.assign( | ||
{ | ||
query, | ||
socket: this, | ||
hostname: this.hostname, | ||
secure: this.secure, | ||
port: this.port | ||
}, | ||
this.opts.transportOptions[name], | ||
this.opts | ||
); | ||
Socket.priorWebsocketSuccess = false; | ||
// console.log(opts); | ||
debug("options: %j", opts); | ||
/** | ||
* Mix in `Emitter`. | ||
*/ | ||
return new transports[name](opts); | ||
} | ||
Emitter(Socket.prototype); | ||
/** | ||
* Initializes transport to use and starts probe. | ||
* | ||
* @api private | ||
*/ | ||
open() { | ||
let transport; | ||
if ( | ||
this.opts.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 | ||
const self = this; | ||
setTimeout(function() { | ||
self.emit("error", "No transports available"); | ||
}, 0); | ||
return; | ||
} else { | ||
transport = this.transports[0]; | ||
} | ||
this.readyState = "opening"; | ||
/** | ||
* Protocol version. | ||
* | ||
* @api public | ||
*/ | ||
// Retry with the next transport if the transport is disabled (jsonp: false) | ||
try { | ||
transport = this.createTransport(transport); | ||
} catch (e) { | ||
debug("error while creating transport: %s", e); | ||
this.transports.shift(); | ||
this.open(); | ||
return; | ||
} | ||
Socket.protocol = parser.protocol; // this is an int | ||
transport.open(); | ||
this.setTransport(transport); | ||
} | ||
/** | ||
* Expose deps for legacy compatibility | ||
* and standalone browser access. | ||
*/ | ||
/** | ||
* Sets the current transport. Disables the existing one (if any). | ||
* | ||
* @api private | ||
*/ | ||
setTransport(transport) { | ||
debug("setting transport %s", transport.name); | ||
const self = this; | ||
Socket.Socket = Socket; | ||
Socket.Transport = require('./transport'); | ||
Socket.transports = require('./transports/index'); | ||
Socket.parser = require('engine.io-parser'); | ||
if (this.transport) { | ||
debug("clearing existing transport %s", this.transport.name); | ||
this.transport.removeAllListeners(); | ||
} | ||
/** | ||
* Creates transport of the given type. | ||
* | ||
* @param {String} transport name | ||
* @return {Transport} | ||
* @api private | ||
*/ | ||
// set up transport | ||
this.transport = transport; | ||
Socket.prototype.createTransport = function (name) { | ||
debug('creating transport "%s"', name); | ||
var query = clone(this.query); | ||
// 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"); | ||
}); | ||
} | ||
// append engine.io protocol identifier | ||
query.EIO = parser.protocol; | ||
/** | ||
* Probes a transport. | ||
* | ||
* @param {String} transport name | ||
* @api private | ||
*/ | ||
probe(name) { | ||
debug('probing transport "%s"', name); | ||
let transport = this.createTransport(name, { probe: 1 }); | ||
let failed = false; | ||
const self = this; | ||
// transport name | ||
query.transport = name; | ||
Socket.priorWebsocketSuccess = false; | ||
// per-transport options | ||
var options = this.transportOptions[name] || {}; | ||
function onTransportOpen() { | ||
if (self.onlyBinaryUpgrades) { | ||
const upgradeLosesBinary = | ||
!this.supportsBinary && self.transport.supportsBinary; | ||
failed = failed || upgradeLosesBinary; | ||
} | ||
if (failed) return; | ||
// session id if we already have one | ||
if (this.id) query.sid = this.id; | ||
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; | ||
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, | ||
withCredentials: options.withCredentials || this.withCredentials, | ||
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), | ||
isReactNative: this.isReactNative | ||
}); | ||
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"); | ||
return transport; | ||
}; | ||
cleanup(); | ||
function clone (obj) { | ||
var o = {}; | ||
for (var i in obj) { | ||
if (obj.hasOwnProperty(i)) { | ||
o[i] = obj[i]; | ||
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); | ||
const err = new Error("probe error"); | ||
err.transport = transport.name; | ||
self.emit("upgradeError", err); | ||
} | ||
}); | ||
} | ||
} | ||
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'; | ||
function freezeTransport() { | ||
if (failed) return; | ||
// 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; | ||
} | ||
// Any callback called by transport should be ignored since now | ||
failed = true; | ||
transport.open(); | ||
this.setTransport(transport); | ||
}; | ||
cleanup(); | ||
/** | ||
* Sets the current transport. Disables the existing one (if any). | ||
* | ||
* @api private | ||
*/ | ||
transport.close(); | ||
transport = null; | ||
} | ||
Socket.prototype.setTransport = function (transport) { | ||
debug('setting transport %s', transport.name); | ||
var self = this; | ||
// Handle any error that happens while probing | ||
function onerror(err) { | ||
const error = new Error("probe error: " + err); | ||
error.transport = transport.name; | ||
if (this.transport) { | ||
debug('clearing existing transport %s', this.transport.name); | ||
this.transport.removeAllListeners(); | ||
} | ||
freezeTransport(); | ||
// set up transport | ||
this.transport = transport; | ||
debug('probe transport "%s" failed because of error: %s', name, err); | ||
// 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'); | ||
}); | ||
}; | ||
self.emit("upgradeError", error); | ||
} | ||
/** | ||
* Probes a transport. | ||
* | ||
* @param {String} transport name | ||
* @api private | ||
*/ | ||
function onTransportClose() { | ||
onerror("transport closed"); | ||
} | ||
Socket.prototype.probe = function (name) { | ||
debug('probing transport "%s"', name); | ||
var transport = this.createTransport(name, { probe: 1 }); | ||
var failed = false; | ||
var self = this; | ||
// When the socket is closed while we're probing | ||
function onclose() { | ||
onerror("socket closed"); | ||
} | ||
Socket.priorWebsocketSuccess = false; | ||
// 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(); | ||
} | ||
} | ||
function onTransportOpen () { | ||
if (self.onlyBinaryUpgrades) { | ||
var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; | ||
failed = failed || upgradeLosesBinary; | ||
// 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); | ||
} | ||
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; | ||
transport.once("open", onTransportOpen); | ||
transport.once("error", onerror); | ||
transport.once("close", onTransportClose); | ||
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'); | ||
this.once("close", onclose); | ||
this.once("upgrading", onupgrade); | ||
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); | ||
} | ||
}); | ||
transport.open(); | ||
} | ||
function freezeTransport () { | ||
if (failed) return; | ||
/** | ||
* Called when connection is deemed open. | ||
* | ||
* @api public | ||
*/ | ||
onOpen() { | ||
debug("socket open"); | ||
this.readyState = "open"; | ||
Socket.priorWebsocketSuccess = "websocket" === this.transport.name; | ||
this.emit("open"); | ||
this.flush(); | ||
// Any callback called by transport should be ignored since now | ||
failed = true; | ||
cleanup(); | ||
transport.close(); | ||
transport = null; | ||
// we check for `readyState` in case an `open` | ||
// listener already closed the socket | ||
if ( | ||
"open" === this.readyState && | ||
this.opts.upgrade && | ||
this.transport.pause | ||
) { | ||
debug("starting upgrade probes"); | ||
let i = 0; | ||
const l = this.upgrades.length; | ||
for (; i < l; i++) { | ||
this.probe(this.upgrades[i]); | ||
} | ||
} | ||
} | ||
// Handle any error that happens while probing | ||
function onerror (err) { | ||
var error = new Error('probe error: ' + err); | ||
error.transport = transport.name; | ||
/** | ||
* Handles a packet. | ||
* | ||
* @api private | ||
*/ | ||
onPacket(packet) { | ||
if ( | ||
"opening" === this.readyState || | ||
"open" === this.readyState || | ||
"closing" === this.readyState | ||
) { | ||
debug('socket receive: type "%s", data "%s"', packet.type, packet.data); | ||
freezeTransport(); | ||
this.emit("packet", packet); | ||
debug('probe transport "%s" failed because of error: %s', name, err); | ||
// Socket is live - any packet counts | ||
this.emit("heartbeat"); | ||
self.emit('upgradeError', error); | ||
} | ||
switch (packet.type) { | ||
case "open": | ||
this.onHandshake(JSON.parse(packet.data)); | ||
break; | ||
function onTransportClose () { | ||
onerror('transport closed'); | ||
} | ||
case "ping": | ||
this.resetPingTimeout(); | ||
this.sendPacket("pong"); | ||
this.emit("pong"); | ||
break; | ||
// When the socket is closed while we're probing | ||
function onclose () { | ||
onerror('socket closed'); | ||
} | ||
case "error": | ||
const err = new Error("server error"); | ||
err.code = packet.data; | ||
this.onError(err); | ||
break; | ||
// 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(); | ||
case "message": | ||
this.emit("data", packet.data); | ||
this.emit("message", packet.data); | ||
break; | ||
} | ||
} else { | ||
debug('packet received with socket readyState "%s"', this.readyState); | ||
} | ||
} | ||
// 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); | ||
/** | ||
* Called upon handshake completion. | ||
* | ||
* @param {Object} handshake obj | ||
* @api private | ||
*/ | ||
onHandshake(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.resetPingTimeout(); | ||
} | ||
transport.once('open', onTransportOpen); | ||
transport.once('error', onerror); | ||
transport.once('close', onTransportClose); | ||
/** | ||
* Sets and resets ping timeout timer based on server pings. | ||
* | ||
* @api private | ||
*/ | ||
resetPingTimeout() { | ||
clearTimeout(this.pingTimeoutTimer); | ||
this.pingTimeoutTimer = setTimeout(() => { | ||
this.onClose("ping timeout"); | ||
}, this.pingInterval + this.pingTimeout); | ||
} | ||
this.once('close', onclose); | ||
this.once('upgrading', onupgrade); | ||
/** | ||
* Called on `drain` event | ||
* | ||
* @api private | ||
*/ | ||
onDrain() { | ||
this.writeBuffer.splice(0, this.prevBufferLen); | ||
transport.open(); | ||
}; | ||
// 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; | ||
/** | ||
* 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]); | ||
if (0 === this.writeBuffer.length) { | ||
this.emit("drain"); | ||
} else { | ||
this.flush(); | ||
} | ||
} | ||
}; | ||
/** | ||
* 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; | ||
/** | ||
* Flush write buffers. | ||
* | ||
* @api private | ||
*/ | ||
flush() { | ||
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"); | ||
} | ||
} else { | ||
debug('packet received with socket readyState "%s"', this.readyState); | ||
} | ||
}; | ||
/** | ||
* Called upon handshake completion. | ||
* | ||
* @param {Object} handshake obj | ||
* @api private | ||
*/ | ||
/** | ||
* Sends a message. | ||
* | ||
* @param {String} message. | ||
* @param {Function} callback function. | ||
* @param {Object} options. | ||
* @return {Socket} for chaining. | ||
* @api public | ||
*/ | ||
write(msg, options, fn) { | ||
this.sendPacket("message", msg, options, fn); | ||
return this; | ||
} | ||
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(); | ||
send(msg, options, fn) { | ||
this.sendPacket("message", msg, options, fn); | ||
return this; | ||
} | ||
// Prolong liveness of socket on heartbeat | ||
this.removeListener('heartbeat', this.onHeartbeat); | ||
this.on('heartbeat', this.onHeartbeat); | ||
}; | ||
/** | ||
* Sends a packet. | ||
* | ||
* @param {String} packet type. | ||
* @param {String} data. | ||
* @param {Object} options. | ||
* @param {Function} callback function. | ||
* @api private | ||
*/ | ||
sendPacket(type, data, options, fn) { | ||
if ("function" === typeof data) { | ||
fn = data; | ||
data = undefined; | ||
} | ||
/** | ||
* Resets ping timeout. | ||
* | ||
* @api private | ||
*/ | ||
if ("function" === typeof options) { | ||
fn = options; | ||
options = null; | ||
} | ||
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)); | ||
}; | ||
if ("closing" === this.readyState || "closed" === this.readyState) { | ||
return; | ||
} | ||
/** | ||
* Pings server every `this.pingInterval` and expects response | ||
* within `this.pingTimeout` or closes connection. | ||
* | ||
* @api private | ||
*/ | ||
options = options || {}; | ||
options.compress = false !== options.compress; | ||
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 { | ||
const packet = { | ||
type: type, | ||
data: data, | ||
options: options | ||
}; | ||
this.emit("packetCreate", packet); | ||
this.writeBuffer.push(packet); | ||
if (fn) this.once("flush", fn); | ||
this.flush(); | ||
} | ||
}; | ||
/** | ||
* Flush write buffers. | ||
* | ||
* @api private | ||
*/ | ||
/** | ||
* Closes the connection. | ||
* | ||
* @api private | ||
*/ | ||
close() { | ||
const self = this; | ||
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'); | ||
} | ||
}; | ||
if ("opening" === this.readyState || "open" === this.readyState) { | ||
this.readyState = "closing"; | ||
/** | ||
* Sends a message. | ||
* | ||
* @param {String} message. | ||
* @param {Function} callback function. | ||
* @param {Object} options. | ||
* @return {Socket} for chaining. | ||
* @api public | ||
*/ | ||
if (this.writeBuffer.length) { | ||
this.once("drain", function() { | ||
if (this.upgrading) { | ||
waitForUpgrade(); | ||
} else { | ||
close(); | ||
} | ||
}); | ||
} else if (this.upgrading) { | ||
waitForUpgrade(); | ||
} else { | ||
close(); | ||
} | ||
} | ||
Socket.prototype.write = | ||
Socket.prototype.send = function (msg, options, fn) { | ||
this.sendPacket('message', msg, options, fn); | ||
return this; | ||
}; | ||
function close() { | ||
self.onClose("forced close"); | ||
debug("socket closing - telling transport to close"); | ||
self.transport.close(); | ||
} | ||
/** | ||
* Sends a packet. | ||
* | ||
* @param {String} packet type. | ||
* @param {String} data. | ||
* @param {Object} options. | ||
* @param {Function} callback function. | ||
* @api private | ||
*/ | ||
function cleanupAndClose() { | ||
self.removeListener("upgrade", cleanupAndClose); | ||
self.removeListener("upgradeError", cleanupAndClose); | ||
close(); | ||
} | ||
Socket.prototype.sendPacket = function (type, data, options, fn) { | ||
if ('function' === typeof data) { | ||
fn = data; | ||
data = undefined; | ||
} | ||
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); | ||
} | ||
if ('function' === typeof options) { | ||
fn = options; | ||
options = null; | ||
return this; | ||
} | ||
if ('closing' === this.readyState || 'closed' === this.readyState) { | ||
return; | ||
/** | ||
* Called upon transport error | ||
* | ||
* @api private | ||
*/ | ||
onError(err) { | ||
debug("socket error %j", err); | ||
Socket.priorWebsocketSuccess = false; | ||
this.emit("error", err); | ||
this.onClose("transport error", err); | ||
} | ||
options = options || {}; | ||
options.compress = false !== options.compress; | ||
/** | ||
* Called upon transport close. | ||
* | ||
* @api private | ||
*/ | ||
onClose(reason, desc) { | ||
if ( | ||
"opening" === this.readyState || | ||
"open" === this.readyState || | ||
"closing" === this.readyState | ||
) { | ||
debug('socket close with reason: "%s"', reason); | ||
const self = this; | ||
var packet = { | ||
type: type, | ||
data: data, | ||
options: options | ||
}; | ||
this.emit('packetCreate', packet); | ||
this.writeBuffer.push(packet); | ||
if (fn) this.once('flush', fn); | ||
this.flush(); | ||
}; | ||
// clear timers | ||
clearTimeout(this.pingIntervalTimer); | ||
clearTimeout(this.pingTimeoutTimer); | ||
/** | ||
* Closes the connection. | ||
* | ||
* @api private | ||
*/ | ||
// stop event from firing again for transport | ||
this.transport.removeAllListeners("close"); | ||
Socket.prototype.close = function () { | ||
if ('opening' === this.readyState || 'open' === this.readyState) { | ||
this.readyState = 'closing'; | ||
// ensure transport won't stay open | ||
this.transport.close(); | ||
var self = this; | ||
// ignore further transport communication | ||
this.transport.removeAllListeners(); | ||
if (this.writeBuffer.length) { | ||
this.once('drain', function () { | ||
if (this.upgrading) { | ||
waitForUpgrade(); | ||
} else { | ||
close(); | ||
} | ||
}); | ||
} else if (this.upgrading) { | ||
waitForUpgrade(); | ||
} else { | ||
close(); | ||
} | ||
} | ||
// set ready state | ||
this.readyState = "closed"; | ||
function close () { | ||
self.onClose('forced close'); | ||
debug('socket closing - telling transport to close'); | ||
self.transport.close(); | ||
} | ||
// clear session id | ||
this.id = null; | ||
function cleanupAndClose () { | ||
self.removeListener('upgrade', cleanupAndClose); | ||
self.removeListener('upgradeError', cleanupAndClose); | ||
close(); | ||
// 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; | ||
} | ||
} | ||
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); | ||
/** | ||
* Filters upgrades, returning only those matching client transports. | ||
* | ||
* @param {Array} server upgrades | ||
* @api private | ||
* | ||
*/ | ||
filterUpgrades(upgrades) { | ||
const filteredUpgrades = []; | ||
let i = 0; | ||
const j = upgrades.length; | ||
for (; i < j; i++) { | ||
if (~index(this.transports, upgrades[i])) | ||
filteredUpgrades.push(upgrades[i]); | ||
} | ||
return filteredUpgrades; | ||
} | ||
} | ||
return this; | ||
}; | ||
Socket.priorWebsocketSuccess = false; | ||
/** | ||
* Called upon transport error | ||
* Protocol version. | ||
* | ||
* @api private | ||
* @api public | ||
*/ | ||
Socket.prototype.onError = function (err) { | ||
debug('socket error %j', err); | ||
Socket.priorWebsocketSuccess = false; | ||
this.emit('error', err); | ||
this.onClose('transport error', err); | ||
}; | ||
Socket.protocol = parser.protocol; // this is an int | ||
/** | ||
* 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; | ||
function clone(obj) { | ||
const o = {}; | ||
for (let i in obj) { | ||
if (obj.hasOwnProperty(i)) { | ||
o[i] = obj[i]; | ||
} | ||
} | ||
}; | ||
return o; | ||
} | ||
/** | ||
* 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; | ||
}; | ||
module.exports = Socket; |
@@ -1,161 +0,117 @@ | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const parser = require("engine.io-parser"); | ||
const Emitter = require("component-emitter"); | ||
var parser = require('engine.io-parser'); | ||
var Emitter = require('component-emitter'); | ||
class Transport extends Emitter { | ||
/** | ||
* Transport abstract constructor. | ||
* | ||
* @param {Object} options. | ||
* @api private | ||
*/ | ||
constructor(opts) { | ||
super(); | ||
/** | ||
* Module exports. | ||
*/ | ||
this.opts = opts; | ||
this.query = opts.query; | ||
this.readyState = ""; | ||
this.socket = opts.socket; | ||
} | ||
module.exports = Transport; | ||
/** | ||
* Emits an error. | ||
* | ||
* @param {String} str | ||
* @return {Transport} for chaining | ||
* @api public | ||
*/ | ||
onError(msg, desc) { | ||
const err = new Error(msg); | ||
err.type = "TransportError"; | ||
err.description = desc; | ||
this.emit("error", err); | ||
return this; | ||
} | ||
/** | ||
* Transport abstract constructor. | ||
* | ||
* @param {Object} options. | ||
* @api private | ||
*/ | ||
/** | ||
* Opens the transport. | ||
* | ||
* @api public | ||
*/ | ||
open() { | ||
if ("closed" === this.readyState || "" === this.readyState) { | ||
this.readyState = "opening"; | ||
this.doOpen(); | ||
} | ||
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; | ||
this.withCredentials = opts.withCredentials; | ||
return this; | ||
} | ||
// 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; | ||
/** | ||
* Closes the transport. | ||
* | ||
* @api private | ||
*/ | ||
close() { | ||
if ("opening" === this.readyState || "open" === this.readyState) { | ||
this.doClose(); | ||
this.onClose(); | ||
} | ||
// results of ReactNative environment detection | ||
this.isReactNative = opts.isReactNative; | ||
return this; | ||
} | ||
// other options for Node.js client | ||
this.extraHeaders = opts.extraHeaders; | ||
this.localAddress = opts.localAddress; | ||
} | ||
/** | ||
* Sends multiple packets. | ||
* | ||
* @param {Array} packets | ||
* @api private | ||
*/ | ||
send(packets) { | ||
if ("open" === this.readyState) { | ||
this.write(packets); | ||
} else { | ||
throw new Error("Transport not open"); | ||
} | ||
} | ||
/** | ||
* Mix in `Emitter`. | ||
*/ | ||
/** | ||
* Called upon open | ||
* | ||
* @api private | ||
*/ | ||
onOpen() { | ||
this.readyState = "open"; | ||
this.writable = true; | ||
this.emit("open"); | ||
} | ||
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(); | ||
/** | ||
* Called with data. | ||
* | ||
* @param {String} data | ||
* @api private | ||
*/ | ||
onData(data) { | ||
const packet = parser.decodePacket(data, this.socket.binaryType); | ||
this.onPacket(packet); | ||
} | ||
return this; | ||
}; | ||
/** | ||
* Closes the transport. | ||
* | ||
* @api private | ||
*/ | ||
Transport.prototype.close = function () { | ||
if ('opening' === this.readyState || 'open' === this.readyState) { | ||
this.doClose(); | ||
this.onClose(); | ||
/** | ||
* Called with a decoded packet. | ||
*/ | ||
onPacket(packet) { | ||
this.emit("packet", packet); | ||
} | ||
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 close. | ||
* | ||
* @api private | ||
*/ | ||
onClose() { | ||
this.readyState = "closed"; | ||
this.emit("close"); | ||
} | ||
}; | ||
} | ||
/** | ||
* 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'); | ||
}; | ||
module.exports = Transport; |
@@ -1,14 +0,6 @@ | ||
/** | ||
* Module dependencies | ||
*/ | ||
const XMLHttpRequest = require("xmlhttprequest-ssl"); | ||
const XHR = require("./polling-xhr"); | ||
const JSONP = require("./polling-jsonp"); | ||
const websocket = require("./websocket"); | ||
var XMLHttpRequest = require('xmlhttprequest-ssl'); | ||
var XHR = require('./polling-xhr'); | ||
var JSONP = require('./polling-jsonp'); | ||
var websocket = require('./websocket'); | ||
/** | ||
* Export transports. | ||
*/ | ||
exports.polling = polling; | ||
@@ -24,11 +16,11 @@ exports.websocket = websocket; | ||
function polling (opts) { | ||
var xhr; | ||
var xd = false; | ||
var xs = false; | ||
var jsonp = false !== opts.jsonp; | ||
function polling(opts) { | ||
let xhr; | ||
let xd = false; | ||
let xs = false; | ||
const jsonp = false !== opts.jsonp; | ||
if (typeof location !== 'undefined') { | ||
var isSSL = 'https:' === location.protocol; | ||
var port = location.port; | ||
if (typeof location !== "undefined") { | ||
const isSSL = "https:" === location.protocol; | ||
let port = location.port; | ||
@@ -48,8 +40,8 @@ // some user agents have empty `location.port` | ||
if ('open' in xhr && !opts.forceJSONP) { | ||
if ("open" in xhr && !opts.forceJSONP) { | ||
return new XHR(opts); | ||
} else { | ||
if (!jsonp) throw new Error('JSONP disabled'); | ||
if (!jsonp) throw new Error("JSONP disabled"); | ||
return new JSONP(opts); | ||
} | ||
} |
@@ -1,26 +0,11 @@ | ||
/** | ||
* Module requirements. | ||
*/ | ||
const Polling = require("./polling"); | ||
var Polling = require('./polling'); | ||
var inherit = require('component-inherit'); | ||
const rNewline = /\n/g; | ||
const rEscapedNewline = /\\n/g; | ||
/** | ||
* Module exports. | ||
*/ | ||
module.exports = JSONPPolling; | ||
/** | ||
* Cached regular expressions. | ||
*/ | ||
var rNewline = /\n/g; | ||
var rEscapedNewline = /\\n/g; | ||
/** | ||
* Global JSONP callbacks. | ||
*/ | ||
var callbacks; | ||
let callbacks; | ||
@@ -31,210 +16,200 @@ /** | ||
function empty () { } | ||
function empty() {} | ||
/** | ||
* Until https://github.com/tc39/proposal-global is shipped. | ||
*/ | ||
function glob () { | ||
return typeof self !== 'undefined' ? self | ||
: typeof window !== 'undefined' ? window | ||
: typeof global !== 'undefined' ? global : {}; | ||
} | ||
class JSONPPolling extends Polling { | ||
/** | ||
* JSONP Polling constructor. | ||
* | ||
* @param {Object} opts. | ||
* @api public | ||
*/ | ||
constructor(opts) { | ||
super(opts); | ||
/** | ||
* JSONP Polling constructor. | ||
* | ||
* @param {Object} opts. | ||
* @api public | ||
*/ | ||
this.query = this.query || {}; | ||
function JSONPPolling (opts) { | ||
Polling.call(this, opts); | ||
// 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 | ||
callbacks = global.___eio = global.___eio || []; | ||
} | ||
this.query = this.query || {}; | ||
// callback identifier | ||
this.index = callbacks.length; | ||
// 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 | ||
var global = glob(); | ||
callbacks = global.___eio = (global.___eio || []); | ||
} | ||
// add callback to jsonp global | ||
const self = this; | ||
callbacks.push(function(msg) { | ||
self.onData(msg); | ||
}); | ||
// callback identifier | ||
this.index = callbacks.length; | ||
// append to query string | ||
this.query.j = this.index; | ||
// add callback to jsonp global | ||
var self = this; | ||
callbacks.push(function (msg) { | ||
self.onData(msg); | ||
}); | ||
// prevent spurious errors from being emitted when the window is unloaded | ||
if (typeof addEventListener === "function") { | ||
addEventListener( | ||
"beforeunload", | ||
function() { | ||
if (self.script) self.script.onerror = empty; | ||
}, | ||
false | ||
); | ||
} | ||
} | ||
// append to query string | ||
this.query.j = this.index; | ||
// prevent spurious errors from being emitted when the window is unloaded | ||
if (typeof addEventListener === 'function') { | ||
addEventListener('beforeunload', function () { | ||
if (self.script) self.script.onerror = empty; | ||
}, false); | ||
/** | ||
* JSONP only supports binary as base64 encoded strings | ||
*/ | ||
get supportsBinary() { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Inherits from Polling. | ||
*/ | ||
/** | ||
* Closes the socket. | ||
* | ||
* @api private | ||
*/ | ||
doClose() { | ||
if (this.script) { | ||
this.script.parentNode.removeChild(this.script); | ||
this.script = null; | ||
} | ||
inherit(JSONPPolling, Polling); | ||
if (this.form) { | ||
this.form.parentNode.removeChild(this.form); | ||
this.form = null; | ||
this.iframe = null; | ||
} | ||
/* | ||
* 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; | ||
super.doClose(); | ||
} | ||
if (this.form) { | ||
this.form.parentNode.removeChild(this.form); | ||
this.form = null; | ||
this.iframe = null; | ||
} | ||
/** | ||
* Starts a poll cycle. | ||
* | ||
* @api private | ||
*/ | ||
doPoll() { | ||
const self = this; | ||
const script = document.createElement("script"); | ||
Polling.prototype.doClose.call(this); | ||
}; | ||
if (this.script) { | ||
this.script.parentNode.removeChild(this.script); | ||
this.script = null; | ||
} | ||
/** | ||
* Starts a poll cycle. | ||
* | ||
* @api private | ||
*/ | ||
script.async = true; | ||
script.src = this.uri(); | ||
script.onerror = function(e) { | ||
self.onError("jsonp poll error", e); | ||
}; | ||
JSONPPolling.prototype.doPoll = function () { | ||
var self = this; | ||
var script = document.createElement('script'); | ||
const insertAt = document.getElementsByTagName("script")[0]; | ||
if (insertAt) { | ||
insertAt.parentNode.insertBefore(script, insertAt); | ||
} else { | ||
(document.head || document.body).appendChild(script); | ||
} | ||
this.script = script; | ||
if (this.script) { | ||
this.script.parentNode.removeChild(this.script); | ||
this.script = null; | ||
} | ||
const isUAgecko = | ||
"undefined" !== typeof navigator && /gecko/i.test(navigator.userAgent); | ||
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); | ||
if (isUAgecko) { | ||
setTimeout(function() { | ||
const iframe = document.createElement("iframe"); | ||
document.body.appendChild(iframe); | ||
document.body.removeChild(iframe); | ||
}, 100); | ||
} | ||
} | ||
this.script = script; | ||
var isUAgecko = 'undefined' !== typeof navigator && /gecko/i.test(navigator.userAgent); | ||
/** | ||
* Writes with a hidden iframe. | ||
* | ||
* @param {String} data to send | ||
* @param {Function} called upon flush. | ||
* @api private | ||
*/ | ||
doWrite(data, fn) { | ||
const self = this; | ||
let iframe; | ||
if (isUAgecko) { | ||
setTimeout(function () { | ||
var iframe = document.createElement('iframe'); | ||
document.body.appendChild(iframe); | ||
document.body.removeChild(iframe); | ||
}, 100); | ||
} | ||
}; | ||
if (!this.form) { | ||
const form = document.createElement("form"); | ||
const area = document.createElement("textarea"); | ||
const id = (this.iframeId = "eio_iframe_" + this.index); | ||
/** | ||
* Writes with a hidden iframe. | ||
* | ||
* @param {String} data to send | ||
* @param {Function} called upon flush. | ||
* @api private | ||
*/ | ||
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); | ||
JSONPPolling.prototype.doWrite = function (data, fn) { | ||
var self = this; | ||
this.form = form; | ||
this.area = area; | ||
} | ||
if (!this.form) { | ||
var form = document.createElement('form'); | ||
var area = document.createElement('textarea'); | ||
var id = this.iframeId = 'eio_iframe_' + this.index; | ||
var iframe; | ||
this.form.action = this.uri(); | ||
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); | ||
function complete() { | ||
initIframe(); | ||
fn(); | ||
} | ||
this.form = form; | ||
this.area = area; | ||
} | ||
function initIframe() { | ||
if (self.iframe) { | ||
try { | ||
self.form.removeChild(self.iframe); | ||
} catch (e) { | ||
self.onError("jsonp polling iframe removal error", e); | ||
} | ||
} | ||
this.form.action = this.uri(); | ||
function complete () { | ||
initIframe(); | ||
fn(); | ||
} | ||
function initIframe () { | ||
if (self.iframe) { | ||
try { | ||
self.form.removeChild(self.iframe); | ||
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher) | ||
const html = '<iframe src="javascript:0" name="' + self.iframeId + '">'; | ||
iframe = document.createElement(html); | ||
} catch (e) { | ||
self.onError('jsonp polling iframe removal error', e); | ||
iframe = document.createElement("iframe"); | ||
iframe.name = self.iframeId; | ||
iframe.src = "javascript:0"; | ||
} | ||
} | ||
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; | ||
} | ||
iframe.id = self.iframeId; | ||
initIframe(); | ||
self.form.appendChild(iframe); | ||
self.iframe = iframe; | ||
} | ||
// 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"); | ||
initIframe(); | ||
try { | ||
this.form.submit(); | ||
} catch (e) {} | ||
// 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'); | ||
if (this.iframe.attachEvent) { | ||
this.iframe.onreadystatechange = function() { | ||
if (self.iframe.readyState === "complete") { | ||
complete(); | ||
} | ||
}; | ||
} else { | ||
this.iframe.onload = complete; | ||
} | ||
} | ||
} | ||
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; | ||
} | ||
}; | ||
module.exports = JSONPPolling; |
/* global attachEvent */ | ||
/** | ||
* Module requirements. | ||
*/ | ||
const XMLHttpRequest = require("xmlhttprequest-ssl"); | ||
const Polling = require("./polling"); | ||
const Emitter = require("component-emitter"); | ||
const { pick } = require("../util"); | ||
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'); | ||
const debug = require("debug")("engine.io-client:polling-xhr"); | ||
/** | ||
* Module exports. | ||
* Empty function | ||
*/ | ||
module.exports = XHR; | ||
module.exports.Request = Request; | ||
function empty() {} | ||
/** | ||
* Empty function | ||
*/ | ||
const hasXHR2 = (function() { | ||
const XMLHttpRequest = require("xmlhttprequest-ssl"); | ||
const xhr = new XMLHttpRequest({ xdomain: false }); | ||
return null != xhr.responseType; | ||
})(); | ||
function empty () {} | ||
class XHR extends Polling { | ||
/** | ||
* XHR Polling constructor. | ||
* | ||
* @param {Object} opts | ||
* @api public | ||
*/ | ||
constructor(opts) { | ||
super(opts); | ||
/** | ||
* XHR Polling constructor. | ||
* | ||
* @param {Object} opts | ||
* @api public | ||
*/ | ||
if (typeof location !== "undefined") { | ||
const isSSL = "https:" === location.protocol; | ||
let port = location.port; | ||
function XHR (opts) { | ||
Polling.call(this, opts); | ||
this.requestTimeout = opts.requestTimeout; | ||
this.extraHeaders = opts.extraHeaders; | ||
// some user agents have empty `location.port` | ||
if (!port) { | ||
port = isSSL ? 443 : 80; | ||
} | ||
if (typeof location !== 'undefined') { | ||
var isSSL = 'https:' === location.protocol; | ||
var port = location.port; | ||
// some user agents have empty `location.port` | ||
if (!port) { | ||
port = isSSL ? 443 : 80; | ||
this.xd = | ||
(typeof location !== "undefined" && | ||
opts.hostname !== location.hostname) || | ||
port !== opts.port; | ||
this.xs = opts.secure !== isSSL; | ||
} | ||
/** | ||
* XHR supports binary | ||
*/ | ||
const forceBase64 = opts && opts.forceBase64; | ||
this.supportsBinary = hasXHR2 && !forceBase64; | ||
} | ||
this.xd = (typeof location !== 'undefined' && opts.hostname !== location.hostname) || | ||
port !== opts.port; | ||
this.xs = opts.secure !== isSSL; | ||
/** | ||
* Creates a request. | ||
* | ||
* @param {String} method | ||
* @api private | ||
*/ | ||
request(opts = {}) { | ||
Object.assign( | ||
opts, | ||
{ supportsBinary: this.supportsBinary, xd: this.xd, xs: this.xs }, | ||
this.opts | ||
); | ||
return new Request(this.uri(), opts); | ||
} | ||
} | ||
/** | ||
* Inherits from Polling. | ||
*/ | ||
/** | ||
* Sends data. | ||
* | ||
* @param {String} data to send. | ||
* @param {Function} called upon flush. | ||
* @api private | ||
*/ | ||
doWrite(data, fn) { | ||
const isBinary = typeof data !== "string" && data !== undefined; | ||
const req = this.request({ | ||
method: "POST", | ||
data: data, | ||
isBinary: isBinary | ||
}); | ||
const self = this; | ||
req.on("success", fn); | ||
req.on("error", function(err) { | ||
self.onError("xhr post error", err); | ||
}); | ||
} | ||
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; | ||
opts.withCredentials = this.withCredentials; | ||
// 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.withCredentials = opts.withCredentials; | ||
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(); | ||
/** | ||
* Starts a poll cycle. | ||
* | ||
* @api private | ||
*/ | ||
doPoll() { | ||
debug("xhr poll"); | ||
const req = this.request(); | ||
const self = this; | ||
req.on("data", function(data) { | ||
self.onData(data); | ||
}); | ||
req.on("error", function(err) { | ||
self.onError("xhr poll error", err); | ||
}); | ||
} | ||
} | ||
/** | ||
* Mix in `Emitter`. | ||
*/ | ||
class Request extends Emitter { | ||
/** | ||
* Request constructor | ||
* | ||
* @param {Object} options | ||
* @api public | ||
*/ | ||
constructor(uri, opts) { | ||
super(); | ||
this.opts = opts; | ||
Emitter(Request.prototype); | ||
this.method = opts.method || "GET"; | ||
this.uri = uri; | ||
this.async = false !== opts.async; | ||
this.data = undefined !== opts.data ? opts.data : null; | ||
this.isBinary = opts.isBinary; | ||
this.supportsBinary = opts.supportsBinary; | ||
/** | ||
* Creates the XHR object and sends the request. | ||
* | ||
* @api private | ||
*/ | ||
this.create(); | ||
} | ||
Request.prototype.create = function () { | ||
var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR }; | ||
/** | ||
* Creates the XHR object and sends the request. | ||
* | ||
* @api private | ||
*/ | ||
create() { | ||
const opts = pick( | ||
this.opts, | ||
"agent", | ||
"enablesXDR", | ||
"pfx", | ||
"key", | ||
"passphrase", | ||
"cert", | ||
"ca", | ||
"ciphers", | ||
"rejectUnauthorized" | ||
); | ||
opts.xdomain = !!this.opts.xd; | ||
opts.xscheme = !!this.opts.xs; | ||
// 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; | ||
const xhr = (this.xhr = new XMLHttpRequest(opts)); | ||
const self = this; | ||
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]); | ||
debug("xhr open %s: %s", this.method, this.uri); | ||
xhr.open(this.method, this.uri, this.async); | ||
try { | ||
if (this.opts.extraHeaders) { | ||
xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true); | ||
for (let i in this.opts.extraHeaders) { | ||
if (this.opts.extraHeaders.hasOwnProperty(i)) { | ||
xhr.setRequestHeader(i, this.opts.extraHeaders[i]); | ||
} | ||
} | ||
} | ||
} catch (e) { | ||
console.log(e); | ||
} | ||
} catch (e) {} | ||
if ('POST' === this.method) { | ||
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 { | ||
if (this.isBinary) { | ||
xhr.setRequestHeader('Content-type', 'application/octet-stream'); | ||
} else { | ||
xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); | ||
} | ||
xhr.setRequestHeader("Accept", "*/*"); | ||
} catch (e) {} | ||
} | ||
try { | ||
xhr.setRequestHeader('Accept', '*/*'); | ||
} catch (e) {} | ||
// ie6 check | ||
if ("withCredentials" in xhr) { | ||
xhr.withCredentials = this.opts.withCredentials; | ||
} | ||
// ie6 check | ||
if ('withCredentials' in xhr) { | ||
xhr.withCredentials = this.withCredentials; | ||
} | ||
if (this.opts.requestTimeout) { | ||
xhr.timeout = this.opts.requestTimeout; | ||
} | ||
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) { | ||
try { | ||
const contentType = xhr.getResponseHeader("Content-Type"); | ||
if ( | ||
(self.supportsBinary && | ||
contentType === "application/octet-stream") || | ||
contentType === "application/octet-stream; charset=UTF-8" | ||
) { | ||
xhr.responseType = "arraybuffer"; | ||
} | ||
} catch (e) {} | ||
} | ||
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(typeof xhr.status === "number" ? xhr.status : 0); | ||
}, 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 (this.hasXDR()) { | ||
xhr.onload = function () { | ||
self.onLoad(); | ||
}; | ||
xhr.onerror = function () { | ||
self.onError(xhr.responseText); | ||
}; | ||
} else { | ||
xhr.onreadystatechange = function () { | ||
if (xhr.readyState === 2) { | ||
try { | ||
var contentType = xhr.getResponseHeader('Content-Type'); | ||
if (self.supportsBinary && contentType === 'application/octet-stream' || contentType === 'application/octet-stream; charset=UTF-8') { | ||
xhr.responseType = 'arraybuffer'; | ||
} | ||
} catch (e) {} | ||
} | ||
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(typeof xhr.status === 'number' ? xhr.status : 0); | ||
}, 0); | ||
} | ||
}; | ||
if (typeof document !== "undefined") { | ||
this.index = Request.requestsCount++; | ||
Request.requests[this.index] = this; | ||
} | ||
} | ||
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; | ||
/** | ||
* Called upon successful response. | ||
* | ||
* @api private | ||
*/ | ||
onSuccess() { | ||
this.emit("success"); | ||
this.cleanup(); | ||
} | ||
if (typeof document !== 'undefined') { | ||
this.index = Request.requestsCount++; | ||
Request.requests[this.index] = this; | ||
/** | ||
* Called if we have data. | ||
* | ||
* @api private | ||
*/ | ||
onData(data) { | ||
this.emit("data", data); | ||
this.onSuccess(); | ||
} | ||
}; | ||
/** | ||
* Called upon successful response. | ||
* | ||
* @api private | ||
*/ | ||
/** | ||
* Called upon error. | ||
* | ||
* @api private | ||
*/ | ||
onError(err) { | ||
this.emit("error", err); | ||
this.cleanup(true); | ||
} | ||
Request.prototype.onSuccess = function () { | ||
this.emit('success'); | ||
this.cleanup(); | ||
}; | ||
/** | ||
* Cleans up house. | ||
* | ||
* @api private | ||
*/ | ||
cleanup(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; | ||
} | ||
/** | ||
* Called if we have data. | ||
* | ||
* @api private | ||
*/ | ||
if (fromError) { | ||
try { | ||
this.xhr.abort(); | ||
} catch (e) {} | ||
} | ||
Request.prototype.onData = function (data) { | ||
this.emit('data', data); | ||
this.onSuccess(); | ||
}; | ||
if (typeof document !== "undefined") { | ||
delete Request.requests[this.index]; | ||
} | ||
/** | ||
* 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; | ||
this.xhr = null; | ||
} | ||
// xmlhttprequest | ||
if (this.hasXDR()) { | ||
this.xhr.onload = this.xhr.onerror = empty; | ||
} else { | ||
this.xhr.onreadystatechange = empty; | ||
} | ||
if (fromError) { | ||
/** | ||
* Called upon load. | ||
* | ||
* @api private | ||
*/ | ||
onLoad() { | ||
let data; | ||
try { | ||
this.xhr.abort(); | ||
} catch (e) {} | ||
let contentType; | ||
try { | ||
contentType = this.xhr.getResponseHeader("Content-Type"); | ||
} catch (e) {} | ||
if ( | ||
contentType === "application/octet-stream" || | ||
contentType === "application/octet-stream; charset=UTF-8" | ||
) { | ||
data = this.xhr.response || this.xhr.responseText; | ||
} else { | ||
data = this.xhr.responseText; | ||
} | ||
} catch (e) { | ||
this.onError(e); | ||
} | ||
if (null != data) { | ||
this.onData(data); | ||
} | ||
} | ||
if (typeof document !== 'undefined') { | ||
delete Request.requests[this.index]; | ||
/** | ||
* Check if it has XDomainRequest. | ||
* | ||
* @api private | ||
*/ | ||
hasXDR() { | ||
return typeof XDomainRequest !== "undefined" && !this.xs && this.enablesXDR; | ||
} | ||
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' || contentType === 'application/octet-stream; charset=UTF-8') { | ||
data = this.xhr.response || this.xhr.responseText; | ||
} else { | ||
data = this.xhr.responseText; | ||
} | ||
} catch (e) { | ||
this.onError(e); | ||
/** | ||
* Aborts the request. | ||
* | ||
* @api public | ||
*/ | ||
abort() { | ||
this.cleanup(); | ||
} | ||
if (null != data) { | ||
this.onData(data); | ||
} | ||
}; | ||
} | ||
/** | ||
* Check if it has XDomainRequest. | ||
* | ||
* @api private | ||
*/ | ||
Request.prototype.hasXDR = function () { | ||
return typeof XDomainRequest !== 'undefined' && !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 | ||
@@ -402,7 +359,7 @@ * memory leaks (e.g. when using IE) and to ensure that no spurious error is | ||
if (typeof document !== 'undefined') { | ||
if (typeof attachEvent === 'function') { | ||
attachEvent('onunload', unloadHandler); | ||
} else if (typeof addEventListener === 'function') { | ||
var terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload'; | ||
if (typeof document !== "undefined") { | ||
if (typeof attachEvent === "function") { | ||
attachEvent("onunload", unloadHandler); | ||
} else if (typeof addEventListener === "function") { | ||
const terminationEvent = "onpagehide" in self ? "pagehide" : "unload"; | ||
addEventListener(terminationEvent, unloadHandler, false); | ||
@@ -412,4 +369,4 @@ } | ||
function unloadHandler () { | ||
for (var i in Request.requests) { | ||
function unloadHandler() { | ||
for (let i in Request.requests) { | ||
if (Request.requests.hasOwnProperty(i)) { | ||
@@ -420,1 +377,4 @@ Request.requests[i].abort(); | ||
} | ||
module.exports = XHR; | ||
module.exports.Request = Request; |
@@ -1,245 +0,212 @@ | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const Transport = require("../transport"); | ||
const parseqs = require("parseqs"); | ||
const parser = require("engine.io-parser"); | ||
const yeast = require("yeast"); | ||
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'); | ||
const debug = require("debug")("engine.io-client:polling"); | ||
/** | ||
* Module exports. | ||
*/ | ||
class Polling extends Transport { | ||
/** | ||
* Transport name. | ||
*/ | ||
get name() { | ||
return "polling"; | ||
} | ||
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; | ||
/** | ||
* Opens the socket (triggers polling). We write a PING message to determine | ||
* when the transport is open. | ||
* | ||
* @api private | ||
*/ | ||
doOpen() { | ||
this.poll(); | ||
} | ||
Transport.call(this, opts); | ||
} | ||
/** | ||
* Inherits from Transport. | ||
*/ | ||
/** | ||
* Pauses polling. | ||
* | ||
* @param {Function} callback upon buffers are flushed and transport is paused | ||
* @api private | ||
*/ | ||
pause(onPause) { | ||
const self = this; | ||
inherit(Polling, Transport); | ||
this.readyState = "pausing"; | ||
/** | ||
* Transport name. | ||
*/ | ||
function pause() { | ||
debug("paused"); | ||
self.readyState = "paused"; | ||
onPause(); | ||
} | ||
Polling.prototype.name = 'polling'; | ||
if (this.polling || !this.writable) { | ||
let total = 0; | ||
/** | ||
* Opens the socket (triggers polling). We write a PING message to determine | ||
* when the transport is open. | ||
* | ||
* @api private | ||
*/ | ||
if (this.polling) { | ||
debug("we are currently polling - waiting to pause"); | ||
total++; | ||
this.once("pollComplete", function() { | ||
debug("pre-pause polling complete"); | ||
--total || pause(); | ||
}); | ||
} | ||
Polling.prototype.doOpen = function () { | ||
this.poll(); | ||
}; | ||
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(); | ||
} | ||
} | ||
/** | ||
* Pauses polling. | ||
* | ||
* @param {Function} callback upon buffers are flushed and transport is paused | ||
* @api private | ||
*/ | ||
/** | ||
* Starts polling cycle. | ||
* | ||
* @api public | ||
*/ | ||
poll() { | ||
debug("polling"); | ||
this.polling = true; | ||
this.doPoll(); | ||
this.emit("poll"); | ||
} | ||
Polling.prototype.pause = function (onPause) { | ||
var self = this; | ||
/** | ||
* Overloads onData to detect payloads. | ||
* | ||
* @api private | ||
*/ | ||
onData(data) { | ||
const self = this; | ||
debug("polling got data %s", data); | ||
const callback = function(packet, index, total) { | ||
// if its the first message we consider the transport open | ||
if ("opening" === self.readyState) { | ||
self.onOpen(); | ||
} | ||
this.readyState = 'pausing'; | ||
// if its a close packet, we close the ongoing requests | ||
if ("close" === packet.type) { | ||
self.onClose(); | ||
return false; | ||
} | ||
function pause () { | ||
debug('paused'); | ||
self.readyState = 'paused'; | ||
onPause(); | ||
} | ||
// otherwise bypass onData and handle the message | ||
self.onPacket(packet); | ||
}; | ||
if (this.polling || !this.writable) { | ||
var total = 0; | ||
// decode payload | ||
parser.decodePayload(data, this.socket.binaryType, callback); | ||
if (this.polling) { | ||
debug('we are currently polling - waiting to pause'); | ||
total++; | ||
this.once('pollComplete', function () { | ||
debug('pre-pause polling complete'); | ||
--total || pause(); | ||
}); | ||
} | ||
// 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 (!this.writable) { | ||
debug('we are currently writing - waiting to pause'); | ||
total++; | ||
this.once('drain', function () { | ||
debug('pre-pause writing complete'); | ||
--total || pause(); | ||
}); | ||
if ("open" === this.readyState) { | ||
this.poll(); | ||
} else { | ||
debug('ignoring poll - transport state "%s"', this.readyState); | ||
} | ||
} | ||
} else { | ||
pause(); | ||
} | ||
}; | ||
/** | ||
* Starts polling cycle. | ||
* | ||
* @api public | ||
*/ | ||
/** | ||
* For polling, send a close packet. | ||
* | ||
* @api private | ||
*/ | ||
doClose() { | ||
const self = this; | ||
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(); | ||
function close() { | ||
debug("writing close packet"); | ||
self.write([{ type: "close" }]); | ||
} | ||
// 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(); | ||
if ("open" === this.readyState) { | ||
debug("transport open - closing"); | ||
close(); | ||
} else { | ||
debug('ignoring poll - transport state "%s"', this.readyState); | ||
// in case we're trying to close while | ||
// handshaking is in progress (GH-164) | ||
debug("transport not open - deferring close"); | ||
this.once("open", close); | ||
} | ||
} | ||
}; | ||
/** | ||
* For polling, send a close packet. | ||
* | ||
* @api private | ||
*/ | ||
/** | ||
* Writes a packets payload. | ||
* | ||
* @param {Array} data packets | ||
* @param {Function} drain callback | ||
* @api private | ||
*/ | ||
write(packets) { | ||
const self = this; | ||
this.writable = false; | ||
const callbackfn = function() { | ||
self.writable = true; | ||
self.emit("drain"); | ||
}; | ||
Polling.prototype.doClose = function () { | ||
var self = this; | ||
function close () { | ||
debug('writing close packet'); | ||
self.write([{ type: 'close' }]); | ||
parser.encodePayload(packets, this.supportsBinary, function(data) { | ||
self.doWrite(data, callbackfn); | ||
}); | ||
} | ||
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); | ||
} | ||
}; | ||
/** | ||
* Generates uri for connection. | ||
* | ||
* @api private | ||
*/ | ||
uri() { | ||
let query = this.query || {}; | ||
const schema = this.opts.secure ? "https" : "http"; | ||
let port = ""; | ||
/** | ||
* Writes a packets payload. | ||
* | ||
* @param {Array} data packets | ||
* @param {Function} drain callback | ||
* @api private | ||
*/ | ||
// cache busting is forced | ||
if (false !== this.opts.timestampRequests) { | ||
query[this.opts.timestampParam] = yeast(); | ||
} | ||
Polling.prototype.write = function (packets) { | ||
var self = this; | ||
this.writable = false; | ||
var callbackfn = function () { | ||
self.writable = true; | ||
self.emit('drain'); | ||
}; | ||
if (!this.supportsBinary && !query.sid) { | ||
query.b64 = 1; | ||
} | ||
parser.encodePayload(packets, this.supportsBinary, function (data) { | ||
self.doWrite(data, callbackfn); | ||
}); | ||
}; | ||
query = parseqs.encode(query); | ||
/** | ||
* Generates uri for connection. | ||
* | ||
* @api private | ||
*/ | ||
// avoid port if default for schema | ||
if ( | ||
this.opts.port && | ||
(("https" === schema && Number(this.opts.port) !== 443) || | ||
("http" === schema && Number(this.opts.port) !== 80)) | ||
) { | ||
port = ":" + this.opts.port; | ||
} | ||
Polling.prototype.uri = function () { | ||
var query = this.query || {}; | ||
var schema = this.secure ? 'https' : 'http'; | ||
var port = ''; | ||
// prepend ? to query | ||
if (query.length) { | ||
query = "?" + query; | ||
} | ||
// cache busting is forced | ||
if (false !== this.timestampRequests) { | ||
query[this.timestampParam] = yeast(); | ||
const ipv6 = this.opts.hostname.indexOf(":") !== -1; | ||
return ( | ||
schema + | ||
"://" + | ||
(ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) + | ||
port + | ||
this.opts.path + | ||
query | ||
); | ||
} | ||
} | ||
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; | ||
}; | ||
module.exports = Polling; |
@@ -1,24 +0,21 @@ | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const Transport = require("../transport"); | ||
const parser = require("engine.io-parser"); | ||
const parseqs = require("parseqs"); | ||
const yeast = require("yeast"); | ||
const { pick } = require("../util"); | ||
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'); | ||
const debug = require("debug")("engine.io-client:websocket"); | ||
var BrowserWebSocket, NodeWebSocket; | ||
let BrowserWebSocket, NodeWebSocket; | ||
if (typeof WebSocket !== 'undefined') { | ||
if (typeof WebSocket !== "undefined") { | ||
BrowserWebSocket = WebSocket; | ||
} else if (typeof self !== 'undefined') { | ||
} else if (typeof self !== "undefined") { | ||
BrowserWebSocket = self.WebSocket || self.MozWebSocket; | ||
} | ||
if (typeof window === 'undefined') { | ||
if (typeof window === "undefined") { | ||
try { | ||
NodeWebSocket = require('ws'); | ||
} catch (e) { } | ||
NodeWebSocket = require("ws"); | ||
} catch (e) {} | ||
} | ||
@@ -32,265 +29,260 @@ | ||
var WebSocketImpl = BrowserWebSocket || NodeWebSocket; | ||
let WebSocketImpl = BrowserWebSocket || NodeWebSocket; | ||
/** | ||
* Module exports. | ||
*/ | ||
class WS extends Transport { | ||
/** | ||
* WebSocket transport constructor. | ||
* | ||
* @api {Object} connection options | ||
* @api public | ||
*/ | ||
constructor(opts) { | ||
super(opts); | ||
module.exports = WS; | ||
const forceBase64 = opts && opts.forceBase64; | ||
if (forceBase64) { | ||
this.supportsBinary = false; | ||
} | ||
this.usingBrowserWebSocket = BrowserWebSocket && !opts.forceNode; | ||
if (!this.usingBrowserWebSocket) { | ||
WebSocketImpl = NodeWebSocket; | ||
} | ||
// WebSockets support binary | ||
this.supportsBinary = true; | ||
} | ||
/** | ||
* WebSocket transport constructor. | ||
* | ||
* @api {Object} connection options | ||
* @api public | ||
*/ | ||
function WS (opts) { | ||
var forceBase64 = (opts && opts.forceBase64); | ||
if (forceBase64) { | ||
this.supportsBinary = false; | ||
/** | ||
* Transport name. | ||
* | ||
* @api public | ||
*/ | ||
get name() { | ||
return "websocket"; | ||
} | ||
this.perMessageDeflate = opts.perMessageDeflate; | ||
this.usingBrowserWebSocket = BrowserWebSocket && !opts.forceNode; | ||
this.protocols = opts.protocols; | ||
if (!this.usingBrowserWebSocket) { | ||
WebSocketImpl = NodeWebSocket; | ||
} | ||
Transport.call(this, opts); | ||
} | ||
/** | ||
* Inherits from Transport. | ||
*/ | ||
/** | ||
* Opens socket. | ||
* | ||
* @api private | ||
*/ | ||
doOpen() { | ||
if (!this.check()) { | ||
// let probe timeout | ||
return; | ||
} | ||
inherit(WS, Transport); | ||
const uri = this.uri(); | ||
const protocols = this.opts.protocols; | ||
const opts = pick( | ||
this.opts, | ||
"agent", | ||
"perMessageDeflate", | ||
"pfx", | ||
"key", | ||
"passphrase", | ||
"cert", | ||
"ca", | ||
"ciphers", | ||
"rejectUnauthorized", | ||
"localAddress" | ||
); | ||
if (this.opts.extraHeaders) { | ||
opts.headers = this.opts.extraHeaders; | ||
} | ||
/** | ||
* Transport name. | ||
* | ||
* @api public | ||
*/ | ||
try { | ||
this.ws = | ||
this.usingBrowserWebSocket && !this.opts.isReactNative | ||
? protocols | ||
? new WebSocketImpl(uri, protocols) | ||
: new WebSocketImpl(uri) | ||
: new WebSocketImpl(uri, protocols, opts); | ||
} catch (err) { | ||
return this.emit("error", err); | ||
} | ||
WS.prototype.name = 'websocket'; | ||
if (this.ws.binaryType === undefined) { | ||
this.supportsBinary = false; | ||
} | ||
/* | ||
* WebSockets support binary | ||
*/ | ||
if (this.ws.supports && this.ws.supports.binary) { | ||
this.supportsBinary = true; | ||
this.ws.binaryType = "nodebuffer"; | ||
} else { | ||
this.ws.binaryType = "arraybuffer"; | ||
} | ||
WS.prototype.supportsBinary = true; | ||
/** | ||
* Opens socket. | ||
* | ||
* @api private | ||
*/ | ||
WS.prototype.doOpen = function () { | ||
if (!this.check()) { | ||
// let probe timeout | ||
return; | ||
this.addEventListeners(); | ||
} | ||
var uri = this.uri(); | ||
var protocols = this.protocols; | ||
var opts = { | ||
agent: this.agent, | ||
perMessageDeflate: this.perMessageDeflate | ||
}; | ||
/** | ||
* Adds event listeners to the socket | ||
* | ||
* @api private | ||
*/ | ||
addEventListeners() { | ||
const self = this; | ||
// 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; | ||
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); | ||
}; | ||
} | ||
if (this.localAddress) { | ||
opts.localAddress = this.localAddress; | ||
} | ||
try { | ||
this.ws = | ||
this.usingBrowserWebSocket && !this.isReactNative | ||
? protocols | ||
? new WebSocketImpl(uri, protocols) | ||
: new WebSocketImpl(uri) | ||
: new WebSocketImpl(uri, protocols, opts); | ||
} catch (err) { | ||
return this.emit('error', err); | ||
} | ||
/** | ||
* Writes data to socket. | ||
* | ||
* @param {Array} array of packets. | ||
* @api private | ||
*/ | ||
write(packets) { | ||
const self = this; | ||
this.writable = false; | ||
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) { | ||
// encodePacket efficient as it uses WS framing | ||
// no need for encodePayload | ||
let total = packets.length; | ||
let i = 0; | ||
const l = total; | ||
for (; i < l; i++) { | ||
(function(packet) { | ||
parser.encodePacket(packet, self.supportsBinary, function(data) { | ||
// always create a new object (GH-437) | ||
var opts = {}; | ||
if (packet.options) { | ||
opts.compress = packet.options.compress; | ||
} | ||
const opts = {}; | ||
if (!self.usingBrowserWebSocket) { | ||
if (packet.options) { | ||
opts.compress = packet.options.compress; | ||
} | ||
if (self.perMessageDeflate) { | ||
var len = 'string' === typeof data ? Buffer.byteLength(data) : data.length; | ||
if (len < self.perMessageDeflate.threshold) { | ||
opts.compress = false; | ||
if (self.opts.perMessageDeflate) { | ||
const len = | ||
"string" === typeof data | ||
? Buffer.byteLength(data) | ||
: data.length; | ||
if (len < self.opts.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); | ||
// 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"); | ||
} | ||
} catch (e) { | ||
debug('websocket closed before onclose event'); | ||
} | ||
--total || done(); | ||
}); | ||
})(packets[i]); | ||
} | ||
--total || done(); | ||
}); | ||
})(packets[i]); | ||
} | ||
function done () { | ||
self.emit('flush'); | ||
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); | ||
// 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 | ||
*/ | ||
/** | ||
* Called upon close | ||
* | ||
* @api private | ||
*/ | ||
onClose() { | ||
Transport.prototype.onClose.call(this); | ||
} | ||
WS.prototype.onClose = function () { | ||
Transport.prototype.onClose.call(this); | ||
}; | ||
/** | ||
* Closes socket. | ||
* | ||
* @api private | ||
*/ | ||
doClose() { | ||
if (typeof this.ws !== "undefined") { | ||
this.ws.close(); | ||
} | ||
} | ||
/** | ||
* Closes socket. | ||
* | ||
* @api private | ||
*/ | ||
/** | ||
* Generates uri for connection. | ||
* | ||
* @api private | ||
*/ | ||
uri() { | ||
let query = this.query || {}; | ||
const schema = this.opts.secure ? "wss" : "ws"; | ||
let port = ""; | ||
WS.prototype.doClose = function () { | ||
if (typeof this.ws !== 'undefined') { | ||
this.ws.close(); | ||
} | ||
}; | ||
// avoid port if default for schema | ||
if ( | ||
this.opts.port && | ||
(("wss" === schema && Number(this.opts.port) !== 443) || | ||
("ws" === schema && Number(this.opts.port) !== 80)) | ||
) { | ||
port = ":" + this.opts.port; | ||
} | ||
/** | ||
* Generates uri for connection. | ||
* | ||
* @api private | ||
*/ | ||
// append timestamp to URI | ||
if (this.opts.timestampRequests) { | ||
query[this.opts.timestampParam] = yeast(); | ||
} | ||
WS.prototype.uri = function () { | ||
var query = this.query || {}; | ||
var schema = this.secure ? 'wss' : 'ws'; | ||
var port = ''; | ||
// communicate binary support capabilities | ||
if (!this.supportsBinary) { | ||
query.b64 = 1; | ||
} | ||
// avoid port if default for schema | ||
if (this.port && (('wss' === schema && Number(this.port) !== 443) || | ||
('ws' === schema && Number(this.port) !== 80))) { | ||
port = ':' + this.port; | ||
} | ||
query = parseqs.encode(query); | ||
// append timestamp to URI | ||
if (this.timestampRequests) { | ||
query[this.timestampParam] = yeast(); | ||
} | ||
// prepend ? to query | ||
if (query.length) { | ||
query = "?" + query; | ||
} | ||
// communicate binary support capabilities | ||
if (!this.supportsBinary) { | ||
query.b64 = 1; | ||
const ipv6 = this.opts.hostname.indexOf(":") !== -1; | ||
return ( | ||
schema + | ||
"://" + | ||
(ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) + | ||
port + | ||
this.opts.path + | ||
query | ||
); | ||
} | ||
query = parseqs.encode(query); | ||
// prepend ? to query | ||
if (query.length) { | ||
query = '?' + query; | ||
/** | ||
* Feature detection for WebSocket. | ||
* | ||
* @return {Boolean} whether this transport is available. | ||
* @api public | ||
*/ | ||
check() { | ||
return ( | ||
!!WebSocketImpl && | ||
!("__initialize" in WebSocketImpl && this.name === WS.prototype.name) | ||
); | ||
} | ||
} | ||
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 !!WebSocketImpl && !('__initialize' in WebSocketImpl && this.name === WS.prototype.name); | ||
}; | ||
module.exports = WS; |
// browser shim for xmlhttprequest module | ||
var hasCORS = require('has-cors'); | ||
const hasCORS = require("has-cors"); | ||
module.exports = function (opts) { | ||
var xdomain = opts.xdomain; | ||
module.exports = function(opts) { | ||
const 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; | ||
const 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; | ||
const enablesXDR = opts.enablesXDR; | ||
// XMLHttpRequest can be disabled on IE | ||
try { | ||
if ('undefined' !== typeof XMLHttpRequest && (!xdomain || hasCORS)) { | ||
if ("undefined" !== typeof XMLHttpRequest && (!xdomain || hasCORS)) { | ||
return new XMLHttpRequest(); | ||
} | ||
} catch (e) { } | ||
} catch (e) {} | ||
@@ -27,12 +27,14 @@ // Use XDomainRequest for IE8 if enablesXDR is true | ||
try { | ||
if ('undefined' !== typeof XDomainRequest && !xscheme && enablesXDR) { | ||
if ("undefined" !== typeof XDomainRequest && !xscheme && enablesXDR) { | ||
return new XDomainRequest(); | ||
} | ||
} catch (e) { } | ||
} catch (e) {} | ||
if (!xdomain) { | ||
try { | ||
return new self[['Active'].concat('Object').join('X')]('Microsoft.XMLHTTP'); | ||
} catch (e) { } | ||
return new self[["Active"].concat("Object").join("X")]( | ||
"Microsoft.XMLHTTP" | ||
); | ||
} catch (e) {} | ||
} | ||
}; |
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "3.4.0", | ||
"version": "4.0.0-alpha.0", | ||
"main": "lib/index.js", | ||
@@ -29,5 +29,4 @@ "homepage": "https://github.com/socketio/engine.io-client", | ||
"component-emitter": "1.2.1", | ||
"component-inherit": "0.0.3", | ||
"debug": "~4.1.0", | ||
"engine.io-parser": "~2.2.0", | ||
"engine.io-parser": "~4.0.0-alpha.0", | ||
"has-cors": "1.1.0", | ||
@@ -37,3 +36,3 @@ "indexof": "0.0.1", | ||
"parseuri": "0.0.5", | ||
"ws": "~6.1.0", | ||
"ws": "~7.2.1", | ||
"xmlhttprequest-ssl": "~1.5.4", | ||
@@ -43,25 +42,17 @@ "yeast": "0.1.2" | ||
"devDependencies": { | ||
"babel-core": "^6.24.0", | ||
"babel-eslint": "4.1.7", | ||
"babel-loader": "^6.4.1", | ||
"babel-preset-es2015": "^6.24.0", | ||
"@babel/core": "^7.7.7", | ||
"@babel/plugin-transform-object-assign": "~7.8.3", | ||
"@babel/preset-env": "^7.7.7", | ||
"babel-eslint": "^10.0.3", | ||
"babel-loader": "^8.0.6", | ||
"blob": "^0.0.4", | ||
"concat-stream": "^1.6.0", | ||
"del": "^2.2.2", | ||
"derequire": "^2.0.6", | ||
"engine.io": "3.4.0", | ||
"eslint-config-standard": "4.4.0", | ||
"eslint-plugin-standard": "1.3.1", | ||
"engine.io": "git+https://github.com/socketio/engine.io.git#v4", | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.9.0", | ||
"expect.js": "^0.3.1", | ||
"express": "4.15.2", | ||
"gulp": "3.9.1", | ||
"gulp-eslint": "1.1.1", | ||
"gulp-file": "^0.3.0", | ||
"gulp-istanbul": "^1.1.1", | ||
"gulp-mocha": "^4.3.0", | ||
"gulp-task-listing": "1.0.1", | ||
"istanbul": "^0.4.5", | ||
"mocha": "^3.2.0", | ||
"webpack": "1.12.12", | ||
"webpack-stream": "^3.2.0", | ||
"prettier": "^1.19.1", | ||
"webpack": "^4.41.5", | ||
"webpack-cli": "^3.3.10", | ||
"zuul": "3.11.1", | ||
@@ -72,3 +63,9 @@ "zuul-builder-webpack": "^1.2.0", | ||
"scripts": { | ||
"test": "gulp test" | ||
"test": "npm run lint && npm run format:check && if test \"$BROWSERS\" = \"1\" ; then npm run test:browser; else npm run test:node; fi", | ||
"test:node": "mocha --reporter dot --require test/support/server.js test/index.js", | ||
"test:browser": "zuul test/index.js", | ||
"build": "webpack --config ./support/webpack.config.js", | ||
"format:check": "prettier --check 'lib/**/*.js' 'test/**/*.js'", | ||
"format:fix": "prettier --write 'lib/**/*.js' 'test/**/*.js'", | ||
"lint": "eslint lib/**/*.js test/**/*.js" | ||
}, | ||
@@ -86,4 +83,4 @@ "browser": { | ||
"lib/", | ||
"engine.io.js" | ||
"dist/" | ||
] | ||
} |
@@ -175,5 +175,5 @@ | ||
- `ping` | ||
- Fired upon _flushing_ a ping packet (ie: actual packet write out) | ||
- Fired upon receiving a ping packet. | ||
- `pong` | ||
- Fired upon receiving a pong packet. | ||
- Fired upon _flushing_ a pong packet (ie: actual packet write out) | ||
@@ -180,0 +180,0 @@ #### Methods |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10
18
15
114203
1853
1
1
+ Addedengine.io-parser@4.0.3(transitive)
+ Addedws@7.2.5(transitive)
- Removedcomponent-inherit@0.0.3
- Removedafter@0.8.2(transitive)
- Removedarraybuffer.slice@0.0.7(transitive)
- Removedasync-limiter@1.0.1(transitive)
- Removedblob@0.0.5(transitive)
- Removedcomponent-inherit@0.0.3(transitive)
- Removedengine.io-parser@2.2.1(transitive)
- Removedhas-binary2@1.0.3(transitive)
- Removedisarray@2.0.1(transitive)
- Removedws@6.1.4(transitive)
Updatedws@~7.2.1