mirror of
https://github.com/socketio/socket.io.git
synced 2026-04-30 03:00:39 -04:00
2956 lines
57 KiB
JavaScript
2956 lines
57 KiB
JavaScript
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.eio=e():"undefined"!=typeof global?global.eio=e():"undefined"!=typeof self&&(self.eio=e())}(function(){var define,module,exports;
|
|
return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
|
|
module.exports = require('./lib/');
|
|
|
|
},{"./lib/":3}],2:[function(require,module,exports){
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var Emitter = require('emitter');
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
module.exports = Emitter;
|
|
|
|
/**
|
|
* Compatibility with `WebSocket#addEventListener`.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Emitter.prototype.addEventListener = Emitter.prototype.on;
|
|
|
|
/**
|
|
* Compatibility with `WebSocket#removeEventListener`.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Emitter.prototype.removeEventListener = Emitter.prototype.off;
|
|
|
|
/**
|
|
* Node-compatible `EventEmitter#removeListener`
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Emitter.prototype.removeListener = Emitter.prototype.off;
|
|
|
|
},{"emitter":15}],3:[function(require,module,exports){
|
|
|
|
module.exports = require('./socket');
|
|
|
|
/**
|
|
* Exports parser
|
|
*
|
|
* @api public
|
|
*
|
|
*/
|
|
module.exports.parser = require('engine.io-parser');
|
|
|
|
},{"./socket":4,"engine.io-parser":16}],4:[function(require,module,exports){
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var util = require('./util')
|
|
, transports = require('./transports')
|
|
, Emitter = require('./emitter')
|
|
, debug = require('debug')('engine.io-client:socket')
|
|
, index = require('indexof')
|
|
, parser = require('engine.io-parser');
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
module.exports = Socket;
|
|
|
|
/**
|
|
* Global reference.
|
|
*/
|
|
|
|
var global = require('global');
|
|
|
|
/**
|
|
* Noop function.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
function noop(){}
|
|
|
|
/**
|
|
* Socket constructor.
|
|
*
|
|
* @param {String|Object} uri or options
|
|
* @param {Object} options
|
|
* @api public
|
|
*/
|
|
|
|
function Socket(uri, opts){
|
|
if (!(this instanceof Socket)) return new Socket(uri, opts);
|
|
|
|
opts = opts || {};
|
|
|
|
if (uri && 'object' == typeof uri) {
|
|
opts = uri;
|
|
uri = null;
|
|
}
|
|
|
|
if (uri) {
|
|
uri = util.parseUri(uri);
|
|
opts.host = uri.host;
|
|
opts.secure = uri.protocol == 'https' || uri.protocol == 'wss';
|
|
opts.port = uri.port;
|
|
if (uri.query) opts.query = uri.query;
|
|
}
|
|
|
|
this.secure = null != opts.secure ? opts.secure :
|
|
(global.location && 'https:' == location.protocol);
|
|
|
|
if (opts.host) {
|
|
var pieces = opts.host.split(':');
|
|
opts.hostname = pieces.shift();
|
|
if (pieces.length) opts.port = pieces.pop();
|
|
}
|
|
|
|
this.agent = opts.agent || false;
|
|
this.hostname = opts.hostname ||
|
|
(global.location ? location.hostname : 'localhost');
|
|
this.port = opts.port || (global.location && location.port ?
|
|
location.port :
|
|
(this.secure ? 443 : 80));
|
|
this.query = opts.query || {};
|
|
if ('string' == typeof this.query) this.query = util.qsParse(this.query);
|
|
this.upgrade = false !== opts.upgrade;
|
|
this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/';
|
|
this.forceJSONP = !!opts.forceJSONP;
|
|
this.timestampParam = opts.timestampParam || 't';
|
|
this.timestampRequests = opts.timestampRequests;
|
|
this.flashPath = opts.flashPath || '';
|
|
this.transports = opts.transports || ['polling', 'websocket', 'flashsocket'];
|
|
this.readyState = '';
|
|
this.writeBuffer = [];
|
|
this.callbackBuffer = [];
|
|
this.policyPort = opts.policyPort || 843;
|
|
this.open();
|
|
|
|
Socket.sockets.push(this);
|
|
Socket.sockets.evs.emit('add', this);
|
|
}
|
|
|
|
/**
|
|
* Mix in `Emitter`.
|
|
*/
|
|
|
|
Emitter(Socket.prototype);
|
|
|
|
/**
|
|
* Protocol version.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Socket.protocol = parser.protocol; // this is an int
|
|
|
|
/**
|
|
* Static EventEmitter.
|
|
*/
|
|
|
|
Socket.sockets = [];
|
|
Socket.sockets.evs = new Emitter;
|
|
|
|
/**
|
|
* Expose deps for legacy compatibility
|
|
* and standalone browser access.
|
|
*/
|
|
|
|
Socket.Socket = Socket;
|
|
Socket.Transport = require('./transport');
|
|
Socket.Emitter = require('./emitter');
|
|
Socket.transports = require('./transports');
|
|
Socket.util = require('./util');
|
|
Socket.parser = require('engine.io-parser');
|
|
|
|
/**
|
|
* Creates transport of the given type.
|
|
*
|
|
* @param {String} transport name
|
|
* @return {Transport}
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.createTransport = function (name) {
|
|
debug('creating transport "%s"', name);
|
|
var query = clone(this.query);
|
|
|
|
// append engine.io protocol identifier
|
|
query.EIO = parser.protocol;
|
|
|
|
// transport name
|
|
query.transport = name;
|
|
|
|
// session id if we already have one
|
|
if (this.id) query.sid = this.id;
|
|
|
|
var transport = new transports[name]({
|
|
agent: this.agent,
|
|
hostname: this.hostname,
|
|
port: this.port,
|
|
secure: this.secure,
|
|
path: this.path,
|
|
query: query,
|
|
forceJSONP: this.forceJSONP,
|
|
timestampRequests: this.timestampRequests,
|
|
timestampParam: this.timestampParam,
|
|
flashPath: this.flashPath,
|
|
policyPort: this.policyPort
|
|
});
|
|
|
|
return transport;
|
|
};
|
|
|
|
function clone (obj) {
|
|
var o = {};
|
|
for (var i in obj) {
|
|
if (obj.hasOwnProperty(i)) {
|
|
o[i] = obj[i];
|
|
}
|
|
}
|
|
return o;
|
|
}
|
|
|
|
/**
|
|
* Initializes transport to use and starts probe.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.open = function () {
|
|
var transport = this.transports[0];
|
|
this.readyState = 'opening';
|
|
var transport = this.createTransport(transport);
|
|
transport.open();
|
|
this.setTransport(transport);
|
|
};
|
|
|
|
/**
|
|
* Sets the current transport. Disables the existing one (if any).
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.setTransport = function(transport){
|
|
debug('setting transport %s', transport.name);
|
|
var self = this;
|
|
|
|
if (this.transport) {
|
|
debug('clearing existing transport %s', this.transport.name);
|
|
this.transport.removeAllListeners();
|
|
}
|
|
|
|
// set up transport
|
|
this.transport = transport;
|
|
|
|
// set up transport listeners
|
|
transport
|
|
.on('drain', function(){
|
|
self.onDrain();
|
|
})
|
|
.on('packet', function(packet){
|
|
self.onPacket(packet);
|
|
})
|
|
.on('error', function(e){
|
|
self.onError(e);
|
|
})
|
|
.on('close', function(){
|
|
self.onClose('transport close');
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Probes a transport.
|
|
*
|
|
* @param {String} transport name
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.probe = function (name) {
|
|
debug('probing transport "%s"', name);
|
|
var transport = this.createTransport(name, { probe: 1 })
|
|
, failed = false
|
|
, self = this;
|
|
|
|
transport.once('open', function () {
|
|
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);
|
|
|
|
debug('pausing current transport "%s"', self.transport.name);
|
|
self.transport.pause(function () {
|
|
if (failed) return;
|
|
if ('closed' == self.readyState || 'closing' == self.readyState) {
|
|
return;
|
|
}
|
|
debug('changing transport and sending upgrade packet');
|
|
transport.removeListener('error', onerror);
|
|
self.emit('upgrade', transport);
|
|
self.setTransport(transport);
|
|
transport.send([{ type: 'upgrade' }]);
|
|
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('error', err);
|
|
}
|
|
});
|
|
});
|
|
|
|
transport.once('error', onerror);
|
|
function onerror(err) {
|
|
if (failed) return;
|
|
|
|
// Any callback called by transport should be ignored since now
|
|
failed = true;
|
|
|
|
var error = new Error('probe error: ' + err);
|
|
error.transport = transport.name;
|
|
|
|
transport.close();
|
|
transport = null;
|
|
|
|
debug('probe transport "%s" failed because of error: %s', name, err);
|
|
|
|
self.emit('error', error);
|
|
}
|
|
|
|
transport.open();
|
|
|
|
this.once('close', function () {
|
|
if (transport) {
|
|
debug('socket closed prematurely - aborting probe');
|
|
failed = true;
|
|
transport.close();
|
|
transport = null;
|
|
}
|
|
});
|
|
|
|
this.once('upgrading', function (to) {
|
|
if (transport && to.name != transport.name) {
|
|
debug('"%s" works - aborting "%s"', to.name, transport.name);
|
|
transport.close();
|
|
transport = null;
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Called when connection is deemed open.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Socket.prototype.onOpen = function () {
|
|
debug('socket open');
|
|
this.readyState = 'open';
|
|
this.emit('open');
|
|
this.onopen && this.onopen.call(this);
|
|
this.flush();
|
|
|
|
// we check for `readyState` in case an `open`
|
|
// listener alreay closed the socket
|
|
if ('open' == this.readyState && this.upgrade && this.transport.pause) {
|
|
debug('starting upgrade probes');
|
|
for (var i = 0, l = this.upgrades.length; i < l; i++) {
|
|
this.probe(this.upgrades[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handles a packet.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.onPacket = function (packet) {
|
|
if ('opening' == this.readyState || 'open' == this.readyState) {
|
|
debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
|
|
|
|
this.emit('packet', packet);
|
|
|
|
// Socket is live - any packet counts
|
|
this.emit('heartbeat');
|
|
|
|
switch (packet.type) {
|
|
case 'open':
|
|
this.onHandshake(util.parseJSON(packet.data));
|
|
break;
|
|
|
|
case 'pong':
|
|
this.setPing();
|
|
break;
|
|
|
|
case 'error':
|
|
var err = new Error('server error');
|
|
err.code = packet.data;
|
|
this.emit('error', err);
|
|
break;
|
|
|
|
case 'message':
|
|
this.emit('data', packet.data);
|
|
this.emit('message', packet.data);
|
|
var event = { data: packet.data };
|
|
event.toString = function () {
|
|
return packet.data;
|
|
};
|
|
this.onmessage && this.onmessage.call(this, event);
|
|
break;
|
|
}
|
|
} else {
|
|
debug('packet received with socket readyState "%s"', this.readyState);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called upon handshake completion.
|
|
*
|
|
* @param {Object} handshake obj
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.onHandshake = function (data) {
|
|
this.emit('handshake', data);
|
|
this.id = data.sid;
|
|
this.transport.query.sid = data.sid;
|
|
this.upgrades = this.filterUpgrades(data.upgrades);
|
|
this.pingInterval = data.pingInterval;
|
|
this.pingTimeout = data.pingTimeout;
|
|
this.onOpen();
|
|
this.setPing();
|
|
|
|
// Prolong liveness of socket on heartbeat
|
|
this.removeListener('heartbeat', this.onHeartbeat);
|
|
this.on('heartbeat', this.onHeartbeat);
|
|
};
|
|
|
|
/**
|
|
* Resets ping timeout.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.onHeartbeat = function (timeout) {
|
|
clearTimeout(this.pingTimeoutTimer);
|
|
var self = this;
|
|
self.pingTimeoutTimer = setTimeout(function () {
|
|
if ('closed' == self.readyState) return;
|
|
self.onClose('ping timeout');
|
|
}, timeout || (self.pingInterval + self.pingTimeout));
|
|
};
|
|
|
|
/**
|
|
* Pings server every `this.pingInterval` and expects response
|
|
* within `this.pingTimeout` or closes connection.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.setPing = function () {
|
|
var self = this;
|
|
clearTimeout(self.pingIntervalTimer);
|
|
self.pingIntervalTimer = setTimeout(function () {
|
|
debug('writing ping packet - expecting pong within %sms', self.pingTimeout);
|
|
self.ping();
|
|
self.onHeartbeat(self.pingTimeout);
|
|
}, self.pingInterval);
|
|
};
|
|
|
|
/**
|
|
* Sends a ping packet.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Socket.prototype.ping = function () {
|
|
this.sendPacket('ping');
|
|
};
|
|
|
|
/**
|
|
* Called on `drain` event
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.onDrain = function() {
|
|
for (var i = 0; i < this.prevBufferLen; i++) {
|
|
if (this.callbackBuffer[i]) {
|
|
this.callbackBuffer[i]();
|
|
}
|
|
}
|
|
|
|
this.writeBuffer.splice(0, this.prevBufferLen);
|
|
this.callbackBuffer.splice(0, this.prevBufferLen);
|
|
|
|
// setting prevBufferLen = 0 is very important
|
|
// for example, when upgrading, upgrade packet is sent over,
|
|
// and a nonzero prevBufferLen could cause problems on `drain`
|
|
this.prevBufferLen = 0;
|
|
|
|
if (this.writeBuffer.length == 0) {
|
|
this.emit('drain');
|
|
} else {
|
|
this.flush();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Flush write buffers.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.flush = function () {
|
|
if ('closed' != this.readyState && this.transport.writable &&
|
|
!this.upgrading && this.writeBuffer.length) {
|
|
debug('flushing %d packets in socket', this.writeBuffer.length);
|
|
this.transport.send(this.writeBuffer);
|
|
// keep track of current length of writeBuffer
|
|
// splice writeBuffer and callbackBuffer on `drain`
|
|
this.prevBufferLen = this.writeBuffer.length;
|
|
this.emit('flush');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sends a message.
|
|
*
|
|
* @param {String} message.
|
|
* @param {Function} callback function.
|
|
* @return {Socket} for chaining.
|
|
* @api public
|
|
*/
|
|
|
|
Socket.prototype.write =
|
|
Socket.prototype.send = function (msg, fn) {
|
|
this.sendPacket('message', msg, fn);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Sends a packet.
|
|
*
|
|
* @param {String} packet type.
|
|
* @param {String} data.
|
|
* @param {Function} callback function.
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.sendPacket = function (type, data, fn) {
|
|
var packet = { type: type, data: data };
|
|
this.emit('packetCreate', packet);
|
|
this.writeBuffer.push(packet);
|
|
this.callbackBuffer.push(fn);
|
|
this.flush();
|
|
};
|
|
|
|
/**
|
|
* Closes the connection.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.close = function () {
|
|
if ('opening' == this.readyState || 'open' == this.readyState) {
|
|
this.onClose('forced close');
|
|
debug('socket closing - telling transport to close');
|
|
this.transport.close();
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Called upon transport error
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.onError = function (err) {
|
|
debug('socket error %j', err);
|
|
this.emit('error', err);
|
|
this.onerror && this.onerror.call(this, err);
|
|
this.onClose('transport error', err);
|
|
};
|
|
|
|
/**
|
|
* Called upon transport close.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Socket.prototype.onClose = function (reason, desc) {
|
|
if ('opening' == this.readyState || 'open' == this.readyState) {
|
|
debug('socket close with reason: "%s"', reason);
|
|
var self = this;
|
|
|
|
// clear timers
|
|
clearTimeout(this.pingIntervalTimer);
|
|
clearTimeout(this.pingTimeoutTimer);
|
|
|
|
// clean buffers in next tick, so developers can still
|
|
// grab the buffers on `close` event
|
|
setTimeout(function() {
|
|
self.writeBuffer = [];
|
|
self.callbackBuffer = [];
|
|
self.prevBufferLen = 0;
|
|
}, 0);
|
|
|
|
// ignore further transport communication
|
|
this.transport.removeAllListeners();
|
|
|
|
// set ready state
|
|
var prev = this.readyState;
|
|
this.readyState = 'closed';
|
|
|
|
// clear session id
|
|
this.id = null;
|
|
|
|
// emit events
|
|
if (prev == 'open') {
|
|
this.emit('close', reason, desc);
|
|
this.onclose && this.onclose.call(this);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|
|
},{"./emitter":2,"./transport":5,"./transports":7,"./util":12,"debug":14,"engine.io-parser":16,"global":19,"indexof":21}],5:[function(require,module,exports){
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var util = require('./util')
|
|
, parser = require('engine.io-parser')
|
|
, Emitter = require('./emitter');
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
module.exports = Transport;
|
|
|
|
/**
|
|
* Transport abstract constructor.
|
|
*
|
|
* @param {Object} options.
|
|
* @api private
|
|
*/
|
|
|
|
function Transport (opts) {
|
|
this.path = opts.path;
|
|
this.hostname = opts.hostname;
|
|
this.port = opts.port;
|
|
this.secure = opts.secure;
|
|
this.query = opts.query;
|
|
this.timestampParam = opts.timestampParam;
|
|
this.timestampRequests = opts.timestampRequests;
|
|
this.readyState = '';
|
|
this.agent = opts.agent || false;
|
|
}
|
|
|
|
/**
|
|
* Mix in `Emitter`.
|
|
*/
|
|
|
|
Emitter(Transport.prototype);
|
|
|
|
/**
|
|
* Emits an error.
|
|
*
|
|
* @param {String} str
|
|
* @return {Transport} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
Transport.prototype.onError = function (msg, desc) {
|
|
var err = new Error(msg);
|
|
err.type = 'TransportError';
|
|
err.description = desc;
|
|
this.emit('error', err);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Opens the transport.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Transport.prototype.open = function () {
|
|
if ('closed' == this.readyState || '' == this.readyState) {
|
|
this.readyState = 'opening';
|
|
this.doOpen();
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Closes the transport.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Transport.prototype.close = function () {
|
|
if ('opening' == this.readyState || 'open' == this.readyState) {
|
|
this.doClose();
|
|
this.onClose();
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Sends multiple packets.
|
|
*
|
|
* @param {Array} packets
|
|
* @api private
|
|
*/
|
|
|
|
Transport.prototype.send = function(packets){
|
|
if ('open' == this.readyState) {
|
|
this.write(packets);
|
|
} else {
|
|
throw new Error('Transport not open');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called upon open
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Transport.prototype.onOpen = function () {
|
|
this.readyState = 'open';
|
|
this.writable = true;
|
|
this.emit('open');
|
|
};
|
|
|
|
/**
|
|
* Called with data.
|
|
*
|
|
* @param {String} data
|
|
* @api private
|
|
*/
|
|
|
|
Transport.prototype.onData = function (data) {
|
|
this.onPacket(parser.decodePacket(data));
|
|
};
|
|
|
|
/**
|
|
* 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');
|
|
};
|
|
|
|
},{"./emitter":2,"./util":12,"engine.io-parser":16}],6:[function(require,module,exports){
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var WS = require('./websocket')
|
|
, util = require('../util')
|
|
, debug = require('debug')('engine.io-client:flashsocket');
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
module.exports = FlashWS;
|
|
|
|
/**
|
|
* Global reference.
|
|
*/
|
|
|
|
var global = require('global');
|
|
|
|
/**
|
|
* Obfuscated key for Blue Coat.
|
|
*/
|
|
|
|
var xobject = global[['Active'].concat('Object').join('X')];
|
|
|
|
/**
|
|
* FlashWS constructor.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
function FlashWS (options) {
|
|
WS.call(this, options);
|
|
this.flashPath = options.flashPath;
|
|
this.policyPort = options.policyPort;
|
|
};
|
|
|
|
/**
|
|
* Inherits from WebSocket.
|
|
*/
|
|
|
|
util.inherits(FlashWS, WS);
|
|
|
|
/**
|
|
* Transport name.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
FlashWS.prototype.name = 'flashsocket';
|
|
|
|
/**
|
|
* Opens the transport.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
FlashWS.prototype.doOpen = function () {
|
|
if (!this.check()) {
|
|
// let the probe timeout
|
|
return;
|
|
}
|
|
|
|
// instrument websocketjs logging
|
|
function log (type) {
|
|
return function(){
|
|
var str = Array.prototype.join.call(arguments, ' ');
|
|
debug('[websocketjs %s] %s', type, str);
|
|
};
|
|
};
|
|
|
|
WEB_SOCKET_LOGGER = { log: log('debug'), error: log('error') };
|
|
WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;
|
|
WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true;
|
|
|
|
if ('undefined' == typeof WEB_SOCKET_SWF_LOCATION) {
|
|
WEB_SOCKET_SWF_LOCATION = this.flashPath + 'WebSocketMainInsecure.swf';
|
|
}
|
|
|
|
// dependencies
|
|
var deps = [this.flashPath + 'web_socket.js'];
|
|
|
|
if ('undefined' == typeof swfobject) {
|
|
deps.unshift(this.flashPath + 'swfobject.js');
|
|
}
|
|
|
|
var self = this;
|
|
|
|
load(deps, function () {
|
|
self.ready(function () {
|
|
WebSocket.__addTask(function () {
|
|
self.socket = new WebSocket(self.uri());
|
|
self.addEventListeners();
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Override to prevent closing uninitialized flashsocket.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
FlashWS.prototype.doClose = function () {
|
|
if (!this.socket) return;
|
|
var self = this;
|
|
WebSocket.__addTask(function() {
|
|
WS.prototype.doClose.call(self);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Writes to the Flash socket.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
FlashWS.prototype.write = function() {
|
|
var self = this, args = arguments;
|
|
WebSocket.__addTask(function () {
|
|
WS.prototype.write.apply(self, args);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Called upon dependencies are loaded.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
FlashWS.prototype.ready = function (fn) {
|
|
if (typeof WebSocket == 'undefined' ||
|
|
!('__initialize' in WebSocket) || !swfobject) {
|
|
return;
|
|
}
|
|
|
|
if (swfobject.getFlashPlayerVersion().major < 10) {
|
|
return;
|
|
}
|
|
|
|
function init () {
|
|
// Only start downloading the swf file when the checked that this browser
|
|
// actually supports it
|
|
if (!FlashWS.loaded) {
|
|
if (843 != self.policyPort) {
|
|
WebSocket.loadFlashPolicyFile('xmlsocket://' + self.hostname + ':' + self.policyPort);
|
|
}
|
|
|
|
WebSocket.__initialize();
|
|
FlashWS.loaded = true;
|
|
}
|
|
|
|
fn.call(self);
|
|
}
|
|
|
|
var self = this;
|
|
if (document.body) {
|
|
return init();
|
|
}
|
|
|
|
util.load(init);
|
|
};
|
|
|
|
/**
|
|
* Feature detection for flashsocket.
|
|
*
|
|
* @return {Boolean} whether this transport is available.
|
|
* @api public
|
|
*/
|
|
|
|
FlashWS.prototype.check = function () {
|
|
if ('undefined' == typeof window) {
|
|
return false;
|
|
}
|
|
|
|
if (typeof WebSocket != 'undefined' && !('__initialize' in WebSocket)) {
|
|
return false;
|
|
}
|
|
|
|
if (xobject) {
|
|
var control = null;
|
|
try {
|
|
control = new xobject('ShockwaveFlash.ShockwaveFlash');
|
|
} catch (e) { }
|
|
if (control) {
|
|
return true;
|
|
}
|
|
} else {
|
|
for (var i = 0, l = navigator.plugins.length; i < l; i++) {
|
|
for (var j = 0, m = navigator.plugins[i].length; j < m; j++) {
|
|
if (navigator.plugins[i][j].description == 'Shockwave Flash') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Lazy loading of scripts.
|
|
* Based on $script by Dustin Diaz - MIT
|
|
*/
|
|
|
|
var scripts = {};
|
|
|
|
/**
|
|
* Injects a script. Keeps tracked of injected ones.
|
|
*
|
|
* @param {String} path
|
|
* @param {Function} callback
|
|
* @api private
|
|
*/
|
|
|
|
function create (path, fn) {
|
|
if (scripts[path]) return fn();
|
|
|
|
var el = document.createElement('script');
|
|
var loaded = false;
|
|
|
|
debug('loading "%s"', path);
|
|
el.onload = el.onreadystatechange = function () {
|
|
if (loaded || scripts[path]) return;
|
|
var rs = el.readyState;
|
|
if (!rs || 'loaded' == rs || 'complete' == rs) {
|
|
debug('loaded "%s"', path);
|
|
el.onload = el.onreadystatechange = null;
|
|
loaded = true;
|
|
scripts[path] = true;
|
|
fn();
|
|
}
|
|
};
|
|
|
|
el.async = 1;
|
|
el.src = path;
|
|
|
|
var head = document.getElementsByTagName('head')[0];
|
|
head.insertBefore(el, head.firstChild);
|
|
};
|
|
|
|
/**
|
|
* Loads scripts and fires a callback.
|
|
*
|
|
* @param {Array} paths
|
|
* @param {Function} callback
|
|
*/
|
|
|
|
function load (arr, fn) {
|
|
function process (i) {
|
|
if (!arr[i]) return fn();
|
|
create(arr[i], function () {
|
|
process(++i);
|
|
});
|
|
};
|
|
|
|
process(0);
|
|
};
|
|
|
|
},{"../util":12,"./websocket":11,"debug":14,"global":19}],7:[function(require,module,exports){
|
|
|
|
/**
|
|
* Module dependencies
|
|
*/
|
|
|
|
var XHR = require('./polling-xhr')
|
|
, JSONP = require('./polling-jsonp')
|
|
, websocket = require('./websocket')
|
|
, flashsocket = require('./flashsocket')
|
|
, util = require('../util');
|
|
|
|
/**
|
|
* Export transports.
|
|
*/
|
|
|
|
exports.polling = polling;
|
|
exports.websocket = websocket;
|
|
exports.flashsocket = flashsocket;
|
|
|
|
/**
|
|
* Global reference.
|
|
*/
|
|
|
|
var global = require('global');
|
|
|
|
/**
|
|
* Polling transport polymorphic constructor.
|
|
* Decides on xhr vs jsonp based on feature detection.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
function polling (opts) {
|
|
var xhr
|
|
, xd = false;
|
|
|
|
if (global.location) {
|
|
var isSSL = 'https:' == location.protocol;
|
|
var port = location.port;
|
|
|
|
// some user agents have empty `location.port`
|
|
if (!port) {
|
|
port = isSSL ? 443 : 80;
|
|
}
|
|
|
|
xd = opts.hostname != location.hostname || port != opts.port;
|
|
}
|
|
|
|
xhr = util.request(xd, opts);
|
|
|
|
if (xhr && !opts.forceJSONP) {
|
|
return new XHR(opts);
|
|
} else {
|
|
return new JSONP(opts);
|
|
}
|
|
};
|
|
|
|
},{"../util":12,"./flashsocket":6,"./polling-jsonp":8,"./polling-xhr":9,"./websocket":11,"global":19}],8:[function(require,module,exports){
|
|
|
|
/**
|
|
* Module requirements.
|
|
*/
|
|
|
|
var Polling = require('./polling')
|
|
, util = require('../util');
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
module.exports = JSONPPolling;
|
|
|
|
/**
|
|
* Global reference.
|
|
*/
|
|
|
|
var global = require('global');
|
|
|
|
/**
|
|
* Cached regular expressions.
|
|
*/
|
|
|
|
var rNewline = /\n/g;
|
|
|
|
/**
|
|
* Global JSONP callbacks.
|
|
*/
|
|
|
|
var callbacks;
|
|
|
|
/**
|
|
* Callbacks count.
|
|
*/
|
|
|
|
var index = 0;
|
|
|
|
/**
|
|
* Noop.
|
|
*/
|
|
|
|
function empty () { }
|
|
|
|
/**
|
|
* JSONP Polling constructor.
|
|
*
|
|
* @param {Object} opts.
|
|
* @api public
|
|
*/
|
|
|
|
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
|
|
if (!global.___eio) global.___eio = [];
|
|
callbacks = global.___eio;
|
|
}
|
|
|
|
// callback identifier
|
|
this.index = callbacks.length;
|
|
|
|
// add callback to jsonp global
|
|
var self = this;
|
|
callbacks.push(function (msg) {
|
|
self.onData(msg);
|
|
});
|
|
|
|
// append to query string
|
|
this.query.j = this.index;
|
|
};
|
|
|
|
/**
|
|
* Inherits from Polling.
|
|
*/
|
|
|
|
util.inherits(JSONPPolling, Polling);
|
|
|
|
/**
|
|
* Opens the socket.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
JSONPPolling.prototype.doOpen = function () {
|
|
var self = this;
|
|
util.defer(function () {
|
|
Polling.prototype.doOpen.call(self);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Closes the socket
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
JSONPPolling.prototype.doClose = function () {
|
|
if (this.script) {
|
|
this.script.parentNode.removeChild(this.script);
|
|
this.script = null;
|
|
}
|
|
|
|
if (this.form) {
|
|
this.form.parentNode.removeChild(this.form);
|
|
this.form = null;
|
|
}
|
|
|
|
Polling.prototype.doClose.call(this);
|
|
};
|
|
|
|
/**
|
|
* Starts a poll cycle.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
JSONPPolling.prototype.doPoll = function () {
|
|
var self = this;
|
|
var script = document.createElement('script');
|
|
|
|
if (this.script) {
|
|
this.script.parentNode.removeChild(this.script);
|
|
this.script = null;
|
|
}
|
|
|
|
script.async = true;
|
|
script.src = this.uri();
|
|
script.onerror = function(e){
|
|
self.onError('jsonp poll error',e);
|
|
}
|
|
|
|
var insertAt = document.getElementsByTagName('script')[0];
|
|
insertAt.parentNode.insertBefore(script, insertAt);
|
|
this.script = script;
|
|
|
|
|
|
if (util.ua.gecko) {
|
|
setTimeout(function () {
|
|
var iframe = document.createElement('iframe');
|
|
document.body.appendChild(iframe);
|
|
document.body.removeChild(iframe);
|
|
}, 100);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Writes with a hidden iframe.
|
|
*
|
|
* @param {String} data to send
|
|
* @param {Function} called upon flush.
|
|
* @api private
|
|
*/
|
|
|
|
JSONPPolling.prototype.doWrite = function (data, fn) {
|
|
var self = this;
|
|
|
|
if (!this.form) {
|
|
var form = document.createElement('form');
|
|
var area = document.createElement('textarea');
|
|
var id = this.iframeId = 'eio_iframe_' + this.index;
|
|
var iframe;
|
|
|
|
form.className = 'socketio';
|
|
form.style.position = 'absolute';
|
|
form.style.top = '-1000px';
|
|
form.style.left = '-1000px';
|
|
form.target = id;
|
|
form.method = 'POST';
|
|
form.setAttribute('accept-charset', 'utf-8');
|
|
area.name = 'd';
|
|
form.appendChild(area);
|
|
document.body.appendChild(form);
|
|
|
|
this.form = form;
|
|
this.area = area;
|
|
}
|
|
|
|
this.form.action = this.uri();
|
|
|
|
function complete () {
|
|
initIframe();
|
|
fn();
|
|
};
|
|
|
|
function initIframe () {
|
|
if (self.iframe) {
|
|
try {
|
|
self.form.removeChild(self.iframe);
|
|
} catch (e) {
|
|
self.onError('jsonp polling iframe removal error', e);
|
|
}
|
|
}
|
|
|
|
try {
|
|
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
|
|
var html = '<iframe src="javascript:0" name="'+ self.iframeId +'">';
|
|
iframe = document.createElement(html);
|
|
} catch (e) {
|
|
iframe = document.createElement('iframe');
|
|
iframe.name = self.iframeId;
|
|
iframe.src = 'javascript:0';
|
|
}
|
|
|
|
iframe.id = self.iframeId;
|
|
|
|
self.form.appendChild(iframe);
|
|
self.iframe = iframe;
|
|
};
|
|
|
|
initIframe();
|
|
|
|
// escape \n to prevent it from being converted into \r\n by some UAs
|
|
this.area.value = data.replace(rNewline, '\\n');
|
|
|
|
try {
|
|
this.form.submit();
|
|
} catch(e) {}
|
|
|
|
if (this.iframe.attachEvent) {
|
|
this.iframe.onreadystatechange = function(){
|
|
if (self.iframe.readyState == 'complete') {
|
|
complete();
|
|
}
|
|
};
|
|
} else {
|
|
this.iframe.onload = complete;
|
|
}
|
|
};
|
|
|
|
},{"../util":12,"./polling":10,"global":19}],9:[function(require,module,exports){
|
|
/**
|
|
* Module requirements.
|
|
*/
|
|
|
|
var Polling = require('./polling')
|
|
, util = require('../util')
|
|
, Emitter = require('../emitter')
|
|
, debug = require('debug')('engine.io-client:polling-xhr');
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
module.exports = XHR;
|
|
module.exports.Request = Request;
|
|
|
|
/**
|
|
* Global reference.
|
|
*/
|
|
|
|
var global = require('global');
|
|
|
|
/**
|
|
* Obfuscated key for Blue Coat.
|
|
*/
|
|
|
|
var xobject = global[['Active'].concat('Object').join('X')];
|
|
|
|
/**
|
|
* Empty function
|
|
*/
|
|
|
|
function empty(){}
|
|
|
|
/**
|
|
* XHR Polling constructor.
|
|
*
|
|
* @param {Object} opts
|
|
* @api public
|
|
*/
|
|
|
|
function XHR(opts){
|
|
Polling.call(this, opts);
|
|
|
|
if (global.location) {
|
|
var isSSL = 'https:' == location.protocol;
|
|
var port = location.port;
|
|
|
|
// some user agents have empty `location.port`
|
|
if (!port) {
|
|
port = isSSL ? 443 : 80;
|
|
}
|
|
|
|
this.xd = opts.hostname != global.location.hostname ||
|
|
port != opts.port;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inherits from Polling.
|
|
*/
|
|
|
|
util.inherits(XHR, Polling);
|
|
|
|
/**
|
|
* Opens the socket
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
XHR.prototype.doOpen = function(){
|
|
var self = this;
|
|
util.defer(function(){
|
|
Polling.prototype.doOpen.call(self);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Creates a request.
|
|
*
|
|
* @param {String} method
|
|
* @api private
|
|
*/
|
|
|
|
XHR.prototype.request = function(opts){
|
|
opts = opts || {};
|
|
opts.uri = this.uri();
|
|
opts.xd = this.xd;
|
|
opts.agent = this.agent || false;
|
|
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 req = this.request({ method: 'POST', data: data });
|
|
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.async = false !== opts.async;
|
|
this.data = undefined != opts.data ? opts.data : null;
|
|
this.agent = opts.agent;
|
|
this.create();
|
|
}
|
|
|
|
/**
|
|
* Mix in `Emitter`.
|
|
*/
|
|
|
|
Emitter(Request.prototype);
|
|
|
|
/**
|
|
* Creates the XHR object and sends the request.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Request.prototype.create = function(){
|
|
var xhr = this.xhr = util.request(this.xd, { agent: this.agent });
|
|
var self = this;
|
|
|
|
try {
|
|
debug('xhr open %s: %s', this.method, this.uri);
|
|
xhr.open(this.method, this.uri, this.async);
|
|
|
|
if ('POST' == this.method) {
|
|
try {
|
|
xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
|
|
} catch (e) {}
|
|
}
|
|
|
|
// ie6 check
|
|
if ('withCredentials' in xhr) {
|
|
xhr.withCredentials = true;
|
|
}
|
|
|
|
xhr.onreadystatechange = function(){
|
|
var data;
|
|
|
|
try {
|
|
if (4 != xhr.readyState) return;
|
|
if (200 == xhr.status || 1223 == xhr.status) {
|
|
data = xhr.responseText;
|
|
} else {
|
|
self.onError(xhr.status);
|
|
}
|
|
} catch (e) {
|
|
self.onError(e);
|
|
}
|
|
|
|
if (null != data) {
|
|
self.onData(data);
|
|
}
|
|
};
|
|
|
|
debug('xhr data %s', this.data);
|
|
xhr.send(this.data);
|
|
} catch (e) {
|
|
// Need to defer since .create() is called directly from 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 (xobject) {
|
|
this.index = Request.requestsCount++;
|
|
Request.requests[this.index] = this;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called upon successful response.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Request.prototype.onSuccess = function(){
|
|
this.emit('success');
|
|
this.cleanup();
|
|
};
|
|
|
|
/**
|
|
* Called if we have data.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Request.prototype.onData = function(data){
|
|
this.emit('data', data);
|
|
this.onSuccess();
|
|
};
|
|
|
|
/**
|
|
* Called upon error.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Request.prototype.onError = function(err){
|
|
this.emit('error', err);
|
|
this.cleanup();
|
|
};
|
|
|
|
/**
|
|
* Cleans up house.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Request.prototype.cleanup = function(){
|
|
if ('undefined' == typeof this.xhr ) {
|
|
return;
|
|
}
|
|
// xmlhttprequest
|
|
this.xhr.onreadystatechange = empty;
|
|
|
|
try {
|
|
this.xhr.abort();
|
|
} catch(e) {}
|
|
|
|
if (xobject) {
|
|
delete Request.requests[this.index];
|
|
}
|
|
|
|
this.xhr = null;
|
|
};
|
|
|
|
/**
|
|
* Aborts the request.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Request.prototype.abort = function(){
|
|
this.cleanup();
|
|
};
|
|
|
|
if (xobject) {
|
|
Request.requestsCount = 0;
|
|
Request.requests = {};
|
|
|
|
global.attachEvent('onunload', function(){
|
|
for (var i in Request.requests) {
|
|
if (Request.requests.hasOwnProperty(i)) {
|
|
Request.requests[i].abort();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
},{"../emitter":2,"../util":12,"./polling":10,"debug":14,"global":19}],10:[function(require,module,exports){
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var Transport = require('../transport')
|
|
, util = require('../util')
|
|
, parser = require('engine.io-parser')
|
|
, debug = require('debug')('engine.io-client:polling');
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
module.exports = Polling;
|
|
|
|
/**
|
|
* Global reference.
|
|
*/
|
|
|
|
var global = require('global');
|
|
|
|
/**
|
|
* Polling interface.
|
|
*
|
|
* @param {Object} opts
|
|
* @api private
|
|
*/
|
|
|
|
function Polling(opts){
|
|
Transport.call(this, opts);
|
|
}
|
|
|
|
/**
|
|
* Inherits from Transport.
|
|
*/
|
|
|
|
util.inherits(Polling, Transport);
|
|
|
|
/**
|
|
* Transport name.
|
|
*/
|
|
|
|
Polling.prototype.name = 'polling';
|
|
|
|
/**
|
|
* Opens the socket (triggers polling). We write a PING message to determine
|
|
* when the transport is open.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Polling.prototype.doOpen = function(){
|
|
this.poll();
|
|
};
|
|
|
|
/**
|
|
* Pauses polling.
|
|
*
|
|
* @param {Function} callback upon buffers are flushed and transport is paused
|
|
* @api private
|
|
*/
|
|
|
|
Polling.prototype.pause = function(onPause){
|
|
var pending = 0;
|
|
var self = this;
|
|
|
|
this.readyState = 'pausing';
|
|
|
|
function pause(){
|
|
debug('paused');
|
|
self.readyState = 'paused';
|
|
onPause();
|
|
}
|
|
|
|
if (this.polling || !this.writable) {
|
|
var total = 0;
|
|
|
|
if (this.polling) {
|
|
debug('we are currently polling - waiting to pause');
|
|
total++;
|
|
this.once('pollComplete', function(){
|
|
debug('pre-pause polling complete');
|
|
--total || pause();
|
|
});
|
|
}
|
|
|
|
if (!this.writable) {
|
|
debug('we are currently writing - waiting to pause');
|
|
total++;
|
|
this.once('drain', function(){
|
|
debug('pre-pause writing complete');
|
|
--total || pause();
|
|
});
|
|
}
|
|
} else {
|
|
pause();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Starts polling cycle.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Polling.prototype.poll = function(){
|
|
debug('polling');
|
|
this.polling = true;
|
|
this.doPoll();
|
|
this.emit('poll');
|
|
};
|
|
|
|
/**
|
|
* Overloads onData to detect payloads.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Polling.prototype.onData = function(data){
|
|
var self = this;
|
|
debug('polling got data %s', data);
|
|
|
|
// decode payload
|
|
parser.decodePayload(data, function(packet, index, total) {
|
|
// if its the first message we consider the transport open
|
|
if ('opening' == self.readyState) {
|
|
self.onOpen();
|
|
}
|
|
|
|
// if its a close packet, we close the ongoing requests
|
|
if ('close' == packet.type) {
|
|
self.onClose();
|
|
return false;
|
|
}
|
|
|
|
// otherwise bypass onData and handle the message
|
|
self.onPacket(packet);
|
|
});
|
|
|
|
// if an event did not trigger closing
|
|
if ('closed' != this.readyState) {
|
|
// if we got data we're not polling
|
|
this.polling = false;
|
|
this.emit('pollComplete');
|
|
|
|
if ('open' == this.readyState) {
|
|
this.poll();
|
|
} else {
|
|
debug('ignoring poll - transport state "%s"', this.readyState);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* For polling, send a close packet.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Polling.prototype.doClose = function(){
|
|
var self = this;
|
|
|
|
function close(){
|
|
debug('writing close packet');
|
|
self.write([{ type: 'close' }]);
|
|
}
|
|
|
|
if ('open' == this.readyState) {
|
|
debug('transport open - closing');
|
|
close();
|
|
} else {
|
|
// in case we're trying to close while
|
|
// handshaking is in progress (GH-164)
|
|
debug('transport not open - deferring close');
|
|
this.once('open', close);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Writes a packets payload.
|
|
*
|
|
* @param {Array} data packets
|
|
* @param {Function} drain callback
|
|
* @api private
|
|
*/
|
|
|
|
Polling.prototype.write = function(packets){
|
|
var self = this;
|
|
this.writable = false;
|
|
this.doWrite(parser.encodePayload(packets), function(){
|
|
self.writable = true;
|
|
self.emit('drain');
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Generates uri for connection.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Polling.prototype.uri = function(){
|
|
var query = this.query || {};
|
|
var schema = this.secure ? 'https' : 'http';
|
|
var port = '';
|
|
|
|
// cache busting is forced for IE / android / iOS6 ಠ_ಠ
|
|
if ('ActiveXObject' in global
|
|
|| util.ua.chromeframe
|
|
|| util.ua.android
|
|
|| util.ua.ios6
|
|
|| this.timestampRequests) {
|
|
if (false !== this.timestampRequests) {
|
|
query[this.timestampParam] = +new Date;
|
|
}
|
|
}
|
|
|
|
query = util.qs(query);
|
|
|
|
// avoid port if default for schema
|
|
if (this.port && (('https' == schema && this.port != 443) ||
|
|
('http' == schema && this.port != 80))) {
|
|
port = ':' + this.port;
|
|
}
|
|
|
|
// prepend ? to query
|
|
if (query.length) {
|
|
query = '?' + query;
|
|
}
|
|
|
|
return schema + '://' + this.hostname + port + this.path + query;
|
|
};
|
|
|
|
},{"../transport":5,"../util":12,"debug":14,"engine.io-parser":16,"global":19}],11:[function(require,module,exports){
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var Transport = require('../transport')
|
|
, WebSocket = require('ws')
|
|
, parser = require('engine.io-parser')
|
|
, util = require('../util')
|
|
, debug = require('debug')('engine.io-client:websocket');
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
module.exports = WS;
|
|
|
|
/**
|
|
* Global reference.
|
|
*/
|
|
|
|
var global = require('global');
|
|
|
|
/**
|
|
* WebSocket transport constructor.
|
|
*
|
|
* @api {Object} connection options
|
|
* @api public
|
|
*/
|
|
|
|
function WS(opts){
|
|
Transport.call(this, opts);
|
|
};
|
|
|
|
/**
|
|
* Inherits from Transport.
|
|
*/
|
|
|
|
util.inherits(WS, Transport);
|
|
|
|
/**
|
|
* Transport name.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
WS.prototype.name = 'websocket';
|
|
|
|
/**
|
|
* Opens socket.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
WS.prototype.doOpen = function(){
|
|
if (!this.check()) {
|
|
// let probe timeout
|
|
return;
|
|
}
|
|
|
|
var self = this;
|
|
var uri = this.uri();
|
|
var protocols = void(0);
|
|
var opts = { agent: this.agent };
|
|
|
|
this.socket = new WebSocket(uri, protocols, opts);
|
|
this.addEventListeners();
|
|
};
|
|
|
|
/**
|
|
* Adds event listeners to the socket
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
WS.prototype.addEventListeners = function() {
|
|
var self = this;
|
|
|
|
this.socket.onopen = function(){
|
|
self.onOpen();
|
|
};
|
|
this.socket.onclose = function(){
|
|
self.onClose();
|
|
};
|
|
this.socket.onmessage = function(ev){
|
|
self.onData(ev.data);
|
|
};
|
|
this.socket.onerror = function(e){
|
|
self.onError('websocket error', e);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Override `onData` to use a timer on iOS.
|
|
* See: https://gist.github.com/mloughran/2052006
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
if ('undefined' != typeof navigator
|
|
&& /iPad|iPhone|iPod/i.test(navigator.userAgent)) {
|
|
WS.prototype.onData = function(data){
|
|
var self = this;
|
|
setTimeout(function(){
|
|
Transport.prototype.onData.call(self, data);
|
|
}, 0);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
for (var i = 0, l = packets.length; i < l; i++) {
|
|
this.socket.send(parser.encodePacket(packets[i]));
|
|
}
|
|
function ondrain() {
|
|
self.writable = true;
|
|
self.emit('drain');
|
|
}
|
|
// fake drain
|
|
// defer to next tick to allow Socket to clear writeBuffer
|
|
setTimeout(ondrain, 0);
|
|
};
|
|
|
|
/**
|
|
* Called upon close
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
WS.prototype.onClose = function(){
|
|
Transport.prototype.onClose.call(this);
|
|
};
|
|
|
|
/**
|
|
* Closes socket.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
WS.prototype.doClose = function(){
|
|
if (typeof this.socket !== 'undefined') {
|
|
this.socket.close();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generates uri for connection.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
WS.prototype.uri = function(){
|
|
var query = this.query || {};
|
|
var schema = this.secure ? 'wss' : 'ws';
|
|
var port = '';
|
|
|
|
// avoid port if default for schema
|
|
if (this.port && (('wss' == schema && this.port != 443)
|
|
|| ('ws' == schema && this.port != 80))) {
|
|
port = ':' + this.port;
|
|
}
|
|
|
|
// append timestamp to URI
|
|
if (this.timestampRequests) {
|
|
query[this.timestampParam] = +new Date;
|
|
}
|
|
|
|
query = util.qs(query);
|
|
|
|
// prepend ? to query
|
|
if (query.length) {
|
|
query = '?' + query;
|
|
}
|
|
|
|
return schema + '://' + this.hostname + port + this.path + query;
|
|
};
|
|
|
|
/**
|
|
* Feature detection for WebSocket.
|
|
*
|
|
* @return {Boolean} whether this transport is available.
|
|
* @api public
|
|
*/
|
|
|
|
WS.prototype.check = function(){
|
|
return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name);
|
|
};
|
|
|
|
},{"../transport":5,"../util":12,"debug":14,"engine.io-parser":16,"global":19,"ws":22}],12:[function(require,module,exports){
|
|
|
|
var global = require('global');
|
|
|
|
/**
|
|
* Status of page load.
|
|
*/
|
|
|
|
var pageLoaded = false;
|
|
|
|
/**
|
|
* Inheritance.
|
|
*
|
|
* @param {Function} ctor a
|
|
* @param {Function} ctor b
|
|
* @api private
|
|
*/
|
|
|
|
exports.inherits = function inherits (a, b) {
|
|
function c () { }
|
|
c.prototype = b.prototype;
|
|
a.prototype = new c;
|
|
};
|
|
|
|
/**
|
|
* Object.keys
|
|
*/
|
|
|
|
exports.keys = Object.keys || function (obj) {
|
|
var ret = [];
|
|
var has = Object.prototype.hasOwnProperty;
|
|
|
|
for (var i in obj) {
|
|
if (has.call(obj, i)) {
|
|
ret.push(i);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Adds an event.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
exports.on = function (element, event, fn, capture) {
|
|
if (element.attachEvent) {
|
|
element.attachEvent('on' + event, fn);
|
|
} else if (element.addEventListener) {
|
|
element.addEventListener(event, fn, capture);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Load utility.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
exports.load = function (fn) {
|
|
if (global.document && document.readyState === 'complete' || pageLoaded) {
|
|
return fn();
|
|
}
|
|
|
|
exports.on(global, 'load', fn, false);
|
|
};
|
|
|
|
/**
|
|
* Change the internal pageLoaded value.
|
|
*/
|
|
|
|
if ('undefined' != typeof window) {
|
|
exports.load(function () {
|
|
pageLoaded = true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Defers a function to ensure a spinner is not displayed by the browser.
|
|
*
|
|
* @param {Function} fn
|
|
* @api private
|
|
*/
|
|
|
|
exports.defer = function (fn) {
|
|
if (!exports.ua.webkit || 'undefined' != typeof importScripts) {
|
|
return fn();
|
|
}
|
|
|
|
exports.load(function () {
|
|
setTimeout(fn, 100);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* JSON parse.
|
|
*
|
|
* @see Based on jQuery#parseJSON (MIT) and JSON2
|
|
* @api private
|
|
*/
|
|
|
|
var rvalidchars = /^[\],:{}\s]*$/;
|
|
var rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
|
|
var rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
|
|
var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
|
|
var rtrimLeft = /^\s+/;
|
|
var rtrimRight = /\s+$/;
|
|
|
|
exports.parseJSON = function (data) {
|
|
if ('string' != typeof data || !data) {
|
|
return null;
|
|
}
|
|
|
|
data = data.replace(rtrimLeft, '').replace(rtrimRight, '');
|
|
|
|
// Attempt to parse using the native JSON parser first
|
|
if (global.JSON && JSON.parse) {
|
|
return JSON.parse(data);
|
|
}
|
|
|
|
if (rvalidchars.test(data.replace(rvalidescape, '@')
|
|
.replace(rvalidtokens, ']')
|
|
.replace(rvalidbraces, ''))) {
|
|
return (new Function('return ' + data))();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* UA / engines detection namespace.
|
|
*
|
|
* @namespace
|
|
*/
|
|
|
|
exports.ua = {};
|
|
|
|
/**
|
|
* Whether the UA supports CORS for XHR.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
exports.ua.hasCORS = require('has-cors');
|
|
|
|
/**
|
|
* Detect webkit.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
exports.ua.webkit = 'undefined' != typeof navigator &&
|
|
/webkit/i.test(navigator.userAgent);
|
|
|
|
/**
|
|
* Detect gecko.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
exports.ua.gecko = 'undefined' != typeof navigator &&
|
|
/gecko/i.test(navigator.userAgent);
|
|
|
|
/**
|
|
* Detect android;
|
|
*/
|
|
|
|
exports.ua.android = 'undefined' != typeof navigator &&
|
|
/android/i.test(navigator.userAgent);
|
|
|
|
/**
|
|
* Detect iOS.
|
|
*/
|
|
|
|
exports.ua.ios = 'undefined' != typeof navigator &&
|
|
/^(iPad|iPhone|iPod)$/.test(navigator.platform);
|
|
exports.ua.ios6 = exports.ua.ios && /OS 6_/.test(navigator.userAgent);
|
|
|
|
/**
|
|
* Detect Chrome Frame.
|
|
*/
|
|
|
|
exports.ua.chromeframe = Boolean(global.externalHost);
|
|
|
|
/**
|
|
* XHR request helper.
|
|
*
|
|
* @param {Boolean} whether we need xdomain
|
|
* @param {Object} opts Optional "options" object
|
|
* @api private
|
|
*/
|
|
|
|
exports.request = function request (xdomain, opts) {
|
|
opts = opts || {};
|
|
opts.xdomain = xdomain;
|
|
|
|
try {
|
|
var _XMLHttpRequest = require('xmlhttprequest');
|
|
return new _XMLHttpRequest(opts);
|
|
} catch (e) {}
|
|
|
|
// XMLHttpRequest can be disabled on IE
|
|
try {
|
|
if ('undefined' != typeof XMLHttpRequest && (!xdomain || exports.ua.hasCORS)) {
|
|
return new XMLHttpRequest();
|
|
}
|
|
} catch (e) { }
|
|
|
|
if (!xdomain) {
|
|
try {
|
|
return new ActiveXObject('Microsoft.XMLHTTP');
|
|
} catch(e) { }
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Parses an URI
|
|
*
|
|
* @author Steven Levithan <stevenlevithan.com> (MIT license)
|
|
* @api private
|
|
*/
|
|
|
|
var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
|
|
|
|
var parts = [
|
|
'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host'
|
|
, 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'
|
|
];
|
|
|
|
exports.parseUri = function (str) {
|
|
var m = re.exec(str || '')
|
|
, uri = {}
|
|
, i = 14;
|
|
|
|
while (i--) {
|
|
uri[parts[i]] = m[i] || '';
|
|
}
|
|
|
|
return uri;
|
|
};
|
|
|
|
/**
|
|
* Compiles a querystring
|
|
*
|
|
* @param {Object}
|
|
* @api private
|
|
*/
|
|
|
|
exports.qs = function (obj) {
|
|
var str = '';
|
|
|
|
for (var i in obj) {
|
|
if (obj.hasOwnProperty(i)) {
|
|
if (str.length) str += '&';
|
|
str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);
|
|
}
|
|
}
|
|
|
|
return str;
|
|
};
|
|
|
|
/**
|
|
* Parses a simple querystring.
|
|
*
|
|
* @param {String} qs
|
|
* @api private
|
|
*/
|
|
|
|
exports.qsParse = function(qs){
|
|
var qry = {};
|
|
var pairs = qs.split('&');
|
|
for (var i = 0, l = pairs.length; i < l; i++) {
|
|
var pair = pairs[i].split('=');
|
|
qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
|
|
}
|
|
return qry;
|
|
};
|
|
|
|
},{"global":19,"has-cors":20,"xmlhttprequest":13}],13:[function(require,module,exports){
|
|
// browser shim for xmlhttprequest module
|
|
var hasCORS = require('has-cors');
|
|
|
|
module.exports = function(opts) {
|
|
var xdomain = opts.xdomain;
|
|
|
|
// XMLHttpRequest can be disabled on IE
|
|
try {
|
|
if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) {
|
|
return new XMLHttpRequest();
|
|
}
|
|
} catch (e) { }
|
|
|
|
if (!xdomain) {
|
|
try {
|
|
return new ActiveXObject('Microsoft.XMLHTTP');
|
|
} catch(e) { }
|
|
}
|
|
}
|
|
|
|
},{"has-cors":20}],14:[function(require,module,exports){
|
|
|
|
/**
|
|
* Expose `debug()` as the module.
|
|
*/
|
|
|
|
module.exports = debug;
|
|
|
|
/**
|
|
* Create a debugger with the given `name`.
|
|
*
|
|
* @param {String} name
|
|
* @return {Type}
|
|
* @api public
|
|
*/
|
|
|
|
function debug(name) {
|
|
if (!debug.enabled(name)) return function(){};
|
|
|
|
return function(fmt){
|
|
var curr = new Date;
|
|
var ms = curr - (debug[name] || curr);
|
|
debug[name] = curr;
|
|
|
|
fmt = name
|
|
+ ' '
|
|
+ fmt
|
|
+ ' +' + debug.humanize(ms);
|
|
|
|
// This hackery is required for IE8
|
|
// where `console.log` doesn't have 'apply'
|
|
window.console
|
|
&& console.log
|
|
&& Function.prototype.apply.call(console.log, console, arguments);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The currently active debug mode names.
|
|
*/
|
|
|
|
debug.names = [];
|
|
debug.skips = [];
|
|
|
|
/**
|
|
* Enables a debug mode by name. This can include modes
|
|
* separated by a colon and wildcards.
|
|
*
|
|
* @param {String} name
|
|
* @api public
|
|
*/
|
|
|
|
debug.enable = function(name) {
|
|
try {
|
|
localStorage.debug = name;
|
|
} catch(e){}
|
|
|
|
var split = (name || '').split(/[\s,]+/)
|
|
, len = split.length;
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
name = split[i].replace('*', '.*?');
|
|
if (name[0] === '-') {
|
|
debug.skips.push(new RegExp('^' + name.substr(1) + '$'));
|
|
}
|
|
else {
|
|
debug.names.push(new RegExp('^' + name + '$'));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Disable debug output.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
debug.disable = function(){
|
|
debug.enable('');
|
|
};
|
|
|
|
/**
|
|
* Humanize the given `ms`.
|
|
*
|
|
* @param {Number} m
|
|
* @return {String}
|
|
* @api private
|
|
*/
|
|
|
|
debug.humanize = function(ms) {
|
|
var sec = 1000
|
|
, min = 60 * 1000
|
|
, hour = 60 * min;
|
|
|
|
if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
|
|
if (ms >= min) return (ms / min).toFixed(1) + 'm';
|
|
if (ms >= sec) return (ms / sec | 0) + 's';
|
|
return ms + 'ms';
|
|
};
|
|
|
|
/**
|
|
* Returns true if the given mode name is enabled, false otherwise.
|
|
*
|
|
* @param {String} name
|
|
* @return {Boolean}
|
|
* @api public
|
|
*/
|
|
|
|
debug.enabled = function(name) {
|
|
for (var i = 0, len = debug.skips.length; i < len; i++) {
|
|
if (debug.skips[i].test(name)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (var i = 0, len = debug.names.length; i < len; i++) {
|
|
if (debug.names[i].test(name)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// persist
|
|
|
|
if (window.localStorage) debug.enable(localStorage.debug);
|
|
|
|
},{}],15:[function(require,module,exports){
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var index = require('indexof');
|
|
|
|
/**
|
|
* Expose `Emitter`.
|
|
*/
|
|
|
|
module.exports = Emitter;
|
|
|
|
/**
|
|
* Initialize a new `Emitter`.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
function Emitter(obj) {
|
|
if (obj) return mixin(obj);
|
|
};
|
|
|
|
/**
|
|
* Mixin the emitter properties.
|
|
*
|
|
* @param {Object} obj
|
|
* @return {Object}
|
|
* @api private
|
|
*/
|
|
|
|
function mixin(obj) {
|
|
for (var key in Emitter.prototype) {
|
|
obj[key] = Emitter.prototype[key];
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* Listen on the given `event` with `fn`.
|
|
*
|
|
* @param {String} event
|
|
* @param {Function} fn
|
|
* @return {Emitter}
|
|
* @api public
|
|
*/
|
|
|
|
Emitter.prototype.on = function(event, fn){
|
|
this._callbacks = this._callbacks || {};
|
|
(this._callbacks[event] = this._callbacks[event] || [])
|
|
.push(fn);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Adds an `event` listener that will be invoked a single
|
|
* time then automatically removed.
|
|
*
|
|
* @param {String} event
|
|
* @param {Function} fn
|
|
* @return {Emitter}
|
|
* @api public
|
|
*/
|
|
|
|
Emitter.prototype.once = function(event, fn){
|
|
var self = this;
|
|
this._callbacks = this._callbacks || {};
|
|
|
|
function on() {
|
|
self.off(event, on);
|
|
fn.apply(this, arguments);
|
|
}
|
|
|
|
fn._off = on;
|
|
this.on(event, on);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Remove the given callback for `event` or all
|
|
* registered callbacks.
|
|
*
|
|
* @param {String} event
|
|
* @param {Function} fn
|
|
* @return {Emitter}
|
|
* @api public
|
|
*/
|
|
|
|
Emitter.prototype.off =
|
|
Emitter.prototype.removeListener =
|
|
Emitter.prototype.removeAllListeners = function(event, fn){
|
|
this._callbacks = this._callbacks || {};
|
|
|
|
// all
|
|
if (0 == arguments.length) {
|
|
this._callbacks = {};
|
|
return this;
|
|
}
|
|
|
|
// specific event
|
|
var callbacks = this._callbacks[event];
|
|
if (!callbacks) return this;
|
|
|
|
// remove all handlers
|
|
if (1 == arguments.length) {
|
|
delete this._callbacks[event];
|
|
return this;
|
|
}
|
|
|
|
// remove specific handler
|
|
var i = index(callbacks, fn._off || fn);
|
|
if (~i) callbacks.splice(i, 1);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Emit `event` with the given args.
|
|
*
|
|
* @param {String} event
|
|
* @param {Mixed} ...
|
|
* @return {Emitter}
|
|
*/
|
|
|
|
Emitter.prototype.emit = function(event){
|
|
this._callbacks = this._callbacks || {};
|
|
var args = [].slice.call(arguments, 1)
|
|
, callbacks = this._callbacks[event];
|
|
|
|
if (callbacks) {
|
|
callbacks = callbacks.slice(0);
|
|
for (var i = 0, len = callbacks.length; i < len; ++i) {
|
|
callbacks[i].apply(this, args);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Return array of callbacks for `event`.
|
|
*
|
|
* @param {String} event
|
|
* @return {Array}
|
|
* @api public
|
|
*/
|
|
|
|
Emitter.prototype.listeners = function(event){
|
|
this._callbacks = this._callbacks || {};
|
|
return this._callbacks[event] || [];
|
|
};
|
|
|
|
/**
|
|
* Check if this emitter has `event` handlers.
|
|
*
|
|
* @param {String} event
|
|
* @return {Boolean}
|
|
* @api public
|
|
*/
|
|
|
|
Emitter.prototype.hasListeners = function(event){
|
|
return !! this.listeners(event).length;
|
|
};
|
|
|
|
},{"indexof":21}],16:[function(require,module,exports){
|
|
|
|
module.exports = require('./lib/');
|
|
|
|
},{"./lib/":17}],17:[function(require,module,exports){
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var keys = require('./keys');
|
|
|
|
/**
|
|
* Current protocol version.
|
|
*/
|
|
exports.protocol = 2;
|
|
|
|
/**
|
|
* Packet types.
|
|
*/
|
|
|
|
var packets = exports.packets = {
|
|
open: 0 // non-ws
|
|
, close: 1 // non-ws
|
|
, ping: 2
|
|
, pong: 3
|
|
, message: 4
|
|
, upgrade: 5
|
|
, noop: 6
|
|
};
|
|
|
|
var packetslist = keys(packets);
|
|
|
|
/**
|
|
* Premade error packet.
|
|
*/
|
|
|
|
var err = { type: 'error', data: 'parser error' };
|
|
|
|
/**
|
|
* Encodes a packet.
|
|
*
|
|
* <packet type id> [ `:` <data> ]
|
|
*
|
|
* Example:
|
|
*
|
|
* 5:hello world
|
|
* 3
|
|
* 4
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
exports.encodePacket = function (packet) {
|
|
var encoded = packets[packet.type];
|
|
|
|
// data fragment is optional
|
|
if (undefined !== packet.data) {
|
|
encoded += String(packet.data);
|
|
}
|
|
|
|
return '' + encoded;
|
|
};
|
|
|
|
/**
|
|
* Decodes a packet.
|
|
*
|
|
* @return {Object} with `type` and `data` (if any)
|
|
* @api private
|
|
*/
|
|
|
|
exports.decodePacket = function (data) {
|
|
var type = data.charAt(0);
|
|
|
|
if (Number(type) != type || !packetslist[type]) {
|
|
return err;
|
|
}
|
|
|
|
if (data.length > 1) {
|
|
return { type: packetslist[type], data: data.substring(1) };
|
|
} else {
|
|
return { type: packetslist[type] };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Encodes multiple messages (payload).
|
|
*
|
|
* <length>:data
|
|
*
|
|
* Example:
|
|
*
|
|
* 11:hello world2:hi
|
|
*
|
|
* @param {Array} packets
|
|
* @api private
|
|
*/
|
|
|
|
exports.encodePayload = function (packets) {
|
|
if (!packets.length) {
|
|
return '0:';
|
|
}
|
|
|
|
var encoded = '';
|
|
var message;
|
|
|
|
for (var i = 0, l = packets.length; i < l; i++) {
|
|
message = exports.encodePacket(packets[i]);
|
|
encoded += message.length + ':' + message;
|
|
}
|
|
|
|
return encoded;
|
|
};
|
|
|
|
/*
|
|
* Decodes data when a payload is maybe expected.
|
|
*
|
|
* @param {String} data, callback method
|
|
* @api public
|
|
*/
|
|
|
|
exports.decodePayload = function (data, callback) {
|
|
var packet;
|
|
if (data == '') {
|
|
// parser error - ignoring payload
|
|
return callback(err, 0, 1);
|
|
}
|
|
|
|
var length = ''
|
|
, n, msg;
|
|
|
|
for (var i = 0, l = data.length; i < l; i++) {
|
|
var chr = data.charAt(i);
|
|
|
|
if (':' != chr) {
|
|
length += chr;
|
|
} else {
|
|
if ('' == length || (length != (n = Number(length)))) {
|
|
// parser error - ignoring payload
|
|
return callback(err, 0, 1);
|
|
}
|
|
|
|
msg = data.substr(i + 1, n);
|
|
|
|
if (length != msg.length) {
|
|
// parser error - ignoring payload
|
|
return callback(err, 0, 1);
|
|
}
|
|
|
|
if (msg.length) {
|
|
packet = exports.decodePacket(msg);
|
|
|
|
if (err.type == packet.type && err.data == packet.data) {
|
|
// parser error in individual packet - ignoring payload
|
|
return callback(err, 0, 1);
|
|
}
|
|
|
|
var ret = callback(packet, i + n, l);
|
|
if (false === ret) return;
|
|
}
|
|
|
|
// advance cursor
|
|
i += n;
|
|
length = '';
|
|
}
|
|
}
|
|
|
|
if (length != '') {
|
|
// parser error - ignoring payload
|
|
return callback(err, 0, 1);
|
|
}
|
|
|
|
};
|
|
|
|
},{"./keys":18}],18:[function(require,module,exports){
|
|
|
|
/**
|
|
* Gets the keys for an object.
|
|
*
|
|
* @return {Array} keys
|
|
* @api private
|
|
*/
|
|
|
|
module.exports = Object.keys || function keys (obj){
|
|
var arr = [];
|
|
var has = Object.prototype.hasOwnProperty;
|
|
|
|
for (var i in obj) {
|
|
if (has.call(obj, i)) {
|
|
arr.push(i);
|
|
}
|
|
}
|
|
return arr;
|
|
};
|
|
|
|
},{}],19:[function(require,module,exports){
|
|
|
|
/**
|
|
* Returns `this`. Execute this without a "context" (i.e. without it being
|
|
* attached to an object of the left-hand side), and `this` points to the
|
|
* "global" scope of the current JS execution.
|
|
*/
|
|
|
|
module.exports = (function () { return this; })();
|
|
|
|
},{}],20:[function(require,module,exports){
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var global = require('global');
|
|
|
|
/**
|
|
* Module exports.
|
|
*
|
|
* Logic borrowed from Modernizr:
|
|
*
|
|
* - https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cors.js
|
|
*/
|
|
|
|
module.exports = 'XMLHttpRequest' in global &&
|
|
'withCredentials' in new global.XMLHttpRequest();
|
|
|
|
},{"global":19}],21:[function(require,module,exports){
|
|
|
|
var indexOf = [].indexOf;
|
|
|
|
module.exports = function(arr, obj){
|
|
if (indexOf) return arr.indexOf(obj);
|
|
for (var i = 0; i < arr.length; ++i) {
|
|
if (arr[i] === obj) return i;
|
|
}
|
|
return -1;
|
|
};
|
|
},{}],22:[function(require,module,exports){
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var global = (function() { return this; })();
|
|
|
|
/**
|
|
* WebSocket constructor.
|
|
*/
|
|
|
|
var WebSocket = global.WebSocket || global.MozWebSocket;
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
module.exports = WebSocket ? ws : null;
|
|
|
|
/**
|
|
* WebSocket constructor.
|
|
*
|
|
* The third `opts` options object gets ignored in web browsers, since it's
|
|
* non-standard, and throws a TypeError if passed to the constructor.
|
|
* See: https://github.com/einaros/ws/issues/227
|
|
*
|
|
* @param {String} uri
|
|
* @param {Array} protocols (optional)
|
|
* @param {Object) opts (optional)
|
|
* @api public
|
|
*/
|
|
|
|
function ws(uri, protocols, opts) {
|
|
var instance;
|
|
if (protocols) {
|
|
instance = new WebSocket(uri, protocols);
|
|
} else {
|
|
instance = new WebSocket(uri);
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
if (WebSocket) ws.prototype = WebSocket.prototype;
|
|
|
|
},{}]},{},[1])
|
|
(1)
|
|
});
|
|
; |