mirror of
https://github.com/socketio/socket.io.git
synced 2026-04-30 03:00:39 -04:00
7918 lines
193 KiB
JavaScript
7918 lines
193 KiB
JavaScript
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.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);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
|
||
|
||
module.exports = _dereq_('./lib/');
|
||
|
||
},{"./lib/":2}],2:[function(_dereq_,module,exports){
|
||
|
||
module.exports = _dereq_('./socket');
|
||
|
||
/**
|
||
* Exports parser
|
||
*
|
||
* @api public
|
||
*
|
||
*/
|
||
module.exports.parser = _dereq_('engine.io-parser');
|
||
|
||
},{"./socket":3,"engine.io-parser":17}],3:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
/**
|
||
* Module dependencies.
|
||
*/
|
||
|
||
var transports = _dereq_('./transports');
|
||
var Emitter = _dereq_('component-emitter');
|
||
var debug = _dereq_('debug')('engine.io-client:socket');
|
||
var index = _dereq_('indexof');
|
||
var parser = _dereq_('engine.io-parser');
|
||
var parseuri = _dereq_('parseuri');
|
||
var parsejson = _dereq_('parsejson');
|
||
var parseqs = _dereq_('parseqs');
|
||
|
||
/**
|
||
* Module exports.
|
||
*/
|
||
|
||
module.exports = Socket;
|
||
|
||
/**
|
||
* Noop function.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
function noop(){}
|
||
|
||
/**
|
||
* Socket constructor.
|
||
*
|
||
* @param {String|Object} uri or options
|
||
* @param {Object} options
|
||
* @api public
|
||
*/
|
||
|
||
function Socket(uri, opts){
|
||
if (!(this instanceof Socket)) return new Socket(uri, opts);
|
||
|
||
opts = opts || {};
|
||
|
||
if (uri && 'object' == typeof uri) {
|
||
opts = uri;
|
||
uri = null;
|
||
}
|
||
|
||
if (uri) {
|
||
uri = parseuri(uri);
|
||
opts.hostname = uri.host;
|
||
opts.secure = uri.protocol == 'https' || uri.protocol == 'wss';
|
||
opts.port = uri.port;
|
||
if (uri.query) opts.query = uri.query;
|
||
} else if (opts.host) {
|
||
opts.hostname = parseuri(opts.host).host;
|
||
}
|
||
|
||
this.secure = null != opts.secure ? opts.secure :
|
||
(global.location && 'https:' == location.protocol);
|
||
|
||
if (opts.hostname && !opts.port) {
|
||
// if no port is specified manually, use the protocol default
|
||
opts.port = this.secure ? '443' : '80';
|
||
}
|
||
|
||
this.agent = opts.agent || false;
|
||
this.hostname = opts.hostname ||
|
||
(global.location ? location.hostname : 'localhost');
|
||
this.port = opts.port || (global.location && location.port ?
|
||
location.port :
|
||
(this.secure ? 443 : 80));
|
||
this.query = opts.query || {};
|
||
if ('string' == typeof this.query) this.query = parseqs.decode(this.query);
|
||
this.upgrade = false !== opts.upgrade;
|
||
this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/';
|
||
this.forceJSONP = !!opts.forceJSONP;
|
||
this.jsonp = false !== opts.jsonp;
|
||
this.forceBase64 = !!opts.forceBase64;
|
||
this.enablesXDR = !!opts.enablesXDR;
|
||
this.timestampParam = opts.timestampParam || 't';
|
||
this.timestampRequests = opts.timestampRequests;
|
||
this.transports = opts.transports || ['polling', 'websocket'];
|
||
this.readyState = '';
|
||
this.writeBuffer = [];
|
||
this.policyPort = opts.policyPort || 843;
|
||
this.rememberUpgrade = opts.rememberUpgrade || false;
|
||
this.binaryType = null;
|
||
this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades;
|
||
this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false;
|
||
|
||
if (true === this.perMessageDeflate) this.perMessageDeflate = {};
|
||
if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) {
|
||
this.perMessageDeflate.threshold = 1024;
|
||
}
|
||
|
||
// SSL options for Node.js client
|
||
this.pfx = opts.pfx || null;
|
||
this.key = opts.key || null;
|
||
this.passphrase = opts.passphrase || null;
|
||
this.cert = opts.cert || null;
|
||
this.ca = opts.ca || null;
|
||
this.ciphers = opts.ciphers || null;
|
||
this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? null : opts.rejectUnauthorized;
|
||
|
||
// other options for Node.js client
|
||
var freeGlobal = typeof global == 'object' && global;
|
||
if (freeGlobal.global === freeGlobal) {
|
||
if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) {
|
||
this.extraHeaders = opts.extraHeaders;
|
||
}
|
||
}
|
||
|
||
this.open();
|
||
}
|
||
|
||
Socket.priorWebsocketSuccess = false;
|
||
|
||
/**
|
||
* Mix in `Emitter`.
|
||
*/
|
||
|
||
Emitter(Socket.prototype);
|
||
|
||
/**
|
||
* Protocol version.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Socket.protocol = parser.protocol; // this is an int
|
||
|
||
/**
|
||
* Expose deps for legacy compatibility
|
||
* and standalone browser access.
|
||
*/
|
||
|
||
Socket.Socket = Socket;
|
||
Socket.Transport = _dereq_('./transport');
|
||
Socket.transports = _dereq_('./transports');
|
||
Socket.parser = _dereq_('engine.io-parser');
|
||
|
||
/**
|
||
* Creates transport of the given type.
|
||
*
|
||
* @param {String} transport name
|
||
* @return {Transport}
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.createTransport = function (name) {
|
||
debug('creating transport "%s"', name);
|
||
var query = clone(this.query);
|
||
|
||
// append engine.io protocol identifier
|
||
query.EIO = parser.protocol;
|
||
|
||
// transport name
|
||
query.transport = name;
|
||
|
||
// session id if we already have one
|
||
if (this.id) query.sid = this.id;
|
||
|
||
var transport = new transports[name]({
|
||
agent: this.agent,
|
||
hostname: this.hostname,
|
||
port: this.port,
|
||
secure: this.secure,
|
||
path: this.path,
|
||
query: query,
|
||
forceJSONP: this.forceJSONP,
|
||
jsonp: this.jsonp,
|
||
forceBase64: this.forceBase64,
|
||
enablesXDR: this.enablesXDR,
|
||
timestampRequests: this.timestampRequests,
|
||
timestampParam: this.timestampParam,
|
||
policyPort: this.policyPort,
|
||
socket: this,
|
||
pfx: this.pfx,
|
||
key: this.key,
|
||
passphrase: this.passphrase,
|
||
cert: this.cert,
|
||
ca: this.ca,
|
||
ciphers: this.ciphers,
|
||
rejectUnauthorized: this.rejectUnauthorized,
|
||
perMessageDeflate: this.perMessageDeflate,
|
||
extraHeaders: this.extraHeaders
|
||
});
|
||
|
||
return transport;
|
||
};
|
||
|
||
function clone (obj) {
|
||
var o = {};
|
||
for (var i in obj) {
|
||
if (obj.hasOwnProperty(i)) {
|
||
o[i] = obj[i];
|
||
}
|
||
}
|
||
return o;
|
||
}
|
||
|
||
/**
|
||
* Initializes transport to use and starts probe.
|
||
*
|
||
* @api private
|
||
*/
|
||
Socket.prototype.open = function () {
|
||
var transport;
|
||
if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) {
|
||
transport = 'websocket';
|
||
} else if (0 === this.transports.length) {
|
||
// Emit error on next tick so it can be listened to
|
||
var self = this;
|
||
setTimeout(function() {
|
||
self.emit('error', 'No transports available');
|
||
}, 0);
|
||
return;
|
||
} else {
|
||
transport = this.transports[0];
|
||
}
|
||
this.readyState = 'opening';
|
||
|
||
// Retry with the next transport if the transport is disabled (jsonp: false)
|
||
try {
|
||
transport = this.createTransport(transport);
|
||
} catch (e) {
|
||
this.transports.shift();
|
||
this.open();
|
||
return;
|
||
}
|
||
|
||
transport.open();
|
||
this.setTransport(transport);
|
||
};
|
||
|
||
/**
|
||
* Sets the current transport. Disables the existing one (if any).
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.setTransport = function(transport){
|
||
debug('setting transport %s', transport.name);
|
||
var self = this;
|
||
|
||
if (this.transport) {
|
||
debug('clearing existing transport %s', this.transport.name);
|
||
this.transport.removeAllListeners();
|
||
}
|
||
|
||
// set up transport
|
||
this.transport = transport;
|
||
|
||
// set up transport listeners
|
||
transport
|
||
.on('drain', function(){
|
||
self.onDrain();
|
||
})
|
||
.on('packet', function(packet){
|
||
self.onPacket(packet);
|
||
})
|
||
.on('error', function(e){
|
||
self.onError(e);
|
||
})
|
||
.on('close', function(){
|
||
self.onClose('transport close');
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Probes a transport.
|
||
*
|
||
* @param {String} transport name
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.probe = function (name) {
|
||
debug('probing transport "%s"', name);
|
||
var transport = this.createTransport(name, { probe: 1 })
|
||
, failed = false
|
||
, self = this;
|
||
|
||
Socket.priorWebsocketSuccess = false;
|
||
|
||
function onTransportOpen(){
|
||
if (self.onlyBinaryUpgrades) {
|
||
var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary;
|
||
failed = failed || upgradeLosesBinary;
|
||
}
|
||
if (failed) return;
|
||
|
||
debug('probe transport "%s" opened', name);
|
||
transport.send([{ type: 'ping', data: 'probe' }]);
|
||
transport.once('packet', function (msg) {
|
||
if (failed) return;
|
||
if ('pong' == msg.type && 'probe' == msg.data) {
|
||
debug('probe transport "%s" pong', name);
|
||
self.upgrading = true;
|
||
self.emit('upgrading', transport);
|
||
if (!transport) return;
|
||
Socket.priorWebsocketSuccess = 'websocket' == transport.name;
|
||
|
||
debug('pausing current transport "%s"', self.transport.name);
|
||
self.transport.pause(function () {
|
||
if (failed) return;
|
||
if ('closed' == self.readyState) return;
|
||
debug('changing transport and sending upgrade packet');
|
||
|
||
cleanup();
|
||
|
||
self.setTransport(transport);
|
||
transport.send([{ type: 'upgrade' }]);
|
||
self.emit('upgrade', transport);
|
||
transport = null;
|
||
self.upgrading = false;
|
||
self.flush();
|
||
});
|
||
} else {
|
||
debug('probe transport "%s" failed', name);
|
||
var err = new Error('probe error');
|
||
err.transport = transport.name;
|
||
self.emit('upgradeError', err);
|
||
}
|
||
});
|
||
}
|
||
|
||
function freezeTransport() {
|
||
if (failed) return;
|
||
|
||
// Any callback called by transport should be ignored since now
|
||
failed = true;
|
||
|
||
cleanup();
|
||
|
||
transport.close();
|
||
transport = null;
|
||
}
|
||
|
||
//Handle any error that happens while probing
|
||
function onerror(err) {
|
||
var error = new Error('probe error: ' + err);
|
||
error.transport = transport.name;
|
||
|
||
freezeTransport();
|
||
|
||
debug('probe transport "%s" failed because of error: %s', name, err);
|
||
|
||
self.emit('upgradeError', error);
|
||
}
|
||
|
||
function onTransportClose(){
|
||
onerror("transport closed");
|
||
}
|
||
|
||
//When the socket is closed while we're probing
|
||
function onclose(){
|
||
onerror("socket closed");
|
||
}
|
||
|
||
//When the socket is upgraded while we're probing
|
||
function onupgrade(to){
|
||
if (transport && to.name != transport.name) {
|
||
debug('"%s" works - aborting "%s"', to.name, transport.name);
|
||
freezeTransport();
|
||
}
|
||
}
|
||
|
||
//Remove all listeners on the transport and on self
|
||
function cleanup(){
|
||
transport.removeListener('open', onTransportOpen);
|
||
transport.removeListener('error', onerror);
|
||
transport.removeListener('close', onTransportClose);
|
||
self.removeListener('close', onclose);
|
||
self.removeListener('upgrading', onupgrade);
|
||
}
|
||
|
||
transport.once('open', onTransportOpen);
|
||
transport.once('error', onerror);
|
||
transport.once('close', onTransportClose);
|
||
|
||
this.once('close', onclose);
|
||
this.once('upgrading', onupgrade);
|
||
|
||
transport.open();
|
||
|
||
};
|
||
|
||
/**
|
||
* Called when connection is deemed open.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Socket.prototype.onOpen = function () {
|
||
debug('socket open');
|
||
this.readyState = 'open';
|
||
Socket.priorWebsocketSuccess = 'websocket' == this.transport.name;
|
||
this.emit('open');
|
||
this.flush();
|
||
|
||
// we check for `readyState` in case an `open`
|
||
// listener already closed the socket
|
||
if ('open' == this.readyState && this.upgrade && this.transport.pause) {
|
||
debug('starting upgrade probes');
|
||
for (var i = 0, l = this.upgrades.length; i < l; i++) {
|
||
this.probe(this.upgrades[i]);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Handles a packet.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.onPacket = function (packet) {
|
||
if ('opening' == this.readyState || 'open' == this.readyState) {
|
||
debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
|
||
|
||
this.emit('packet', packet);
|
||
|
||
// Socket is live - any packet counts
|
||
this.emit('heartbeat');
|
||
|
||
switch (packet.type) {
|
||
case 'open':
|
||
this.onHandshake(parsejson(packet.data));
|
||
break;
|
||
|
||
case 'pong':
|
||
this.setPing();
|
||
this.emit('pong');
|
||
break;
|
||
|
||
case 'error':
|
||
var err = new Error('server error');
|
||
err.code = packet.data;
|
||
this.onError(err);
|
||
break;
|
||
|
||
case 'message':
|
||
this.emit('data', packet.data);
|
||
this.emit('message', packet.data);
|
||
break;
|
||
}
|
||
} else {
|
||
debug('packet received with socket readyState "%s"', this.readyState);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Called upon handshake completion.
|
||
*
|
||
* @param {Object} handshake obj
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.onHandshake = function (data) {
|
||
this.emit('handshake', data);
|
||
this.id = data.sid;
|
||
this.transport.query.sid = data.sid;
|
||
this.upgrades = this.filterUpgrades(data.upgrades);
|
||
this.pingInterval = data.pingInterval;
|
||
this.pingTimeout = data.pingTimeout;
|
||
this.onOpen();
|
||
// In case open handler closes socket
|
||
if ('closed' == this.readyState) return;
|
||
this.setPing();
|
||
|
||
// Prolong liveness of socket on heartbeat
|
||
this.removeListener('heartbeat', this.onHeartbeat);
|
||
this.on('heartbeat', this.onHeartbeat);
|
||
};
|
||
|
||
/**
|
||
* Resets ping timeout.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.onHeartbeat = function (timeout) {
|
||
clearTimeout(this.pingTimeoutTimer);
|
||
var self = this;
|
||
self.pingTimeoutTimer = setTimeout(function () {
|
||
if ('closed' == self.readyState) return;
|
||
self.onClose('ping timeout');
|
||
}, timeout || (self.pingInterval + self.pingTimeout));
|
||
};
|
||
|
||
/**
|
||
* Pings server every `this.pingInterval` and expects response
|
||
* within `this.pingTimeout` or closes connection.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.setPing = function () {
|
||
var self = this;
|
||
clearTimeout(self.pingIntervalTimer);
|
||
self.pingIntervalTimer = setTimeout(function () {
|
||
debug('writing ping packet - expecting pong within %sms', self.pingTimeout);
|
||
self.ping();
|
||
self.onHeartbeat(self.pingTimeout);
|
||
}, self.pingInterval);
|
||
};
|
||
|
||
/**
|
||
* Sends a ping packet.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.ping = function () {
|
||
var self = this;
|
||
this.sendPacket('ping', function(){
|
||
self.emit('ping');
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Called on `drain` event
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.onDrain = function() {
|
||
this.writeBuffer.splice(0, this.prevBufferLen);
|
||
|
||
// setting prevBufferLen = 0 is very important
|
||
// for example, when upgrading, upgrade packet is sent over,
|
||
// and a nonzero prevBufferLen could cause problems on `drain`
|
||
this.prevBufferLen = 0;
|
||
|
||
if (0 === this.writeBuffer.length) {
|
||
this.emit('drain');
|
||
} else {
|
||
this.flush();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Flush write buffers.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.flush = function () {
|
||
if ('closed' != this.readyState && this.transport.writable &&
|
||
!this.upgrading && this.writeBuffer.length) {
|
||
debug('flushing %d packets in socket', this.writeBuffer.length);
|
||
this.transport.send(this.writeBuffer);
|
||
// keep track of current length of writeBuffer
|
||
// splice writeBuffer and callbackBuffer on `drain`
|
||
this.prevBufferLen = this.writeBuffer.length;
|
||
this.emit('flush');
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Sends a message.
|
||
*
|
||
* @param {String} message.
|
||
* @param {Function} callback function.
|
||
* @param {Object} options.
|
||
* @return {Socket} for chaining.
|
||
* @api public
|
||
*/
|
||
|
||
Socket.prototype.write =
|
||
Socket.prototype.send = function (msg, options, fn) {
|
||
this.sendPacket('message', msg, options, fn);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Sends a packet.
|
||
*
|
||
* @param {String} packet type.
|
||
* @param {String} data.
|
||
* @param {Object} options.
|
||
* @param {Function} callback function.
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.sendPacket = function (type, data, options, fn) {
|
||
if('function' == typeof data) {
|
||
fn = data;
|
||
data = undefined;
|
||
}
|
||
|
||
if ('function' == typeof options) {
|
||
fn = options;
|
||
options = null;
|
||
}
|
||
|
||
if ('closing' == this.readyState || 'closed' == this.readyState) {
|
||
return;
|
||
}
|
||
|
||
options = options || {};
|
||
options.compress = false !== options.compress;
|
||
|
||
var packet = {
|
||
type: type,
|
||
data: data,
|
||
options: options
|
||
};
|
||
this.emit('packetCreate', packet);
|
||
this.writeBuffer.push(packet);
|
||
if (fn) this.once('flush', fn);
|
||
this.flush();
|
||
};
|
||
|
||
/**
|
||
* Closes the connection.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.close = function () {
|
||
if ('opening' == this.readyState || 'open' == this.readyState) {
|
||
this.readyState = 'closing';
|
||
|
||
var self = this;
|
||
|
||
if (this.writeBuffer.length) {
|
||
this.once('drain', function() {
|
||
if (this.upgrading) {
|
||
waitForUpgrade();
|
||
} else {
|
||
close();
|
||
}
|
||
});
|
||
} else if (this.upgrading) {
|
||
waitForUpgrade();
|
||
} else {
|
||
close();
|
||
}
|
||
}
|
||
|
||
function close() {
|
||
self.onClose('forced close');
|
||
debug('socket closing - telling transport to close');
|
||
self.transport.close();
|
||
}
|
||
|
||
function cleanupAndClose() {
|
||
self.removeListener('upgrade', cleanupAndClose);
|
||
self.removeListener('upgradeError', cleanupAndClose);
|
||
close();
|
||
}
|
||
|
||
function waitForUpgrade() {
|
||
// wait for upgrade to finish since we can't send packets while pausing a transport
|
||
self.once('upgrade', cleanupAndClose);
|
||
self.once('upgradeError', cleanupAndClose);
|
||
}
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Called upon transport error
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.onError = function (err) {
|
||
debug('socket error %j', err);
|
||
Socket.priorWebsocketSuccess = false;
|
||
this.emit('error', err);
|
||
this.onClose('transport error', err);
|
||
};
|
||
|
||
/**
|
||
* Called upon transport close.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Socket.prototype.onClose = function (reason, desc) {
|
||
if ('opening' == this.readyState || 'open' == this.readyState || 'closing' == this.readyState) {
|
||
debug('socket close with reason: "%s"', reason);
|
||
var self = this;
|
||
|
||
// clear timers
|
||
clearTimeout(this.pingIntervalTimer);
|
||
clearTimeout(this.pingTimeoutTimer);
|
||
|
||
// stop event from firing again for transport
|
||
this.transport.removeAllListeners('close');
|
||
|
||
// ensure transport won't stay open
|
||
this.transport.close();
|
||
|
||
// ignore further transport communication
|
||
this.transport.removeAllListeners();
|
||
|
||
// set ready state
|
||
this.readyState = 'closed';
|
||
|
||
// clear session id
|
||
this.id = null;
|
||
|
||
// emit close event
|
||
this.emit('close', reason, desc);
|
||
|
||
// clean buffers after, so users can still
|
||
// grab the buffers on `close` event
|
||
self.writeBuffer = [];
|
||
self.prevBufferLen = 0;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Filters upgrades, returning only those matching client transports.
|
||
*
|
||
* @param {Array} server upgrades
|
||
* @api private
|
||
*
|
||
*/
|
||
|
||
Socket.prototype.filterUpgrades = function (upgrades) {
|
||
var filteredUpgrades = [];
|
||
for (var i = 0, j = upgrades.length; i<j; i++) {
|
||
if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]);
|
||
}
|
||
return filteredUpgrades;
|
||
};
|
||
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{"./transport":4,"./transports":5,"component-emitter":12,"debug":14,"engine.io-parser":17,"indexof":26,"parsejson":27,"parseqs":28,"parseuri":29}],4:[function(_dereq_,module,exports){
|
||
/**
|
||
* Module dependencies.
|
||
*/
|
||
|
||
var parser = _dereq_('engine.io-parser');
|
||
var Emitter = _dereq_('component-emitter');
|
||
|
||
/**
|
||
* Module exports.
|
||
*/
|
||
|
||
module.exports = Transport;
|
||
|
||
/**
|
||
* Transport abstract constructor.
|
||
*
|
||
* @param {Object} options.
|
||
* @api private
|
||
*/
|
||
|
||
function Transport (opts) {
|
||
this.path = opts.path;
|
||
this.hostname = opts.hostname;
|
||
this.port = opts.port;
|
||
this.secure = opts.secure;
|
||
this.query = opts.query;
|
||
this.timestampParam = opts.timestampParam;
|
||
this.timestampRequests = opts.timestampRequests;
|
||
this.readyState = '';
|
||
this.agent = opts.agent || false;
|
||
this.socket = opts.socket;
|
||
this.enablesXDR = opts.enablesXDR;
|
||
|
||
// SSL options for Node.js client
|
||
this.pfx = opts.pfx;
|
||
this.key = opts.key;
|
||
this.passphrase = opts.passphrase;
|
||
this.cert = opts.cert;
|
||
this.ca = opts.ca;
|
||
this.ciphers = opts.ciphers;
|
||
this.rejectUnauthorized = opts.rejectUnauthorized;
|
||
|
||
// other options for Node.js client
|
||
this.extraHeaders = opts.extraHeaders;
|
||
}
|
||
|
||
/**
|
||
* Mix in `Emitter`.
|
||
*/
|
||
|
||
Emitter(Transport.prototype);
|
||
|
||
/**
|
||
* Emits an error.
|
||
*
|
||
* @param {String} str
|
||
* @return {Transport} for chaining
|
||
* @api public
|
||
*/
|
||
|
||
Transport.prototype.onError = function (msg, desc) {
|
||
var err = new Error(msg);
|
||
err.type = 'TransportError';
|
||
err.description = desc;
|
||
this.emit('error', err);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Opens the transport.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Transport.prototype.open = function () {
|
||
if ('closed' == this.readyState || '' == this.readyState) {
|
||
this.readyState = 'opening';
|
||
this.doOpen();
|
||
}
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Closes the transport.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Transport.prototype.close = function () {
|
||
if ('opening' == this.readyState || 'open' == this.readyState) {
|
||
this.doClose();
|
||
this.onClose();
|
||
}
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Sends multiple packets.
|
||
*
|
||
* @param {Array} packets
|
||
* @api private
|
||
*/
|
||
|
||
Transport.prototype.send = function(packets){
|
||
if ('open' == this.readyState) {
|
||
this.write(packets);
|
||
} else {
|
||
throw new Error('Transport not open');
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Called upon open
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Transport.prototype.onOpen = function () {
|
||
this.readyState = 'open';
|
||
this.writable = true;
|
||
this.emit('open');
|
||
};
|
||
|
||
/**
|
||
* Called with data.
|
||
*
|
||
* @param {String} data
|
||
* @api private
|
||
*/
|
||
|
||
Transport.prototype.onData = function(data){
|
||
var packet = parser.decodePacket(data, this.socket.binaryType);
|
||
this.onPacket(packet);
|
||
};
|
||
|
||
/**
|
||
* Called with a decoded packet.
|
||
*/
|
||
|
||
Transport.prototype.onPacket = function (packet) {
|
||
this.emit('packet', packet);
|
||
};
|
||
|
||
/**
|
||
* Called upon close.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Transport.prototype.onClose = function () {
|
||
this.readyState = 'closed';
|
||
this.emit('close');
|
||
};
|
||
|
||
},{"component-emitter":12,"engine.io-parser":17}],5:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
/**
|
||
* Module dependencies
|
||
*/
|
||
|
||
var XMLHttpRequest = _dereq_('xmlhttprequest-ssl');
|
||
var XHR = _dereq_('./polling-xhr');
|
||
var JSONP = _dereq_('./polling-jsonp');
|
||
var websocket = _dereq_('./websocket');
|
||
|
||
/**
|
||
* Export transports.
|
||
*/
|
||
|
||
exports.polling = polling;
|
||
exports.websocket = websocket;
|
||
|
||
/**
|
||
* Polling transport polymorphic constructor.
|
||
* Decides on xhr vs jsonp based on feature detection.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
function polling(opts){
|
||
var xhr;
|
||
var xd = false;
|
||
var xs = false;
|
||
var jsonp = false !== opts.jsonp;
|
||
|
||
if (global.location) {
|
||
var isSSL = 'https:' == location.protocol;
|
||
var port = location.port;
|
||
|
||
// some user agents have empty `location.port`
|
||
if (!port) {
|
||
port = isSSL ? 443 : 80;
|
||
}
|
||
|
||
xd = opts.hostname != location.hostname || port != opts.port;
|
||
xs = opts.secure != isSSL;
|
||
}
|
||
|
||
opts.xdomain = xd;
|
||
opts.xscheme = xs;
|
||
xhr = new XMLHttpRequest(opts);
|
||
|
||
if ('open' in xhr && !opts.forceJSONP) {
|
||
return new XHR(opts);
|
||
} else {
|
||
if (!jsonp) throw new Error('JSONP disabled');
|
||
return new JSONP(opts);
|
||
}
|
||
}
|
||
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{"./polling-jsonp":6,"./polling-xhr":7,"./websocket":9,"xmlhttprequest-ssl":10}],6:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
|
||
/**
|
||
* Module requirements.
|
||
*/
|
||
|
||
var Polling = _dereq_('./polling');
|
||
var inherit = _dereq_('component-inherit');
|
||
|
||
/**
|
||
* Module exports.
|
||
*/
|
||
|
||
module.exports = JSONPPolling;
|
||
|
||
/**
|
||
* Cached regular expressions.
|
||
*/
|
||
|
||
var rNewline = /\n/g;
|
||
var rEscapedNewline = /\\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);
|
||
|
||
this.query = this.query || {};
|
||
|
||
// define global callbacks array if not present
|
||
// we do this here (lazily) to avoid unneeded global pollution
|
||
if (!callbacks) {
|
||
// we need to consider multiple engines in the same page
|
||
if (!global.___eio) global.___eio = [];
|
||
callbacks = global.___eio;
|
||
}
|
||
|
||
// callback identifier
|
||
this.index = callbacks.length;
|
||
|
||
// add callback to jsonp global
|
||
var self = this;
|
||
callbacks.push(function (msg) {
|
||
self.onData(msg);
|
||
});
|
||
|
||
// append to query string
|
||
this.query.j = this.index;
|
||
|
||
// prevent spurious errors from being emitted when the window is unloaded
|
||
if (global.document && global.addEventListener) {
|
||
global.addEventListener('beforeunload', function () {
|
||
if (self.script) self.script.onerror = empty;
|
||
}, false);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Inherits from Polling.
|
||
*/
|
||
|
||
inherit(JSONPPolling, Polling);
|
||
|
||
/*
|
||
* JSONP only supports binary as base64 encoded strings
|
||
*/
|
||
|
||
JSONPPolling.prototype.supportsBinary = false;
|
||
|
||
/**
|
||
* Closes the socket.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
JSONPPolling.prototype.doClose = function () {
|
||
if (this.script) {
|
||
this.script.parentNode.removeChild(this.script);
|
||
this.script = null;
|
||
}
|
||
|
||
if (this.form) {
|
||
this.form.parentNode.removeChild(this.form);
|
||
this.form = null;
|
||
this.iframe = null;
|
||
}
|
||
|
||
Polling.prototype.doClose.call(this);
|
||
};
|
||
|
||
/**
|
||
* Starts a poll cycle.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
JSONPPolling.prototype.doPoll = function () {
|
||
var self = this;
|
||
var script = document.createElement('script');
|
||
|
||
if (this.script) {
|
||
this.script.parentNode.removeChild(this.script);
|
||
this.script = null;
|
||
}
|
||
|
||
script.async = true;
|
||
script.src = this.uri();
|
||
script.onerror = function(e){
|
||
self.onError('jsonp poll error',e);
|
||
};
|
||
|
||
var insertAt = document.getElementsByTagName('script')[0];
|
||
insertAt.parentNode.insertBefore(script, insertAt);
|
||
this.script = script;
|
||
|
||
var isUAgecko = 'undefined' != typeof navigator && /gecko/i.test(navigator.userAgent);
|
||
|
||
if (isUAgecko) {
|
||
setTimeout(function () {
|
||
var iframe = document.createElement('iframe');
|
||
document.body.appendChild(iframe);
|
||
document.body.removeChild(iframe);
|
||
}, 100);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Writes with a hidden iframe.
|
||
*
|
||
* @param {String} data to send
|
||
* @param {Function} called upon flush.
|
||
* @api private
|
||
*/
|
||
|
||
JSONPPolling.prototype.doWrite = function (data, fn) {
|
||
var self = this;
|
||
|
||
if (!this.form) {
|
||
var form = document.createElement('form');
|
||
var area = document.createElement('textarea');
|
||
var id = this.iframeId = 'eio_iframe_' + this.index;
|
||
var iframe;
|
||
|
||
form.className = 'socketio';
|
||
form.style.position = 'absolute';
|
||
form.style.top = '-1000px';
|
||
form.style.left = '-1000px';
|
||
form.target = id;
|
||
form.method = 'POST';
|
||
form.setAttribute('accept-charset', 'utf-8');
|
||
area.name = 'd';
|
||
form.appendChild(area);
|
||
document.body.appendChild(form);
|
||
|
||
this.form = form;
|
||
this.area = area;
|
||
}
|
||
|
||
this.form.action = this.uri();
|
||
|
||
function complete () {
|
||
initIframe();
|
||
fn();
|
||
}
|
||
|
||
function initIframe () {
|
||
if (self.iframe) {
|
||
try {
|
||
self.form.removeChild(self.iframe);
|
||
} catch (e) {
|
||
self.onError('jsonp polling iframe removal error', e);
|
||
}
|
||
}
|
||
|
||
try {
|
||
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
|
||
var html = '<iframe src="javascript:0" name="'+ self.iframeId +'">';
|
||
iframe = document.createElement(html);
|
||
} catch (e) {
|
||
iframe = document.createElement('iframe');
|
||
iframe.name = self.iframeId;
|
||
iframe.src = 'javascript:0';
|
||
}
|
||
|
||
iframe.id = self.iframeId;
|
||
|
||
self.form.appendChild(iframe);
|
||
self.iframe = iframe;
|
||
}
|
||
|
||
initIframe();
|
||
|
||
// escape \n to prevent it from being converted into \r\n by some UAs
|
||
// double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side
|
||
data = data.replace(rEscapedNewline, '\\\n');
|
||
this.area.value = data.replace(rNewline, '\\n');
|
||
|
||
try {
|
||
this.form.submit();
|
||
} catch(e) {}
|
||
|
||
if (this.iframe.attachEvent) {
|
||
this.iframe.onreadystatechange = function(){
|
||
if (self.iframe.readyState == 'complete') {
|
||
complete();
|
||
}
|
||
};
|
||
} else {
|
||
this.iframe.onload = complete;
|
||
}
|
||
};
|
||
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{"./polling":8,"component-inherit":13}],7:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
/**
|
||
* Module requirements.
|
||
*/
|
||
|
||
var XMLHttpRequest = _dereq_('xmlhttprequest-ssl');
|
||
var Polling = _dereq_('./polling');
|
||
var Emitter = _dereq_('component-emitter');
|
||
var inherit = _dereq_('component-inherit');
|
||
var debug = _dereq_('debug')('engine.io-client:polling-xhr');
|
||
|
||
/**
|
||
* Module exports.
|
||
*/
|
||
|
||
module.exports = XHR;
|
||
module.exports.Request = Request;
|
||
|
||
/**
|
||
* Empty function
|
||
*/
|
||
|
||
function empty(){}
|
||
|
||
/**
|
||
* XHR Polling constructor.
|
||
*
|
||
* @param {Object} opts
|
||
* @api public
|
||
*/
|
||
|
||
function XHR(opts){
|
||
Polling.call(this, opts);
|
||
|
||
if (global.location) {
|
||
var isSSL = 'https:' == location.protocol;
|
||
var port = location.port;
|
||
|
||
// some user agents have empty `location.port`
|
||
if (!port) {
|
||
port = isSSL ? 443 : 80;
|
||
}
|
||
|
||
this.xd = opts.hostname != global.location.hostname ||
|
||
port != opts.port;
|
||
this.xs = opts.secure != isSSL;
|
||
} else {
|
||
this.extraHeaders = opts.extraHeaders;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Inherits from Polling.
|
||
*/
|
||
|
||
inherit(XHR, Polling);
|
||
|
||
/**
|
||
* XHR supports binary
|
||
*/
|
||
|
||
XHR.prototype.supportsBinary = true;
|
||
|
||
/**
|
||
* Creates a request.
|
||
*
|
||
* @param {String} method
|
||
* @api private
|
||
*/
|
||
|
||
XHR.prototype.request = function(opts){
|
||
opts = opts || {};
|
||
opts.uri = this.uri();
|
||
opts.xd = this.xd;
|
||
opts.xs = this.xs;
|
||
opts.agent = this.agent || false;
|
||
opts.supportsBinary = this.supportsBinary;
|
||
opts.enablesXDR = this.enablesXDR;
|
||
|
||
// SSL options for Node.js client
|
||
opts.pfx = this.pfx;
|
||
opts.key = this.key;
|
||
opts.passphrase = this.passphrase;
|
||
opts.cert = this.cert;
|
||
opts.ca = this.ca;
|
||
opts.ciphers = this.ciphers;
|
||
opts.rejectUnauthorized = this.rejectUnauthorized;
|
||
|
||
// 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;
|
||
|
||
// SSL options for Node.js client
|
||
this.pfx = opts.pfx;
|
||
this.key = opts.key;
|
||
this.passphrase = opts.passphrase;
|
||
this.cert = opts.cert;
|
||
this.ca = opts.ca;
|
||
this.ciphers = opts.ciphers;
|
||
this.rejectUnauthorized = opts.rejectUnauthorized;
|
||
|
||
// other options for Node.js client
|
||
this.extraHeaders = opts.extraHeaders;
|
||
|
||
this.create();
|
||
}
|
||
|
||
/**
|
||
* Mix in `Emitter`.
|
||
*/
|
||
|
||
Emitter(Request.prototype);
|
||
|
||
/**
|
||
* Creates the XHR object and sends the request.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Request.prototype.create = function(){
|
||
var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };
|
||
|
||
// SSL options for Node.js client
|
||
opts.pfx = this.pfx;
|
||
opts.key = this.key;
|
||
opts.passphrase = this.passphrase;
|
||
opts.cert = this.cert;
|
||
opts.ca = this.ca;
|
||
opts.ciphers = this.ciphers;
|
||
opts.rejectUnauthorized = this.rejectUnauthorized;
|
||
|
||
var xhr = this.xhr = new XMLHttpRequest(opts);
|
||
var self = this;
|
||
|
||
try {
|
||
debug('xhr open %s: %s', this.method, this.uri);
|
||
xhr.open(this.method, this.uri, this.async);
|
||
try {
|
||
if (this.extraHeaders) {
|
||
xhr.setDisableHeaderCheck(true);
|
||
for (var i in this.extraHeaders) {
|
||
if (this.extraHeaders.hasOwnProperty(i)) {
|
||
xhr.setRequestHeader(i, this.extraHeaders[i]);
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {}
|
||
if (this.supportsBinary) {
|
||
// This has to be done after open because Firefox is stupid
|
||
// http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension
|
||
xhr.responseType = 'arraybuffer';
|
||
}
|
||
|
||
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) {}
|
||
}
|
||
|
||
// ie6 check
|
||
if ('withCredentials' in xhr) {
|
||
xhr.withCredentials = true;
|
||
}
|
||
|
||
if (this.hasXDR()) {
|
||
xhr.onload = function(){
|
||
self.onLoad();
|
||
};
|
||
xhr.onerror = function(){
|
||
self.onError(xhr.responseText);
|
||
};
|
||
} else {
|
||
xhr.onreadystatechange = function(){
|
||
if (4 != xhr.readyState) return;
|
||
if (200 == xhr.status || 1223 == xhr.status) {
|
||
self.onLoad();
|
||
} else {
|
||
// make sure the `error` event handler that's user-set
|
||
// does not throw in the same tick and gets caught here
|
||
setTimeout(function(){
|
||
self.onError(xhr.status);
|
||
}, 0);
|
||
}
|
||
};
|
||
}
|
||
|
||
debug('xhr data %s', this.data);
|
||
xhr.send(this.data);
|
||
} catch (e) {
|
||
// Need to defer since .create() is called directly fhrom the constructor
|
||
// and thus the 'error' event can only be only bound *after* this exception
|
||
// occurs. Therefore, also, we cannot throw here at all.
|
||
setTimeout(function() {
|
||
self.onError(e);
|
||
}, 0);
|
||
return;
|
||
}
|
||
|
||
if (global.document) {
|
||
this.index = Request.requestsCount++;
|
||
Request.requests[this.index] = this;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Called upon successful response.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Request.prototype.onSuccess = function(){
|
||
this.emit('success');
|
||
this.cleanup();
|
||
};
|
||
|
||
/**
|
||
* Called if we have data.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Request.prototype.onData = function(data){
|
||
this.emit('data', data);
|
||
this.onSuccess();
|
||
};
|
||
|
||
/**
|
||
* Called upon error.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Request.prototype.onError = function(err){
|
||
this.emit('error', err);
|
||
this.cleanup(true);
|
||
};
|
||
|
||
/**
|
||
* Cleans up house.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Request.prototype.cleanup = function(fromError){
|
||
if ('undefined' == typeof this.xhr || null === this.xhr) {
|
||
return;
|
||
}
|
||
// xmlhttprequest
|
||
if (this.hasXDR()) {
|
||
this.xhr.onload = this.xhr.onerror = empty;
|
||
} else {
|
||
this.xhr.onreadystatechange = empty;
|
||
}
|
||
|
||
if (fromError) {
|
||
try {
|
||
this.xhr.abort();
|
||
} catch(e) {}
|
||
}
|
||
|
||
if (global.document) {
|
||
delete Request.requests[this.index];
|
||
}
|
||
|
||
this.xhr = null;
|
||
};
|
||
|
||
/**
|
||
* Called upon load.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Request.prototype.onLoad = function(){
|
||
var data;
|
||
try {
|
||
var contentType;
|
||
try {
|
||
contentType = this.xhr.getResponseHeader('Content-Type').split(';')[0];
|
||
} catch (e) {}
|
||
if (contentType === 'application/octet-stream') {
|
||
data = this.xhr.response;
|
||
} else {
|
||
if (!this.supportsBinary) {
|
||
data = this.xhr.responseText;
|
||
} else {
|
||
try {
|
||
data = String.fromCharCode.apply(null, new Uint8Array(this.xhr.response));
|
||
} catch (e) {
|
||
var ui8Arr = new Uint8Array(this.xhr.response);
|
||
var dataArray = [];
|
||
for (var idx = 0, length = ui8Arr.length; idx < length; idx++) {
|
||
dataArray.push(ui8Arr[idx]);
|
||
}
|
||
|
||
data = String.fromCharCode.apply(null, dataArray);
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
this.onError(e);
|
||
}
|
||
if (null != data) {
|
||
this.onData(data);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Check if it has XDomainRequest.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Request.prototype.hasXDR = function(){
|
||
return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR;
|
||
};
|
||
|
||
/**
|
||
* Aborts the request.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Request.prototype.abort = function(){
|
||
this.cleanup();
|
||
};
|
||
|
||
/**
|
||
* Aborts pending requests when unloading the window. This is needed to prevent
|
||
* memory leaks (e.g. when using IE) and to ensure that no spurious error is
|
||
* emitted.
|
||
*/
|
||
|
||
if (global.document) {
|
||
Request.requestsCount = 0;
|
||
Request.requests = {};
|
||
if (global.attachEvent) {
|
||
global.attachEvent('onunload', unloadHandler);
|
||
} else if (global.addEventListener) {
|
||
global.addEventListener('beforeunload', unloadHandler, false);
|
||
}
|
||
}
|
||
|
||
function unloadHandler() {
|
||
for (var i in Request.requests) {
|
||
if (Request.requests.hasOwnProperty(i)) {
|
||
Request.requests[i].abort();
|
||
}
|
||
}
|
||
}
|
||
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{"./polling":8,"component-emitter":12,"component-inherit":13,"debug":14,"xmlhttprequest-ssl":10}],8:[function(_dereq_,module,exports){
|
||
/**
|
||
* Module dependencies.
|
||
*/
|
||
|
||
var Transport = _dereq_('../transport');
|
||
var parseqs = _dereq_('parseqs');
|
||
var parser = _dereq_('engine.io-parser');
|
||
var inherit = _dereq_('component-inherit');
|
||
var yeast = _dereq_('yeast');
|
||
var debug = _dereq_('debug')('engine.io-client:polling');
|
||
|
||
/**
|
||
* Module exports.
|
||
*/
|
||
|
||
module.exports = Polling;
|
||
|
||
/**
|
||
* Is XHR2 supported?
|
||
*/
|
||
|
||
var hasXHR2 = (function() {
|
||
var XMLHttpRequest = _dereq_('xmlhttprequest-ssl');
|
||
var xhr = new XMLHttpRequest({ xdomain: false });
|
||
return null != xhr.responseType;
|
||
})();
|
||
|
||
/**
|
||
* Polling interface.
|
||
*
|
||
* @param {Object} opts
|
||
* @api private
|
||
*/
|
||
|
||
function Polling(opts){
|
||
var forceBase64 = (opts && opts.forceBase64);
|
||
if (!hasXHR2 || forceBase64) {
|
||
this.supportsBinary = false;
|
||
}
|
||
Transport.call(this, opts);
|
||
}
|
||
|
||
/**
|
||
* Inherits from Transport.
|
||
*/
|
||
|
||
inherit(Polling, Transport);
|
||
|
||
/**
|
||
* Transport name.
|
||
*/
|
||
|
||
Polling.prototype.name = 'polling';
|
||
|
||
/**
|
||
* Opens the socket (triggers polling). We write a PING message to determine
|
||
* when the transport is open.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Polling.prototype.doOpen = function(){
|
||
this.poll();
|
||
};
|
||
|
||
/**
|
||
* Pauses polling.
|
||
*
|
||
* @param {Function} callback upon buffers are flushed and transport is paused
|
||
* @api private
|
||
*/
|
||
|
||
Polling.prototype.pause = function(onPause){
|
||
var 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);
|
||
var callback = function(packet, index, total) {
|
||
// if its the first message we consider the transport open
|
||
if ('opening' == self.readyState) {
|
||
self.onOpen();
|
||
}
|
||
|
||
// if its a close packet, we close the ongoing requests
|
||
if ('close' == packet.type) {
|
||
self.onClose();
|
||
return false;
|
||
}
|
||
|
||
// otherwise bypass onData and handle the message
|
||
self.onPacket(packet);
|
||
};
|
||
|
||
// decode payload
|
||
parser.decodePayload(data, this.socket.binaryType, callback);
|
||
|
||
// if an event did not trigger closing
|
||
if ('closed' != this.readyState) {
|
||
// if we got data we're not polling
|
||
this.polling = false;
|
||
this.emit('pollComplete');
|
||
|
||
if ('open' == this.readyState) {
|
||
this.poll();
|
||
} else {
|
||
debug('ignoring poll - transport state "%s"', this.readyState);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* For polling, send a close packet.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Polling.prototype.doClose = function(){
|
||
var self = this;
|
||
|
||
function close(){
|
||
debug('writing close packet');
|
||
self.write([{ type: 'close' }]);
|
||
}
|
||
|
||
if ('open' == this.readyState) {
|
||
debug('transport open - closing');
|
||
close();
|
||
} else {
|
||
// in case we're trying to close while
|
||
// handshaking is in progress (GH-164)
|
||
debug('transport not open - deferring close');
|
||
this.once('open', close);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Writes a packets payload.
|
||
*
|
||
* @param {Array} data packets
|
||
* @param {Function} drain callback
|
||
* @api private
|
||
*/
|
||
|
||
Polling.prototype.write = function(packets){
|
||
var self = this;
|
||
this.writable = false;
|
||
var callbackfn = function() {
|
||
self.writable = true;
|
||
self.emit('drain');
|
||
};
|
||
|
||
var self = this;
|
||
parser.encodePayload(packets, this.supportsBinary, function(data) {
|
||
self.doWrite(data, callbackfn);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Generates uri for connection.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Polling.prototype.uri = function(){
|
||
var query = this.query || {};
|
||
var schema = this.secure ? 'https' : 'http';
|
||
var port = '';
|
||
|
||
// cache busting is forced
|
||
if (false !== this.timestampRequests) {
|
||
query[this.timestampParam] = yeast();
|
||
}
|
||
|
||
if (!this.supportsBinary && !query.sid) {
|
||
query.b64 = 1;
|
||
}
|
||
|
||
query = parseqs.encode(query);
|
||
|
||
// avoid port if default for schema
|
||
if (this.port && (('https' == schema && this.port != 443) ||
|
||
('http' == schema && 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;
|
||
};
|
||
|
||
},{"../transport":4,"component-inherit":13,"debug":14,"engine.io-parser":17,"parseqs":28,"xmlhttprequest-ssl":10,"yeast":47}],9:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
/**
|
||
* Module dependencies.
|
||
*/
|
||
|
||
var Transport = _dereq_('../transport');
|
||
var parser = _dereq_('engine.io-parser');
|
||
var parseqs = _dereq_('parseqs');
|
||
var inherit = _dereq_('component-inherit');
|
||
var yeast = _dereq_('yeast');
|
||
var debug = _dereq_('debug')('engine.io-client:websocket');
|
||
|
||
/**
|
||
* Get either the `WebSocket` or `MozWebSocket` globals
|
||
* in the browser or the WebSocket-compatible interface
|
||
* exposed by `ws` for Node environment.
|
||
*/
|
||
|
||
var WebSocket = global.WebSocket || global.MozWebSocket || _dereq_('ws');
|
||
|
||
/**
|
||
* Module exports.
|
||
*/
|
||
|
||
module.exports = WS;
|
||
|
||
/**
|
||
* WebSocket transport constructor.
|
||
*
|
||
* @api {Object} connection options
|
||
* @api public
|
||
*/
|
||
|
||
function WS(opts){
|
||
var forceBase64 = (opts && opts.forceBase64);
|
||
if (forceBase64) {
|
||
this.supportsBinary = false;
|
||
}
|
||
this.perMessageDeflate = opts.perMessageDeflate;
|
||
Transport.call(this, opts);
|
||
}
|
||
|
||
/**
|
||
* Inherits from Transport.
|
||
*/
|
||
|
||
inherit(WS, Transport);
|
||
|
||
/**
|
||
* Transport name.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
WS.prototype.name = 'websocket';
|
||
|
||
/*
|
||
* WebSockets support binary
|
||
*/
|
||
|
||
WS.prototype.supportsBinary = true;
|
||
|
||
/**
|
||
* Opens socket.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
WS.prototype.doOpen = function(){
|
||
if (!this.check()) {
|
||
// let probe timeout
|
||
return;
|
||
}
|
||
|
||
var self = this;
|
||
var uri = this.uri();
|
||
var protocols = void(0);
|
||
var opts = {
|
||
agent: this.agent,
|
||
perMessageDeflate: this.perMessageDeflate
|
||
};
|
||
|
||
// SSL options for Node.js client
|
||
opts.pfx = this.pfx;
|
||
opts.key = this.key;
|
||
opts.passphrase = this.passphrase;
|
||
opts.cert = this.cert;
|
||
opts.ca = this.ca;
|
||
opts.ciphers = this.ciphers;
|
||
opts.rejectUnauthorized = this.rejectUnauthorized;
|
||
if (this.extraHeaders) {
|
||
opts.headers = this.extraHeaders;
|
||
}
|
||
|
||
this.ws = new WebSocket(uri, protocols, opts);
|
||
|
||
if (this.ws.binaryType === undefined) {
|
||
this.supportsBinary = false;
|
||
}
|
||
|
||
if (this.ws.supports && this.ws.supports.binary) {
|
||
this.supportsBinary = true;
|
||
this.ws.binaryType = 'buffer';
|
||
} 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);
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 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;
|
||
|
||
var isBrowserWebSocket = global.WebSocket && this.ws instanceof global.WebSocket;
|
||
|
||
// 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 (!isBrowserWebSocket) {
|
||
// always create a new object (GH-437)
|
||
var opts = {};
|
||
if (packet.options) {
|
||
opts.compress = packet.options.compress;
|
||
}
|
||
|
||
if (self.perMessageDeflate) {
|
||
var len = 'string' == typeof data ? global.Buffer.byteLength(data) : data.length;
|
||
if (len < self.perMessageDeflate.threshold) {
|
||
opts.compress = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
//Sometimes the websocket has already been closed but the browser didn't
|
||
//have a chance of informing us about it yet, in that case send will
|
||
//throw an error
|
||
try {
|
||
if (isBrowserWebSocket) {
|
||
// TypeError is thrown when passing the second argument on Safari
|
||
self.ws.send(data);
|
||
} else {
|
||
self.ws.send(data, opts);
|
||
}
|
||
} catch (e){
|
||
debug('websocket closed before onclose event');
|
||
}
|
||
|
||
--total || done();
|
||
});
|
||
})(packets[i]);
|
||
}
|
||
|
||
function done(){
|
||
self.emit('flush');
|
||
|
||
// fake drain
|
||
// defer to next tick to allow Socket to clear writeBuffer
|
||
setTimeout(function(){
|
||
self.writable = true;
|
||
self.emit('drain');
|
||
}, 0);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Called upon close
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
WS.prototype.onClose = function(){
|
||
Transport.prototype.onClose.call(this);
|
||
};
|
||
|
||
/**
|
||
* Closes socket.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
WS.prototype.doClose = function(){
|
||
if (typeof this.ws !== 'undefined') {
|
||
this.ws.close();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Generates uri for connection.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
WS.prototype.uri = function(){
|
||
var query = this.query || {};
|
||
var schema = this.secure ? 'wss' : 'ws';
|
||
var port = '';
|
||
|
||
// avoid port if default for schema
|
||
if (this.port && (('wss' == schema && this.port != 443)
|
||
|| ('ws' == schema && this.port != 80))) {
|
||
port = ':' + this.port;
|
||
}
|
||
|
||
// append timestamp to URI
|
||
if (this.timestampRequests) {
|
||
query[this.timestampParam] = yeast();
|
||
}
|
||
|
||
// communicate binary support capabilities
|
||
if (!this.supportsBinary) {
|
||
query.b64 = 1;
|
||
}
|
||
|
||
query = parseqs.encode(query);
|
||
|
||
// prepend ? to query
|
||
if (query.length) {
|
||
query = '?' + query;
|
||
}
|
||
|
||
var ipv6 = this.hostname.indexOf(':') !== -1;
|
||
return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query;
|
||
};
|
||
|
||
/**
|
||
* Feature detection for WebSocket.
|
||
*
|
||
* @return {Boolean} whether this transport is available.
|
||
* @api public
|
||
*/
|
||
|
||
WS.prototype.check = function(){
|
||
return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name);
|
||
};
|
||
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{"../transport":4,"component-inherit":13,"debug":14,"engine.io-parser":17,"parseqs":28,"ws":30,"yeast":47}],10:[function(_dereq_,module,exports){
|
||
// browser shim for xmlhttprequest module
|
||
var hasCORS = _dereq_('has-cors');
|
||
|
||
module.exports = function(opts) {
|
||
var xdomain = opts.xdomain;
|
||
|
||
// scheme must be same when usign XDomainRequest
|
||
// http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
|
||
var xscheme = opts.xscheme;
|
||
|
||
// XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default.
|
||
// https://github.com/Automattic/engine.io-client/pull/217
|
||
var enablesXDR = opts.enablesXDR;
|
||
|
||
// XMLHttpRequest can be disabled on IE
|
||
try {
|
||
if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) {
|
||
return new XMLHttpRequest();
|
||
}
|
||
} catch (e) { }
|
||
|
||
// Use XDomainRequest for IE8 if enablesXDR is true
|
||
// because loading bar keeps flashing when using jsonp-polling
|
||
// https://github.com/yujiosaka/socke.io-ie8-loading-example
|
||
try {
|
||
if ('undefined' != typeof XDomainRequest && !xscheme && enablesXDR) {
|
||
return new XDomainRequest();
|
||
}
|
||
} catch (e) { }
|
||
|
||
if (!xdomain) {
|
||
try {
|
||
return new ActiveXObject('Microsoft.XMLHTTP');
|
||
} catch(e) { }
|
||
}
|
||
}
|
||
|
||
},{"has-cors":25}],11:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
/**
|
||
* Create a blob builder even when vendor prefixes exist
|
||
*/
|
||
|
||
var BlobBuilder = global.BlobBuilder
|
||
|| global.WebKitBlobBuilder
|
||
|| global.MSBlobBuilder
|
||
|| global.MozBlobBuilder;
|
||
|
||
/**
|
||
* Check if Blob constructor is supported
|
||
*/
|
||
|
||
var blobSupported = (function() {
|
||
try {
|
||
var a = new Blob(['hi']);
|
||
return a.size === 2;
|
||
} catch(e) {
|
||
return false;
|
||
}
|
||
})();
|
||
|
||
/**
|
||
* Check if Blob constructor supports ArrayBufferViews
|
||
* Fails in Safari 6, so we need to map to ArrayBuffers there.
|
||
*/
|
||
|
||
var blobSupportsArrayBufferView = blobSupported && (function() {
|
||
try {
|
||
var b = new Blob([new Uint8Array([1,2])]);
|
||
return b.size === 2;
|
||
} catch(e) {
|
||
return false;
|
||
}
|
||
})();
|
||
|
||
/**
|
||
* Check if BlobBuilder is supported
|
||
*/
|
||
|
||
var blobBuilderSupported = BlobBuilder
|
||
&& BlobBuilder.prototype.append
|
||
&& BlobBuilder.prototype.getBlob;
|
||
|
||
/**
|
||
* Helper function that maps ArrayBufferViews to ArrayBuffers
|
||
* Used by BlobBuilder constructor and old browsers that didn't
|
||
* support it in the Blob constructor.
|
||
*/
|
||
|
||
function mapArrayBufferViews(ary) {
|
||
for (var i = 0; i < ary.length; i++) {
|
||
var chunk = ary[i];
|
||
if (chunk.buffer instanceof ArrayBuffer) {
|
||
var buf = chunk.buffer;
|
||
|
||
// if this is a subarray, make a copy so we only
|
||
// include the subarray region from the underlying buffer
|
||
if (chunk.byteLength !== buf.byteLength) {
|
||
var copy = new Uint8Array(chunk.byteLength);
|
||
copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength));
|
||
buf = copy.buffer;
|
||
}
|
||
|
||
ary[i] = buf;
|
||
}
|
||
}
|
||
}
|
||
|
||
function BlobBuilderConstructor(ary, options) {
|
||
options = options || {};
|
||
|
||
var bb = new BlobBuilder();
|
||
mapArrayBufferViews(ary);
|
||
|
||
for (var i = 0; i < ary.length; i++) {
|
||
bb.append(ary[i]);
|
||
}
|
||
|
||
return (options.type) ? bb.getBlob(options.type) : bb.getBlob();
|
||
};
|
||
|
||
function BlobConstructor(ary, options) {
|
||
mapArrayBufferViews(ary);
|
||
return new Blob(ary, options || {});
|
||
};
|
||
|
||
module.exports = (function() {
|
||
if (blobSupported) {
|
||
return blobSupportsArrayBufferView ? global.Blob : BlobConstructor;
|
||
} else if (blobBuilderSupported) {
|
||
return BlobBuilderConstructor;
|
||
} else {
|
||
return undefined;
|
||
}
|
||
})();
|
||
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{}],12:[function(_dereq_,module,exports){
|
||
|
||
/**
|
||
* Expose `Emitter`.
|
||
*/
|
||
|
||
module.exports = Emitter;
|
||
|
||
/**
|
||
* Initialize a new `Emitter`.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
function Emitter(obj) {
|
||
if (obj) return mixin(obj);
|
||
};
|
||
|
||
/**
|
||
* Mixin the emitter properties.
|
||
*
|
||
* @param {Object} obj
|
||
* @return {Object}
|
||
* @api private
|
||
*/
|
||
|
||
function mixin(obj) {
|
||
for (var key in Emitter.prototype) {
|
||
obj[key] = Emitter.prototype[key];
|
||
}
|
||
return obj;
|
||
}
|
||
|
||
/**
|
||
* Listen on the given `event` with `fn`.
|
||
*
|
||
* @param {String} event
|
||
* @param {Function} fn
|
||
* @return {Emitter}
|
||
* @api public
|
||
*/
|
||
|
||
Emitter.prototype.on =
|
||
Emitter.prototype.addEventListener = function(event, fn){
|
||
this._callbacks = this._callbacks || {};
|
||
(this._callbacks[event] = this._callbacks[event] || [])
|
||
.push(fn);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Adds an `event` listener that will be invoked a single
|
||
* time then automatically removed.
|
||
*
|
||
* @param {String} event
|
||
* @param {Function} fn
|
||
* @return {Emitter}
|
||
* @api public
|
||
*/
|
||
|
||
Emitter.prototype.once = function(event, fn){
|
||
var self = this;
|
||
this._callbacks = this._callbacks || {};
|
||
|
||
function on() {
|
||
self.off(event, on);
|
||
fn.apply(this, arguments);
|
||
}
|
||
|
||
on.fn = fn;
|
||
this.on(event, on);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Remove the given callback for `event` or all
|
||
* registered callbacks.
|
||
*
|
||
* @param {String} event
|
||
* @param {Function} fn
|
||
* @return {Emitter}
|
||
* @api public
|
||
*/
|
||
|
||
Emitter.prototype.off =
|
||
Emitter.prototype.removeListener =
|
||
Emitter.prototype.removeAllListeners =
|
||
Emitter.prototype.removeEventListener = function(event, fn){
|
||
this._callbacks = this._callbacks || {};
|
||
|
||
// all
|
||
if (0 == arguments.length) {
|
||
this._callbacks = {};
|
||
return this;
|
||
}
|
||
|
||
// specific event
|
||
var callbacks = this._callbacks[event];
|
||
if (!callbacks) return this;
|
||
|
||
// remove all handlers
|
||
if (1 == arguments.length) {
|
||
delete this._callbacks[event];
|
||
return this;
|
||
}
|
||
|
||
// remove specific handler
|
||
var cb;
|
||
for (var i = 0; i < callbacks.length; i++) {
|
||
cb = callbacks[i];
|
||
if (cb === fn || cb.fn === fn) {
|
||
callbacks.splice(i, 1);
|
||
break;
|
||
}
|
||
}
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Emit `event` with the given args.
|
||
*
|
||
* @param {String} event
|
||
* @param {Mixed} ...
|
||
* @return {Emitter}
|
||
*/
|
||
|
||
Emitter.prototype.emit = function(event){
|
||
this._callbacks = this._callbacks || {};
|
||
var args = [].slice.call(arguments, 1)
|
||
, callbacks = this._callbacks[event];
|
||
|
||
if (callbacks) {
|
||
callbacks = callbacks.slice(0);
|
||
for (var i = 0, len = callbacks.length; i < len; ++i) {
|
||
callbacks[i].apply(this, args);
|
||
}
|
||
}
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Return array of callbacks for `event`.
|
||
*
|
||
* @param {String} event
|
||
* @return {Array}
|
||
* @api public
|
||
*/
|
||
|
||
Emitter.prototype.listeners = function(event){
|
||
this._callbacks = this._callbacks || {};
|
||
return this._callbacks[event] || [];
|
||
};
|
||
|
||
/**
|
||
* Check if this emitter has `event` handlers.
|
||
*
|
||
* @param {String} event
|
||
* @return {Boolean}
|
||
* @api public
|
||
*/
|
||
|
||
Emitter.prototype.hasListeners = function(event){
|
||
return !! this.listeners(event).length;
|
||
};
|
||
|
||
},{}],13:[function(_dereq_,module,exports){
|
||
|
||
module.exports = function(a, b){
|
||
var fn = function(){};
|
||
fn.prototype = b.prototype;
|
||
a.prototype = new fn;
|
||
a.prototype.constructor = a;
|
||
};
|
||
},{}],14:[function(_dereq_,module,exports){
|
||
|
||
/**
|
||
* This is the web browser implementation of `debug()`.
|
||
*
|
||
* Expose `debug()` as the module.
|
||
*/
|
||
|
||
exports = module.exports = _dereq_('./debug');
|
||
exports.log = log;
|
||
exports.formatArgs = formatArgs;
|
||
exports.save = save;
|
||
exports.load = load;
|
||
exports.useColors = useColors;
|
||
exports.storage = 'undefined' != typeof chrome
|
||
&& 'undefined' != typeof chrome.storage
|
||
? chrome.storage.local
|
||
: localstorage();
|
||
|
||
/**
|
||
* Colors.
|
||
*/
|
||
|
||
exports.colors = [
|
||
'lightseagreen',
|
||
'forestgreen',
|
||
'goldenrod',
|
||
'dodgerblue',
|
||
'darkorchid',
|
||
'crimson'
|
||
];
|
||
|
||
/**
|
||
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
|
||
* and the Firebug extension (any Firefox version) are known
|
||
* to support "%c" CSS customizations.
|
||
*
|
||
* TODO: add a `localStorage` variable to explicitly enable/disable colors
|
||
*/
|
||
|
||
function useColors() {
|
||
// is webkit? http://stackoverflow.com/a/16459606/376773
|
||
return ('WebkitAppearance' in document.documentElement.style) ||
|
||
// is firebug? http://stackoverflow.com/a/398120/376773
|
||
(window.console && (console.firebug || (console.exception && console.table))) ||
|
||
// is firefox >= v31?
|
||
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
|
||
(navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31);
|
||
}
|
||
|
||
/**
|
||
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
|
||
*/
|
||
|
||
exports.formatters.j = function(v) {
|
||
return JSON.stringify(v);
|
||
};
|
||
|
||
|
||
/**
|
||
* Colorize log arguments if enabled.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
function formatArgs() {
|
||
var args = arguments;
|
||
var useColors = this.useColors;
|
||
|
||
args[0] = (useColors ? '%c' : '')
|
||
+ this.namespace
|
||
+ (useColors ? ' %c' : ' ')
|
||
+ args[0]
|
||
+ (useColors ? '%c ' : ' ')
|
||
+ '+' + exports.humanize(this.diff);
|
||
|
||
if (!useColors) return args;
|
||
|
||
var c = 'color: ' + this.color;
|
||
args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));
|
||
|
||
// the final "%c" is somewhat tricky, because there could be other
|
||
// arguments passed either before or after the %c, so we need to
|
||
// figure out the correct index to insert the CSS into
|
||
var index = 0;
|
||
var lastC = 0;
|
||
args[0].replace(/%[a-z%]/g, function(match) {
|
||
if ('%' === match) return;
|
||
index++;
|
||
if ('%c' === match) {
|
||
// we only are interested in the *last* %c
|
||
// (the user may have provided their own)
|
||
lastC = index;
|
||
}
|
||
});
|
||
|
||
args.splice(lastC, 0, c);
|
||
return args;
|
||
}
|
||
|
||
/**
|
||
* Invokes `console.log()` when available.
|
||
* No-op when `console.log` is not a "function".
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
function log() {
|
||
// this hackery is required for IE8/9, where
|
||
// the `console.log` function doesn't have 'apply'
|
||
return 'object' === typeof console
|
||
&& console.log
|
||
&& Function.prototype.apply.call(console.log, console, arguments);
|
||
}
|
||
|
||
/**
|
||
* Save `namespaces`.
|
||
*
|
||
* @param {String} namespaces
|
||
* @api private
|
||
*/
|
||
|
||
function save(namespaces) {
|
||
try {
|
||
if (null == namespaces) {
|
||
exports.storage.removeItem('debug');
|
||
} else {
|
||
exports.storage.debug = namespaces;
|
||
}
|
||
} catch(e) {}
|
||
}
|
||
|
||
/**
|
||
* Load `namespaces`.
|
||
*
|
||
* @return {String} returns the previously persisted debug modes
|
||
* @api private
|
||
*/
|
||
|
||
function load() {
|
||
var r;
|
||
try {
|
||
r = exports.storage.debug;
|
||
} catch(e) {}
|
||
return r;
|
||
}
|
||
|
||
/**
|
||
* Enable namespaces listed in `localStorage.debug` initially.
|
||
*/
|
||
|
||
exports.enable(load());
|
||
|
||
/**
|
||
* Localstorage attempts to return the localstorage.
|
||
*
|
||
* This is necessary because safari throws
|
||
* when a user disables cookies/localstorage
|
||
* and you attempt to access it.
|
||
*
|
||
* @return {LocalStorage}
|
||
* @api private
|
||
*/
|
||
|
||
function localstorage(){
|
||
try {
|
||
return window.localStorage;
|
||
} catch (e) {}
|
||
}
|
||
|
||
},{"./debug":15}],15:[function(_dereq_,module,exports){
|
||
|
||
/**
|
||
* This is the common logic for both the Node.js and web browser
|
||
* implementations of `debug()`.
|
||
*
|
||
* Expose `debug()` as the module.
|
||
*/
|
||
|
||
exports = module.exports = debug;
|
||
exports.coerce = coerce;
|
||
exports.disable = disable;
|
||
exports.enable = enable;
|
||
exports.enabled = enabled;
|
||
exports.humanize = _dereq_('ms');
|
||
|
||
/**
|
||
* The currently active debug mode names, and names to skip.
|
||
*/
|
||
|
||
exports.names = [];
|
||
exports.skips = [];
|
||
|
||
/**
|
||
* Map of special "%n" handling functions, for the debug "format" argument.
|
||
*
|
||
* Valid key names are a single, lowercased letter, i.e. "n".
|
||
*/
|
||
|
||
exports.formatters = {};
|
||
|
||
/**
|
||
* Previously assigned color.
|
||
*/
|
||
|
||
var prevColor = 0;
|
||
|
||
/**
|
||
* Previous log timestamp.
|
||
*/
|
||
|
||
var prevTime;
|
||
|
||
/**
|
||
* Select a color.
|
||
*
|
||
* @return {Number}
|
||
* @api private
|
||
*/
|
||
|
||
function selectColor() {
|
||
return exports.colors[prevColor++ % exports.colors.length];
|
||
}
|
||
|
||
/**
|
||
* Create a debugger with the given `namespace`.
|
||
*
|
||
* @param {String} namespace
|
||
* @return {Function}
|
||
* @api public
|
||
*/
|
||
|
||
function debug(namespace) {
|
||
|
||
// define the `disabled` version
|
||
function disabled() {
|
||
}
|
||
disabled.enabled = false;
|
||
|
||
// define the `enabled` version
|
||
function enabled() {
|
||
|
||
var self = enabled;
|
||
|
||
// set `diff` timestamp
|
||
var curr = +new Date();
|
||
var ms = curr - (prevTime || curr);
|
||
self.diff = ms;
|
||
self.prev = prevTime;
|
||
self.curr = curr;
|
||
prevTime = curr;
|
||
|
||
// add the `color` if not set
|
||
if (null == self.useColors) self.useColors = exports.useColors();
|
||
if (null == self.color && self.useColors) self.color = selectColor();
|
||
|
||
var args = Array.prototype.slice.call(arguments);
|
||
|
||
args[0] = exports.coerce(args[0]);
|
||
|
||
if ('string' !== typeof args[0]) {
|
||
// anything else let's inspect with %o
|
||
args = ['%o'].concat(args);
|
||
}
|
||
|
||
// apply any `formatters` transformations
|
||
var index = 0;
|
||
args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
|
||
// if we encounter an escaped % then don't increase the array index
|
||
if (match === '%') return match;
|
||
index++;
|
||
var formatter = exports.formatters[format];
|
||
if ('function' === typeof formatter) {
|
||
var val = args[index];
|
||
match = formatter.call(self, val);
|
||
|
||
// now we need to remove `args[index]` since it's inlined in the `format`
|
||
args.splice(index, 1);
|
||
index--;
|
||
}
|
||
return match;
|
||
});
|
||
|
||
if ('function' === typeof exports.formatArgs) {
|
||
args = exports.formatArgs.apply(self, args);
|
||
}
|
||
var logFn = enabled.log || exports.log || console.log.bind(console);
|
||
logFn.apply(self, args);
|
||
}
|
||
enabled.enabled = true;
|
||
|
||
var fn = exports.enabled(namespace) ? enabled : disabled;
|
||
|
||
fn.namespace = namespace;
|
||
|
||
return fn;
|
||
}
|
||
|
||
/**
|
||
* Enables a debug mode by namespaces. This can include modes
|
||
* separated by a colon and wildcards.
|
||
*
|
||
* @param {String} namespaces
|
||
* @api public
|
||
*/
|
||
|
||
function enable(namespaces) {
|
||
exports.save(namespaces);
|
||
|
||
var split = (namespaces || '').split(/[\s,]+/);
|
||
var len = split.length;
|
||
|
||
for (var i = 0; i < len; i++) {
|
||
if (!split[i]) continue; // ignore empty strings
|
||
namespaces = split[i].replace(/\*/g, '.*?');
|
||
if (namespaces[0] === '-') {
|
||
exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
|
||
} else {
|
||
exports.names.push(new RegExp('^' + namespaces + '$'));
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Disable debug output.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
function disable() {
|
||
exports.enable('');
|
||
}
|
||
|
||
/**
|
||
* Returns true if the given mode name is enabled, false otherwise.
|
||
*
|
||
* @param {String} name
|
||
* @return {Boolean}
|
||
* @api public
|
||
*/
|
||
|
||
function enabled(name) {
|
||
var i, len;
|
||
for (i = 0, len = exports.skips.length; i < len; i++) {
|
||
if (exports.skips[i].test(name)) {
|
||
return false;
|
||
}
|
||
}
|
||
for (i = 0, len = exports.names.length; i < len; i++) {
|
||
if (exports.names[i].test(name)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Coerce `val`.
|
||
*
|
||
* @param {Mixed} val
|
||
* @return {Mixed}
|
||
* @api private
|
||
*/
|
||
|
||
function coerce(val) {
|
||
if (val instanceof Error) return val.stack || val.message;
|
||
return val;
|
||
}
|
||
|
||
},{"ms":16}],16:[function(_dereq_,module,exports){
|
||
/**
|
||
* Helpers.
|
||
*/
|
||
|
||
var s = 1000;
|
||
var m = s * 60;
|
||
var h = m * 60;
|
||
var d = h * 24;
|
||
var y = d * 365.25;
|
||
|
||
/**
|
||
* Parse or format the given `val`.
|
||
*
|
||
* Options:
|
||
*
|
||
* - `long` verbose formatting [false]
|
||
*
|
||
* @param {String|Number} val
|
||
* @param {Object} options
|
||
* @return {String|Number}
|
||
* @api public
|
||
*/
|
||
|
||
module.exports = function(val, options){
|
||
options = options || {};
|
||
if ('string' == typeof val) return parse(val);
|
||
return options.long
|
||
? long(val)
|
||
: short(val);
|
||
};
|
||
|
||
/**
|
||
* Parse the given `str` and return milliseconds.
|
||
*
|
||
* @param {String} str
|
||
* @return {Number}
|
||
* @api private
|
||
*/
|
||
|
||
function parse(str) {
|
||
str = '' + str;
|
||
if (str.length > 10000) return;
|
||
var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);
|
||
if (!match) return;
|
||
var n = parseFloat(match[1]);
|
||
var type = (match[2] || 'ms').toLowerCase();
|
||
switch (type) {
|
||
case 'years':
|
||
case 'year':
|
||
case 'yrs':
|
||
case 'yr':
|
||
case 'y':
|
||
return n * y;
|
||
case 'days':
|
||
case 'day':
|
||
case 'd':
|
||
return n * d;
|
||
case 'hours':
|
||
case 'hour':
|
||
case 'hrs':
|
||
case 'hr':
|
||
case 'h':
|
||
return n * h;
|
||
case 'minutes':
|
||
case 'minute':
|
||
case 'mins':
|
||
case 'min':
|
||
case 'm':
|
||
return n * m;
|
||
case 'seconds':
|
||
case 'second':
|
||
case 'secs':
|
||
case 'sec':
|
||
case 's':
|
||
return n * s;
|
||
case 'milliseconds':
|
||
case 'millisecond':
|
||
case 'msecs':
|
||
case 'msec':
|
||
case 'ms':
|
||
return n;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Short format for `ms`.
|
||
*
|
||
* @param {Number} ms
|
||
* @return {String}
|
||
* @api private
|
||
*/
|
||
|
||
function short(ms) {
|
||
if (ms >= d) return Math.round(ms / d) + 'd';
|
||
if (ms >= h) return Math.round(ms / h) + 'h';
|
||
if (ms >= m) return Math.round(ms / m) + 'm';
|
||
if (ms >= s) return Math.round(ms / s) + 's';
|
||
return ms + 'ms';
|
||
}
|
||
|
||
/**
|
||
* Long format for `ms`.
|
||
*
|
||
* @param {Number} ms
|
||
* @return {String}
|
||
* @api private
|
||
*/
|
||
|
||
function long(ms) {
|
||
return plural(ms, d, 'day')
|
||
|| plural(ms, h, 'hour')
|
||
|| plural(ms, m, 'minute')
|
||
|| plural(ms, s, 'second')
|
||
|| ms + ' ms';
|
||
}
|
||
|
||
/**
|
||
* Pluralization helper.
|
||
*/
|
||
|
||
function plural(ms, n, name) {
|
||
if (ms < n) return;
|
||
if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
|
||
return Math.ceil(ms / n) + ' ' + name + 's';
|
||
}
|
||
|
||
},{}],17:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
/**
|
||
* Module dependencies.
|
||
*/
|
||
|
||
var keys = _dereq_('./keys');
|
||
var hasBinary = _dereq_('has-binary');
|
||
var sliceBuffer = _dereq_('arraybuffer.slice');
|
||
var base64encoder = _dereq_('base64-arraybuffer');
|
||
var after = _dereq_('after');
|
||
var utf8 = _dereq_('utf8');
|
||
|
||
/**
|
||
* Check if we are running an android browser. That requires us to use
|
||
* ArrayBuffer with polling transports...
|
||
*
|
||
* http://ghinda.net/jpeg-blob-ajax-android/
|
||
*/
|
||
|
||
var isAndroid = navigator.userAgent.match(/Android/i);
|
||
|
||
/**
|
||
* Check if we are running in PhantomJS.
|
||
* Uploading a Blob with PhantomJS does not work correctly, as reported here:
|
||
* https://github.com/ariya/phantomjs/issues/11395
|
||
* @type boolean
|
||
*/
|
||
var isPhantomJS = /PhantomJS/i.test(navigator.userAgent);
|
||
|
||
/**
|
||
* When true, avoids using Blobs to encode payloads.
|
||
* @type boolean
|
||
*/
|
||
var dontSendBlobs = isAndroid || isPhantomJS;
|
||
|
||
/**
|
||
* Current protocol version.
|
||
*/
|
||
|
||
exports.protocol = 3;
|
||
|
||
/**
|
||
* 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' };
|
||
|
||
/**
|
||
* Create a blob api even for blob builder when vendor prefixes exist
|
||
*/
|
||
|
||
var Blob = _dereq_('blob');
|
||
|
||
/**
|
||
* Encodes a packet.
|
||
*
|
||
* <packet type id> [ <data> ]
|
||
*
|
||
* Example:
|
||
*
|
||
* 5hello world
|
||
* 3
|
||
* 4
|
||
*
|
||
* Binary is encoded in an identical principle
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
|
||
if ('function' == typeof supportsBinary) {
|
||
callback = supportsBinary;
|
||
supportsBinary = false;
|
||
}
|
||
|
||
if ('function' == typeof utf8encode) {
|
||
callback = utf8encode;
|
||
utf8encode = null;
|
||
}
|
||
|
||
var data = (packet.data === undefined)
|
||
? undefined
|
||
: packet.data.buffer || packet.data;
|
||
|
||
if (global.ArrayBuffer && data instanceof ArrayBuffer) {
|
||
return encodeArrayBuffer(packet, supportsBinary, callback);
|
||
} else if (Blob && data instanceof global.Blob) {
|
||
return encodeBlob(packet, supportsBinary, callback);
|
||
}
|
||
|
||
// might be an object with { base64: true, data: dataAsBase64String }
|
||
if (data && data.base64) {
|
||
return encodeBase64Object(packet, callback);
|
||
}
|
||
|
||
// Sending data as a utf-8 string
|
||
var encoded = packets[packet.type];
|
||
|
||
// data fragment is optional
|
||
if (undefined !== packet.data) {
|
||
encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data);
|
||
}
|
||
|
||
return callback('' + encoded);
|
||
|
||
};
|
||
|
||
function encodeBase64Object(packet, callback) {
|
||
// packet data is an object { base64: true, data: dataAsBase64String }
|
||
var message = 'b' + exports.packets[packet.type] + packet.data.data;
|
||
return callback(message);
|
||
}
|
||
|
||
/**
|
||
* Encode packet helpers for binary types
|
||
*/
|
||
|
||
function encodeArrayBuffer(packet, supportsBinary, callback) {
|
||
if (!supportsBinary) {
|
||
return exports.encodeBase64Packet(packet, callback);
|
||
}
|
||
|
||
var data = packet.data;
|
||
var contentArray = new Uint8Array(data);
|
||
var resultBuffer = new Uint8Array(1 + data.byteLength);
|
||
|
||
resultBuffer[0] = packets[packet.type];
|
||
for (var i = 0; i < contentArray.length; i++) {
|
||
resultBuffer[i+1] = contentArray[i];
|
||
}
|
||
|
||
return callback(resultBuffer.buffer);
|
||
}
|
||
|
||
function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) {
|
||
if (!supportsBinary) {
|
||
return exports.encodeBase64Packet(packet, callback);
|
||
}
|
||
|
||
var fr = new FileReader();
|
||
fr.onload = function() {
|
||
packet.data = fr.result;
|
||
exports.encodePacket(packet, supportsBinary, true, callback);
|
||
};
|
||
return fr.readAsArrayBuffer(packet.data);
|
||
}
|
||
|
||
function encodeBlob(packet, supportsBinary, callback) {
|
||
if (!supportsBinary) {
|
||
return exports.encodeBase64Packet(packet, callback);
|
||
}
|
||
|
||
if (dontSendBlobs) {
|
||
return encodeBlobAsArrayBuffer(packet, supportsBinary, callback);
|
||
}
|
||
|
||
var length = new Uint8Array(1);
|
||
length[0] = packets[packet.type];
|
||
var blob = new Blob([length.buffer, packet.data]);
|
||
|
||
return callback(blob);
|
||
}
|
||
|
||
/**
|
||
* Encodes a packet with binary data in a base64 string
|
||
*
|
||
* @param {Object} packet, has `type` and `data`
|
||
* @return {String} base64 encoded message
|
||
*/
|
||
|
||
exports.encodeBase64Packet = function(packet, callback) {
|
||
var message = 'b' + exports.packets[packet.type];
|
||
if (Blob && packet.data instanceof global.Blob) {
|
||
var fr = new FileReader();
|
||
fr.onload = function() {
|
||
var b64 = fr.result.split(',')[1];
|
||
callback(message + b64);
|
||
};
|
||
return fr.readAsDataURL(packet.data);
|
||
}
|
||
|
||
var b64data;
|
||
try {
|
||
b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data));
|
||
} catch (e) {
|
||
// iPhone Safari doesn't let you apply with typed arrays
|
||
var typed = new Uint8Array(packet.data);
|
||
var basic = new Array(typed.length);
|
||
for (var i = 0; i < typed.length; i++) {
|
||
basic[i] = typed[i];
|
||
}
|
||
b64data = String.fromCharCode.apply(null, basic);
|
||
}
|
||
message += global.btoa(b64data);
|
||
return callback(message);
|
||
};
|
||
|
||
/**
|
||
* Decodes a packet. Changes format to Blob if requested.
|
||
*
|
||
* @return {Object} with `type` and `data` (if any)
|
||
* @api private
|
||
*/
|
||
|
||
exports.decodePacket = function (data, binaryType, utf8decode) {
|
||
// String data
|
||
if (typeof data == 'string' || data === undefined) {
|
||
if (data.charAt(0) == 'b') {
|
||
return exports.decodeBase64Packet(data.substr(1), binaryType);
|
||
}
|
||
|
||
if (utf8decode) {
|
||
try {
|
||
data = utf8.decode(data);
|
||
} catch (e) {
|
||
return err;
|
||
}
|
||
}
|
||
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] };
|
||
}
|
||
}
|
||
|
||
var asArray = new Uint8Array(data);
|
||
var type = asArray[0];
|
||
var rest = sliceBuffer(data, 1);
|
||
if (Blob && binaryType === 'blob') {
|
||
rest = new Blob([rest]);
|
||
}
|
||
return { type: packetslist[type], data: rest };
|
||
};
|
||
|
||
/**
|
||
* Decodes a packet encoded in a base64 string
|
||
*
|
||
* @param {String} base64 encoded message
|
||
* @return {Object} with `type` and `data` (if any)
|
||
*/
|
||
|
||
exports.decodeBase64Packet = function(msg, binaryType) {
|
||
var type = packetslist[msg.charAt(0)];
|
||
if (!global.ArrayBuffer) {
|
||
return { type: type, data: { base64: true, data: msg.substr(1) } };
|
||
}
|
||
|
||
var data = base64encoder.decode(msg.substr(1));
|
||
|
||
if (binaryType === 'blob' && Blob) {
|
||
data = new Blob([data]);
|
||
}
|
||
|
||
return { type: type, data: data };
|
||
};
|
||
|
||
/**
|
||
* Encodes multiple messages (payload).
|
||
*
|
||
* <length>:data
|
||
*
|
||
* Example:
|
||
*
|
||
* 11:hello world2:hi
|
||
*
|
||
* If any contents are binary, they will be encoded as base64 strings. Base64
|
||
* encoded strings are marked with a b before the length specifier
|
||
*
|
||
* @param {Array} packets
|
||
* @api private
|
||
*/
|
||
|
||
exports.encodePayload = function (packets, supportsBinary, callback) {
|
||
if (typeof supportsBinary == 'function') {
|
||
callback = supportsBinary;
|
||
supportsBinary = null;
|
||
}
|
||
|
||
var isBinary = hasBinary(packets);
|
||
|
||
if (supportsBinary && isBinary) {
|
||
if (Blob && !dontSendBlobs) {
|
||
return exports.encodePayloadAsBlob(packets, callback);
|
||
}
|
||
|
||
return exports.encodePayloadAsArrayBuffer(packets, callback);
|
||
}
|
||
|
||
if (!packets.length) {
|
||
return callback('0:');
|
||
}
|
||
|
||
function setLengthHeader(message) {
|
||
return message.length + ':' + message;
|
||
}
|
||
|
||
function encodeOne(packet, doneCallback) {
|
||
exports.encodePacket(packet, !isBinary ? false : supportsBinary, true, function(message) {
|
||
doneCallback(null, setLengthHeader(message));
|
||
});
|
||
}
|
||
|
||
map(packets, encodeOne, function(err, results) {
|
||
return callback(results.join(''));
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Async array map using after
|
||
*/
|
||
|
||
function map(ary, each, done) {
|
||
var result = new Array(ary.length);
|
||
var next = after(ary.length, done);
|
||
|
||
var eachWithIndex = function(i, el, cb) {
|
||
each(el, function(error, msg) {
|
||
result[i] = msg;
|
||
cb(error, result);
|
||
});
|
||
};
|
||
|
||
for (var i = 0; i < ary.length; i++) {
|
||
eachWithIndex(i, ary[i], next);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Decodes data when a payload is maybe expected. Possible binary contents are
|
||
* decoded from their base64 representation
|
||
*
|
||
* @param {String} data, callback method
|
||
* @api public
|
||
*/
|
||
|
||
exports.decodePayload = function (data, binaryType, callback) {
|
||
if (typeof data != 'string') {
|
||
return exports.decodePayloadAsBinary(data, binaryType, callback);
|
||
}
|
||
|
||
if (typeof binaryType === 'function') {
|
||
callback = binaryType;
|
||
binaryType = null;
|
||
}
|
||
|
||
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, binaryType, true);
|
||
|
||
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);
|
||
}
|
||
|
||
};
|
||
|
||
/**
|
||
* Encodes multiple messages (payload) as binary.
|
||
*
|
||
* <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
|
||
* 255><data>
|
||
*
|
||
* Example:
|
||
* 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
|
||
*
|
||
* @param {Array} packets
|
||
* @return {ArrayBuffer} encoded payload
|
||
* @api private
|
||
*/
|
||
|
||
exports.encodePayloadAsArrayBuffer = function(packets, callback) {
|
||
if (!packets.length) {
|
||
return callback(new ArrayBuffer(0));
|
||
}
|
||
|
||
function encodeOne(packet, doneCallback) {
|
||
exports.encodePacket(packet, true, true, function(data) {
|
||
return doneCallback(null, data);
|
||
});
|
||
}
|
||
|
||
map(packets, encodeOne, function(err, encodedPackets) {
|
||
var totalLength = encodedPackets.reduce(function(acc, p) {
|
||
var len;
|
||
if (typeof p === 'string'){
|
||
len = p.length;
|
||
} else {
|
||
len = p.byteLength;
|
||
}
|
||
return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2
|
||
}, 0);
|
||
|
||
var resultArray = new Uint8Array(totalLength);
|
||
|
||
var bufferIndex = 0;
|
||
encodedPackets.forEach(function(p) {
|
||
var isString = typeof p === 'string';
|
||
var ab = p;
|
||
if (isString) {
|
||
var view = new Uint8Array(p.length);
|
||
for (var i = 0; i < p.length; i++) {
|
||
view[i] = p.charCodeAt(i);
|
||
}
|
||
ab = view.buffer;
|
||
}
|
||
|
||
if (isString) { // not true binary
|
||
resultArray[bufferIndex++] = 0;
|
||
} else { // true binary
|
||
resultArray[bufferIndex++] = 1;
|
||
}
|
||
|
||
var lenStr = ab.byteLength.toString();
|
||
for (var i = 0; i < lenStr.length; i++) {
|
||
resultArray[bufferIndex++] = parseInt(lenStr[i]);
|
||
}
|
||
resultArray[bufferIndex++] = 255;
|
||
|
||
var view = new Uint8Array(ab);
|
||
for (var i = 0; i < view.length; i++) {
|
||
resultArray[bufferIndex++] = view[i];
|
||
}
|
||
});
|
||
|
||
return callback(resultArray.buffer);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Encode as Blob
|
||
*/
|
||
|
||
exports.encodePayloadAsBlob = function(packets, callback) {
|
||
function encodeOne(packet, doneCallback) {
|
||
exports.encodePacket(packet, true, true, function(encoded) {
|
||
var binaryIdentifier = new Uint8Array(1);
|
||
binaryIdentifier[0] = 1;
|
||
if (typeof encoded === 'string') {
|
||
var view = new Uint8Array(encoded.length);
|
||
for (var i = 0; i < encoded.length; i++) {
|
||
view[i] = encoded.charCodeAt(i);
|
||
}
|
||
encoded = view.buffer;
|
||
binaryIdentifier[0] = 0;
|
||
}
|
||
|
||
var len = (encoded instanceof ArrayBuffer)
|
||
? encoded.byteLength
|
||
: encoded.size;
|
||
|
||
var lenStr = len.toString();
|
||
var lengthAry = new Uint8Array(lenStr.length + 1);
|
||
for (var i = 0; i < lenStr.length; i++) {
|
||
lengthAry[i] = parseInt(lenStr[i]);
|
||
}
|
||
lengthAry[lenStr.length] = 255;
|
||
|
||
if (Blob) {
|
||
var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]);
|
||
doneCallback(null, blob);
|
||
}
|
||
});
|
||
}
|
||
|
||
map(packets, encodeOne, function(err, results) {
|
||
return callback(new Blob(results));
|
||
});
|
||
};
|
||
|
||
/*
|
||
* Decodes data when a payload is maybe expected. Strings are decoded by
|
||
* interpreting each byte as a key code for entries marked to start with 0. See
|
||
* description of encodePayloadAsBinary
|
||
*
|
||
* @param {ArrayBuffer} data, callback method
|
||
* @api public
|
||
*/
|
||
|
||
exports.decodePayloadAsBinary = function (data, binaryType, callback) {
|
||
if (typeof binaryType === 'function') {
|
||
callback = binaryType;
|
||
binaryType = null;
|
||
}
|
||
|
||
var bufferTail = data;
|
||
var buffers = [];
|
||
|
||
var numberTooLong = false;
|
||
while (bufferTail.byteLength > 0) {
|
||
var tailArray = new Uint8Array(bufferTail);
|
||
var isString = tailArray[0] === 0;
|
||
var msgLength = '';
|
||
|
||
for (var i = 1; ; i++) {
|
||
if (tailArray[i] == 255) break;
|
||
|
||
if (msgLength.length > 310) {
|
||
numberTooLong = true;
|
||
break;
|
||
}
|
||
|
||
msgLength += tailArray[i];
|
||
}
|
||
|
||
if(numberTooLong) return callback(err, 0, 1);
|
||
|
||
bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length);
|
||
msgLength = parseInt(msgLength);
|
||
|
||
var msg = sliceBuffer(bufferTail, 0, msgLength);
|
||
if (isString) {
|
||
try {
|
||
msg = String.fromCharCode.apply(null, new Uint8Array(msg));
|
||
} catch (e) {
|
||
// iPhone Safari doesn't let you apply to typed arrays
|
||
var typed = new Uint8Array(msg);
|
||
msg = '';
|
||
for (var i = 0; i < typed.length; i++) {
|
||
msg += String.fromCharCode(typed[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
buffers.push(msg);
|
||
bufferTail = sliceBuffer(bufferTail, msgLength);
|
||
}
|
||
|
||
var total = buffers.length;
|
||
buffers.forEach(function(buffer, i) {
|
||
callback(exports.decodePacket(buffer, binaryType, true), i, total);
|
||
});
|
||
};
|
||
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{"./keys":18,"after":19,"arraybuffer.slice":20,"base64-arraybuffer":21,"blob":11,"has-binary":22,"utf8":24}],18:[function(_dereq_,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(_dereq_,module,exports){
|
||
module.exports = after
|
||
|
||
function after(count, callback, err_cb) {
|
||
var bail = false
|
||
err_cb = err_cb || noop
|
||
proxy.count = count
|
||
|
||
return (count === 0) ? callback() : proxy
|
||
|
||
function proxy(err, result) {
|
||
if (proxy.count <= 0) {
|
||
throw new Error('after called too many times')
|
||
}
|
||
--proxy.count
|
||
|
||
// after first error, rest are passed to err_cb
|
||
if (err) {
|
||
bail = true
|
||
callback(err)
|
||
// future error callbacks will go to error handler
|
||
callback = err_cb
|
||
} else if (proxy.count === 0 && !bail) {
|
||
callback(null, result)
|
||
}
|
||
}
|
||
}
|
||
|
||
function noop() {}
|
||
|
||
},{}],20:[function(_dereq_,module,exports){
|
||
/**
|
||
* An abstraction for slicing an arraybuffer even when
|
||
* ArrayBuffer.prototype.slice is not supported
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
module.exports = function(arraybuffer, start, end) {
|
||
var bytes = arraybuffer.byteLength;
|
||
start = start || 0;
|
||
end = end || bytes;
|
||
|
||
if (arraybuffer.slice) { return arraybuffer.slice(start, end); }
|
||
|
||
if (start < 0) { start += bytes; }
|
||
if (end < 0) { end += bytes; }
|
||
if (end > bytes) { end = bytes; }
|
||
|
||
if (start >= bytes || start >= end || bytes === 0) {
|
||
return new ArrayBuffer(0);
|
||
}
|
||
|
||
var abv = new Uint8Array(arraybuffer);
|
||
var result = new Uint8Array(end - start);
|
||
for (var i = start, ii = 0; i < end; i++, ii++) {
|
||
result[ii] = abv[i];
|
||
}
|
||
return result.buffer;
|
||
};
|
||
|
||
},{}],21:[function(_dereq_,module,exports){
|
||
/*
|
||
* base64-arraybuffer
|
||
* https://github.com/niklasvh/base64-arraybuffer
|
||
*
|
||
* Copyright (c) 2012 Niklas von Hertzen
|
||
* Licensed under the MIT license.
|
||
*/
|
||
(function(chars){
|
||
"use strict";
|
||
|
||
exports.encode = function(arraybuffer) {
|
||
var bytes = new Uint8Array(arraybuffer),
|
||
i, len = bytes.length, base64 = "";
|
||
|
||
for (i = 0; i < len; i+=3) {
|
||
base64 += chars[bytes[i] >> 2];
|
||
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
|
||
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
|
||
base64 += chars[bytes[i + 2] & 63];
|
||
}
|
||
|
||
if ((len % 3) === 2) {
|
||
base64 = base64.substring(0, base64.length - 1) + "=";
|
||
} else if (len % 3 === 1) {
|
||
base64 = base64.substring(0, base64.length - 2) + "==";
|
||
}
|
||
|
||
return base64;
|
||
};
|
||
|
||
exports.decode = function(base64) {
|
||
var bufferLength = base64.length * 0.75,
|
||
len = base64.length, i, p = 0,
|
||
encoded1, encoded2, encoded3, encoded4;
|
||
|
||
if (base64[base64.length - 1] === "=") {
|
||
bufferLength--;
|
||
if (base64[base64.length - 2] === "=") {
|
||
bufferLength--;
|
||
}
|
||
}
|
||
|
||
var arraybuffer = new ArrayBuffer(bufferLength),
|
||
bytes = new Uint8Array(arraybuffer);
|
||
|
||
for (i = 0; i < len; i+=4) {
|
||
encoded1 = chars.indexOf(base64[i]);
|
||
encoded2 = chars.indexOf(base64[i+1]);
|
||
encoded3 = chars.indexOf(base64[i+2]);
|
||
encoded4 = chars.indexOf(base64[i+3]);
|
||
|
||
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
|
||
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
|
||
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
|
||
}
|
||
|
||
return arraybuffer;
|
||
};
|
||
})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
|
||
|
||
},{}],22:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
|
||
/*
|
||
* Module requirements.
|
||
*/
|
||
|
||
var isArray = _dereq_('isarray');
|
||
|
||
/**
|
||
* Module exports.
|
||
*/
|
||
|
||
module.exports = hasBinary;
|
||
|
||
/**
|
||
* Checks for binary data.
|
||
*
|
||
* Right now only Buffer and ArrayBuffer are supported..
|
||
*
|
||
* @param {Object} anything
|
||
* @api public
|
||
*/
|
||
|
||
function hasBinary(data) {
|
||
|
||
function _hasBinary(obj) {
|
||
if (!obj) return false;
|
||
|
||
if ( (global.Buffer && global.Buffer.isBuffer(obj)) ||
|
||
(global.ArrayBuffer && obj instanceof ArrayBuffer) ||
|
||
(global.Blob && obj instanceof Blob) ||
|
||
(global.File && obj instanceof File)
|
||
) {
|
||
return true;
|
||
}
|
||
|
||
if (isArray(obj)) {
|
||
for (var i = 0; i < obj.length; i++) {
|
||
if (_hasBinary(obj[i])) {
|
||
return true;
|
||
}
|
||
}
|
||
} else if (obj && 'object' == typeof obj) {
|
||
if (obj.toJSON) {
|
||
obj = obj.toJSON();
|
||
}
|
||
|
||
for (var key in obj) {
|
||
if (Object.prototype.hasOwnProperty.call(obj, key) && _hasBinary(obj[key])) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
return _hasBinary(data);
|
||
}
|
||
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{"isarray":23}],23:[function(_dereq_,module,exports){
|
||
module.exports = Array.isArray || function (arr) {
|
||
return Object.prototype.toString.call(arr) == '[object Array]';
|
||
};
|
||
|
||
},{}],24:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
/*! https://mths.be/utf8js v2.0.0 by @mathias */
|
||
;(function(root) {
|
||
|
||
// Detect free variables `exports`
|
||
var freeExports = typeof exports == 'object' && exports;
|
||
|
||
// Detect free variable `module`
|
||
var freeModule = typeof module == 'object' && module &&
|
||
module.exports == freeExports && module;
|
||
|
||
// Detect free variable `global`, from Node.js or Browserified code,
|
||
// and use it as `root`
|
||
var freeGlobal = typeof global == 'object' && global;
|
||
if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
|
||
root = freeGlobal;
|
||
}
|
||
|
||
/*--------------------------------------------------------------------------*/
|
||
|
||
var stringFromCharCode = String.fromCharCode;
|
||
|
||
// Taken from https://mths.be/punycode
|
||
function ucs2decode(string) {
|
||
var output = [];
|
||
var counter = 0;
|
||
var length = string.length;
|
||
var value;
|
||
var extra;
|
||
while (counter < length) {
|
||
value = string.charCodeAt(counter++);
|
||
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
|
||
// high surrogate, and there is a next character
|
||
extra = string.charCodeAt(counter++);
|
||
if ((extra & 0xFC00) == 0xDC00) { // low surrogate
|
||
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
|
||
} else {
|
||
// unmatched surrogate; only append this code unit, in case the next
|
||
// code unit is the high surrogate of a surrogate pair
|
||
output.push(value);
|
||
counter--;
|
||
}
|
||
} else {
|
||
output.push(value);
|
||
}
|
||
}
|
||
return output;
|
||
}
|
||
|
||
// Taken from https://mths.be/punycode
|
||
function ucs2encode(array) {
|
||
var length = array.length;
|
||
var index = -1;
|
||
var value;
|
||
var output = '';
|
||
while (++index < length) {
|
||
value = array[index];
|
||
if (value > 0xFFFF) {
|
||
value -= 0x10000;
|
||
output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
|
||
value = 0xDC00 | value & 0x3FF;
|
||
}
|
||
output += stringFromCharCode(value);
|
||
}
|
||
return output;
|
||
}
|
||
|
||
function checkScalarValue(codePoint) {
|
||
if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
|
||
throw Error(
|
||
'Lone surrogate U+' + codePoint.toString(16).toUpperCase() +
|
||
' is not a scalar value'
|
||
);
|
||
}
|
||
}
|
||
/*--------------------------------------------------------------------------*/
|
||
|
||
function createByte(codePoint, shift) {
|
||
return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80);
|
||
}
|
||
|
||
function encodeCodePoint(codePoint) {
|
||
if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence
|
||
return stringFromCharCode(codePoint);
|
||
}
|
||
var symbol = '';
|
||
if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence
|
||
symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0);
|
||
}
|
||
else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence
|
||
checkScalarValue(codePoint);
|
||
symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0);
|
||
symbol += createByte(codePoint, 6);
|
||
}
|
||
else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence
|
||
symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0);
|
||
symbol += createByte(codePoint, 12);
|
||
symbol += createByte(codePoint, 6);
|
||
}
|
||
symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);
|
||
return symbol;
|
||
}
|
||
|
||
function utf8encode(string) {
|
||
var codePoints = ucs2decode(string);
|
||
var length = codePoints.length;
|
||
var index = -1;
|
||
var codePoint;
|
||
var byteString = '';
|
||
while (++index < length) {
|
||
codePoint = codePoints[index];
|
||
byteString += encodeCodePoint(codePoint);
|
||
}
|
||
return byteString;
|
||
}
|
||
|
||
/*--------------------------------------------------------------------------*/
|
||
|
||
function readContinuationByte() {
|
||
if (byteIndex >= byteCount) {
|
||
throw Error('Invalid byte index');
|
||
}
|
||
|
||
var continuationByte = byteArray[byteIndex] & 0xFF;
|
||
byteIndex++;
|
||
|
||
if ((continuationByte & 0xC0) == 0x80) {
|
||
return continuationByte & 0x3F;
|
||
}
|
||
|
||
// If we end up here, it’s not a continuation byte
|
||
throw Error('Invalid continuation byte');
|
||
}
|
||
|
||
function decodeSymbol() {
|
||
var byte1;
|
||
var byte2;
|
||
var byte3;
|
||
var byte4;
|
||
var codePoint;
|
||
|
||
if (byteIndex > byteCount) {
|
||
throw Error('Invalid byte index');
|
||
}
|
||
|
||
if (byteIndex == byteCount) {
|
||
return false;
|
||
}
|
||
|
||
// Read first byte
|
||
byte1 = byteArray[byteIndex] & 0xFF;
|
||
byteIndex++;
|
||
|
||
// 1-byte sequence (no continuation bytes)
|
||
if ((byte1 & 0x80) == 0) {
|
||
return byte1;
|
||
}
|
||
|
||
// 2-byte sequence
|
||
if ((byte1 & 0xE0) == 0xC0) {
|
||
var byte2 = readContinuationByte();
|
||
codePoint = ((byte1 & 0x1F) << 6) | byte2;
|
||
if (codePoint >= 0x80) {
|
||
return codePoint;
|
||
} else {
|
||
throw Error('Invalid continuation byte');
|
||
}
|
||
}
|
||
|
||
// 3-byte sequence (may include unpaired surrogates)
|
||
if ((byte1 & 0xF0) == 0xE0) {
|
||
byte2 = readContinuationByte();
|
||
byte3 = readContinuationByte();
|
||
codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
|
||
if (codePoint >= 0x0800) {
|
||
checkScalarValue(codePoint);
|
||
return codePoint;
|
||
} else {
|
||
throw Error('Invalid continuation byte');
|
||
}
|
||
}
|
||
|
||
// 4-byte sequence
|
||
if ((byte1 & 0xF8) == 0xF0) {
|
||
byte2 = readContinuationByte();
|
||
byte3 = readContinuationByte();
|
||
byte4 = readContinuationByte();
|
||
codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) |
|
||
(byte3 << 0x06) | byte4;
|
||
if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
|
||
return codePoint;
|
||
}
|
||
}
|
||
|
||
throw Error('Invalid UTF-8 detected');
|
||
}
|
||
|
||
var byteArray;
|
||
var byteCount;
|
||
var byteIndex;
|
||
function utf8decode(byteString) {
|
||
byteArray = ucs2decode(byteString);
|
||
byteCount = byteArray.length;
|
||
byteIndex = 0;
|
||
var codePoints = [];
|
||
var tmp;
|
||
while ((tmp = decodeSymbol()) !== false) {
|
||
codePoints.push(tmp);
|
||
}
|
||
return ucs2encode(codePoints);
|
||
}
|
||
|
||
/*--------------------------------------------------------------------------*/
|
||
|
||
var utf8 = {
|
||
'version': '2.0.0',
|
||
'encode': utf8encode,
|
||
'decode': utf8decode
|
||
};
|
||
|
||
// Some AMD build optimizers, like r.js, check for specific condition patterns
|
||
// like the following:
|
||
if (
|
||
typeof define == 'function' &&
|
||
typeof define.amd == 'object' &&
|
||
define.amd
|
||
) {
|
||
define(function() {
|
||
return utf8;
|
||
});
|
||
} else if (freeExports && !freeExports.nodeType) {
|
||
if (freeModule) { // in Node.js or RingoJS v0.8.0+
|
||
freeModule.exports = utf8;
|
||
} else { // in Narwhal or RingoJS v0.7.0-
|
||
var object = {};
|
||
var hasOwnProperty = object.hasOwnProperty;
|
||
for (var key in utf8) {
|
||
hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]);
|
||
}
|
||
}
|
||
} else { // in Rhino or a web browser
|
||
root.utf8 = utf8;
|
||
}
|
||
|
||
}(this));
|
||
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{}],25:[function(_dereq_,module,exports){
|
||
|
||
/**
|
||
* Module exports.
|
||
*
|
||
* Logic borrowed from Modernizr:
|
||
*
|
||
* - https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cors.js
|
||
*/
|
||
|
||
try {
|
||
module.exports = typeof XMLHttpRequest !== 'undefined' &&
|
||
'withCredentials' in new XMLHttpRequest();
|
||
} catch (err) {
|
||
// if XMLHttp support is disabled in IE then it will throw
|
||
// when trying to create
|
||
module.exports = false;
|
||
}
|
||
|
||
},{}],26:[function(_dereq_,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;
|
||
};
|
||
},{}],27:[function(_dereq_,module,exports){
|
||
(function (global){
|
||
/**
|
||
* 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+$/;
|
||
|
||
module.exports = function parsejson(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))();
|
||
}
|
||
};
|
||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{}],28:[function(_dereq_,module,exports){
|
||
/**
|
||
* Compiles a querystring
|
||
* Returns string representation of the object
|
||
*
|
||
* @param {Object}
|
||
* @api private
|
||
*/
|
||
|
||
exports.encode = 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 into an object
|
||
*
|
||
* @param {String} qs
|
||
* @api private
|
||
*/
|
||
|
||
exports.decode = 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;
|
||
};
|
||
|
||
},{}],29:[function(_dereq_,module,exports){
|
||
/**
|
||
* Parses an URI
|
||
*
|
||
* @author Steven Levithan <stevenlevithan.com> (MIT license)
|
||
* @api private
|
||
*/
|
||
|
||
var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
|
||
|
||
var parts = [
|
||
'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'
|
||
];
|
||
|
||
module.exports = function parseuri(str) {
|
||
var src = str,
|
||
b = str.indexOf('['),
|
||
e = str.indexOf(']');
|
||
|
||
if (b != -1 && e != -1) {
|
||
str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length);
|
||
}
|
||
|
||
var m = re.exec(str || ''),
|
||
uri = {},
|
||
i = 14;
|
||
|
||
while (i--) {
|
||
uri[parts[i]] = m[i] || '';
|
||
}
|
||
|
||
if (b != -1 && e != -1) {
|
||
uri.source = src;
|
||
uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':');
|
||
uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':');
|
||
uri.ipv6uri = true;
|
||
}
|
||
|
||
return uri;
|
||
};
|
||
|
||
},{}],30:[function(_dereq_,module,exports){
|
||
'use strict';
|
||
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
var WS = module.exports = _dereq_('./lib/WebSocket');
|
||
|
||
WS.Server = _dereq_('./lib/WebSocketServer');
|
||
WS.Sender = _dereq_('./lib/Sender');
|
||
WS.Receiver = _dereq_('./lib/Receiver');
|
||
|
||
/**
|
||
* Create a new WebSocket server.
|
||
*
|
||
* @param {Object} options Server options
|
||
* @param {Function} fn Optional connection listener.
|
||
* @returns {WS.Server}
|
||
* @api public
|
||
*/
|
||
WS.createServer = function createServer(options, fn) {
|
||
var server = new WS.Server(options);
|
||
|
||
if (typeof fn === 'function') {
|
||
server.on('connection', fn);
|
||
}
|
||
|
||
return server;
|
||
};
|
||
|
||
/**
|
||
* Create a new WebSocket connection.
|
||
*
|
||
* @param {String} address The URL/address we need to connect to.
|
||
* @param {Function} fn Open listener.
|
||
* @returns {WS}
|
||
* @api public
|
||
*/
|
||
WS.connect = WS.createConnection = function connect(address, fn) {
|
||
var client = new WS(address);
|
||
|
||
if (typeof fn === 'function') {
|
||
client.on('open', fn);
|
||
}
|
||
|
||
return client;
|
||
};
|
||
|
||
},{"./lib/Receiver":38,"./lib/Sender":40,"./lib/WebSocket":43,"./lib/WebSocketServer":44}],31:[function(_dereq_,module,exports){
|
||
(function (Buffer){
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
var util = _dereq_('util');
|
||
|
||
function BufferPool(initialSize, growStrategy, shrinkStrategy) {
|
||
if (this instanceof BufferPool === false) {
|
||
throw new TypeError("Classes can't be function-called");
|
||
}
|
||
|
||
if (typeof initialSize === 'function') {
|
||
shrinkStrategy = growStrategy;
|
||
growStrategy = initialSize;
|
||
initialSize = 0;
|
||
}
|
||
else if (typeof initialSize === 'undefined') {
|
||
initialSize = 0;
|
||
}
|
||
this._growStrategy = (growStrategy || function(db, size) {
|
||
return db.used + size;
|
||
}).bind(null, this);
|
||
this._shrinkStrategy = (shrinkStrategy || function(db) {
|
||
return initialSize;
|
||
}).bind(null, this);
|
||
this._buffer = initialSize ? new Buffer(initialSize) : null;
|
||
this._offset = 0;
|
||
this._used = 0;
|
||
this._changeFactor = 0;
|
||
this.__defineGetter__('size', function(){
|
||
return this._buffer == null ? 0 : this._buffer.length;
|
||
});
|
||
this.__defineGetter__('used', function(){
|
||
return this._used;
|
||
});
|
||
}
|
||
|
||
BufferPool.prototype.get = function(length) {
|
||
if (this._buffer == null || this._offset + length > this._buffer.length) {
|
||
var newBuf = new Buffer(this._growStrategy(length));
|
||
this._buffer = newBuf;
|
||
this._offset = 0;
|
||
}
|
||
this._used += length;
|
||
var buf = this._buffer.slice(this._offset, this._offset + length);
|
||
this._offset += length;
|
||
return buf;
|
||
}
|
||
|
||
BufferPool.prototype.reset = function(forceNewBuffer) {
|
||
var len = this._shrinkStrategy();
|
||
if (len < this.size) this._changeFactor -= 1;
|
||
if (forceNewBuffer || this._changeFactor < -2) {
|
||
this._changeFactor = 0;
|
||
this._buffer = len ? new Buffer(len) : null;
|
||
}
|
||
this._offset = 0;
|
||
this._used = 0;
|
||
}
|
||
|
||
module.exports = BufferPool;
|
||
|
||
}).call(this,_dereq_("buffer").Buffer)
|
||
},{"buffer":undefined,"util":undefined}],32:[function(_dereq_,module,exports){
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
module.exports.BufferUtil = {
|
||
merge: function(mergedBuffer, buffers) {
|
||
var offset = 0;
|
||
for (var i = 0, l = buffers.length; i < l; ++i) {
|
||
var buf = buffers[i];
|
||
buf.copy(mergedBuffer, offset);
|
||
offset += buf.length;
|
||
}
|
||
},
|
||
mask: function(source, mask, output, offset, length) {
|
||
var maskNum = mask.readUInt32LE(0, true);
|
||
var i = 0;
|
||
for (; i < length - 3; i += 4) {
|
||
var num = maskNum ^ source.readUInt32LE(i, true);
|
||
if (num < 0) num = 4294967296 + num;
|
||
output.writeUInt32LE(num, offset + i, true);
|
||
}
|
||
switch (length % 4) {
|
||
case 3: output[offset + i + 2] = source[i + 2] ^ mask[2];
|
||
case 2: output[offset + i + 1] = source[i + 1] ^ mask[1];
|
||
case 1: output[offset + i] = source[i] ^ mask[0];
|
||
case 0:;
|
||
}
|
||
},
|
||
unmask: function(data, mask) {
|
||
var maskNum = mask.readUInt32LE(0, true);
|
||
var length = data.length;
|
||
var i = 0;
|
||
for (; i < length - 3; i += 4) {
|
||
var num = maskNum ^ data.readUInt32LE(i, true);
|
||
if (num < 0) num = 4294967296 + num;
|
||
data.writeUInt32LE(num, i, true);
|
||
}
|
||
switch (length % 4) {
|
||
case 3: data[i + 2] = data[i + 2] ^ mask[2];
|
||
case 2: data[i + 1] = data[i + 1] ^ mask[1];
|
||
case 1: data[i] = data[i] ^ mask[0];
|
||
case 0:;
|
||
}
|
||
}
|
||
}
|
||
|
||
},{}],33:[function(_dereq_,module,exports){
|
||
'use strict';
|
||
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
try {
|
||
module.exports = _dereq_('bufferutil');
|
||
} catch (e) {
|
||
module.exports = _dereq_('./BufferUtil.fallback');
|
||
}
|
||
|
||
},{"./BufferUtil.fallback":32,"bufferutil":undefined}],34:[function(_dereq_,module,exports){
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
module.exports = {
|
||
isValidErrorCode: function(code) {
|
||
return (code >= 1000 && code <= 1011 && code != 1004 && code != 1005 && code != 1006) ||
|
||
(code >= 3000 && code <= 4999);
|
||
},
|
||
1000: 'normal',
|
||
1001: 'going away',
|
||
1002: 'protocol error',
|
||
1003: 'unsupported data',
|
||
1004: 'reserved',
|
||
1005: 'reserved for extensions',
|
||
1006: 'reserved for extensions',
|
||
1007: 'inconsistent or invalid data',
|
||
1008: 'policy violation',
|
||
1009: 'message too big',
|
||
1010: 'extension handshake missing',
|
||
1011: 'an unexpected condition prevented the request from being fulfilled',
|
||
};
|
||
},{}],35:[function(_dereq_,module,exports){
|
||
|
||
var util = _dereq_('util');
|
||
|
||
/**
|
||
* Module exports.
|
||
*/
|
||
|
||
exports.parse = parse;
|
||
exports.format = format;
|
||
|
||
/**
|
||
* Parse extensions header value
|
||
*/
|
||
|
||
function parse(value) {
|
||
value = value || '';
|
||
|
||
var extensions = {};
|
||
|
||
value.split(',').forEach(function(v) {
|
||
var params = v.split(';');
|
||
var token = params.shift().trim();
|
||
var paramsList = extensions[token] = extensions[token] || [];
|
||
var parsedParams = {};
|
||
|
||
params.forEach(function(param) {
|
||
var parts = param.trim().split('=');
|
||
var key = parts[0];
|
||
var value = parts[1];
|
||
if (typeof value === 'undefined') {
|
||
value = true;
|
||
} else {
|
||
// unquote value
|
||
if (value[0] === '"') {
|
||
value = value.slice(1);
|
||
}
|
||
if (value[value.length - 1] === '"') {
|
||
value = value.slice(0, value.length - 1);
|
||
}
|
||
}
|
||
(parsedParams[key] = parsedParams[key] || []).push(value);
|
||
});
|
||
|
||
paramsList.push(parsedParams);
|
||
});
|
||
|
||
return extensions;
|
||
}
|
||
|
||
/**
|
||
* Format extensions header value
|
||
*/
|
||
|
||
function format(value) {
|
||
return Object.keys(value).map(function(token) {
|
||
var paramsList = value[token];
|
||
if (!util.isArray(paramsList)) {
|
||
paramsList = [paramsList];
|
||
}
|
||
return paramsList.map(function(params) {
|
||
return [token].concat(Object.keys(params).map(function(k) {
|
||
var p = params[k];
|
||
if (!util.isArray(p)) p = [p];
|
||
return p.map(function(v) {
|
||
return v === true ? k : k + '=' + v;
|
||
}).join('; ');
|
||
})).join('; ');
|
||
}).join(', ');
|
||
}).join(', ');
|
||
}
|
||
|
||
},{"util":undefined}],36:[function(_dereq_,module,exports){
|
||
(function (Buffer){
|
||
|
||
var zlib = _dereq_('zlib');
|
||
|
||
var AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15];
|
||
var DEFAULT_WINDOW_BITS = 15;
|
||
var DEFAULT_MEM_LEVEL = 8;
|
||
|
||
PerMessageDeflate.extensionName = 'permessage-deflate';
|
||
|
||
/**
|
||
* Per-message Compression Extensions implementation
|
||
*/
|
||
|
||
function PerMessageDeflate(options, isServer) {
|
||
if (this instanceof PerMessageDeflate === false) {
|
||
throw new TypeError("Classes can't be function-called");
|
||
}
|
||
|
||
this._options = options || {};
|
||
this._isServer = !!isServer;
|
||
this._inflate = null;
|
||
this._deflate = null;
|
||
this.params = null;
|
||
}
|
||
|
||
/**
|
||
* Create extension parameters offer
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
PerMessageDeflate.prototype.offer = function() {
|
||
var params = {};
|
||
if (this._options.serverNoContextTakeover) {
|
||
params.server_no_context_takeover = true;
|
||
}
|
||
if (this._options.clientNoContextTakeover) {
|
||
params.client_no_context_takeover = true;
|
||
}
|
||
if (this._options.serverMaxWindowBits) {
|
||
params.server_max_window_bits = this._options.serverMaxWindowBits;
|
||
}
|
||
if (this._options.clientMaxWindowBits) {
|
||
params.client_max_window_bits = this._options.clientMaxWindowBits;
|
||
} else if (this._options.clientMaxWindowBits == null) {
|
||
params.client_max_window_bits = true;
|
||
}
|
||
return params;
|
||
};
|
||
|
||
/**
|
||
* Accept extension offer
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
PerMessageDeflate.prototype.accept = function(paramsList) {
|
||
paramsList = this.normalizeParams(paramsList);
|
||
|
||
var params;
|
||
if (this._isServer) {
|
||
params = this.acceptAsServer(paramsList);
|
||
} else {
|
||
params = this.acceptAsClient(paramsList);
|
||
}
|
||
|
||
this.params = params;
|
||
return params;
|
||
};
|
||
|
||
/**
|
||
* Releases all resources used by the extension
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
PerMessageDeflate.prototype.cleanup = function() {
|
||
if (this._inflate) {
|
||
if (this._inflate.writeInProgress) {
|
||
this._inflate.pendingClose = true;
|
||
} else {
|
||
if (this._inflate.close) this._inflate.close();
|
||
this._inflate = null;
|
||
}
|
||
}
|
||
if (this._deflate) {
|
||
if (this._deflate.writeInProgress) {
|
||
this._deflate.pendingClose = true;
|
||
} else {
|
||
if (this._deflate.close) this._deflate.close();
|
||
this._deflate = null;
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Accept extension offer from client
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
PerMessageDeflate.prototype.acceptAsServer = function(paramsList) {
|
||
var accepted = {};
|
||
var result = paramsList.some(function(params) {
|
||
accepted = {};
|
||
if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) {
|
||
return;
|
||
}
|
||
if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) {
|
||
return;
|
||
}
|
||
if (typeof this._options.serverMaxWindowBits === 'number' &&
|
||
typeof params.server_max_window_bits === 'number' &&
|
||
this._options.serverMaxWindowBits > params.server_max_window_bits) {
|
||
return;
|
||
}
|
||
if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) {
|
||
return;
|
||
}
|
||
|
||
if (this._options.serverNoContextTakeover || params.server_no_context_takeover) {
|
||
accepted.server_no_context_takeover = true;
|
||
}
|
||
if (this._options.clientNoContextTakeover) {
|
||
accepted.client_no_context_takeover = true;
|
||
}
|
||
if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) {
|
||
accepted.client_no_context_takeover = true;
|
||
}
|
||
if (typeof this._options.serverMaxWindowBits === 'number') {
|
||
accepted.server_max_window_bits = this._options.serverMaxWindowBits;
|
||
} else if (typeof params.server_max_window_bits === 'number') {
|
||
accepted.server_max_window_bits = params.server_max_window_bits;
|
||
}
|
||
if (typeof this._options.clientMaxWindowBits === 'number') {
|
||
accepted.client_max_window_bits = this._options.clientMaxWindowBits;
|
||
} else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') {
|
||
accepted.client_max_window_bits = params.client_max_window_bits;
|
||
}
|
||
return true;
|
||
}, this);
|
||
|
||
if (!result) {
|
||
throw new Error('Doesn\'t support the offered configuration');
|
||
}
|
||
|
||
return accepted;
|
||
};
|
||
|
||
/**
|
||
* Accept extension response from server
|
||
*
|
||
* @api privaye
|
||
*/
|
||
|
||
PerMessageDeflate.prototype.acceptAsClient = function(paramsList) {
|
||
var params = paramsList[0];
|
||
if (this._options.clientNoContextTakeover != null) {
|
||
if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) {
|
||
throw new Error('Invalid value for "client_no_context_takeover"');
|
||
}
|
||
}
|
||
if (this._options.clientMaxWindowBits != null) {
|
||
if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) {
|
||
throw new Error('Invalid value for "client_max_window_bits"');
|
||
}
|
||
if (typeof this._options.clientMaxWindowBits === 'number' &&
|
||
(!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) {
|
||
throw new Error('Invalid value for "client_max_window_bits"');
|
||
}
|
||
}
|
||
return params;
|
||
};
|
||
|
||
/**
|
||
* Normalize extensions parameters
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
PerMessageDeflate.prototype.normalizeParams = function(paramsList) {
|
||
return paramsList.map(function(params) {
|
||
Object.keys(params).forEach(function(key) {
|
||
var value = params[key];
|
||
if (value.length > 1) {
|
||
throw new Error('Multiple extension parameters for ' + key);
|
||
}
|
||
|
||
value = value[0];
|
||
|
||
switch (key) {
|
||
case 'server_no_context_takeover':
|
||
case 'client_no_context_takeover':
|
||
if (value !== true) {
|
||
throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')');
|
||
}
|
||
params[key] = true;
|
||
break;
|
||
case 'server_max_window_bits':
|
||
case 'client_max_window_bits':
|
||
if (typeof value === 'string') {
|
||
value = parseInt(value, 10);
|
||
if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) {
|
||
throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')');
|
||
}
|
||
}
|
||
if (!this._isServer && value === true) {
|
||
throw new Error('Missing extension parameter value for ' + key);
|
||
}
|
||
params[key] = value;
|
||
break;
|
||
default:
|
||
throw new Error('Not defined extension parameter (' + key + ')');
|
||
}
|
||
}, this);
|
||
return params;
|
||
}, this);
|
||
};
|
||
|
||
/**
|
||
* Decompress message
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
PerMessageDeflate.prototype.decompress = function (data, fin, callback) {
|
||
var endpoint = this._isServer ? 'client' : 'server';
|
||
|
||
if (!this._inflate) {
|
||
var maxWindowBits = this.params[endpoint + '_max_window_bits'];
|
||
this._inflate = zlib.createInflateRaw({
|
||
windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS
|
||
});
|
||
}
|
||
this._inflate.writeInProgress = true;
|
||
|
||
var self = this;
|
||
var buffers = [];
|
||
|
||
this._inflate.on('error', onError).on('data', onData);
|
||
this._inflate.write(data);
|
||
if (fin) {
|
||
this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff]));
|
||
}
|
||
this._inflate.flush(function() {
|
||
cleanup();
|
||
callback(null, Buffer.concat(buffers));
|
||
});
|
||
|
||
function onError(err) {
|
||
cleanup();
|
||
callback(err);
|
||
}
|
||
|
||
function onData(data) {
|
||
buffers.push(data);
|
||
}
|
||
|
||
function cleanup() {
|
||
if (!self._inflate) return;
|
||
self._inflate.removeListener('error', onError);
|
||
self._inflate.removeListener('data', onData);
|
||
self._inflate.writeInProgress = false;
|
||
if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) {
|
||
if (self._inflate.close) self._inflate.close();
|
||
self._inflate = null;
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Compress message
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
PerMessageDeflate.prototype.compress = function (data, fin, callback) {
|
||
var endpoint = this._isServer ? 'server' : 'client';
|
||
|
||
if (!this._deflate) {
|
||
var maxWindowBits = this.params[endpoint + '_max_window_bits'];
|
||
this._deflate = zlib.createDeflateRaw({
|
||
flush: zlib.Z_SYNC_FLUSH,
|
||
windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS,
|
||
memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL
|
||
});
|
||
}
|
||
this._deflate.writeInProgress = true;
|
||
|
||
var self = this;
|
||
var buffers = [];
|
||
|
||
this._deflate.on('error', onError).on('data', onData);
|
||
this._deflate.write(data);
|
||
this._deflate.flush(function() {
|
||
cleanup();
|
||
var data = Buffer.concat(buffers);
|
||
if (fin) {
|
||
data = data.slice(0, data.length - 4);
|
||
}
|
||
callback(null, data);
|
||
});
|
||
|
||
function onError(err) {
|
||
cleanup();
|
||
callback(err);
|
||
}
|
||
|
||
function onData(data) {
|
||
buffers.push(data);
|
||
}
|
||
|
||
function cleanup() {
|
||
if (!self._deflate) return;
|
||
self._deflate.removeListener('error', onError);
|
||
self._deflate.removeListener('data', onData);
|
||
self._deflate.writeInProgress = false;
|
||
if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) {
|
||
if (self._deflate.close) self._deflate.close();
|
||
self._deflate = null;
|
||
}
|
||
}
|
||
};
|
||
|
||
module.exports = PerMessageDeflate;
|
||
|
||
}).call(this,_dereq_("buffer").Buffer)
|
||
},{"buffer":undefined,"zlib":undefined}],37:[function(_dereq_,module,exports){
|
||
(function (Buffer){
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
var util = _dereq_('util');
|
||
|
||
/**
|
||
* State constants
|
||
*/
|
||
|
||
var EMPTY = 0
|
||
, BODY = 1;
|
||
var BINARYLENGTH = 2
|
||
, BINARYBODY = 3;
|
||
|
||
/**
|
||
* Hixie Receiver implementation
|
||
*/
|
||
|
||
function Receiver () {
|
||
if (this instanceof Receiver === false) {
|
||
throw new TypeError("Classes can't be function-called");
|
||
}
|
||
|
||
this.state = EMPTY;
|
||
this.buffers = [];
|
||
this.messageEnd = -1;
|
||
this.spanLength = 0;
|
||
this.dead = false;
|
||
|
||
this.onerror = function() {};
|
||
this.ontext = function() {};
|
||
this.onbinary = function() {};
|
||
this.onclose = function() {};
|
||
this.onping = function() {};
|
||
this.onpong = function() {};
|
||
}
|
||
|
||
module.exports = Receiver;
|
||
|
||
/**
|
||
* Add new data to the parser.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Receiver.prototype.add = function(data) {
|
||
var self = this;
|
||
function doAdd() {
|
||
if (self.state === EMPTY) {
|
||
if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) {
|
||
self.reset();
|
||
self.onclose();
|
||
return;
|
||
}
|
||
if (data[0] === 0x80) {
|
||
self.messageEnd = 0;
|
||
self.state = BINARYLENGTH;
|
||
data = data.slice(1);
|
||
} else {
|
||
|
||
if (data[0] !== 0x00) {
|
||
self.error('payload must start with 0x00 byte', true);
|
||
return;
|
||
}
|
||
data = data.slice(1);
|
||
self.state = BODY;
|
||
|
||
}
|
||
}
|
||
if (self.state === BINARYLENGTH) {
|
||
var i = 0;
|
||
while ((i < data.length) && (data[i] & 0x80)) {
|
||
self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f);
|
||
++i;
|
||
}
|
||
if (i < data.length) {
|
||
self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f);
|
||
self.state = BINARYBODY;
|
||
++i;
|
||
}
|
||
if (i > 0)
|
||
data = data.slice(i);
|
||
}
|
||
if (self.state === BINARYBODY) {
|
||
var dataleft = self.messageEnd - self.spanLength;
|
||
if (data.length >= dataleft) {
|
||
// consume the whole buffer to finish the frame
|
||
self.buffers.push(data);
|
||
self.spanLength += dataleft;
|
||
self.messageEnd = dataleft;
|
||
return self.parse();
|
||
}
|
||
// frame's not done even if we consume it all
|
||
self.buffers.push(data);
|
||
self.spanLength += data.length;
|
||
return;
|
||
}
|
||
self.buffers.push(data);
|
||
if ((self.messageEnd = bufferIndex(data, 0xFF)) != -1) {
|
||
self.spanLength += self.messageEnd;
|
||
return self.parse();
|
||
}
|
||
else self.spanLength += data.length;
|
||
}
|
||
while(data) data = doAdd();
|
||
};
|
||
|
||
/**
|
||
* Releases all resources used by the receiver.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Receiver.prototype.cleanup = function() {
|
||
this.dead = true;
|
||
this.state = EMPTY;
|
||
this.buffers = [];
|
||
};
|
||
|
||
/**
|
||
* Process buffered data.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Receiver.prototype.parse = function() {
|
||
var output = new Buffer(this.spanLength);
|
||
var outputIndex = 0;
|
||
for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) {
|
||
var buffer = this.buffers[bi];
|
||
buffer.copy(output, outputIndex);
|
||
outputIndex += buffer.length;
|
||
}
|
||
var lastBuffer = this.buffers[this.buffers.length - 1];
|
||
if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd);
|
||
if (this.state !== BODY) --this.messageEnd;
|
||
var tail = null;
|
||
if (this.messageEnd < lastBuffer.length - 1) {
|
||
tail = lastBuffer.slice(this.messageEnd + 1);
|
||
}
|
||
this.reset();
|
||
this.ontext(output.toString('utf8'));
|
||
return tail;
|
||
};
|
||
|
||
/**
|
||
* Handles an error
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.error = function (reason, terminate) {
|
||
this.reset();
|
||
this.onerror(reason, terminate);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Reset parser state
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.reset = function (reason) {
|
||
if (this.dead) return;
|
||
this.state = EMPTY;
|
||
this.buffers = [];
|
||
this.messageEnd = -1;
|
||
this.spanLength = 0;
|
||
};
|
||
|
||
/**
|
||
* Internal api
|
||
*/
|
||
|
||
function bufferIndex(buffer, byte) {
|
||
for (var i = 0, l = buffer.length; i < l; ++i) {
|
||
if (buffer[i] === byte) return i;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
}).call(this,_dereq_("buffer").Buffer)
|
||
},{"buffer":undefined,"util":undefined}],38:[function(_dereq_,module,exports){
|
||
(function (Buffer){
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
var util = _dereq_('util')
|
||
, Validation = _dereq_('./Validation').Validation
|
||
, ErrorCodes = _dereq_('./ErrorCodes')
|
||
, BufferPool = _dereq_('./BufferPool')
|
||
, bufferUtil = _dereq_('./BufferUtil').BufferUtil
|
||
, PerMessageDeflate = _dereq_('./PerMessageDeflate');
|
||
|
||
/**
|
||
* HyBi Receiver implementation
|
||
*/
|
||
|
||
function Receiver (extensions) {
|
||
if (this instanceof Receiver === false) {
|
||
throw new TypeError("Classes can't be function-called");
|
||
}
|
||
|
||
// memory pool for fragmented messages
|
||
var fragmentedPoolPrevUsed = -1;
|
||
this.fragmentedBufferPool = new BufferPool(1024, function(db, length) {
|
||
return db.used + length;
|
||
}, function(db) {
|
||
return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ?
|
||
Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) :
|
||
db.used;
|
||
});
|
||
|
||
// memory pool for unfragmented messages
|
||
var unfragmentedPoolPrevUsed = -1;
|
||
this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) {
|
||
return db.used + length;
|
||
}, function(db) {
|
||
return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ?
|
||
Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) :
|
||
db.used;
|
||
});
|
||
|
||
this.extensions = extensions || {};
|
||
this.state = {
|
||
activeFragmentedOperation: null,
|
||
lastFragment: false,
|
||
masked: false,
|
||
opcode: 0,
|
||
fragmentedOperation: false
|
||
};
|
||
this.overflow = [];
|
||
this.headerBuffer = new Buffer(10);
|
||
this.expectOffset = 0;
|
||
this.expectBuffer = null;
|
||
this.expectHandler = null;
|
||
this.currentMessage = [];
|
||
this.messageHandlers = [];
|
||
this.expectHeader(2, this.processPacket);
|
||
this.dead = false;
|
||
this.processing = false;
|
||
|
||
this.onerror = function() {};
|
||
this.ontext = function() {};
|
||
this.onbinary = function() {};
|
||
this.onclose = function() {};
|
||
this.onping = function() {};
|
||
this.onpong = function() {};
|
||
}
|
||
|
||
module.exports = Receiver;
|
||
|
||
/**
|
||
* Add new data to the parser.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Receiver.prototype.add = function(data) {
|
||
var dataLength = data.length;
|
||
if (dataLength == 0) return;
|
||
if (this.expectBuffer == null) {
|
||
this.overflow.push(data);
|
||
return;
|
||
}
|
||
var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset);
|
||
fastCopy(toRead, data, this.expectBuffer, this.expectOffset);
|
||
this.expectOffset += toRead;
|
||
if (toRead < dataLength) {
|
||
this.overflow.push(data.slice(toRead));
|
||
}
|
||
while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) {
|
||
var bufferForHandler = this.expectBuffer;
|
||
this.expectBuffer = null;
|
||
this.expectOffset = 0;
|
||
this.expectHandler.call(this, bufferForHandler);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Releases all resources used by the receiver.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Receiver.prototype.cleanup = function() {
|
||
this.dead = true;
|
||
this.overflow = null;
|
||
this.headerBuffer = null;
|
||
this.expectBuffer = null;
|
||
this.expectHandler = null;
|
||
this.unfragmentedBufferPool = null;
|
||
this.fragmentedBufferPool = null;
|
||
this.state = null;
|
||
this.currentMessage = null;
|
||
this.onerror = null;
|
||
this.ontext = null;
|
||
this.onbinary = null;
|
||
this.onclose = null;
|
||
this.onping = null;
|
||
this.onpong = null;
|
||
};
|
||
|
||
/**
|
||
* Waits for a certain amount of header bytes to be available, then fires a callback.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.expectHeader = function(length, handler) {
|
||
if (length == 0) {
|
||
handler(null);
|
||
return;
|
||
}
|
||
this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length);
|
||
this.expectHandler = handler;
|
||
var toRead = length;
|
||
while (toRead > 0 && this.overflow.length > 0) {
|
||
var fromOverflow = this.overflow.pop();
|
||
if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead));
|
||
var read = Math.min(fromOverflow.length, toRead);
|
||
fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset);
|
||
this.expectOffset += read;
|
||
toRead -= read;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Waits for a certain amount of data bytes to be available, then fires a callback.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.expectData = function(length, handler) {
|
||
if (length == 0) {
|
||
handler(null);
|
||
return;
|
||
}
|
||
this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation);
|
||
this.expectHandler = handler;
|
||
var toRead = length;
|
||
while (toRead > 0 && this.overflow.length > 0) {
|
||
var fromOverflow = this.overflow.pop();
|
||
if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead));
|
||
var read = Math.min(fromOverflow.length, toRead);
|
||
fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset);
|
||
this.expectOffset += read;
|
||
toRead -= read;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Allocates memory from the buffer pool.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.allocateFromPool = function(length, isFragmented) {
|
||
return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length);
|
||
};
|
||
|
||
/**
|
||
* Start processing a new packet.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.processPacket = function (data) {
|
||
if (this.extensions[PerMessageDeflate.extensionName]) {
|
||
if ((data[0] & 0x30) != 0) {
|
||
this.error('reserved fields (2, 3) must be empty', 1002);
|
||
return;
|
||
}
|
||
} else {
|
||
if ((data[0] & 0x70) != 0) {
|
||
this.error('reserved fields must be empty', 1002);
|
||
return;
|
||
}
|
||
}
|
||
this.state.lastFragment = (data[0] & 0x80) == 0x80;
|
||
this.state.masked = (data[1] & 0x80) == 0x80;
|
||
var compressed = (data[0] & 0x40) == 0x40;
|
||
var opcode = data[0] & 0xf;
|
||
if (opcode === 0) {
|
||
if (compressed) {
|
||
this.error('continuation frame cannot have the Per-message Compressed bits', 1002);
|
||
return;
|
||
}
|
||
// continuation frame
|
||
this.state.fragmentedOperation = true;
|
||
this.state.opcode = this.state.activeFragmentedOperation;
|
||
if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
|
||
this.error('continuation frame cannot follow current opcode', 1002);
|
||
return;
|
||
}
|
||
}
|
||
else {
|
||
if (opcode < 3 && this.state.activeFragmentedOperation != null) {
|
||
this.error('data frames after the initial data frame must have opcode 0', 1002);
|
||
return;
|
||
}
|
||
if (opcode >= 8 && compressed) {
|
||
this.error('control frames cannot have the Per-message Compressed bits', 1002);
|
||
return;
|
||
}
|
||
this.state.compressed = compressed;
|
||
this.state.opcode = opcode;
|
||
if (this.state.lastFragment === false) {
|
||
this.state.fragmentedOperation = true;
|
||
this.state.activeFragmentedOperation = opcode;
|
||
}
|
||
else this.state.fragmentedOperation = false;
|
||
}
|
||
var handler = opcodes[this.state.opcode];
|
||
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002);
|
||
else {
|
||
handler.start.call(this, data);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Endprocessing a packet.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.endPacket = function() {
|
||
if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true);
|
||
else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true);
|
||
this.expectOffset = 0;
|
||
this.expectBuffer = null;
|
||
this.expectHandler = null;
|
||
if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) {
|
||
// end current fragmented operation
|
||
this.state.activeFragmentedOperation = null;
|
||
}
|
||
this.state.lastFragment = false;
|
||
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
|
||
this.state.masked = false;
|
||
this.expectHeader(2, this.processPacket);
|
||
};
|
||
|
||
/**
|
||
* Reset the parser state.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.reset = function() {
|
||
if (this.dead) return;
|
||
this.state = {
|
||
activeFragmentedOperation: null,
|
||
lastFragment: false,
|
||
masked: false,
|
||
opcode: 0,
|
||
fragmentedOperation: false
|
||
};
|
||
this.fragmentedBufferPool.reset(true);
|
||
this.unfragmentedBufferPool.reset(true);
|
||
this.expectOffset = 0;
|
||
this.expectBuffer = null;
|
||
this.expectHandler = null;
|
||
this.overflow = [];
|
||
this.currentMessage = [];
|
||
this.messageHandlers = [];
|
||
};
|
||
|
||
/**
|
||
* Unmask received data.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.unmask = function (mask, buf, binary) {
|
||
if (mask != null && buf != null) bufferUtil.unmask(buf, mask);
|
||
if (binary) return buf;
|
||
return buf != null ? buf.toString('utf8') : '';
|
||
};
|
||
|
||
/**
|
||
* Concatenates a list of buffers.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.concatBuffers = function(buffers) {
|
||
var length = 0;
|
||
for (var i = 0, l = buffers.length; i < l; ++i) length += buffers[i].length;
|
||
var mergedBuffer = new Buffer(length);
|
||
bufferUtil.merge(mergedBuffer, buffers);
|
||
return mergedBuffer;
|
||
};
|
||
|
||
/**
|
||
* Handles an error
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.error = function (reason, protocolErrorCode) {
|
||
this.reset();
|
||
this.onerror(reason, protocolErrorCode);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Execute message handler buffers
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.flush = function() {
|
||
if (this.processing || this.dead) return;
|
||
|
||
var handler = this.messageHandlers.shift();
|
||
if (!handler) return;
|
||
|
||
this.processing = true;
|
||
var self = this;
|
||
|
||
handler(function() {
|
||
self.processing = false;
|
||
self.flush();
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Apply extensions to message
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, callback) {
|
||
var self = this;
|
||
if (compressed) {
|
||
this.extensions[PerMessageDeflate.extensionName].decompress(messageBuffer, fin, function(err, buffer) {
|
||
if (self.dead) return;
|
||
if (err) {
|
||
callback(new Error('invalid compressed data'));
|
||
return;
|
||
}
|
||
callback(null, buffer);
|
||
});
|
||
} else {
|
||
callback(null, messageBuffer);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Buffer utilities
|
||
*/
|
||
|
||
function readUInt16BE(start) {
|
||
return (this[start]<<8) +
|
||
this[start+1];
|
||
}
|
||
|
||
function readUInt32BE(start) {
|
||
return (this[start]<<24) +
|
||
(this[start+1]<<16) +
|
||
(this[start+2]<<8) +
|
||
this[start+3];
|
||
}
|
||
|
||
function fastCopy(length, srcBuffer, dstBuffer, dstOffset) {
|
||
switch (length) {
|
||
default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break;
|
||
case 16: dstBuffer[dstOffset+15] = srcBuffer[15];
|
||
case 15: dstBuffer[dstOffset+14] = srcBuffer[14];
|
||
case 14: dstBuffer[dstOffset+13] = srcBuffer[13];
|
||
case 13: dstBuffer[dstOffset+12] = srcBuffer[12];
|
||
case 12: dstBuffer[dstOffset+11] = srcBuffer[11];
|
||
case 11: dstBuffer[dstOffset+10] = srcBuffer[10];
|
||
case 10: dstBuffer[dstOffset+9] = srcBuffer[9];
|
||
case 9: dstBuffer[dstOffset+8] = srcBuffer[8];
|
||
case 8: dstBuffer[dstOffset+7] = srcBuffer[7];
|
||
case 7: dstBuffer[dstOffset+6] = srcBuffer[6];
|
||
case 6: dstBuffer[dstOffset+5] = srcBuffer[5];
|
||
case 5: dstBuffer[dstOffset+4] = srcBuffer[4];
|
||
case 4: dstBuffer[dstOffset+3] = srcBuffer[3];
|
||
case 3: dstBuffer[dstOffset+2] = srcBuffer[2];
|
||
case 2: dstBuffer[dstOffset+1] = srcBuffer[1];
|
||
case 1: dstBuffer[dstOffset] = srcBuffer[0];
|
||
}
|
||
}
|
||
|
||
function clone(obj) {
|
||
var cloned = {};
|
||
for (var k in obj) {
|
||
if (obj.hasOwnProperty(k)) {
|
||
cloned[k] = obj[k];
|
||
}
|
||
}
|
||
return cloned;
|
||
}
|
||
|
||
/**
|
||
* Opcode handlers
|
||
*/
|
||
|
||
var opcodes = {
|
||
// text
|
||
'1': {
|
||
start: function(data) {
|
||
var self = this;
|
||
// decode length
|
||
var firstLength = data[1] & 0x7f;
|
||
if (firstLength < 126) {
|
||
opcodes['1'].getData.call(self, firstLength);
|
||
}
|
||
else if (firstLength == 126) {
|
||
self.expectHeader(2, function(data) {
|
||
opcodes['1'].getData.call(self, readUInt16BE.call(data, 0));
|
||
});
|
||
}
|
||
else if (firstLength == 127) {
|
||
self.expectHeader(8, function(data) {
|
||
if (readUInt32BE.call(data, 0) != 0) {
|
||
self.error('packets with length spanning more than 32 bit is currently not supported', 1008);
|
||
return;
|
||
}
|
||
opcodes['1'].getData.call(self, readUInt32BE.call(data, 4));
|
||
});
|
||
}
|
||
},
|
||
getData: function(length) {
|
||
var self = this;
|
||
if (self.state.masked) {
|
||
self.expectHeader(4, function(data) {
|
||
var mask = data;
|
||
self.expectData(length, function(data) {
|
||
opcodes['1'].finish.call(self, mask, data);
|
||
});
|
||
});
|
||
}
|
||
else {
|
||
self.expectData(length, function(data) {
|
||
opcodes['1'].finish.call(self, null, data);
|
||
});
|
||
}
|
||
},
|
||
finish: function(mask, data) {
|
||
var self = this;
|
||
var packet = this.unmask(mask, data, true) || new Buffer(0);
|
||
var state = clone(this.state);
|
||
this.messageHandlers.push(function(callback) {
|
||
self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) {
|
||
if (err) return self.error(err.message, 1007);
|
||
if (buffer != null) self.currentMessage.push(buffer);
|
||
|
||
if (state.lastFragment) {
|
||
var messageBuffer = self.concatBuffers(self.currentMessage);
|
||
self.currentMessage = [];
|
||
if (!Validation.isValidUTF8(messageBuffer)) {
|
||
self.error('invalid utf8 sequence', 1007);
|
||
return;
|
||
}
|
||
self.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer});
|
||
}
|
||
callback();
|
||
});
|
||
});
|
||
this.flush();
|
||
this.endPacket();
|
||
}
|
||
},
|
||
// binary
|
||
'2': {
|
||
start: function(data) {
|
||
var self = this;
|
||
// decode length
|
||
var firstLength = data[1] & 0x7f;
|
||
if (firstLength < 126) {
|
||
opcodes['2'].getData.call(self, firstLength);
|
||
}
|
||
else if (firstLength == 126) {
|
||
self.expectHeader(2, function(data) {
|
||
opcodes['2'].getData.call(self, readUInt16BE.call(data, 0));
|
||
});
|
||
}
|
||
else if (firstLength == 127) {
|
||
self.expectHeader(8, function(data) {
|
||
if (readUInt32BE.call(data, 0) != 0) {
|
||
self.error('packets with length spanning more than 32 bit is currently not supported', 1008);
|
||
return;
|
||
}
|
||
opcodes['2'].getData.call(self, readUInt32BE.call(data, 4, true));
|
||
});
|
||
}
|
||
},
|
||
getData: function(length) {
|
||
var self = this;
|
||
if (self.state.masked) {
|
||
self.expectHeader(4, function(data) {
|
||
var mask = data;
|
||
self.expectData(length, function(data) {
|
||
opcodes['2'].finish.call(self, mask, data);
|
||
});
|
||
});
|
||
}
|
||
else {
|
||
self.expectData(length, function(data) {
|
||
opcodes['2'].finish.call(self, null, data);
|
||
});
|
||
}
|
||
},
|
||
finish: function(mask, data) {
|
||
var self = this;
|
||
var packet = this.unmask(mask, data, true) || new Buffer(0);
|
||
var state = clone(this.state);
|
||
this.messageHandlers.push(function(callback) {
|
||
self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) {
|
||
if (err) return self.error(err.message, 1007);
|
||
if (buffer != null) self.currentMessage.push(buffer);
|
||
if (state.lastFragment) {
|
||
var messageBuffer = self.concatBuffers(self.currentMessage);
|
||
self.currentMessage = [];
|
||
self.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer});
|
||
}
|
||
callback();
|
||
});
|
||
});
|
||
this.flush();
|
||
this.endPacket();
|
||
}
|
||
},
|
||
// close
|
||
'8': {
|
||
start: function(data) {
|
||
var self = this;
|
||
if (self.state.lastFragment == false) {
|
||
self.error('fragmented close is not supported', 1002);
|
||
return;
|
||
}
|
||
|
||
// decode length
|
||
var firstLength = data[1] & 0x7f;
|
||
if (firstLength < 126) {
|
||
opcodes['8'].getData.call(self, firstLength);
|
||
}
|
||
else {
|
||
self.error('control frames cannot have more than 125 bytes of data', 1002);
|
||
}
|
||
},
|
||
getData: function(length) {
|
||
var self = this;
|
||
if (self.state.masked) {
|
||
self.expectHeader(4, function(data) {
|
||
var mask = data;
|
||
self.expectData(length, function(data) {
|
||
opcodes['8'].finish.call(self, mask, data);
|
||
});
|
||
});
|
||
}
|
||
else {
|
||
self.expectData(length, function(data) {
|
||
opcodes['8'].finish.call(self, null, data);
|
||
});
|
||
}
|
||
},
|
||
finish: function(mask, data) {
|
||
var self = this;
|
||
data = self.unmask(mask, data, true);
|
||
|
||
var state = clone(this.state);
|
||
this.messageHandlers.push(function() {
|
||
if (data && data.length == 1) {
|
||
self.error('close packets with data must be at least two bytes long', 1002);
|
||
return;
|
||
}
|
||
var code = data && data.length > 1 ? readUInt16BE.call(data, 0) : 1000;
|
||
if (!ErrorCodes.isValidErrorCode(code)) {
|
||
self.error('invalid error code', 1002);
|
||
return;
|
||
}
|
||
var message = '';
|
||
if (data && data.length > 2) {
|
||
var messageBuffer = data.slice(2);
|
||
if (!Validation.isValidUTF8(messageBuffer)) {
|
||
self.error('invalid utf8 sequence', 1007);
|
||
return;
|
||
}
|
||
message = messageBuffer.toString('utf8');
|
||
}
|
||
self.onclose(code, message, {masked: state.masked});
|
||
self.reset();
|
||
});
|
||
this.flush();
|
||
},
|
||
},
|
||
// ping
|
||
'9': {
|
||
start: function(data) {
|
||
var self = this;
|
||
if (self.state.lastFragment == false) {
|
||
self.error('fragmented ping is not supported', 1002);
|
||
return;
|
||
}
|
||
|
||
// decode length
|
||
var firstLength = data[1] & 0x7f;
|
||
if (firstLength < 126) {
|
||
opcodes['9'].getData.call(self, firstLength);
|
||
}
|
||
else {
|
||
self.error('control frames cannot have more than 125 bytes of data', 1002);
|
||
}
|
||
},
|
||
getData: function(length) {
|
||
var self = this;
|
||
if (self.state.masked) {
|
||
self.expectHeader(4, function(data) {
|
||
var mask = data;
|
||
self.expectData(length, function(data) {
|
||
opcodes['9'].finish.call(self, mask, data);
|
||
});
|
||
});
|
||
}
|
||
else {
|
||
self.expectData(length, function(data) {
|
||
opcodes['9'].finish.call(self, null, data);
|
||
});
|
||
}
|
||
},
|
||
finish: function(mask, data) {
|
||
var self = this;
|
||
data = this.unmask(mask, data, true);
|
||
var state = clone(this.state);
|
||
this.messageHandlers.push(function(callback) {
|
||
self.onping(data, {masked: state.masked, binary: true});
|
||
callback();
|
||
});
|
||
this.flush();
|
||
this.endPacket();
|
||
}
|
||
},
|
||
// pong
|
||
'10': {
|
||
start: function(data) {
|
||
var self = this;
|
||
if (self.state.lastFragment == false) {
|
||
self.error('fragmented pong is not supported', 1002);
|
||
return;
|
||
}
|
||
|
||
// decode length
|
||
var firstLength = data[1] & 0x7f;
|
||
if (firstLength < 126) {
|
||
opcodes['10'].getData.call(self, firstLength);
|
||
}
|
||
else {
|
||
self.error('control frames cannot have more than 125 bytes of data', 1002);
|
||
}
|
||
},
|
||
getData: function(length) {
|
||
var self = this;
|
||
if (this.state.masked) {
|
||
this.expectHeader(4, function(data) {
|
||
var mask = data;
|
||
self.expectData(length, function(data) {
|
||
opcodes['10'].finish.call(self, mask, data);
|
||
});
|
||
});
|
||
}
|
||
else {
|
||
this.expectData(length, function(data) {
|
||
opcodes['10'].finish.call(self, null, data);
|
||
});
|
||
}
|
||
},
|
||
finish: function(mask, data) {
|
||
var self = this;
|
||
data = self.unmask(mask, data, true);
|
||
var state = clone(this.state);
|
||
this.messageHandlers.push(function(callback) {
|
||
self.onpong(data, {masked: state.masked, binary: true});
|
||
callback();
|
||
});
|
||
this.flush();
|
||
this.endPacket();
|
||
}
|
||
}
|
||
}
|
||
|
||
}).call(this,_dereq_("buffer").Buffer)
|
||
},{"./BufferPool":31,"./BufferUtil":33,"./ErrorCodes":34,"./PerMessageDeflate":36,"./Validation":42,"buffer":undefined,"util":undefined}],39:[function(_dereq_,module,exports){
|
||
(function (Buffer){
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
var events = _dereq_('events')
|
||
, util = _dereq_('util')
|
||
, EventEmitter = events.EventEmitter;
|
||
|
||
/**
|
||
* Hixie Sender implementation
|
||
*/
|
||
|
||
function Sender(socket) {
|
||
if (this instanceof Sender === false) {
|
||
throw new TypeError("Classes can't be function-called");
|
||
}
|
||
|
||
events.EventEmitter.call(this);
|
||
|
||
this.socket = socket;
|
||
this.continuationFrame = false;
|
||
this.isClosed = false;
|
||
}
|
||
|
||
module.exports = Sender;
|
||
|
||
/**
|
||
* Inherits from EventEmitter.
|
||
*/
|
||
|
||
util.inherits(Sender, events.EventEmitter);
|
||
|
||
/**
|
||
* Frames and writes data.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Sender.prototype.send = function(data, options, cb) {
|
||
if (this.isClosed) return;
|
||
|
||
var isString = typeof data == 'string'
|
||
, length = isString ? Buffer.byteLength(data) : data.length
|
||
, lengthbytes = (length > 127) ? 2 : 1 // assume less than 2**14 bytes
|
||
, writeStartMarker = this.continuationFrame == false
|
||
, writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin)
|
||
, buffer = new Buffer((writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0) + length + ((writeEndMarker && !(options && options.binary)) ? 1 : 0))
|
||
, offset = writeStartMarker ? 1 : 0;
|
||
|
||
if (writeStartMarker) {
|
||
if (options && options.binary) {
|
||
buffer.write('\x80', 'binary');
|
||
// assume length less than 2**14 bytes
|
||
if (lengthbytes > 1)
|
||
buffer.write(String.fromCharCode(128+length/128), offset++, 'binary');
|
||
buffer.write(String.fromCharCode(length&0x7f), offset++, 'binary');
|
||
} else
|
||
buffer.write('\x00', 'binary');
|
||
}
|
||
|
||
if (isString) buffer.write(data, offset, 'utf8');
|
||
else data.copy(buffer, offset, 0);
|
||
|
||
if (writeEndMarker) {
|
||
if (options && options.binary) {
|
||
// sending binary, not writing end marker
|
||
} else
|
||
buffer.write('\xff', offset + length, 'binary');
|
||
this.continuationFrame = false;
|
||
}
|
||
else this.continuationFrame = true;
|
||
|
||
try {
|
||
this.socket.write(buffer, 'binary', cb);
|
||
} catch (e) {
|
||
this.error(e.toString());
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Sends a close instruction to the remote party.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Sender.prototype.close = function(code, data, mask, cb) {
|
||
if (this.isClosed) return;
|
||
this.isClosed = true;
|
||
try {
|
||
if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary'));
|
||
this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb);
|
||
} catch (e) {
|
||
this.error(e.toString());
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Sends a ping message to the remote party. Not available for hixie.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Sender.prototype.ping = function(data, options) {};
|
||
|
||
/**
|
||
* Sends a pong message to the remote party. Not available for hixie.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Sender.prototype.pong = function(data, options) {};
|
||
|
||
/**
|
||
* Handles an error
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Sender.prototype.error = function (reason) {
|
||
this.emit('error', reason);
|
||
return this;
|
||
};
|
||
|
||
}).call(this,_dereq_("buffer").Buffer)
|
||
},{"buffer":undefined,"events":undefined,"util":undefined}],40:[function(_dereq_,module,exports){
|
||
(function (Buffer){
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
var events = _dereq_('events')
|
||
, util = _dereq_('util')
|
||
, EventEmitter = events.EventEmitter
|
||
, ErrorCodes = _dereq_('./ErrorCodes')
|
||
, bufferUtil = _dereq_('./BufferUtil').BufferUtil
|
||
, PerMessageDeflate = _dereq_('./PerMessageDeflate');
|
||
|
||
/**
|
||
* HyBi Sender implementation
|
||
*/
|
||
|
||
function Sender(socket, extensions) {
|
||
if (this instanceof Sender === false) {
|
||
throw new TypeError("Classes can't be function-called");
|
||
}
|
||
|
||
events.EventEmitter.call(this);
|
||
|
||
this._socket = socket;
|
||
this.extensions = extensions || {};
|
||
this.firstFragment = true;
|
||
this.compress = false;
|
||
this.messageHandlers = [];
|
||
this.processing = false;
|
||
}
|
||
|
||
/**
|
||
* Inherits from EventEmitter.
|
||
*/
|
||
|
||
util.inherits(Sender, events.EventEmitter);
|
||
|
||
/**
|
||
* Sends a close instruction to the remote party.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Sender.prototype.close = function(code, data, mask, cb) {
|
||
if (typeof code !== 'undefined') {
|
||
if (typeof code !== 'number' ||
|
||
!ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number');
|
||
}
|
||
code = code || 1000;
|
||
var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0));
|
||
writeUInt16BE.call(dataBuffer, code, 0);
|
||
if (dataBuffer.length > 2) dataBuffer.write(data, 2);
|
||
|
||
var self = this;
|
||
this.messageHandlers.push(function(callback) {
|
||
self.frameAndSend(0x8, dataBuffer, true, mask);
|
||
callback();
|
||
if (typeof cb == 'function') cb();
|
||
});
|
||
this.flush();
|
||
};
|
||
|
||
/**
|
||
* Sends a ping message to the remote party.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Sender.prototype.ping = function(data, options) {
|
||
var mask = options && options.mask;
|
||
var self = this;
|
||
this.messageHandlers.push(function(callback) {
|
||
self.frameAndSend(0x9, data || '', true, mask);
|
||
callback();
|
||
});
|
||
this.flush();
|
||
};
|
||
|
||
/**
|
||
* Sends a pong message to the remote party.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Sender.prototype.pong = function(data, options) {
|
||
var mask = options && options.mask;
|
||
var self = this;
|
||
this.messageHandlers.push(function(callback) {
|
||
self.frameAndSend(0xa, data || '', true, mask);
|
||
callback();
|
||
});
|
||
this.flush();
|
||
};
|
||
|
||
/**
|
||
* Sends text or binary data to the remote party.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
Sender.prototype.send = function(data, options, cb) {
|
||
var finalFragment = options && options.fin === false ? false : true;
|
||
var mask = options && options.mask;
|
||
var compress = options && options.compress;
|
||
var opcode = options && options.binary ? 2 : 1;
|
||
if (this.firstFragment === false) {
|
||
opcode = 0;
|
||
compress = false;
|
||
} else {
|
||
this.firstFragment = false;
|
||
this.compress = compress;
|
||
}
|
||
if (finalFragment) this.firstFragment = true
|
||
|
||
var compressFragment = this.compress;
|
||
|
||
var self = this;
|
||
this.messageHandlers.push(function(callback) {
|
||
self.applyExtensions(data, finalFragment, compressFragment, function(err, data) {
|
||
if (err) {
|
||
if (typeof cb == 'function') cb(err);
|
||
else self.emit('error', err);
|
||
return;
|
||
}
|
||
self.frameAndSend(opcode, data, finalFragment, mask, compress, cb);
|
||
callback();
|
||
});
|
||
});
|
||
this.flush();
|
||
};
|
||
|
||
/**
|
||
* Frames and sends a piece of data according to the HyBi WebSocket protocol.
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, compressed, cb) {
|
||
var canModifyData = false;
|
||
|
||
if (!data) {
|
||
try {
|
||
this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb);
|
||
}
|
||
catch (e) {
|
||
if (typeof cb == 'function') cb(e);
|
||
else this.emit('error', e);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!Buffer.isBuffer(data)) {
|
||
canModifyData = true;
|
||
if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) {
|
||
data = getArrayBuffer(data);
|
||
} else {
|
||
data = new Buffer(data);
|
||
}
|
||
}
|
||
|
||
var dataLength = data.length
|
||
, dataOffset = maskData ? 6 : 2
|
||
, secondByte = dataLength;
|
||
|
||
if (dataLength >= 65536) {
|
||
dataOffset += 8;
|
||
secondByte = 127;
|
||
}
|
||
else if (dataLength > 125) {
|
||
dataOffset += 2;
|
||
secondByte = 126;
|
||
}
|
||
|
||
var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData);
|
||
var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset;
|
||
var outputBuffer = new Buffer(totalLength);
|
||
outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode;
|
||
if (compressed) outputBuffer[0] |= 0x40;
|
||
|
||
switch (secondByte) {
|
||
case 126:
|
||
writeUInt16BE.call(outputBuffer, dataLength, 2);
|
||
break;
|
||
case 127:
|
||
writeUInt32BE.call(outputBuffer, 0, 2);
|
||
writeUInt32BE.call(outputBuffer, dataLength, 6);
|
||
}
|
||
|
||
if (maskData) {
|
||
outputBuffer[1] = secondByte | 0x80;
|
||
var mask = this._randomMask || (this._randomMask = getRandomMask());
|
||
outputBuffer[dataOffset - 4] = mask[0];
|
||
outputBuffer[dataOffset - 3] = mask[1];
|
||
outputBuffer[dataOffset - 2] = mask[2];
|
||
outputBuffer[dataOffset - 1] = mask[3];
|
||
if (mergeBuffers) {
|
||
bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength);
|
||
try {
|
||
this._socket.write(outputBuffer, 'binary', cb);
|
||
}
|
||
catch (e) {
|
||
if (typeof cb == 'function') cb(e);
|
||
else this.emit('error', e);
|
||
}
|
||
}
|
||
else {
|
||
bufferUtil.mask(data, mask, data, 0, dataLength);
|
||
try {
|
||
this._socket.write(outputBuffer, 'binary');
|
||
this._socket.write(data, 'binary', cb);
|
||
}
|
||
catch (e) {
|
||
if (typeof cb == 'function') cb(e);
|
||
else this.emit('error', e);
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
outputBuffer[1] = secondByte;
|
||
if (mergeBuffers) {
|
||
data.copy(outputBuffer, dataOffset);
|
||
try {
|
||
this._socket.write(outputBuffer, 'binary', cb);
|
||
}
|
||
catch (e) {
|
||
if (typeof cb == 'function') cb(e);
|
||
else this.emit('error', e);
|
||
}
|
||
}
|
||
else {
|
||
try {
|
||
this._socket.write(outputBuffer, 'binary');
|
||
this._socket.write(data, 'binary', cb);
|
||
}
|
||
catch (e) {
|
||
if (typeof cb == 'function') cb(e);
|
||
else this.emit('error', e);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Execute message handler buffers
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Sender.prototype.flush = function() {
|
||
if (this.processing) return;
|
||
|
||
var handler = this.messageHandlers.shift();
|
||
if (!handler) return;
|
||
|
||
this.processing = true;
|
||
|
||
var self = this;
|
||
|
||
handler(function() {
|
||
self.processing = false;
|
||
self.flush();
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Apply extensions to message
|
||
*
|
||
* @api private
|
||
*/
|
||
|
||
Sender.prototype.applyExtensions = function(data, fin, compress, callback) {
|
||
if (compress && data) {
|
||
if ((data.buffer || data) instanceof ArrayBuffer) {
|
||
data = getArrayBuffer(data);
|
||
}
|
||
this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback);
|
||
} else {
|
||
callback(null, data);
|
||
}
|
||
};
|
||
|
||
module.exports = Sender;
|
||
|
||
function writeUInt16BE(value, offset) {
|
||
this[offset] = (value & 0xff00)>>8;
|
||
this[offset+1] = value & 0xff;
|
||
}
|
||
|
||
function writeUInt32BE(value, offset) {
|
||
this[offset] = (value & 0xff000000)>>24;
|
||
this[offset+1] = (value & 0xff0000)>>16;
|
||
this[offset+2] = (value & 0xff00)>>8;
|
||
this[offset+3] = value & 0xff;
|
||
}
|
||
|
||
function getArrayBuffer(data) {
|
||
// data is either an ArrayBuffer or ArrayBufferView.
|
||
var array = new Uint8Array(data.buffer || data)
|
||
, l = data.byteLength || data.length
|
||
, o = data.byteOffset || 0
|
||
, buffer = new Buffer(l);
|
||
for (var i = 0; i < l; ++i) {
|
||
buffer[i] = array[o+i];
|
||
}
|
||
return buffer;
|
||
}
|
||
|
||
function getRandomMask() {
|
||
return new Buffer([
|
||
~~(Math.random() * 255),
|
||
~~(Math.random() * 255),
|
||
~~(Math.random() * 255),
|
||
~~(Math.random() * 255)
|
||
]);
|
||
}
|
||
|
||
}).call(this,_dereq_("buffer").Buffer)
|
||
},{"./BufferUtil":33,"./ErrorCodes":34,"./PerMessageDeflate":36,"buffer":undefined,"events":undefined,"util":undefined}],41:[function(_dereq_,module,exports){
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
module.exports.Validation = {
|
||
isValidUTF8: function(buffer) {
|
||
return true;
|
||
}
|
||
};
|
||
|
||
|
||
},{}],42:[function(_dereq_,module,exports){
|
||
'use strict';
|
||
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
try {
|
||
module.exports = _dereq_('utf-8-validate');
|
||
} catch (e) {
|
||
module.exports = _dereq_('./Validation.fallback');
|
||
}
|
||
|
||
},{"./Validation.fallback":41,"utf-8-validate":undefined}],43:[function(_dereq_,module,exports){
|
||
(function (process,Buffer){
|
||
'use strict';
|
||
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
var url = _dereq_('url')
|
||
, util = _dereq_('util')
|
||
, http = _dereq_('http')
|
||
, https = _dereq_('https')
|
||
, crypto = _dereq_('crypto')
|
||
, stream = _dereq_('stream')
|
||
, Ultron = _dereq_('ultron')
|
||
, Options = _dereq_('options')
|
||
, Sender = _dereq_('./Sender')
|
||
, Receiver = _dereq_('./Receiver')
|
||
, SenderHixie = _dereq_('./Sender.hixie')
|
||
, ReceiverHixie = _dereq_('./Receiver.hixie')
|
||
, Extensions = _dereq_('./Extensions')
|
||
, PerMessageDeflate = _dereq_('./PerMessageDeflate')
|
||
, EventEmitter = _dereq_('events').EventEmitter;
|
||
|
||
/**
|
||
* Constants
|
||
*/
|
||
|
||
// Default protocol version
|
||
|
||
var protocolVersion = 13;
|
||
|
||
// Close timeout
|
||
|
||
var closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly
|
||
|
||
/**
|
||
* WebSocket implementation
|
||
*
|
||
* @constructor
|
||
* @param {String} address Connection address.
|
||
* @param {String|Array} protocols WebSocket protocols.
|
||
* @param {Object} options Additional connection options.
|
||
* @api public
|
||
*/
|
||
function WebSocket(address, protocols, options) {
|
||
if (this instanceof WebSocket === false) {
|
||
return new WebSocket(address, protocols, options);
|
||
}
|
||
|
||
EventEmitter.call(this);
|
||
|
||
if (protocols && !Array.isArray(protocols) && 'object' === typeof protocols) {
|
||
// accept the "options" Object as the 2nd argument
|
||
options = protocols;
|
||
protocols = null;
|
||
}
|
||
|
||
if ('string' === typeof protocols) {
|
||
protocols = [ protocols ];
|
||
}
|
||
|
||
if (!Array.isArray(protocols)) {
|
||
protocols = [];
|
||
}
|
||
|
||
this._socket = null;
|
||
this._ultron = null;
|
||
this._closeReceived = false;
|
||
this.bytesReceived = 0;
|
||
this.readyState = null;
|
||
this.supports = {};
|
||
this.extensions = {};
|
||
|
||
if (Array.isArray(address)) {
|
||
initAsServerClient.apply(this, address.concat(options));
|
||
} else {
|
||
initAsClient.apply(this, [address, protocols, options]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Inherits from EventEmitter.
|
||
*/
|
||
util.inherits(WebSocket, EventEmitter);
|
||
|
||
/**
|
||
* Ready States
|
||
*/
|
||
["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function each(state, index) {
|
||
WebSocket.prototype[state] = WebSocket[state] = index;
|
||
});
|
||
|
||
/**
|
||
* Gracefully closes the connection, after sending a description message to the server
|
||
*
|
||
* @param {Object} data to be sent to the server
|
||
* @api public
|
||
*/
|
||
WebSocket.prototype.close = function close(code, data) {
|
||
if (this.readyState === WebSocket.CLOSED) return;
|
||
|
||
if (this.readyState === WebSocket.CONNECTING) {
|
||
this.readyState = WebSocket.CLOSED;
|
||
return;
|
||
}
|
||
|
||
if (this.readyState === WebSocket.CLOSING) {
|
||
if (this._closeReceived && this._isServer) {
|
||
this.terminate();
|
||
}
|
||
return;
|
||
}
|
||
|
||
var self = this;
|
||
try {
|
||
this.readyState = WebSocket.CLOSING;
|
||
this._closeCode = code;
|
||
this._closeMessage = data;
|
||
var mask = !this._isServer;
|
||
this._sender.close(code, data, mask, function(err) {
|
||
if (err) self.emit('error', err);
|
||
|
||
if (self._closeReceived && self._isServer) {
|
||
self.terminate();
|
||
} else {
|
||
// ensure that the connection is cleaned up even when no response of closing handshake.
|
||
clearTimeout(self._closeTimer);
|
||
self._closeTimer = setTimeout(cleanupWebsocketResources.bind(self, true), closeTimeout);
|
||
}
|
||
});
|
||
} catch (e) {
|
||
this.emit('error', e);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Pause the client stream
|
||
*
|
||
* @api public
|
||
*/
|
||
WebSocket.prototype.pause = function pauser() {
|
||
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
|
||
|
||
return this._socket.pause();
|
||
};
|
||
|
||
/**
|
||
* Sends a ping
|
||
*
|
||
* @param {Object} data to be sent to the server
|
||
* @param {Object} Members - mask: boolean, binary: boolean
|
||
* @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open
|
||
* @api public
|
||
*/
|
||
WebSocket.prototype.ping = function ping(data, options, dontFailWhenClosed) {
|
||
if (this.readyState !== WebSocket.OPEN) {
|
||
if (dontFailWhenClosed === true) return;
|
||
throw new Error('not opened');
|
||
}
|
||
|
||
options = options || {};
|
||
|
||
if (typeof options.mask === 'undefined') options.mask = !this._isServer;
|
||
|
||
this._sender.ping(data, options);
|
||
};
|
||
|
||
/**
|
||
* Sends a pong
|
||
*
|
||
* @param {Object} data to be sent to the server
|
||
* @param {Object} Members - mask: boolean, binary: boolean
|
||
* @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open
|
||
* @api public
|
||
*/
|
||
WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) {
|
||
if (this.readyState !== WebSocket.OPEN) {
|
||
if (dontFailWhenClosed === true) return;
|
||
throw new Error('not opened');
|
||
}
|
||
|
||
options = options || {};
|
||
|
||
if (typeof options.mask === 'undefined') options.mask = !this._isServer;
|
||
|
||
this._sender.pong(data, options);
|
||
};
|
||
|
||
/**
|
||
* Resume the client stream
|
||
*
|
||
* @api public
|
||
*/
|
||
WebSocket.prototype.resume = function resume() {
|
||
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
|
||
|
||
return this._socket.resume();
|
||
};
|
||
|
||
/**
|
||
* Sends a piece of data
|
||
*
|
||
* @param {Object} data to be sent to the server
|
||
* @param {Object} Members - mask: boolean, binary: boolean, compress: boolean
|
||
* @param {function} Optional callback which is executed after the send completes
|
||
* @api public
|
||
*/
|
||
|
||
WebSocket.prototype.send = function send(data, options, cb) {
|
||
if (typeof options === 'function') {
|
||
cb = options;
|
||
options = {};
|
||
}
|
||
|
||
if (this.readyState !== WebSocket.OPEN) {
|
||
if (typeof cb === 'function') cb(new Error('not opened'));
|
||
else throw new Error('not opened');
|
||
return;
|
||
}
|
||
|
||
if (!data) data = '';
|
||
if (this._queue) {
|
||
var self = this;
|
||
this._queue.push(function() { self.send(data, options, cb); });
|
||
return;
|
||
}
|
||
|
||
options = options || {};
|
||
options.fin = true;
|
||
|
||
if (typeof options.binary === 'undefined') {
|
||
options.binary = (data instanceof ArrayBuffer || data instanceof Buffer ||
|
||
data instanceof Uint8Array ||
|
||
data instanceof Uint16Array ||
|
||
data instanceof Uint32Array ||
|
||
data instanceof Int8Array ||
|
||
data instanceof Int16Array ||
|
||
data instanceof Int32Array ||
|
||
data instanceof Float32Array ||
|
||
data instanceof Float64Array);
|
||
}
|
||
|
||
if (typeof options.mask === 'undefined') options.mask = !this._isServer;
|
||
if (typeof options.compress === 'undefined') options.compress = true;
|
||
if (!this.extensions[PerMessageDeflate.extensionName]) {
|
||
options.compress = false;
|
||
}
|
||
|
||
var readable = typeof stream.Readable === 'function'
|
||
? stream.Readable
|
||
: stream.Stream;
|
||
|
||
if (data instanceof readable) {
|
||
startQueue(this);
|
||
var self = this;
|
||
|
||
sendStream(this, data, options, function send(error) {
|
||
process.nextTick(function tock() {
|
||
executeQueueSends(self);
|
||
});
|
||
|
||
if (typeof cb === 'function') cb(error);
|
||
});
|
||
} else {
|
||
this._sender.send(data, options, cb);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Streams data through calls to a user supplied function
|
||
*
|
||
* @param {Object} Members - mask: boolean, binary: boolean, compress: boolean
|
||
* @param {function} 'function (error, send)' which is executed on successive ticks of which send is 'function (data, final)'.
|
||
* @api public
|
||
*/
|
||
WebSocket.prototype.stream = function stream(options, cb) {
|
||
if (typeof options === 'function') {
|
||
cb = options;
|
||
options = {};
|
||
}
|
||
|
||
var self = this;
|
||
|
||
if (typeof cb !== 'function') throw new Error('callback must be provided');
|
||
|
||
if (this.readyState !== WebSocket.OPEN) {
|
||
if (typeof cb === 'function') cb(new Error('not opened'));
|
||
else throw new Error('not opened');
|
||
return;
|
||
}
|
||
|
||
if (this._queue) {
|
||
this._queue.push(function () { self.stream(options, cb); });
|
||
return;
|
||
}
|
||
|
||
options = options || {};
|
||
|
||
if (typeof options.mask === 'undefined') options.mask = !this._isServer;
|
||
if (typeof options.compress === 'undefined') options.compress = true;
|
||
if (!this.extensions[PerMessageDeflate.extensionName]) {
|
||
options.compress = false;
|
||
}
|
||
|
||
startQueue(this);
|
||
|
||
function send(data, final) {
|
||
try {
|
||
if (self.readyState !== WebSocket.OPEN) throw new Error('not opened');
|
||
options.fin = final === true;
|
||
self._sender.send(data, options);
|
||
if (!final) process.nextTick(cb.bind(null, null, send));
|
||
else executeQueueSends(self);
|
||
} catch (e) {
|
||
if (typeof cb === 'function') cb(e);
|
||
else {
|
||
delete self._queue;
|
||
self.emit('error', e);
|
||
}
|
||
}
|
||
}
|
||
|
||
process.nextTick(cb.bind(null, null, send));
|
||
};
|
||
|
||
/**
|
||
* Immediately shuts down the connection
|
||
*
|
||
* @api public
|
||
*/
|
||
WebSocket.prototype.terminate = function terminate() {
|
||
if (this.readyState === WebSocket.CLOSED) return;
|
||
|
||
if (this._socket) {
|
||
this.readyState = WebSocket.CLOSING;
|
||
|
||
// End the connection
|
||
try { this._socket.end(); }
|
||
catch (e) {
|
||
// Socket error during end() call, so just destroy it right now
|
||
cleanupWebsocketResources.call(this, true);
|
||
return;
|
||
}
|
||
|
||
// Add a timeout to ensure that the connection is completely
|
||
// cleaned up within 30 seconds, even if the clean close procedure
|
||
// fails for whatever reason
|
||
// First cleanup any pre-existing timeout from an earlier "terminate" call,
|
||
// if one exists. Otherwise terminate calls in quick succession will leak timeouts
|
||
// and hold the program open for `closeTimout` time.
|
||
if (this._closeTimer) { clearTimeout(this._closeTimer); }
|
||
this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout);
|
||
} else if (this.readyState === WebSocket.CONNECTING) {
|
||
cleanupWebsocketResources.call(this, true);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Expose bufferedAmount
|
||
*
|
||
* @api public
|
||
*/
|
||
Object.defineProperty(WebSocket.prototype, 'bufferedAmount', {
|
||
get: function get() {
|
||
var amount = 0;
|
||
if (this._socket) {
|
||
amount = this._socket.bufferSize || 0;
|
||
}
|
||
return amount;
|
||
}
|
||
});
|
||
|
||
/**
|
||
* Emulates the W3C Browser based WebSocket interface using function members.
|
||
*
|
||
* @see http://dev.w3.org/html5/websockets/#the-websocket-interface
|
||
* @api public
|
||
*/
|
||
['open', 'error', 'close', 'message'].forEach(function(method) {
|
||
Object.defineProperty(WebSocket.prototype, 'on' + method, {
|
||
/**
|
||
* Returns the current listener
|
||
*
|
||
* @returns {Mixed} the set function or undefined
|
||
* @api public
|
||
*/
|
||
get: function get() {
|
||
var listener = this.listeners(method)[0];
|
||
return listener ? (listener._listener ? listener._listener : listener) : undefined;
|
||
},
|
||
|
||
/**
|
||
* Start listening for events
|
||
*
|
||
* @param {Function} listener the listener
|
||
* @returns {Mixed} the set function or undefined
|
||
* @api public
|
||
*/
|
||
set: function set(listener) {
|
||
this.removeAllListeners(method);
|
||
this.addEventListener(method, listener);
|
||
}
|
||
});
|
||
});
|
||
|
||
/**
|
||
* Emulates the W3C Browser based WebSocket interface using addEventListener.
|
||
*
|
||
* @see https://developer.mozilla.org/en/DOM/element.addEventListener
|
||
* @see http://dev.w3.org/html5/websockets/#the-websocket-interface
|
||
* @api public
|
||
*/
|
||
WebSocket.prototype.addEventListener = function(method, listener) {
|
||
var target = this;
|
||
|
||
function onMessage (data, flags) {
|
||
listener.call(target, new MessageEvent(data, !!flags.binary, target));
|
||
}
|
||
|
||
function onClose (code, message) {
|
||
listener.call(target, new CloseEvent(code, message, target));
|
||
}
|
||
|
||
function onError (event) {
|
||
event.type = 'error';
|
||
event.target = target;
|
||
listener.call(target, event);
|
||
}
|
||
|
||
function onOpen () {
|
||
listener.call(target, new OpenEvent(target));
|
||
}
|
||
|
||
if (typeof listener === 'function') {
|
||
if (method === 'message') {
|
||
// store a reference so we can return the original function from the
|
||
// addEventListener hook
|
||
onMessage._listener = listener;
|
||
this.on(method, onMessage);
|
||
} else if (method === 'close') {
|
||
// store a reference so we can return the original function from the
|
||
// addEventListener hook
|
||
onClose._listener = listener;
|
||
this.on(method, onClose);
|
||
} else if (method === 'error') {
|
||
// store a reference so we can return the original function from the
|
||
// addEventListener hook
|
||
onError._listener = listener;
|
||
this.on(method, onError);
|
||
} else if (method === 'open') {
|
||
// store a reference so we can return the original function from the
|
||
// addEventListener hook
|
||
onOpen._listener = listener;
|
||
this.on(method, onOpen);
|
||
} else {
|
||
this.on(method, listener);
|
||
}
|
||
}
|
||
};
|
||
|
||
module.exports = WebSocket;
|
||
module.exports.buildHostHeader = buildHostHeader
|
||
|
||
/**
|
||
* W3C MessageEvent
|
||
*
|
||
* @see http://www.w3.org/TR/html5/comms.html
|
||
* @constructor
|
||
* @api private
|
||
*/
|
||
function MessageEvent(dataArg, isBinary, target) {
|
||
this.type = 'message';
|
||
this.data = dataArg;
|
||
this.target = target;
|
||
this.binary = isBinary; // non-standard.
|
||
}
|
||
|
||
/**
|
||
* W3C CloseEvent
|
||
*
|
||
* @see http://www.w3.org/TR/html5/comms.html
|
||
* @constructor
|
||
* @api private
|
||
*/
|
||
function CloseEvent(code, reason, target) {
|
||
this.type = 'close';
|
||
this.wasClean = (typeof code === 'undefined' || code === 1000);
|
||
this.code = code;
|
||
this.reason = reason;
|
||
this.target = target;
|
||
}
|
||
|
||
/**
|
||
* W3C OpenEvent
|
||
*
|
||
* @see http://www.w3.org/TR/html5/comms.html
|
||
* @constructor
|
||
* @api private
|
||
*/
|
||
function OpenEvent(target) {
|
||
this.type = 'open';
|
||
this.target = target;
|
||
}
|
||
|
||
// Append port number to Host header, only if specified in the url
|
||
// and non-default
|
||
function buildHostHeader(isSecure, hostname, port) {
|
||
var headerHost = hostname;
|
||
if (hostname) {
|
||
if ((isSecure && (port != 443)) || (!isSecure && (port != 80))){
|
||
headerHost = headerHost + ':' + port;
|
||
}
|
||
}
|
||
return headerHost;
|
||
}
|
||
|
||
/**
|
||
* Entirely private apis,
|
||
* which may or may not be bound to a sepcific WebSocket instance.
|
||
*/
|
||
function initAsServerClient(req, socket, upgradeHead, options) {
|
||
options = new Options({
|
||
protocolVersion: protocolVersion,
|
||
protocol: null,
|
||
extensions: {}
|
||
}).merge(options);
|
||
|
||
// expose state properties
|
||
this.protocol = options.value.protocol;
|
||
this.protocolVersion = options.value.protocolVersion;
|
||
this.extensions = options.value.extensions;
|
||
this.supports.binary = (this.protocolVersion !== 'hixie-76');
|
||
this.upgradeReq = req;
|
||
this.readyState = WebSocket.CONNECTING;
|
||
this._isServer = true;
|
||
|
||
// establish connection
|
||
if (options.value.protocolVersion === 'hixie-76') {
|
||
establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead);
|
||
} else {
|
||
establishConnection.call(this, Receiver, Sender, socket, upgradeHead);
|
||
}
|
||
}
|
||
|
||
function initAsClient(address, protocols, options) {
|
||
options = new Options({
|
||
origin: null,
|
||
protocolVersion: protocolVersion,
|
||
host: null,
|
||
headers: null,
|
||
protocol: protocols.join(','),
|
||
agent: null,
|
||
|
||
// ssl-related options
|
||
pfx: null,
|
||
key: null,
|
||
passphrase: null,
|
||
cert: null,
|
||
ca: null,
|
||
ciphers: null,
|
||
rejectUnauthorized: null,
|
||
perMessageDeflate: true,
|
||
localAddress: null
|
||
}).merge(options);
|
||
|
||
if (options.value.protocolVersion !== 8 && options.value.protocolVersion !== 13) {
|
||
throw new Error('unsupported protocol version');
|
||
}
|
||
|
||
// verify URL and establish http class
|
||
var serverUrl = url.parse(address);
|
||
var isUnixSocket = serverUrl.protocol === 'ws+unix:';
|
||
if (!serverUrl.host && !isUnixSocket) throw new Error('invalid url');
|
||
var isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:';
|
||
var httpObj = isSecure ? https : http;
|
||
var port = serverUrl.port || (isSecure ? 443 : 80);
|
||
var auth = serverUrl.auth;
|
||
|
||
// prepare extensions
|
||
var extensionsOffer = {};
|
||
var perMessageDeflate;
|
||
if (options.value.perMessageDeflate) {
|
||
perMessageDeflate = new PerMessageDeflate(typeof options.value.perMessageDeflate !== true ? options.value.perMessageDeflate : {}, false);
|
||
extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer();
|
||
}
|
||
|
||
// expose state properties
|
||
this._isServer = false;
|
||
this.url = address;
|
||
this.protocolVersion = options.value.protocolVersion;
|
||
this.supports.binary = (this.protocolVersion !== 'hixie-76');
|
||
|
||
// begin handshake
|
||
var key = new Buffer(options.value.protocolVersion + '-' + Date.now()).toString('base64');
|
||
var shasum = crypto.createHash('sha1');
|
||
shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
|
||
var expectedServerKey = shasum.digest('base64');
|
||
|
||
var agent = options.value.agent;
|
||
|
||
var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port)
|
||
|
||
var requestOptions = {
|
||
port: port,
|
||
host: serverUrl.hostname,
|
||
headers: {
|
||
'Connection': 'Upgrade',
|
||
'Upgrade': 'websocket',
|
||
'Host': headerHost,
|
||
'Sec-WebSocket-Version': options.value.protocolVersion,
|
||
'Sec-WebSocket-Key': key
|
||
}
|
||
};
|
||
|
||
// If we have basic auth.
|
||
if (auth) {
|
||
requestOptions.headers.Authorization = 'Basic ' + new Buffer(auth).toString('base64');
|
||
}
|
||
|
||
if (options.value.protocol) {
|
||
requestOptions.headers['Sec-WebSocket-Protocol'] = options.value.protocol;
|
||
}
|
||
|
||
if (options.value.host) {
|
||
requestOptions.headers.Host = options.value.host;
|
||
}
|
||
|
||
if (options.value.headers) {
|
||
for (var header in options.value.headers) {
|
||
if (options.value.headers.hasOwnProperty(header)) {
|
||
requestOptions.headers[header] = options.value.headers[header];
|
||
}
|
||
}
|
||
}
|
||
|
||
if (Object.keys(extensionsOffer).length) {
|
||
requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer);
|
||
}
|
||
|
||
if (options.isDefinedAndNonNull('pfx')
|
||
|| options.isDefinedAndNonNull('key')
|
||
|| options.isDefinedAndNonNull('passphrase')
|
||
|| options.isDefinedAndNonNull('cert')
|
||
|| options.isDefinedAndNonNull('ca')
|
||
|| options.isDefinedAndNonNull('ciphers')
|
||
|| options.isDefinedAndNonNull('rejectUnauthorized')) {
|
||
|
||
if (options.isDefinedAndNonNull('pfx')) requestOptions.pfx = options.value.pfx;
|
||
if (options.isDefinedAndNonNull('key')) requestOptions.key = options.value.key;
|
||
if (options.isDefinedAndNonNull('passphrase')) requestOptions.passphrase = options.value.passphrase;
|
||
if (options.isDefinedAndNonNull('cert')) requestOptions.cert = options.value.cert;
|
||
if (options.isDefinedAndNonNull('ca')) requestOptions.ca = options.value.ca;
|
||
if (options.isDefinedAndNonNull('ciphers')) requestOptions.ciphers = options.value.ciphers;
|
||
if (options.isDefinedAndNonNull('rejectUnauthorized')) requestOptions.rejectUnauthorized = options.value.rejectUnauthorized;
|
||
|
||
if (!agent) {
|
||
// global agent ignores client side certificates
|
||
agent = new httpObj.Agent(requestOptions);
|
||
}
|
||
}
|
||
|
||
requestOptions.path = serverUrl.path || '/';
|
||
|
||
if (agent) {
|
||
requestOptions.agent = agent;
|
||
}
|
||
|
||
if (isUnixSocket) {
|
||
requestOptions.socketPath = serverUrl.pathname;
|
||
}
|
||
|
||
if (options.value.localAddress) {
|
||
requestOptions.localAddress = options.value.localAddress;
|
||
}
|
||
|
||
if (options.value.origin) {
|
||
if (options.value.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.value.origin;
|
||
else requestOptions.headers.Origin = options.value.origin;
|
||
}
|
||
|
||
var self = this;
|
||
var req = httpObj.request(requestOptions);
|
||
|
||
req.on('error', function onerror(error) {
|
||
self.emit('error', error);
|
||
cleanupWebsocketResources.call(self, error);
|
||
});
|
||
|
||
req.once('response', function response(res) {
|
||
var error;
|
||
|
||
if (!self.emit('unexpected-response', req, res)) {
|
||
error = new Error('unexpected server response (' + res.statusCode + ')');
|
||
req.abort();
|
||
self.emit('error', error);
|
||
}
|
||
|
||
cleanupWebsocketResources.call(self, error);
|
||
});
|
||
|
||
req.once('upgrade', function upgrade(res, socket, upgradeHead) {
|
||
if (self.readyState === WebSocket.CLOSED) {
|
||
// client closed before server accepted connection
|
||
self.emit('close');
|
||
self.removeAllListeners();
|
||
socket.end();
|
||
return;
|
||
}
|
||
|
||
var serverKey = res.headers['sec-websocket-accept'];
|
||
if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) {
|
||
self.emit('error', 'invalid server key');
|
||
self.removeAllListeners();
|
||
socket.end();
|
||
return;
|
||
}
|
||
|
||
var serverProt = res.headers['sec-websocket-protocol'];
|
||
var protList = (options.value.protocol || "").split(/, */);
|
||
var protError = null;
|
||
|
||
if (!options.value.protocol && serverProt) {
|
||
protError = 'server sent a subprotocol even though none requested';
|
||
} else if (options.value.protocol && !serverProt) {
|
||
protError = 'server sent no subprotocol even though requested';
|
||
} else if (serverProt && protList.indexOf(serverProt) === -1) {
|
||
protError = 'server responded with an invalid protocol';
|
||
}
|
||
|
||
if (protError) {
|
||
self.emit('error', protError);
|
||
self.removeAllListeners();
|
||
socket.end();
|
||
return;
|
||
} else if (serverProt) {
|
||
self.protocol = serverProt;
|
||
}
|
||
|
||
var serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']);
|
||
if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) {
|
||
try {
|
||
perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]);
|
||
} catch (err) {
|
||
self.emit('error', 'invalid extension parameter');
|
||
self.removeAllListeners();
|
||
socket.end();
|
||
return;
|
||
}
|
||
self.extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||
}
|
||
|
||
establishConnection.call(self, Receiver, Sender, socket, upgradeHead);
|
||
|
||
// perform cleanup on http resources
|
||
req.removeAllListeners();
|
||
req = null;
|
||
agent = null;
|
||
});
|
||
|
||
req.end();
|
||
this.readyState = WebSocket.CONNECTING;
|
||
}
|
||
|
||
function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) {
|
||
var ultron = this._ultron = new Ultron(socket)
|
||
, called = false
|
||
, self = this;
|
||
|
||
socket.setTimeout(0);
|
||
socket.setNoDelay(true);
|
||
|
||
this._receiver = new ReceiverClass(this.extensions);
|
||
this._socket = socket;
|
||
|
||
// socket cleanup handlers
|
||
ultron.on('end', cleanupWebsocketResources.bind(this));
|
||
ultron.on('close', cleanupWebsocketResources.bind(this));
|
||
ultron.on('error', cleanupWebsocketResources.bind(this));
|
||
|
||
// ensure that the upgradeHead is added to the receiver
|
||
function firstHandler(data) {
|
||
if (called || self.readyState === WebSocket.CLOSED) return;
|
||
|
||
called = true;
|
||
socket.removeListener('data', firstHandler);
|
||
ultron.on('data', realHandler);
|
||
|
||
if (upgradeHead && upgradeHead.length > 0) {
|
||
realHandler(upgradeHead);
|
||
upgradeHead = null;
|
||
}
|
||
|
||
if (data) realHandler(data);
|
||
}
|
||
|
||
// subsequent packets are pushed straight to the receiver
|
||
function realHandler(data) {
|
||
self.bytesReceived += data.length;
|
||
self._receiver.add(data);
|
||
}
|
||
|
||
ultron.on('data', firstHandler);
|
||
|
||
// if data was passed along with the http upgrade,
|
||
// this will schedule a push of that on to the receiver.
|
||
// this has to be done on next tick, since the caller
|
||
// hasn't had a chance to set event handlers on this client
|
||
// object yet.
|
||
process.nextTick(firstHandler);
|
||
|
||
// receiver event handlers
|
||
self._receiver.ontext = function ontext(data, flags) {
|
||
flags = flags || {};
|
||
|
||
self.emit('message', data, flags);
|
||
};
|
||
|
||
self._receiver.onbinary = function onbinary(data, flags) {
|
||
flags = flags || {};
|
||
|
||
flags.binary = true;
|
||
self.emit('message', data, flags);
|
||
};
|
||
|
||
self._receiver.onping = function onping(data, flags) {
|
||
flags = flags || {};
|
||
|
||
self.pong(data, {
|
||
mask: !self._isServer,
|
||
binary: flags.binary === true
|
||
}, true);
|
||
|
||
self.emit('ping', data, flags);
|
||
};
|
||
|
||
self._receiver.onpong = function onpong(data, flags) {
|
||
self.emit('pong', data, flags || {});
|
||
};
|
||
|
||
self._receiver.onclose = function onclose(code, data, flags) {
|
||
flags = flags || {};
|
||
|
||
self._closeReceived = true;
|
||
self.close(code, data);
|
||
};
|
||
|
||
self._receiver.onerror = function onerror(reason, errorCode) {
|
||
// close the connection when the receiver reports a HyBi error code
|
||
self.close(typeof errorCode !== 'undefined' ? errorCode : 1002, '');
|
||
self.emit('error', reason, errorCode);
|
||
};
|
||
|
||
// finalize the client
|
||
this._sender = new SenderClass(socket, this.extensions);
|
||
this._sender.on('error', function onerror(error) {
|
||
self.close(1002, '');
|
||
self.emit('error', error);
|
||
});
|
||
|
||
this.readyState = WebSocket.OPEN;
|
||
this.emit('open');
|
||
}
|
||
|
||
function startQueue(instance) {
|
||
instance._queue = instance._queue || [];
|
||
}
|
||
|
||
function executeQueueSends(instance) {
|
||
var queue = instance._queue;
|
||
if (typeof queue === 'undefined') return;
|
||
|
||
delete instance._queue;
|
||
for (var i = 0, l = queue.length; i < l; ++i) {
|
||
queue[i]();
|
||
}
|
||
}
|
||
|
||
function sendStream(instance, stream, options, cb) {
|
||
stream.on('data', function incoming(data) {
|
||
if (instance.readyState !== WebSocket.OPEN) {
|
||
if (typeof cb === 'function') cb(new Error('not opened'));
|
||
else {
|
||
delete instance._queue;
|
||
instance.emit('error', new Error('not opened'));
|
||
}
|
||
return;
|
||
}
|
||
|
||
options.fin = false;
|
||
instance._sender.send(data, options);
|
||
});
|
||
|
||
stream.on('end', function end() {
|
||
if (instance.readyState !== WebSocket.OPEN) {
|
||
if (typeof cb === 'function') cb(new Error('not opened'));
|
||
else {
|
||
delete instance._queue;
|
||
instance.emit('error', new Error('not opened'));
|
||
}
|
||
return;
|
||
}
|
||
|
||
options.fin = true;
|
||
instance._sender.send(null, options);
|
||
|
||
if (typeof cb === 'function') cb(null);
|
||
});
|
||
}
|
||
|
||
function cleanupWebsocketResources(error) {
|
||
if (this.readyState === WebSocket.CLOSED) return;
|
||
|
||
var emitClose = this.readyState !== WebSocket.CONNECTING;
|
||
this.readyState = WebSocket.CLOSED;
|
||
|
||
clearTimeout(this._closeTimer);
|
||
this._closeTimer = null;
|
||
|
||
if (emitClose) {
|
||
// If the connection was closed abnormally (with an error), or if
|
||
// the close control frame was not received then the close code
|
||
// must default to 1006.
|
||
if (error || !this._closeReceived) {
|
||
this._closeCode = 1006;
|
||
}
|
||
this.emit('close', this._closeCode || 1000, this._closeMessage || '');
|
||
}
|
||
|
||
if (this._socket) {
|
||
if (this._ultron) this._ultron.destroy();
|
||
this._socket.on('error', function onerror() {
|
||
try { this.destroy(); }
|
||
catch (e) {}
|
||
});
|
||
|
||
try {
|
||
if (!error) this._socket.end();
|
||
else this._socket.destroy();
|
||
} catch (e) { /* Ignore termination errors */ }
|
||
|
||
this._socket = null;
|
||
this._ultron = null;
|
||
}
|
||
|
||
if (this._sender) {
|
||
this._sender.removeAllListeners();
|
||
this._sender = null;
|
||
}
|
||
|
||
if (this._receiver) {
|
||
this._receiver.cleanup();
|
||
this._receiver = null;
|
||
}
|
||
|
||
if (this.extensions[PerMessageDeflate.extensionName]) {
|
||
this.extensions[PerMessageDeflate.extensionName].cleanup();
|
||
}
|
||
|
||
this.extensions = null;
|
||
|
||
this.removeAllListeners();
|
||
this.on('error', function onerror() {}); // catch all errors after this
|
||
delete this._queue;
|
||
}
|
||
|
||
}).call(this,_dereq_('_process'),_dereq_("buffer").Buffer)
|
||
},{"./Extensions":35,"./PerMessageDeflate":36,"./Receiver":38,"./Receiver.hixie":37,"./Sender":40,"./Sender.hixie":39,"_process":undefined,"buffer":undefined,"crypto":undefined,"events":undefined,"http":undefined,"https":undefined,"options":45,"stream":undefined,"ultron":46,"url":undefined,"util":undefined}],44:[function(_dereq_,module,exports){
|
||
(function (Buffer){
|
||
/*!
|
||
* ws: a node.js websocket client
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
var util = _dereq_('util')
|
||
, events = _dereq_('events')
|
||
, http = _dereq_('http')
|
||
, crypto = _dereq_('crypto')
|
||
, Options = _dereq_('options')
|
||
, WebSocket = _dereq_('./WebSocket')
|
||
, Extensions = _dereq_('./Extensions')
|
||
, PerMessageDeflate = _dereq_('./PerMessageDeflate')
|
||
, tls = _dereq_('tls')
|
||
, url = _dereq_('url');
|
||
|
||
/**
|
||
* WebSocket Server implementation
|
||
*/
|
||
|
||
function WebSocketServer(options, callback) {
|
||
if (this instanceof WebSocketServer === false) {
|
||
return new WebSocketServer(options, callback);
|
||
}
|
||
|
||
events.EventEmitter.call(this);
|
||
|
||
options = new Options({
|
||
host: '0.0.0.0',
|
||
port: null,
|
||
server: null,
|
||
verifyClient: null,
|
||
handleProtocols: null,
|
||
path: null,
|
||
noServer: false,
|
||
disableHixie: false,
|
||
clientTracking: true,
|
||
perMessageDeflate: true
|
||
}).merge(options);
|
||
|
||
if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) {
|
||
throw new TypeError('`port` or a `server` must be provided');
|
||
}
|
||
|
||
var self = this;
|
||
|
||
if (options.isDefinedAndNonNull('port')) {
|
||
this._server = http.createServer(function (req, res) {
|
||
var body = http.STATUS_CODES[426];
|
||
res.writeHead(426, {
|
||
'Content-Length': body.length,
|
||
'Content-Type': 'text/plain'
|
||
});
|
||
res.end(body);
|
||
});
|
||
this._server.allowHalfOpen = false;
|
||
this._server.listen(options.value.port, options.value.host, callback);
|
||
this._closeServer = function() { if (self._server) self._server.close(); };
|
||
}
|
||
else if (options.value.server) {
|
||
this._server = options.value.server;
|
||
if (options.value.path) {
|
||
// take note of the path, to avoid collisions when multiple websocket servers are
|
||
// listening on the same http server
|
||
if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) {
|
||
throw new Error('two instances of WebSocketServer cannot listen on the same http server path');
|
||
}
|
||
if (typeof this._server._webSocketPaths !== 'object') {
|
||
this._server._webSocketPaths = {};
|
||
}
|
||
this._server._webSocketPaths[options.value.path] = 1;
|
||
}
|
||
}
|
||
if (this._server) this._server.once('listening', function() { self.emit('listening'); });
|
||
|
||
if (typeof this._server != 'undefined') {
|
||
this._server.on('error', function(error) {
|
||
self.emit('error', error)
|
||
});
|
||
this._server.on('upgrade', function(req, socket, upgradeHead) {
|
||
//copy upgradeHead to avoid retention of large slab buffers used in node core
|
||
var head = new Buffer(upgradeHead.length);
|
||
upgradeHead.copy(head);
|
||
|
||
self.handleUpgrade(req, socket, head, function(client) {
|
||
self.emit('connection'+req.url, client);
|
||
self.emit('connection', client);
|
||
});
|
||
});
|
||
}
|
||
|
||
this.options = options.value;
|
||
this.path = options.value.path;
|
||
this.clients = [];
|
||
}
|
||
|
||
/**
|
||
* Inherits from EventEmitter.
|
||
*/
|
||
|
||
util.inherits(WebSocketServer, events.EventEmitter);
|
||
|
||
/**
|
||
* Immediately shuts down the connection.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
WebSocketServer.prototype.close = function(callback) {
|
||
// terminate all associated clients
|
||
var error = null;
|
||
try {
|
||
for (var i = 0, l = this.clients.length; i < l; ++i) {
|
||
this.clients[i].terminate();
|
||
}
|
||
}
|
||
catch (e) {
|
||
error = e;
|
||
}
|
||
|
||
// remove path descriptor, if any
|
||
if (this.path && this._server._webSocketPaths) {
|
||
delete this._server._webSocketPaths[this.path];
|
||
if (Object.keys(this._server._webSocketPaths).length == 0) {
|
||
delete this._server._webSocketPaths;
|
||
}
|
||
}
|
||
|
||
// close the http server if it was internally created
|
||
try {
|
||
if (typeof this._closeServer !== 'undefined') {
|
||
this._closeServer();
|
||
}
|
||
}
|
||
finally {
|
||
delete this._server;
|
||
}
|
||
if(callback)
|
||
callback(error);
|
||
else if(error)
|
||
throw error;
|
||
}
|
||
|
||
/**
|
||
* Handle a HTTP Upgrade request.
|
||
*
|
||
* @api public
|
||
*/
|
||
|
||
WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) {
|
||
// check for wrong path
|
||
if (this.options.path) {
|
||
var u = url.parse(req.url);
|
||
if (u && u.pathname !== this.options.path) return;
|
||
}
|
||
|
||
if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') {
|
||
abortConnection(socket, 400, 'Bad Request');
|
||
return;
|
||
}
|
||
|
||
if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments);
|
||
else handleHybiUpgrade.apply(this, arguments);
|
||
}
|
||
|
||
module.exports = WebSocketServer;
|
||
|
||
/**
|
||
* Entirely private apis,
|
||
* which may or may not be bound to a sepcific WebSocket instance.
|
||
*/
|
||
|
||
function handleHybiUpgrade(req, socket, upgradeHead, cb) {
|
||
// handle premature socket errors
|
||
var errorHandler = function() {
|
||
try { socket.destroy(); } catch (e) {}
|
||
}
|
||
socket.on('error', errorHandler);
|
||
|
||
// verify key presence
|
||
if (!req.headers['sec-websocket-key']) {
|
||
abortConnection(socket, 400, 'Bad Request');
|
||
return;
|
||
}
|
||
|
||
// verify version
|
||
var version = parseInt(req.headers['sec-websocket-version']);
|
||
if ([8, 13].indexOf(version) === -1) {
|
||
abortConnection(socket, 400, 'Bad Request');
|
||
return;
|
||
}
|
||
|
||
// verify protocol
|
||
var protocols = req.headers['sec-websocket-protocol'];
|
||
|
||
// verify client
|
||
var origin = version < 13 ?
|
||
req.headers['sec-websocket-origin'] :
|
||
req.headers['origin'];
|
||
|
||
// handle extensions offer
|
||
var extensionsOffer = Extensions.parse(req.headers['sec-websocket-extensions']);
|
||
|
||
// handler to call when the connection sequence completes
|
||
var self = this;
|
||
var completeHybiUpgrade2 = function(protocol) {
|
||
|
||
// calc key
|
||
var key = req.headers['sec-websocket-key'];
|
||
var shasum = crypto.createHash('sha1');
|
||
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||
key = shasum.digest('base64');
|
||
|
||
var headers = [
|
||
'HTTP/1.1 101 Switching Protocols'
|
||
, 'Upgrade: websocket'
|
||
, 'Connection: Upgrade'
|
||
, 'Sec-WebSocket-Accept: ' + key
|
||
];
|
||
|
||
if (typeof protocol != 'undefined') {
|
||
headers.push('Sec-WebSocket-Protocol: ' + protocol);
|
||
}
|
||
|
||
var extensions = {};
|
||
try {
|
||
extensions = acceptExtensions.call(self, extensionsOffer);
|
||
} catch (err) {
|
||
abortConnection(socket, 400, 'Bad Request');
|
||
return;
|
||
}
|
||
|
||
if (Object.keys(extensions).length) {
|
||
var serverExtensions = {};
|
||
Object.keys(extensions).forEach(function(token) {
|
||
serverExtensions[token] = [extensions[token].params]
|
||
});
|
||
headers.push('Sec-WebSocket-Extensions: ' + Extensions.format(serverExtensions));
|
||
}
|
||
|
||
// allows external modification/inspection of handshake headers
|
||
self.emit('headers', headers);
|
||
|
||
socket.setTimeout(0);
|
||
socket.setNoDelay(true);
|
||
try {
|
||
socket.write(headers.concat('', '').join('\r\n'));
|
||
}
|
||
catch (e) {
|
||
// if the upgrade write fails, shut the connection down hard
|
||
try { socket.destroy(); } catch (e) {}
|
||
return;
|
||
}
|
||
|
||
var client = new WebSocket([req, socket, upgradeHead], {
|
||
protocolVersion: version,
|
||
protocol: protocol,
|
||
extensions: extensions
|
||
});
|
||
|
||
if (self.options.clientTracking) {
|
||
self.clients.push(client);
|
||
client.on('close', function() {
|
||
var index = self.clients.indexOf(client);
|
||
if (index != -1) {
|
||
self.clients.splice(index, 1);
|
||
}
|
||
});
|
||
}
|
||
|
||
// signal upgrade complete
|
||
socket.removeListener('error', errorHandler);
|
||
cb(client);
|
||
}
|
||
|
||
// optionally call external protocol selection handler before
|
||
// calling completeHybiUpgrade2
|
||
var completeHybiUpgrade1 = function() {
|
||
// choose from the sub-protocols
|
||
if (typeof self.options.handleProtocols == 'function') {
|
||
var protList = (protocols || "").split(/, */);
|
||
var callbackCalled = false;
|
||
var res = self.options.handleProtocols(protList, function(result, protocol) {
|
||
callbackCalled = true;
|
||
if (!result) abortConnection(socket, 401, 'Unauthorized');
|
||
else completeHybiUpgrade2(protocol);
|
||
});
|
||
if (!callbackCalled) {
|
||
// the handleProtocols handler never called our callback
|
||
abortConnection(socket, 501, 'Could not process protocols');
|
||
}
|
||
return;
|
||
} else {
|
||
if (typeof protocols !== 'undefined') {
|
||
completeHybiUpgrade2(protocols.split(/, */)[0]);
|
||
}
|
||
else {
|
||
completeHybiUpgrade2();
|
||
}
|
||
}
|
||
}
|
||
|
||
// optionally call external client verification handler
|
||
if (typeof this.options.verifyClient == 'function') {
|
||
var info = {
|
||
origin: origin,
|
||
secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined',
|
||
req: req
|
||
};
|
||
if (this.options.verifyClient.length == 2) {
|
||
this.options.verifyClient(info, function(result, code, name) {
|
||
if (typeof code === 'undefined') code = 401;
|
||
if (typeof name === 'undefined') name = http.STATUS_CODES[code];
|
||
|
||
if (!result) abortConnection(socket, code, name);
|
||
else completeHybiUpgrade1();
|
||
});
|
||
return;
|
||
}
|
||
else if (!this.options.verifyClient(info)) {
|
||
abortConnection(socket, 401, 'Unauthorized');
|
||
return;
|
||
}
|
||
}
|
||
|
||
completeHybiUpgrade1();
|
||
}
|
||
|
||
function handleHixieUpgrade(req, socket, upgradeHead, cb) {
|
||
// handle premature socket errors
|
||
var errorHandler = function() {
|
||
try { socket.destroy(); } catch (e) {}
|
||
}
|
||
socket.on('error', errorHandler);
|
||
|
||
// bail if options prevent hixie
|
||
if (this.options.disableHixie) {
|
||
abortConnection(socket, 401, 'Hixie support disabled');
|
||
return;
|
||
}
|
||
|
||
// verify key presence
|
||
if (!req.headers['sec-websocket-key2']) {
|
||
abortConnection(socket, 400, 'Bad Request');
|
||
return;
|
||
}
|
||
|
||
var origin = req.headers['origin']
|
||
, self = this;
|
||
|
||
// setup handshake completion to run after client has been verified
|
||
var onClientVerified = function() {
|
||
var wshost;
|
||
if (!req.headers['x-forwarded-host'])
|
||
wshost = req.headers.host;
|
||
else
|
||
wshost = req.headers['x-forwarded-host'];
|
||
var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url
|
||
, protocol = req.headers['sec-websocket-protocol'];
|
||
|
||
// handshake completion code to run once nonce has been successfully retrieved
|
||
var completeHandshake = function(nonce, rest) {
|
||
// calculate key
|
||
var k1 = req.headers['sec-websocket-key1']
|
||
, k2 = req.headers['sec-websocket-key2']
|
||
, md5 = crypto.createHash('md5');
|
||
|
||
[k1, k2].forEach(function (k) {
|
||
var n = parseInt(k.replace(/[^\d]/g, ''))
|
||
, spaces = k.replace(/[^ ]/g, '').length;
|
||
if (spaces === 0 || n % spaces !== 0){
|
||
abortConnection(socket, 400, 'Bad Request');
|
||
return;
|
||
}
|
||
n /= spaces;
|
||
md5.update(String.fromCharCode(
|
||
n >> 24 & 0xFF,
|
||
n >> 16 & 0xFF,
|
||
n >> 8 & 0xFF,
|
||
n & 0xFF));
|
||
});
|
||
md5.update(nonce.toString('binary'));
|
||
|
||
var headers = [
|
||
'HTTP/1.1 101 Switching Protocols'
|
||
, 'Upgrade: WebSocket'
|
||
, 'Connection: Upgrade'
|
||
, 'Sec-WebSocket-Location: ' + location
|
||
];
|
||
if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol);
|
||
if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin);
|
||
|
||
socket.setTimeout(0);
|
||
socket.setNoDelay(true);
|
||
try {
|
||
// merge header and hash buffer
|
||
var headerBuffer = new Buffer(headers.concat('', '').join('\r\n'));
|
||
var hashBuffer = new Buffer(md5.digest('binary'), 'binary');
|
||
var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length);
|
||
headerBuffer.copy(handshakeBuffer, 0);
|
||
hashBuffer.copy(handshakeBuffer, headerBuffer.length);
|
||
|
||
// do a single write, which - upon success - causes a new client websocket to be setup
|
||
socket.write(handshakeBuffer, 'binary', function(err) {
|
||
if (err) return; // do not create client if an error happens
|
||
var client = new WebSocket([req, socket, rest], {
|
||
protocolVersion: 'hixie-76',
|
||
protocol: protocol
|
||
});
|
||
if (self.options.clientTracking) {
|
||
self.clients.push(client);
|
||
client.on('close', function() {
|
||
var index = self.clients.indexOf(client);
|
||
if (index != -1) {
|
||
self.clients.splice(index, 1);
|
||
}
|
||
});
|
||
}
|
||
|
||
// signal upgrade complete
|
||
socket.removeListener('error', errorHandler);
|
||
cb(client);
|
||
});
|
||
}
|
||
catch (e) {
|
||
try { socket.destroy(); } catch (e) {}
|
||
return;
|
||
}
|
||
}
|
||
|
||
// retrieve nonce
|
||
var nonceLength = 8;
|
||
if (upgradeHead && upgradeHead.length >= nonceLength) {
|
||
var nonce = upgradeHead.slice(0, nonceLength);
|
||
var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null;
|
||
completeHandshake.call(self, nonce, rest);
|
||
}
|
||
else {
|
||
// nonce not present in upgradeHead, so we must wait for enough data
|
||
// data to arrive before continuing
|
||
var nonce = new Buffer(nonceLength);
|
||
upgradeHead.copy(nonce, 0);
|
||
var received = upgradeHead.length;
|
||
var rest = null;
|
||
var handler = function (data) {
|
||
var toRead = Math.min(data.length, nonceLength - received);
|
||
if (toRead === 0) return;
|
||
data.copy(nonce, received, 0, toRead);
|
||
received += toRead;
|
||
if (received == nonceLength) {
|
||
socket.removeListener('data', handler);
|
||
if (toRead < data.length) rest = data.slice(toRead);
|
||
completeHandshake.call(self, nonce, rest);
|
||
}
|
||
}
|
||
socket.on('data', handler);
|
||
}
|
||
}
|
||
|
||
// verify client
|
||
if (typeof this.options.verifyClient == 'function') {
|
||
var info = {
|
||
origin: origin,
|
||
secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined',
|
||
req: req
|
||
};
|
||
if (this.options.verifyClient.length == 2) {
|
||
var self = this;
|
||
this.options.verifyClient(info, function(result, code, name) {
|
||
if (typeof code === 'undefined') code = 401;
|
||
if (typeof name === 'undefined') name = http.STATUS_CODES[code];
|
||
|
||
if (!result) abortConnection(socket, code, name);
|
||
else onClientVerified.apply(self);
|
||
});
|
||
return;
|
||
}
|
||
else if (!this.options.verifyClient(info)) {
|
||
abortConnection(socket, 401, 'Unauthorized');
|
||
return;
|
||
}
|
||
}
|
||
|
||
// no client verification required
|
||
onClientVerified();
|
||
}
|
||
|
||
function acceptExtensions(offer) {
|
||
var extensions = {};
|
||
var options = this.options.perMessageDeflate;
|
||
if (options && offer[PerMessageDeflate.extensionName]) {
|
||
var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true);
|
||
perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]);
|
||
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||
}
|
||
return extensions;
|
||
}
|
||
|
||
function abortConnection(socket, code, name) {
|
||
try {
|
||
var response = [
|
||
'HTTP/1.1 ' + code + ' ' + name,
|
||
'Content-type: text/html'
|
||
];
|
||
socket.write(response.concat('', '').join('\r\n'));
|
||
}
|
||
catch (e) { /* ignore errors - we've aborted this connection */ }
|
||
finally {
|
||
// ensure that an early aborted connection is shut down completely
|
||
try { socket.destroy(); } catch (e) {}
|
||
}
|
||
}
|
||
|
||
}).call(this,_dereq_("buffer").Buffer)
|
||
},{"./Extensions":35,"./PerMessageDeflate":36,"./WebSocket":43,"buffer":undefined,"crypto":undefined,"events":undefined,"http":undefined,"options":45,"tls":undefined,"url":undefined,"util":undefined}],45:[function(_dereq_,module,exports){
|
||
/*!
|
||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||
* MIT Licensed
|
||
*/
|
||
|
||
var fs = _dereq_('fs');
|
||
|
||
function Options(defaults) {
|
||
var internalValues = {};
|
||
var values = this.value = {};
|
||
Object.keys(defaults).forEach(function(key) {
|
||
internalValues[key] = defaults[key];
|
||
Object.defineProperty(values, key, {
|
||
get: function() { return internalValues[key]; },
|
||
configurable: false,
|
||
enumerable: true
|
||
});
|
||
});
|
||
this.reset = function() {
|
||
Object.keys(defaults).forEach(function(key) {
|
||
internalValues[key] = defaults[key];
|
||
});
|
||
return this;
|
||
};
|
||
this.merge = function(options, required) {
|
||
options = options || {};
|
||
if (Object.prototype.toString.call(required) === '[object Array]') {
|
||
var missing = [];
|
||
for (var i = 0, l = required.length; i < l; ++i) {
|
||
var key = required[i];
|
||
if (!(key in options)) {
|
||
missing.push(key);
|
||
}
|
||
}
|
||
if (missing.length > 0) {
|
||
if (missing.length > 1) {
|
||
throw new Error('options ' +
|
||
missing.slice(0, missing.length - 1).join(', ') + ' and ' +
|
||
missing[missing.length - 1] + ' must be defined');
|
||
}
|
||
else throw new Error('option ' + missing[0] + ' must be defined');
|
||
}
|
||
}
|
||
Object.keys(options).forEach(function(key) {
|
||
if (key in internalValues) {
|
||
internalValues[key] = options[key];
|
||
}
|
||
});
|
||
return this;
|
||
};
|
||
this.copy = function(keys) {
|
||
var obj = {};
|
||
Object.keys(defaults).forEach(function(key) {
|
||
if (keys.indexOf(key) !== -1) {
|
||
obj[key] = values[key];
|
||
}
|
||
});
|
||
return obj;
|
||
};
|
||
this.read = function(filename, cb) {
|
||
if (typeof cb == 'function') {
|
||
var self = this;
|
||
fs.readFile(filename, function(error, data) {
|
||
if (error) return cb(error);
|
||
var conf = JSON.parse(data);
|
||
self.merge(conf);
|
||
cb();
|
||
});
|
||
}
|
||
else {
|
||
var conf = JSON.parse(fs.readFileSync(filename));
|
||
this.merge(conf);
|
||
}
|
||
return this;
|
||
};
|
||
this.isDefined = function(key) {
|
||
return typeof values[key] != 'undefined';
|
||
};
|
||
this.isDefinedAndNonNull = function(key) {
|
||
return typeof values[key] != 'undefined' && values[key] !== null;
|
||
};
|
||
Object.freeze(values);
|
||
Object.freeze(this);
|
||
}
|
||
|
||
module.exports = Options;
|
||
|
||
},{"fs":undefined}],46:[function(_dereq_,module,exports){
|
||
'use strict';
|
||
|
||
var has = Object.prototype.hasOwnProperty;
|
||
|
||
/**
|
||
* An auto incrementing id which we can use to create "unique" Ultron instances
|
||
* so we can track the event emitters that are added through the Ultron
|
||
* interface.
|
||
*
|
||
* @type {Number}
|
||
* @private
|
||
*/
|
||
var id = 0;
|
||
|
||
/**
|
||
* Ultron is high-intelligence robot. It gathers intelligence so it can start improving
|
||
* upon his rudimentary design. It will learn from your EventEmitting patterns
|
||
* and exterminate them.
|
||
*
|
||
* @constructor
|
||
* @param {EventEmitter} ee EventEmitter instance we need to wrap.
|
||
* @api public
|
||
*/
|
||
function Ultron(ee) {
|
||
if (!(this instanceof Ultron)) return new Ultron(ee);
|
||
|
||
this.id = id++;
|
||
this.ee = ee;
|
||
}
|
||
|
||
/**
|
||
* Register a new EventListener for the given event.
|
||
*
|
||
* @param {String} event Name of the event.
|
||
* @param {Functon} fn Callback function.
|
||
* @param {Mixed} context The context of the function.
|
||
* @returns {Ultron}
|
||
* @api public
|
||
*/
|
||
Ultron.prototype.on = function on(event, fn, context) {
|
||
fn.__ultron = this.id;
|
||
this.ee.on(event, fn, context);
|
||
|
||
return this;
|
||
};
|
||
/**
|
||
* Add an EventListener that's only called once.
|
||
*
|
||
* @param {String} event Name of the event.
|
||
* @param {Function} fn Callback function.
|
||
* @param {Mixed} context The context of the function.
|
||
* @returns {Ultron}
|
||
* @api public
|
||
*/
|
||
Ultron.prototype.once = function once(event, fn, context) {
|
||
fn.__ultron = this.id;
|
||
this.ee.once(event, fn, context);
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Remove the listeners we assigned for the given event.
|
||
*
|
||
* @returns {Ultron}
|
||
* @api public
|
||
*/
|
||
Ultron.prototype.remove = function remove() {
|
||
var args = arguments
|
||
, event;
|
||
|
||
//
|
||
// When no event names are provided we assume that we need to clear all the
|
||
// events that were assigned through us.
|
||
//
|
||
if (args.length === 1 && 'string' === typeof args[0]) {
|
||
args = args[0].split(/[, ]+/);
|
||
} else if (!args.length) {
|
||
args = [];
|
||
|
||
for (event in this.ee._events) {
|
||
if (has.call(this.ee._events, event)) args.push(event);
|
||
}
|
||
}
|
||
|
||
for (var i = 0; i < args.length; i++) {
|
||
var listeners = this.ee.listeners(args[i]);
|
||
|
||
for (var j = 0; j < listeners.length; j++) {
|
||
event = listeners[j];
|
||
|
||
//
|
||
// Once listeners have a `listener` property that stores the real listener
|
||
// in the EventEmitter that ships with Node.js.
|
||
//
|
||
if (event.listener) {
|
||
if (event.listener.__ultron !== this.id) continue;
|
||
delete event.listener.__ultron;
|
||
} else {
|
||
if (event.__ultron !== this.id) continue;
|
||
delete event.__ultron;
|
||
}
|
||
|
||
this.ee.removeListener(args[i], event);
|
||
}
|
||
}
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Destroy the Ultron instance, remove all listeners and release all references.
|
||
*
|
||
* @returns {Boolean}
|
||
* @api public
|
||
*/
|
||
Ultron.prototype.destroy = function destroy() {
|
||
if (!this.ee) return false;
|
||
|
||
this.remove();
|
||
this.ee = null;
|
||
|
||
return true;
|
||
};
|
||
|
||
//
|
||
// Expose the module.
|
||
//
|
||
module.exports = Ultron;
|
||
|
||
},{}],47:[function(_dereq_,module,exports){
|
||
'use strict';
|
||
|
||
var alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split('')
|
||
, length = 64
|
||
, map = {}
|
||
, seed = 0
|
||
, i = 0
|
||
, prev;
|
||
|
||
/**
|
||
* Return a string representing the specified number.
|
||
*
|
||
* @param {Number} num The number to convert.
|
||
* @returns {String} The string representation of the number.
|
||
* @api public
|
||
*/
|
||
function encode(num) {
|
||
var encoded = '';
|
||
|
||
do {
|
||
encoded = alphabet[num % length] + encoded;
|
||
num = Math.floor(num / length);
|
||
} while (num > 0);
|
||
|
||
return encoded;
|
||
}
|
||
|
||
/**
|
||
* Return the integer value specified by the given string.
|
||
*
|
||
* @param {String} str The string to convert.
|
||
* @returns {Number} The integer value represented by the string.
|
||
* @api public
|
||
*/
|
||
function decode(str) {
|
||
var decoded = 0;
|
||
|
||
for (i = 0; i < str.length; i++) {
|
||
decoded = decoded * length + map[str.charAt(i)];
|
||
}
|
||
|
||
return decoded;
|
||
}
|
||
|
||
/**
|
||
* Yeast: A tiny growing id generator.
|
||
*
|
||
* @returns {String} A unique id.
|
||
* @api public
|
||
*/
|
||
function yeast() {
|
||
var now = encode(+new Date());
|
||
|
||
if (now !== prev) return seed = 0, prev = now;
|
||
return now +'.'+ encode(seed++);
|
||
}
|
||
|
||
//
|
||
// Map each character to its index.
|
||
//
|
||
for (; i < length; i++) map[alphabet[i]] = i;
|
||
|
||
//
|
||
// Expose the `yeast`, `encode` and `decode` functions.
|
||
//
|
||
yeast.encode = encode;
|
||
yeast.decode = decode;
|
||
module.exports = yeast;
|
||
|
||
},{}]},{},[1])(1)
|
||
});
|