Shuo/client/components/socket.io.js

6173 lines
152 KiB
JavaScript
Raw Normal View History

2014-06-20 01:33:49 +00:00
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.io=e():"undefined"!=typeof global?global.io=e():"undefined"!=typeof self&&(self.io=e())}(function(){var define,module,exports;
return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = require('./lib/');
},{"./lib/":2}],2:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Module dependencies.
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
var url = require('./url');
var parser = require('socket.io-parser');
var Manager = require('./manager');
var debug = require('debug')('socket.io-client');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = exports = lookup;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Managers cache.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var cache = exports.managers = {};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Looks up an existing `Manager` for multiplexing.
* If the user summons:
*
* `io('http://localhost/a');`
* `io('http://localhost/b');`
*
* We reuse the existing instance based on same scheme/port/host,
* and we initialize sockets for each namespace.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function lookup(uri, opts) {
if (typeof uri == 'object') {
opts = uri;
uri = undefined;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
opts = opts || {};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var parsed = url(uri);
var source = parsed.source;
var id = parsed.id;
var io;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (opts.forceNew || opts['force new connection'] || false === opts.multiplex) {
debug('ignoring socket cache for %s', source);
io = Manager(source, opts);
} else {
if (!cache[id]) {
debug('new io instance for %s', source);
cache[id] = Manager(source, opts);
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
io = cache[id];
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return io.socket(parsed.path);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Protocol version.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
exports.protocol = parser.protocol;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* `connect`.
*
* @param {String} uri
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
exports.connect = lookup;
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Expose constructors for standalone build.
*
* @api public
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
exports.Manager = require('./manager');
exports.Socket = require('./socket');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"./manager":3,"./socket":5,"./url":6,"debug":9,"socket.io-parser":40}],3:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module dependencies.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var url = require('./url');
var eio = require('engine.io-client');
var Socket = require('./socket');
var Emitter = require('component-emitter');
var parser = require('socket.io-parser');
var on = require('./on');
var bind = require('component-bind');
var object = require('object-component');
var debug = require('debug')('socket.io-client:manager');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = Manager;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* `Manager` constructor.
*
* @param {String} engine instance or engine uri/opts
* @param {Object} options
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function Manager(uri, opts){
if (!(this instanceof Manager)) return new Manager(uri, opts);
if (uri && ('object' == typeof uri)) {
opts = uri;
uri = undefined;
}
opts = opts || {};
opts.path = opts.path || '/socket.io';
this.nsps = {};
this.subs = [];
this.opts = opts;
this.reconnection(opts.reconnection !== false);
this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);
this.reconnectionDelay(opts.reconnectionDelay || 1000);
this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);
this.timeout(null == opts.timeout ? 20000 : opts.timeout);
this.readyState = 'closed';
this.uri = uri;
this.connected = 0;
this.attempts = 0;
this.encoding = false;
this.packetBuffer = [];
this.encoder = new parser.Encoder();
this.decoder = new parser.Decoder();
this.open();
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Propagate given event to sockets and emit on `this`
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.emitAll = function() {
this.emit.apply(this, arguments);
for (var nsp in this.nsps) {
this.nsps[nsp].emit.apply(this.nsps[nsp], arguments);
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Mix in `Emitter`.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter(Manager.prototype);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sets the `reconnection` config.
*
* @param {Boolean} true/false if it should automatically reconnect
* @return {Manager} self or value
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.reconnection = function(v){
if (!arguments.length) return this._reconnection;
this._reconnection = !!v;
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sets the reconnection attempts config.
*
* @param {Number} max reconnection attempts before giving up
* @return {Manager} self or value
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.reconnectionAttempts = function(v){
if (!arguments.length) return this._reconnectionAttempts;
this._reconnectionAttempts = v;
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sets the delay between reconnections.
*
* @param {Number} delay
* @return {Manager} self or value
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.reconnectionDelay = function(v){
if (!arguments.length) return this._reconnectionDelay;
this._reconnectionDelay = v;
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sets the maximum delay between reconnections.
*
* @param {Number} delay
* @return {Manager} self or value
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.reconnectionDelayMax = function(v){
if (!arguments.length) return this._reconnectionDelayMax;
this._reconnectionDelayMax = v;
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sets the connection timeout. `false` to disable
*
* @return {Manager} self or value
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.timeout = function(v){
if (!arguments.length) return this._timeout;
this._timeout = v;
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Starts trying to reconnect if reconnection is enabled and we have not
* started reconnecting yet
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.maybeReconnectOnOpen = function() {
if (!this.openReconnect && !this.reconnecting && this._reconnection) {
// keeps reconnection from firing twice for the same reconnection loop
this.openReconnect = true;
this.reconnect();
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sets the current transport `socket`.
*
* @param {Function} optional, callback
* @return {Manager} self
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.open =
Manager.prototype.connect = function(fn){
debug('readyState %s', this.readyState);
if (~this.readyState.indexOf('open')) return this;
debug('opening %s', this.uri);
this.engine = eio(this.uri, this.opts);
var socket = this.engine;
var self = this;
this.readyState = 'opening';
// emit `open`
var openSub = on(socket, 'open', function() {
self.onopen();
fn && fn();
});
// emit `connect_error`
var errorSub = on(socket, 'error', function(data){
debug('connect_error');
self.cleanup();
self.readyState = 'closed';
self.emitAll('connect_error', data);
if (fn) {
var err = new Error('Connection error');
err.data = data;
fn(err);
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
self.maybeReconnectOnOpen();
});
// emit `connect_timeout`
if (false !== this._timeout) {
var timeout = this._timeout;
debug('connect attempt will timeout after %d', timeout);
// set timer
var timer = setTimeout(function(){
debug('connect attempt timed out after %d', timeout);
openSub.destroy();
socket.close();
socket.emit('error', 'timeout');
self.emitAll('connect_timeout', timeout);
}, timeout);
this.subs.push({
destroy: function(){
clearTimeout(timer);
}
2014-06-17 16:20:22 +00:00
});
}
2014-06-20 01:33:49 +00:00
this.subs.push(openSub);
this.subs.push(errorSub);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon transport open.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.onopen = function(){
debug('open');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// clear old subs
this.cleanup();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// mark as open
this.readyState = 'open';
this.emit('open');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// add new subs
var socket = this.engine;
this.subs.push(on(socket, 'data', bind(this, 'ondata')));
this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded')));
this.subs.push(on(socket, 'error', bind(this, 'onerror')));
this.subs.push(on(socket, 'close', bind(this, 'onclose')));
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called with data.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.ondata = function(data){
this.decoder.add(data);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called when parser fully decodes a packet.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.ondecoded = function(packet) {
this.emit('packet', packet);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon socket error.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.onerror = function(err){
debug('error', err);
this.emitAll('error', err);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Creates a new socket for the given `nsp`.
*
* @return {Socket}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.socket = function(nsp){
var socket = this.nsps[nsp];
if (!socket) {
socket = new Socket(this, nsp);
this.nsps[nsp] = socket;
var self = this;
socket.on('connect', function(){
self.connected++;
});
}
return socket;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon a socket close.
*
* @param {Socket} socket
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.destroy = function(socket){
--this.connected || this.close();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Writes a packet.
*
* @param {Object} packet
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.packet = function(packet){
debug('writing packet %j', packet);
var self = this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (!self.encoding) {
// encode, then write to engine with result
self.encoding = true;
this.encoder.encode(packet, function(encodedPackets) {
for (var i = 0; i < encodedPackets.length; i++) {
self.engine.write(encodedPackets[i]);
}
self.encoding = false;
self.processPacketQueue();
});
} else { // add packet to the queue
self.packetBuffer.push(packet);
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* If packet buffer is non-empty, begins encoding the
* next packet in line.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.processPacketQueue = function() {
if (this.packetBuffer.length > 0 && !this.encoding) {
var pack = this.packetBuffer.shift();
this.packet(pack);
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Clean up transport subscriptions and packet buffer.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.cleanup = function(){
var sub;
while (sub = this.subs.shift()) sub.destroy();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.packetBuffer = [];
this.encoding = false;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.decoder.destroy();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Close the current socket.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.close =
Manager.prototype.disconnect = function(){
this.skipReconnect = true;
this.engine.close();
};
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Called upon engine close.
*
* @api private
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
Manager.prototype.onclose = function(reason){
debug('close');
this.cleanup();
this.readyState = 'closed';
this.emit('close', reason);
if (this._reconnection && !this.skipReconnect) {
this.reconnect();
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Attempt a reconnection.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.reconnect = function(){
if (this.reconnecting) return this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var self = this;
this.attempts++;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (this.attempts > this._reconnectionAttempts) {
debug('reconnect failed');
this.emitAll('reconnect_failed');
this.reconnecting = false;
} else {
var delay = this.attempts * this.reconnectionDelay();
delay = Math.min(delay, this.reconnectionDelayMax());
debug('will wait %dms before reconnect attempt', delay);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.reconnecting = true;
var timer = setTimeout(function(){
debug('attempting reconnect');
self.emitAll('reconnect_attempt', self.attempts);
self.emitAll('reconnecting', self.attempts);
self.open(function(err){
if (err) {
debug('reconnect attempt error');
self.reconnecting = false;
self.reconnect();
self.emitAll('reconnect_error', err.data);
} else {
debug('reconnect success');
self.onreconnect();
}
});
}, delay);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.subs.push({
destroy: function(){
clearTimeout(timer);
}
});
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon successful reconnect.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Manager.prototype.onreconnect = function(){
var attempt = this.attempts;
this.attempts = 0;
this.reconnecting = false;
this.emitAll('reconnect', attempt);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"./on":4,"./socket":5,"./url":6,"component-bind":7,"component-emitter":8,"debug":9,"engine.io-client":11,"object-component":37,"socket.io-parser":40}],4:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = on;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Helper for subscriptions.
*
* @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter`
* @param {String} event name
* @param {Function} callback
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function on(obj, ev, fn) {
obj.on(ev, fn);
return {
destroy: function(){
obj.removeListener(ev, fn);
}
2014-06-17 16:20:22 +00:00
};
2014-06-20 01:33:49 +00:00
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{}],5:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module dependencies.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var parser = require('socket.io-parser');
var Emitter = require('component-emitter');
var toArray = require('to-array');
var on = require('./on');
var bind = require('component-bind');
var debug = require('debug')('socket.io-client:socket');
var hasBin = require('has-binary-data');
var indexOf = require('indexof');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = exports = Socket;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Internal events (blacklisted).
* These events can't be emitted by the user.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var events = {
connect: 1,
connect_error: 1,
connect_timeout: 1,
disconnect: 1,
error: 1,
reconnect: 1,
reconnect_attempt: 1,
reconnect_failed: 1,
reconnect_error: 1,
reconnecting: 1
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Shortcut to `Emitter#emit`.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var emit = Emitter.prototype.emit;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* `Socket` constructor.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function Socket(io, nsp){
this.io = io;
this.nsp = nsp;
this.json = this; // compat
this.ids = 0;
this.acks = {};
this.open();
this.receiveBuffer = [];
this.sendBuffer = [];
this.connected = false;
this.disconnected = true;
this.subEvents();
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Mix in `Emitter`.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter(Socket.prototype);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Subscribe to open, close and packet events
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.subEvents = function() {
var io = this.io;
this.subs = [
on(io, 'open', bind(this, 'onopen')),
on(io, 'packet', bind(this, 'onpacket')),
on(io, 'close', bind(this, 'onclose'))
];
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon engine `open`.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.open =
Socket.prototype.connect = function(){
if (this.connected) return this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.io.open(); // ensure open
if ('open' == this.io.readyState) this.onopen();
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sends a `message` event.
*
* @return {Socket} self
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.send = function(){
var args = toArray(arguments);
args.unshift('message');
this.emit.apply(this, args);
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Override `emit`.
* If the event is in `events`, it's emitted normally.
*
* @param {String} event name
* @return {Socket} self
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.emit = function(ev){
if (events.hasOwnProperty(ev)) {
emit.apply(this, arguments);
return this;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var args = toArray(arguments);
var parserType = parser.EVENT; // default
if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
var packet = { type: parserType, data: args };
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// event ack callback
if ('function' == typeof args[args.length - 1]) {
debug('emitting packet with ack id %d', this.ids);
this.acks[this.ids] = args.pop();
packet.id = this.ids++;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (this.connected) {
this.packet(packet);
} else {
this.sendBuffer.push(packet);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return this;
};
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Sends a packet.
*
* @param {Object} packet
* @api private
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
Socket.prototype.packet = function(packet){
packet.nsp = this.nsp;
this.io.packet(packet);
};
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* "Opens" the socket.
*
* @api private
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
Socket.prototype.onopen = function(){
debug('transport is open - connecting');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// write connect packet if necessary
if ('/' != this.nsp) {
this.packet({ type: parser.CONNECT });
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon engine `close`.
*
* @param {String} reason
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.onclose = function(reason){
debug('close (%s)', reason);
this.connected = false;
this.disconnected = true;
this.emit('disconnect', reason);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called with socket packet.
*
* @param {Object} packet
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.onpacket = function(packet){
if (packet.nsp != this.nsp) return;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
switch (packet.type) {
case parser.CONNECT:
this.onconnect();
break;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
case parser.EVENT:
this.onevent(packet);
break;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
case parser.BINARY_EVENT:
this.onevent(packet);
break;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
case parser.ACK:
this.onack(packet);
break;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
case parser.BINARY_ACK:
this.onack(packet);
break;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
case parser.DISCONNECT:
this.ondisconnect();
break;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
case parser.ERROR:
this.emit('error', packet.data);
break;
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon a server event.
*
* @param {Object} packet
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.onevent = function(packet){
var args = packet.data || [];
debug('emitting event %j', args);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (null != packet.id) {
debug('attaching ack callback to event');
args.push(this.ack(packet.id));
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (this.connected) {
emit.apply(this, args);
} else {
this.receiveBuffer.push(args);
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Produces an ack callback to emit with an event.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.ack = function(id){
var self = this;
var sent = false;
return function(){
// prevent double callbacks
if (sent) return;
sent = true;
var args = toArray(arguments);
debug('sending ack %j', args);
var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
self.packet({
type: type,
id: id,
data: args
});
};
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon a server acknowlegement.
*
* @param {Object} packet
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.onack = function(packet){
debug('calling ack %s with %j', packet.id, packet.data);
var fn = this.acks[packet.id];
fn.apply(this, packet.data);
delete this.acks[packet.id];
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon server connect.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.onconnect = function(){
this.connected = true;
this.disconnected = false;
this.emit('connect');
this.emitBuffered();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Emit buffered events (received and emitted).
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.emitBuffered = function(){
var i;
for (i = 0; i < this.receiveBuffer.length; i++) {
emit.apply(this, this.receiveBuffer[i]);
}
this.receiveBuffer = [];
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
for (i = 0; i < this.sendBuffer.length; i++) {
this.packet(this.sendBuffer[i]);
}
this.sendBuffer = [];
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon server disconnect.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.ondisconnect = function(){
debug('server disconnect (%s)', this.nsp);
this.destroy();
this.onclose('io server disconnect');
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon forced client/server side disconnections,
* this method ensures the manager stops tracking us and
* that reconnections don't get triggered for this.
*
* @api private.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.destroy = function(){
// clean subscriptions to avoid reconnections
for (var i = 0; i < this.subs.length; i++) {
this.subs[i].destroy();
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.io.destroy(this);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Disconnects the socket manually.
*
* @return {Socket} self
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.close =
Socket.prototype.disconnect = function(){
if (!this.connected) return this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
debug('performing disconnect (%s)', this.nsp);
this.packet({ type: parser.DISCONNECT });
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// remove socket from pool
this.destroy();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// fire events
this.onclose('io client disconnect');
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"./on":4,"component-bind":7,"component-emitter":8,"debug":9,"has-binary-data":32,"indexof":36,"socket.io-parser":40,"to-array":43}],6:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
/**
* Module dependencies.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var parseuri = require('parseuri');
var debug = require('debug')('socket.io-client:url');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = url;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* URL parser.
*
* @param {String} url
* @param {Object} An object meant to mimic window.location.
* Defaults to window.location.
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function url(uri, loc){
var obj = uri;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// default to window.location
var loc = loc || global.location;
if (null == uri) uri = loc.protocol + '//' + loc.hostname;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// relative path support
if ('string' == typeof uri) {
if ('/' == uri.charAt(0)) {
if ('undefined' != typeof loc) {
uri = loc.hostname + uri;
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (!/^(https?|wss?):\/\//.test(uri)) {
debug('protocol-less url %s', uri);
if ('undefined' != typeof loc) {
uri = loc.protocol + '//' + uri;
} else {
uri = 'https://' + uri;
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// parse
debug('parse %s', uri);
obj = parseuri(uri);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// make sure we treat `localhost:80` and `localhost` equally
if (!obj.port) {
if (/^(http|ws)$/.test(obj.protocol)) {
obj.port = '80';
}
else if (/^(http|ws)s$/.test(obj.protocol)) {
obj.port = '443';
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
obj.path = obj.path || '/';
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// define unique id
obj.id = obj.protocol + '://' + obj.host + ':' + obj.port;
// define href
obj.href = obj.protocol + '://' + obj.host + (loc && loc.port == obj.port ? '' : (':' + obj.port));
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return obj;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"debug":9,"parseuri":38}],7:[function(require,module,exports){
/**
* Slice reference.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var slice = [].slice;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Bind `obj` to `fn`.
*
* @param {Object} obj
* @param {Function|String} fn or string
* @return {Function}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = function(obj, fn){
if ('string' == typeof fn) fn = obj[fn];
if ('function' != typeof fn) throw new Error('bind() requires a function');
var args = slice.call(arguments, 2);
return function(){
return fn.apply(obj, args.concat(slice.call(arguments)));
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{}],8:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Expose `Emitter`.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = Emitter;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Initialize a new `Emitter`.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function Emitter(obj) {
if (obj) return mixin(obj);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function mixin(obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key];
}
return obj;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
(this._callbacks[event] = this._callbacks[event] || [])
.push(fn);
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.once = function(event, fn){
var self = this;
this._callbacks = this._callbacks || {};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function on() {
self.off(event, on);
fn.apply(this, arguments);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
on.fn = fn;
this.on(event, on);
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// all
if (0 == arguments.length) {
this._callbacks = {};
return this;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// specific event
var callbacks = this._callbacks[event];
if (!callbacks) return this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// remove all handlers
if (1 == arguments.length) {
delete this._callbacks[event];
return this;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// 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;
};
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
Emitter.prototype.emit = function(event){
this._callbacks = this._callbacks || {};
var args = [].slice.call(arguments, 1)
, callbacks = this._callbacks[event];
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (callbacks) {
callbacks = callbacks.slice(0);
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.listeners = function(event){
this._callbacks = this._callbacks || {};
return this._callbacks[event] || [];
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.hasListeners = function(event){
return !! this.listeners(event).length;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{}],9:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Expose `debug()` as the module.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = debug;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Create a debugger with the given `name`.
*
* @param {String} name
* @return {Type}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function debug(name) {
if (!debug.enabled(name)) return function(){};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return function(fmt){
fmt = coerce(fmt);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var curr = new Date;
var ms = curr - (debug[name] || curr);
debug[name] = curr;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
fmt = name
+ ' '
+ fmt
+ ' +' + debug.humanize(ms);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// This hackery is required for IE8
// where `console.log` doesn't have 'apply'
window.console
&& console.log
&& Function.prototype.apply.call(console.log, console, arguments);
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* The currently active debug mode names.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
debug.names = [];
debug.skips = [];
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Enables a debug mode by name. This can include modes
* separated by a colon and wildcards.
*
* @param {String} name
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
debug.enable = function(name) {
try {
localStorage.debug = name;
} catch(e){}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var split = (name || '').split(/[\s,]+/)
, len = split.length;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
for (var i = 0; i < len; i++) {
name = split[i].replace('*', '.*?');
if (name[0] === '-') {
debug.skips.push(new RegExp('^' + name.substr(1) + '$'));
}
else {
debug.names.push(new RegExp('^' + name + '$'));
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Disable debug output.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
debug.disable = function(){
debug.enable('');
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Humanize the given `ms`.
*
* @param {Number} m
* @return {String}
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
debug.humanize = function(ms) {
var sec = 1000
, min = 60 * 1000
, hour = 60 * min;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
if (ms >= min) return (ms / min).toFixed(1) + 'm';
if (ms >= sec) return (ms / sec | 0) + 's';
return ms + 'ms';
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Returns true if the given mode name is enabled, false otherwise.
*
* @param {String} name
* @return {Boolean}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
debug.enabled = function(name) {
for (var i = 0, len = debug.skips.length; i < len; i++) {
if (debug.skips[i].test(name)) {
return false;
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
}
for (var i = 0, len = debug.names.length; i < len; i++) {
if (debug.names[i].test(name)) {
return true;
}
}
return false;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Coerce `val`.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function coerce(val) {
if (val instanceof Error) return val.stack || val.message;
return val;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// persist
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
try {
if (window.localStorage) debug.enable(localStorage.debug);
} catch(e){}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{}],10:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module dependencies.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var index = require('indexof');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Expose `Emitter`.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = Emitter;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Initialize a new `Emitter`.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function Emitter(obj) {
if (obj) return mixin(obj);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function mixin(obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key];
}
return obj;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.on = function(event, fn){
this._callbacks = this._callbacks || {};
(this._callbacks[event] = this._callbacks[event] || [])
.push(fn);
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.once = function(event, fn){
var self = this;
this._callbacks = this._callbacks || {};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function on() {
self.off(event, on);
fn.apply(this, arguments);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
fn._off = on;
this.on(event, on);
return this;
};
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners = function(event, fn){
this._callbacks = this._callbacks || {};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// all
if (0 == arguments.length) {
this._callbacks = {};
return this;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// specific event
var callbacks = this._callbacks[event];
if (!callbacks) return this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// remove all handlers
if (1 == arguments.length) {
delete this._callbacks[event];
return this;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// remove specific handler
var i = index(callbacks, fn._off || fn);
if (~i) callbacks.splice(i, 1);
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.emit = function(event){
this._callbacks = this._callbacks || {};
var args = [].slice.call(arguments, 1)
, callbacks = this._callbacks[event];
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (callbacks) {
callbacks = callbacks.slice(0);
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.listeners = function(event){
this._callbacks = this._callbacks || {};
return this._callbacks[event] || [];
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter.prototype.hasListeners = function(event){
return !! this.listeners(event).length;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"indexof":36}],11:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = require('./lib/');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"./lib/":12}],12:[function(require,module,exports){
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = require('./socket');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Exports parser
*
* @api public
*
*/
module.exports.parser = require('engine.io-parser');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"./socket":13,"engine.io-parser":22}],13:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/**
* Module dependencies.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var transports = require('./transports');
var Emitter = require('component-emitter');
var debug = require('debug')('engine.io-client:socket');
var index = require('indexof');
var parser = require('engine.io-parser');
var parseuri = require('parseuri');
var parsejson = require('parsejson');
var parseqs = require('parseqs');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = Socket;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Noop function.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function noop(){}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Socket constructor.
*
* @param {String|Object} uri or options
* @param {Object} options
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function Socket(uri, opts){
if (!(this instanceof Socket)) return new Socket(uri, opts);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
opts = opts || {};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (uri && 'object' == typeof uri) {
opts = uri;
uri = null;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (uri) {
uri = parseuri(uri);
opts.host = uri.host;
opts.secure = uri.protocol == 'https' || uri.protocol == 'wss';
opts.port = uri.port;
if (uri.query) opts.query = uri.query;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.secure = null != opts.secure ? opts.secure :
(global.location && 'https:' == location.protocol);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (opts.host) {
var pieces = opts.host.split(':');
opts.hostname = pieces.shift();
if (pieces.length) opts.port = pieces.pop();
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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.forceBase64 = !!opts.forceBase64;
this.timestampParam = opts.timestampParam || 't';
this.timestampRequests = opts.timestampRequests;
this.transports = opts.transports || ['polling', 'websocket'];
this.readyState = '';
this.writeBuffer = [];
this.callbackBuffer = [];
this.policyPort = opts.policyPort || 843;
this.rememberUpgrade = opts.rememberUpgrade || false;
this.open();
this.binaryType = null;
this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.priorWebsocketSuccess = false;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Mix in `Emitter`.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter(Socket.prototype);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Protocol version.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.protocol = parser.protocol; // this is an int
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Expose deps for legacy compatibility
* and standalone browser access.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.Socket = Socket;
Socket.Transport = require('./transport');
Socket.transports = require('./transports');
Socket.parser = require('engine.io-parser');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Creates transport of the given type.
*
* @param {String} transport name
* @return {Transport}
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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,
forceBase64: this.forceBase64,
timestampRequests: this.timestampRequests,
timestampParam: this.timestampParam,
policyPort: this.policyPort,
socket: this
});
return transport;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function clone (obj) {
var o = {};
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
o[i] = obj[i];
}
}
return o;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* 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 {
transport = this.transports[0];
}
this.readyState = 'opening';
var transport = this.createTransport(transport);
transport.open();
this.setTransport(transport);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sets the current transport. Disables the existing one (if any).
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.setTransport = function(transport){
debug('setting transport %s', transport.name);
var self = this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (this.transport) {
debug('clearing existing transport %s', this.transport.name);
this.transport.removeAllListeners();
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// 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');
});
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Probes a transport.
*
* @param {String} transport name
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.probe = function (name) {
debug('probing transport "%s"', name);
var transport = this.createTransport(name, { probe: 1 })
, failed = false
, self = this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.priorWebsocketSuccess = false;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function onTransportOpen(){
if (self.onlyBinaryUpgrades) {
var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary;
failed = failed || upgradeLosesBinary;
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
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);
Socket.priorWebsocketSuccess = 'websocket' == transport.name;
debug('pausing current transport "%s"', self.transport.name);
self.transport.pause(function () {
if (failed) return;
if ('closed' == self.readyState || 'closing' == self.readyState) {
return;
}
debug('changing transport and sending upgrade packet');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
cleanup();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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);
}
});
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function freezeTransport() {
if (failed) return;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// Any callback called by transport should be ignored since now
failed = true;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
cleanup();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
transport.close();
transport = null;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
//Handle any error that happens while probing
function onerror(err) {
var error = new Error('probe error: ' + err);
error.transport = transport.name;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
freezeTransport();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
debug('probe transport "%s" failed because of error: %s', name, err);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
self.emit('upgradeError', error);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function onTransportClose(){
onerror("transport closed");
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
//When the socket is closed while we're probing
function onclose(){
onerror("socket closed");
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
//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();
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
//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);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
transport.once('open', onTransportOpen);
transport.once('error', onerror);
transport.once('close', onTransportClose);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.once('close', onclose);
this.once('upgrading', onupgrade);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
transport.open();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called when connection is deemed open.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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]);
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Handles a packet.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.onPacket = function (packet) {
if ('opening' == this.readyState || 'open' == this.readyState) {
debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.emit('packet', packet);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// Socket is live - any packet counts
this.emit('heartbeat');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
switch (packet.type) {
case 'open':
this.onHandshake(parsejson(packet.data));
break;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
case 'pong':
this.setPing();
break;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
case 'error':
var err = new Error('server error');
err.code = packet.data;
this.emit('error', err);
break;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
case 'message':
this.emit('data', packet.data);
this.emit('message', packet.data);
break;
}
} else {
debug('packet received with socket readyState "%s"', this.readyState);
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon handshake completion.
*
* @param {Object} handshake obj
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Resets ping timeout.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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));
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Pings server every `this.pingInterval` and expects response
* within `this.pingTimeout` or closes connection.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sends a ping packet.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.ping = function () {
this.sendPacket('ping');
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called on `drain` event
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.onDrain = function() {
for (var i = 0; i < this.prevBufferLen; i++) {
if (this.callbackBuffer[i]) {
this.callbackBuffer[i]();
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.writeBuffer.splice(0, this.prevBufferLen);
this.callbackBuffer.splice(0, this.prevBufferLen);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// 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;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (this.writeBuffer.length == 0) {
this.emit('drain');
} else {
this.flush();
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Flush write buffers.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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');
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sends a message.
*
* @param {String} message.
* @param {Function} callback function.
* @return {Socket} for chaining.
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.write =
Socket.prototype.send = function (msg, fn) {
this.sendPacket('message', msg, fn);
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sends a packet.
*
* @param {String} packet type.
* @param {String} data.
* @param {Function} callback function.
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.sendPacket = function (type, data, fn) {
var packet = { type: type, data: data };
this.emit('packetCreate', packet);
this.writeBuffer.push(packet);
this.callbackBuffer.push(fn);
this.flush();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Closes the connection.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.close = function () {
if ('opening' == this.readyState || 'open' == this.readyState) {
this.onClose('forced close');
debug('socket closing - telling transport to close');
this.transport.close();
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon transport error
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.onError = function (err) {
debug('socket error %j', err);
Socket.priorWebsocketSuccess = false;
this.emit('error', err);
this.onClose('transport error', err);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon transport close.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Socket.prototype.onClose = function (reason, desc) {
if ('opening' == this.readyState || 'open' == this.readyState) {
debug('socket close with reason: "%s"', reason);
var self = this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// clear timers
clearTimeout(this.pingIntervalTimer);
clearTimeout(this.pingTimeoutTimer);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// clean buffers in next tick, so developers can still
// grab the buffers on `close` event
setTimeout(function() {
self.writeBuffer = [];
self.callbackBuffer = [];
self.prevBufferLen = 0;
}, 0);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// stop event from firing again for transport
this.transport.removeAllListeners('close');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// ensure transport won't stay open
this.transport.close();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// ignore further transport communication
this.transport.removeAllListeners();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// set ready state
this.readyState = 'closed';
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// clear session id
this.id = null;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// emit close event
this.emit('close', reason, desc);
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Filters upgrades, returning only those matching client transports.
*
* @param {Array} server upgrades
* @api private
*
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"./transport":14,"./transports":15,"component-emitter":8,"debug":9,"engine.io-parser":22,"indexof":36,"parsejson":29,"parseqs":30,"parseuri":38}],14:[function(require,module,exports){
/**
* Module dependencies.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var parser = require('engine.io-parser');
var Emitter = require('component-emitter');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = Transport;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Transport abstract constructor.
*
* @param {Object} options.
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Mix in `Emitter`.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter(Transport.prototype);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* A counter used to prevent collisions in the timestamps used
* for cache busting.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Transport.timestamps = 0;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Emits an error.
*
* @param {String} str
* @return {Transport} for chaining
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Transport.prototype.onError = function (msg, desc) {
var err = new Error(msg);
err.type = 'TransportError';
err.description = desc;
this.emit('error', err);
return this;
};
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Opens the transport.
*
* @api public
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
Transport.prototype.open = function () {
if ('closed' == this.readyState || '' == this.readyState) {
this.readyState = 'opening';
this.doOpen();
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Closes the transport.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Transport.prototype.close = function () {
if ('opening' == this.readyState || 'open' == this.readyState) {
this.doClose();
this.onClose();
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return this;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sends multiple packets.
*
* @param {Array} packets
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Transport.prototype.send = function(packets){
if ('open' == this.readyState) {
this.write(packets);
} else {
throw new Error('Transport not open');
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon open
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Transport.prototype.onOpen = function () {
this.readyState = 'open';
this.writable = true;
this.emit('open');
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called with data.
*
* @param {String} data
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Transport.prototype.onData = function(data){
try {
var packet = parser.decodePacket(data, this.socket.binaryType);
this.onPacket(packet);
} catch(e){
e.data = data;
this.onError('parser decode error', e);
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called with a decoded packet.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Transport.prototype.onPacket = function (packet) {
this.emit('packet', packet);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon close.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Transport.prototype.onClose = function () {
this.readyState = 'closed';
this.emit('close');
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"component-emitter":8,"engine.io-parser":22}],15:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/**
* Module dependencies
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var XMLHttpRequest = require('xmlhttprequest');
var XHR = require('./polling-xhr');
var JSONP = require('./polling-jsonp');
var websocket = require('./websocket');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Export transports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
exports.polling = polling;
exports.websocket = websocket;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Polling transport polymorphic constructor.
* Decides on xhr vs jsonp based on feature detection.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function polling(opts){
var xhr;
var xd = false;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (global.location) {
var isSSL = 'https:' == location.protocol;
var port = location.port;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// some user agents have empty `location.port`
if (!port) {
port = isSSL ? 443 : 80;
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
xd = opts.hostname != location.hostname || port != opts.port;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
opts.xdomain = xd;
xhr = new XMLHttpRequest(opts);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if ('open' in xhr && !opts.forceJSONP) {
return new XHR(opts);
} else {
return new JSONP(opts);
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"./polling-jsonp":16,"./polling-xhr":17,"./websocket":19,"xmlhttprequest":20}],16:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
/**
* Module requirements.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var Polling = require('./polling');
var inherit = require('component-inherit');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = JSONPPolling;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Cached regular expressions.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var rNewline = /\n/g;
var rEscapedNewline = /\\n/g;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Global JSONP callbacks.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var callbacks;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Callbacks count.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var index = 0;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Noop.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function empty () { }
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* JSONP Polling constructor.
*
* @param {Object} opts.
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function JSONPPolling (opts) {
Polling.call(this, opts);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// callback identifier
this.index = callbacks.length;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// add callback to jsonp global
var self = this;
callbacks.push(function (msg) {
self.onData(msg);
});
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// 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;
});
}
}
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Inherits from Polling.
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
inherit(JSONPPolling, Polling);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/*
* JSONP only supports binary as base64 encoded strings
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
JSONPPolling.prototype.supportsBinary = false;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Closes the socket.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
JSONPPolling.prototype.doClose = function () {
if (this.script) {
this.script.parentNode.removeChild(this.script);
this.script = null;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (this.form) {
this.form.parentNode.removeChild(this.form);
this.form = null;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Polling.prototype.doClose.call(this);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Starts a poll cycle.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
JSONPPolling.prototype.doPoll = function () {
var self = this;
var script = document.createElement('script');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (this.script) {
this.script.parentNode.removeChild(this.script);
this.script = null;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
script.async = true;
script.src = this.uri();
script.onerror = function(e){
self.onError('jsonp poll error',e);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var insertAt = document.getElementsByTagName('script')[0];
insertAt.parentNode.insertBefore(script, insertAt);
this.script = script;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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);
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Writes with a hidden iframe.
*
* @param {String} data to send
* @param {Function} called upon flush.
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.form.action = this.uri();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function complete () {
initIframe();
fn();
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
function initIframe () {
if (self.iframe) {
try {
self.form.removeChild(self.iframe);
} catch (e) {
self.onError('jsonp polling iframe removal error', e);
}
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
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';
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
iframe.id = self.iframeId;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
self.form.appendChild(iframe);
self.iframe = iframe;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
initIframe();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// 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');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
try {
this.form.submit();
} catch(e) {}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (this.iframe.attachEvent) {
this.iframe.onreadystatechange = function(){
if (self.iframe.readyState == 'complete') {
complete();
}
};
} else {
this.iframe.onload = complete;
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"./polling":18,"component-inherit":21}],17:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/**
* Module requirements.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var XMLHttpRequest = require('xmlhttprequest');
var Polling = require('./polling');
var Emitter = require('component-emitter');
var inherit = require('component-inherit');
var debug = require('debug')('engine.io-client:polling-xhr');
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Module exports.
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
module.exports = XHR;
module.exports.Request = Request;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Empty function
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function empty(){}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* XHR Polling constructor.
*
* @param {Object} opts
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function XHR(opts){
Polling.call(this, opts);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (global.location) {
var isSSL = 'https:' == location.protocol;
var port = location.port;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// some user agents have empty `location.port`
if (!port) {
port = isSSL ? 443 : 80;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.xd = opts.hostname != global.location.hostname ||
port != opts.port;
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Inherits from Polling.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
inherit(XHR, Polling);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* XHR supports binary
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
XHR.prototype.supportsBinary = true;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Creates a request.
*
* @param {String} method
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
XHR.prototype.request = function(opts){
opts = opts || {};
opts.uri = this.uri();
opts.xd = this.xd;
opts.agent = this.agent || false;
opts.supportsBinary = this.supportsBinary;
return new Request(opts);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Sends data.
*
* @param {String} data to send.
* @param {Function} called upon flush.
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Starts a poll cycle.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Request constructor
*
* @param {Object} options
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function Request(opts){
this.method = opts.method || 'GET';
this.uri = opts.uri;
this.xd = !!opts.xd;
this.async = false !== opts.async;
this.data = undefined != opts.data ? opts.data : null;
this.agent = opts.agent;
this.create(opts.isBinary, opts.supportsBinary);
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
/**
* Mix in `Emitter`.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Emitter(Request.prototype);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Creates the XHR object and sends the request.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Request.prototype.create = function(isBinary, supportsBinary){
var xhr = this.xhr = new XMLHttpRequest({ agent: this.agent, xdomain: this.xd });
var self = this;
try {
debug('xhr open %s: %s', this.method, this.uri);
xhr.open(this.method, this.uri, this.async);
if (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';
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
if ('POST' == this.method) {
try {
if (isBinary) {
xhr.setRequestHeader('Content-type', 'application/octet-stream');
} else {
xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
}
} catch (e) {}
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
// ie6 check
if ('withCredentials' in xhr) {
xhr.withCredentials = true;
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
xhr.onreadystatechange = function(){
var data;
2014-06-17 16:20:22 +00:00
try {
2014-06-20 01:33:49 +00:00
if (4 != xhr.readyState) return;
if (200 == xhr.status || 1223 == xhr.status) {
var contentType = xhr.getResponseHeader('Content-Type');
if (contentType === 'application/octet-stream') {
data = xhr.response;
} else {
if (!supportsBinary) {
data = xhr.responseText;
} else {
data = 'ok';
}
}
} 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);
2014-06-17 16:20:22 +00:00
}
} catch (e) {
2014-06-20 01:33:49 +00:00
self.onError(e);
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
if (null != data) {
self.onData(data);
}
};
debug('xhr data %s', this.data);
xhr.send(this.data);
} catch (e) {
// Need to defer since .create() is called directly 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);
2014-06-17 16:20:22 +00:00
}, 0);
2014-06-20 01:33:49 +00:00
return;
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
if (global.document) {
this.index = Request.requestsCount++;
Request.requests[this.index] = this;
}
};
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Called upon successful response.
*
* @api private
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
Request.prototype.onSuccess = function(){
this.emit('success');
this.cleanup();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called if we have data.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Request.prototype.onData = function(data){
this.emit('data', data);
this.onSuccess();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon error.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Request.prototype.onError = function(err){
this.emit('error', err);
this.cleanup();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Cleans up house.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Request.prototype.cleanup = function(){
if ('undefined' == typeof this.xhr || null === this.xhr) {
return;
}
// xmlhttprequest
this.xhr.onreadystatechange = empty;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
try {
this.xhr.abort();
} catch(e) {}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (global.document) {
delete Request.requests[this.index];
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.xhr = null;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Aborts the request.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Request.prototype.abort = function(){
this.cleanup();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* 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.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (global.document) {
Request.requestsCount = 0;
Request.requests = {};
if (global.attachEvent) {
global.attachEvent('onunload', unloadHandler);
} else if (global.addEventListener) {
global.addEventListener('beforeunload', unloadHandler);
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function unloadHandler() {
for (var i in Request.requests) {
if (Request.requests.hasOwnProperty(i)) {
Request.requests[i].abort();
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"./polling":18,"component-emitter":8,"component-inherit":21,"debug":9,"xmlhttprequest":20}],18:[function(require,module,exports){
/**
* Module dependencies.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var Transport = require('../transport');
var parseqs = require('parseqs');
var parser = require('engine.io-parser');
var inherit = require('component-inherit');
var debug = require('debug')('engine.io-client:polling');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = Polling;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Is XHR2 supported?
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var hasXHR2 = (function() {
var XMLHttpRequest = require('xmlhttprequest');
var xhr = new XMLHttpRequest({ agent: this.agent, xdomain: false });
return null != xhr.responseType;
})();
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Polling interface.
*
* @param {Object} opts
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function Polling(opts){
var forceBase64 = (opts && opts.forceBase64);
if (!hasXHR2 || forceBase64) {
this.supportsBinary = false;
}
Transport.call(this, opts);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Inherits from Transport.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
inherit(Polling, Transport);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Transport name.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Polling.prototype.name = 'polling';
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Opens the socket (triggers polling). We write a PING message to determine
* when the transport is open.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Polling.prototype.doOpen = function(){
this.poll();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Pauses polling.
*
* @param {Function} callback upon buffers are flushed and transport is paused
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Polling.prototype.pause = function(onPause){
var pending = 0;
var self = this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.readyState = 'pausing';
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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();
});
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
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();
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Starts polling cycle.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Polling.prototype.poll = function(){
debug('polling');
this.polling = true;
this.doPoll();
this.emit('poll');
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Overloads onData to detect payloads.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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();
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// 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);
2014-06-17 16:20:22 +00:00
};
2014-06-20 01:33:49 +00:00
// decode payload
parser.decodePayload(data, this.socket.binaryType, callback);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// 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);
}
}
};
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* For polling, send a close packet.
*
* @api private
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
Polling.prototype.doClose = function(){
var self = this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function close(){
debug('writing close packet');
self.write([{ type: 'close' }]);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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);
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Writes a packets payload.
*
* @param {Array} data packets
* @param {Function} drain callback
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Polling.prototype.write = function(packets){
var self = this;
this.writable = false;
var callbackfn = function() {
self.writable = true;
self.emit('drain');
2014-06-17 16:20:22 +00:00
};
2014-06-20 01:33:49 +00:00
var self = this;
parser.encodePayload(packets, this.supportsBinary, function(data) {
self.doWrite(data, callbackfn);
});
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Generates uri for connection.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
Polling.prototype.uri = function(){
var query = this.query || {};
var schema = this.secure ? 'https' : 'http';
var port = '';
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// cache busting is forced
if (false !== this.timestampRequests) {
query[this.timestampParam] = +new Date + '-' + Transport.timestamps++;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (!this.supportsBinary && !query.sid) {
query.b64 = 1;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
query = parseqs.encode(query);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// avoid port if default for schema
if (this.port && (('https' == schema && this.port != 443) ||
('http' == schema && this.port != 80))) {
port = ':' + this.port;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// prepend ? to query
if (query.length) {
query = '?' + query;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return schema + '://' + this.hostname + port + this.path + query;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"../transport":14,"component-inherit":21,"debug":9,"engine.io-parser":22,"parseqs":30,"xmlhttprequest":20}],19:[function(require,module,exports){
/**
* Module dependencies.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var Transport = require('../transport');
var parser = require('engine.io-parser');
var parseqs = require('parseqs');
var inherit = require('component-inherit');
var debug = require('debug')('engine.io-client:websocket');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* `ws` exposes a WebSocket-compatible interface in
* Node, or the `WebSocket` or `MozWebSocket` globals
* in the browser.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var WebSocket = require('ws');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Module exports.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = WS;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* WebSocket transport constructor.
*
* @api {Object} connection options
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function WS(opts){
var forceBase64 = (opts && opts.forceBase64);
if (forceBase64) {
this.supportsBinary = false;
}
Transport.call(this, opts);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Inherits from Transport.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
inherit(WS, Transport);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Transport name.
*
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
WS.prototype.name = 'websocket';
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/*
* WebSockets support binary
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
WS.prototype.supportsBinary = true;
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Opens socket.
*
* @api private
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
WS.prototype.doOpen = function(){
if (!this.check()) {
// let probe timeout
return;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var self = this;
var uri = this.uri();
var protocols = void(0);
var opts = { agent: this.agent };
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.ws = new WebSocket(uri, protocols, opts);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (this.ws.binaryType === undefined) {
this.supportsBinary = false;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
this.ws.binaryType = 'arraybuffer';
this.addEventListeners();
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Adds event listeners to the socket
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
WS.prototype.addEventListeners = function(){
var self = this;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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);
};
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Override `onData` to use a timer on iOS.
* See: https://gist.github.com/mloughran/2052006
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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);
};
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Writes data to socket.
*
* @param {Array} array of packets.
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
WS.prototype.write = function(packets){
var self = this;
this.writable = false;
// encodePacket efficient as it uses WS framing
// no need for encodePayload
for (var i = 0, l = packets.length; i < l; i++) {
parser.encodePacket(packets[i], this.supportsBinary, function(data) {
//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 {
self.ws.send(data);
} catch (e){
debug('websocket closed before onclose event');
}
});
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function ondrain() {
self.writable = true;
self.emit('drain');
}
// fake drain
// defer to next tick to allow Socket to clear writeBuffer
setTimeout(ondrain, 0);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Called upon close
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
WS.prototype.onClose = function(){
Transport.prototype.onClose.call(this);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Closes socket.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
WS.prototype.doClose = function(){
if (typeof this.ws !== 'undefined') {
this.ws.close();
}
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Generates uri for connection.
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
WS.prototype.uri = function(){
var query = this.query || {};
var schema = this.secure ? 'wss' : 'ws';
var port = '';
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// avoid port if default for schema
if (this.port && (('wss' == schema && this.port != 443)
|| ('ws' == schema && this.port != 80))) {
port = ':' + this.port;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// append timestamp to URI
if (this.timestampRequests) {
query[this.timestampParam] = +new Date;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// communicate binary support capabilities
if (!this.supportsBinary) {
query.b64 = 1;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
query = parseqs.encode(query);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// prepend ? to query
if (query.length) {
query = '?' + query;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return schema + '://' + this.hostname + port + this.path + query;
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Feature detection for WebSocket.
*
* @return {Boolean} whether this transport is available.
* @api public
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
WS.prototype.check = function(){
return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"../transport":14,"component-inherit":21,"debug":9,"engine.io-parser":22,"parseqs":30,"ws":31}],20:[function(require,module,exports){
// browser shim for xmlhttprequest module
var hasCORS = require('has-cors');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
module.exports = function(opts) {
var xdomain = opts.xdomain;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// XMLHttpRequest can be disabled on IE
try {
if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) {
return new XMLHttpRequest();
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
} catch (e) { }
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (!xdomain) {
try {
return new ActiveXObject('Microsoft.XMLHTTP');
} catch(e) { }
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
},{"has-cors":34}],21:[function(require,module,exports){
module.exports = function(a, b){
var fn = function(){};
fn.prototype = b.prototype;
a.prototype = new fn;
a.prototype.constructor = a;
};
},{}],22:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/**
* Module dependencies.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var keys = require('./keys');
var sliceBuffer = require('arraybuffer.slice');
var base64encoder = require('base64-arraybuffer');
var after = require('after');
var utf8 = require('utf8');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Check if we are running an android browser. That requires us to use
* ArrayBuffer with polling transports...
*
* http://ghinda.net/jpeg-blob-ajax-android/
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var isAndroid = navigator.userAgent.match(/Android/i);
2014-06-17 16:20:22 +00:00
/**
2014-06-20 01:33:49 +00:00
* Current protocol version.
2014-06-17 16:20:22 +00:00
*/
2014-06-20 01:33:49 +00:00
exports.protocol = 2;
/**
* Packet types.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var packets = exports.packets = {
open: 0 // non-ws
, close: 1 // non-ws
, ping: 2
, pong: 3
, message: 4
, upgrade: 5
, noop: 6
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var packetslist = keys(packets);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Premade error packet.
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var err = { type: 'error', data: 'parser error' };
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Create a blob api even for blob builder when vendor prefixes exist
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var Blob = require('blob');
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Encodes a packet.
*
* <packet type id> [ <data> ]
*
* Example:
*
* 5hello world
* 3
* 4
*
* Binary is encoded in an identical principle
*
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
exports.encodePacket = function (packet, supportsBinary, callback) {
if (typeof supportsBinary == 'function') {
callback = supportsBinary;
supportsBinary = false;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var data = (packet.data === undefined)
? undefined
: packet.data.buffer || packet.data;
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (global.ArrayBuffer && data instanceof ArrayBuffer) {
return encodeArrayBuffer(packet, supportsBinary, callback);
} else if (Blob && data instanceof global.Blob) {
return encodeBlob(packet, supportsBinary, callback);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// Sending data as a utf-8 string
var encoded = packets[packet.type];
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
// data fragment is optional
if (undefined !== packet.data) {
encoded += utf8.encode(String(packet.data));
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return callback('' + encoded);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Encode packet helpers for binary types
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function encodeArrayBuffer(packet, supportsBinary, callback) {
if (!supportsBinary) {
return exports.encodeBase64Packet(packet, callback);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var data = packet.data;
var contentArray = new Uint8Array(data);
var resultBuffer = new Uint8Array(1 + data.byteLength);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
resultBuffer[0] = packets[packet.type];
for (var i = 0; i < contentArray.length; i++) {
resultBuffer[i+1] = contentArray[i];
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return callback(resultBuffer.buffer);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) {
if (!supportsBinary) {
return exports.encodeBase64Packet(packet, callback);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var fr = new FileReader();
fr.onload = function() {
packet.data = fr.result;
exports.encodePacket(packet, supportsBinary, callback);
2014-06-17 16:20:22 +00:00
};
2014-06-20 01:33:49 +00:00
return fr.readAsArrayBuffer(packet.data);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
function encodeBlob(packet, supportsBinary, callback) {
if (!supportsBinary) {
return exports.encodeBase64Packet(packet, callback);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (isAndroid) {
return encodeBlobAsArrayBuffer(packet, supportsBinary, callback);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
var length = new Uint8Array(1);
length[0] = packets[packet.type];
var blob = new Blob([length.buffer, packet.data]);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
return callback(blob);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Encodes a packet with binary data in a base64 string
*
* @param {Object} packet, has `type` and `data`
* @return {String} base64 encoded message
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
exports.encodeBase64Packet = function(packet, callback) {
var message = 'b' + exports.packets[packet.type];
if (Blob && packet.data instanceof Blob) {
var fr = new FileReader();
fr.onload = function() {
var b64 = fr.result.split(',')[1];
callback(message + b64);
};
return fr.readAsDataURL(packet.data);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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);
};
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
/**
* Decodes a packet. Changes format to Blob if requested.
*
* @return {Object} with `type` and `data` (if any)
* @api private
*/
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
exports.decodePacket = function (data, binaryType) {
// String data
if (typeof data == 'string' || data === undefined) {
if (data.charAt(0) == 'b') {
return exports.decodeBase64Packet(data.substr(1), binaryType);
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
data = utf8.decode(data);
var type = data.charAt(0);
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (Number(type) != type || !packetslist[type]) {
return err;
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
if (data.length > 1) {
return { type: packetslist[type], data: data.substring(1) };
} else {
return { type: packetslist[type] };
}
}
2014-06-17 16:20:22 +00:00
2014-06-20 01:33:49 +00:00
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;
}
if (supportsBinary) {
if (Blob && !isAndroid) {
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, supportsBinary, 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);
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, 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, 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 = [];
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;
msgLength += tailArray[i];
}
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), i, total);
});
};
},{"./keys":23,"after":24,"arraybuffer.slice":25,"base64-arraybuffer":26,"blob":27,"utf8":28}],23:[function(require,module,exports){
/**
* Gets the keys for an object.
*
* @return {Array} keys
* @api private
*/
module.exports = Object.keys || function keys (obj){
var arr = [];
var has = Object.prototype.hasOwnProperty;
for (var i in obj) {
if (has.call(obj, i)) {
arr.push(i);
}
}
return arr;
};
},{}],24:[function(require,module,exports){
module.exports = after
function after(count, callback, err_cb) {
var bail = false
err_cb = err_cb || noop
proxy.count = count
return (count === 0) ? callback() : proxy
function proxy(err, result) {
if (proxy.count <= 0) {
throw new Error('after called too many times')
}
--proxy.count
// after first error, rest are passed to err_cb
if (err) {
bail = true
callback(err)
// future error callbacks will go to error handler
callback = err_cb
} else if (proxy.count === 0 && !bail) {
callback(null, result)
}
}
}
function noop() {}
},{}],25:[function(require,module,exports){
/**
* An abstraction for slicing an arraybuffer even when
* ArrayBuffer.prototype.slice is not supported
*
* @api public
*/
module.exports = function(arraybuffer, start, end) {
var bytes = arraybuffer.byteLength;
start = start || 0;
end = end || bytes;
if (arraybuffer.slice) { return arraybuffer.slice(start, end); }
if (start < 0) { start += bytes; }
if (end < 0) { end += bytes; }
if (end > bytes) { end = bytes; }
if (start >= bytes || start >= end || bytes === 0) {
return new ArrayBuffer(0);
}
var abv = new Uint8Array(arraybuffer);
var result = new Uint8Array(end - start);
for (var i = start, ii = 0; i < end; i++, ii++) {
result[ii] = abv[i];
}
return result.buffer;
};
},{}],26:[function(require,module,exports){
/*
* base64-arraybuffer
* https://github.com/niklasvh/base64-arraybuffer
*
* Copyright (c) 2012 Niklas von Hertzen
* Licensed under the MIT license.
*/
(function(chars){
"use strict";
exports.encode = function(arraybuffer) {
var bytes = new Uint8Array(arraybuffer),
i, len = bytes.length, base64 = "";
for (i = 0; i < len; i+=3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
base64 += chars[bytes[i + 2] & 63];
}
if ((len % 3) === 2) {
base64 = base64.substring(0, base64.length - 1) + "=";
} else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2) + "==";
}
return base64;
};
exports.decode = function(base64) {
var bufferLength = base64.length * 0.75,
len = base64.length, i, p = 0,
encoded1, encoded2, encoded3, encoded4;
if (base64[base64.length - 1] === "=") {
bufferLength--;
if (base64[base64.length - 2] === "=") {
bufferLength--;
}
}
var arraybuffer = new ArrayBuffer(bufferLength),
bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i+=4) {
encoded1 = chars.indexOf(base64[i]);
encoded2 = chars.indexOf(base64[i+1]);
encoded3 = chars.indexOf(base64[i+2]);
encoded4 = chars.indexOf(base64[i+3]);
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
}
return arraybuffer;
};
})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
},{}],27:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/**
* 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 b = new Blob(['hi']);
return b.size == 2;
} catch(e) {
return false;
}
})();
/**
* Check if BlobBuilder is supported
*/
var blobBuilderSupported = BlobBuilder
&& BlobBuilder.prototype.append
&& BlobBuilder.prototype.getBlob;
function BlobBuilderConstructor(ary, options) {
options = options || {};
var bb = new BlobBuilder();
for (var i = 0; i < ary.length; i++) {
bb.append(ary[i]);
}
return (options.type) ? bb.getBlob(options.type) : bb.getBlob();
};
module.exports = (function() {
if (blobSupported) {
return global.Blob;
} else if (blobBuilderSupported) {
return BlobBuilderConstructor;
} else {
return undefined;
}
})();
},{}],28:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/*! http://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 http://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 http://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 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
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);
// console.log(JSON.stringify(codePoints.map(function(x) {
// return 'U+' + x.toString(16).toUpperCase();
// })));
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) {
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));
},{}],29:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/**
* 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))();
}
};
},{}],30:[function(require,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;
};
},{}],31:[function(require,module,exports){
/**
* Module dependencies.
*/
var global = (function() { return this; })();
/**
* WebSocket constructor.
*/
var WebSocket = global.WebSocket || global.MozWebSocket;
/**
* Module exports.
*/
module.exports = WebSocket ? ws : null;
/**
* WebSocket constructor.
*
* The third `opts` options object gets ignored in web browsers, since it's
* non-standard, and throws a TypeError if passed to the constructor.
* See: https://github.com/einaros/ws/issues/227
*
* @param {String} uri
* @param {Array} protocols (optional)
* @param {Object) opts (optional)
* @api public
*/
function ws(uri, protocols, opts) {
var instance;
if (protocols) {
instance = new WebSocket(uri, protocols);
} else {
instance = new WebSocket(uri);
}
return instance;
}
if (WebSocket) ws.prototype = WebSocket.prototype;
},{}],32:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/*
* Module requirements.
*/
var isArray = require('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 recursiveCheckForBinary(obj) {
if (!obj) return false;
if ( (global.Buffer && 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 (recursiveCheckForBinary(obj[i])) {
return true;
}
}
} else if (obj && 'object' == typeof obj) {
if (obj.toJSON) {
obj = obj.toJSON();
}
for (var key in obj) {
if (recursiveCheckForBinary(obj[key])) {
return true;
}
}
}
return false;
}
return recursiveCheckForBinary(data);
}
},{"isarray":33}],33:[function(require,module,exports){
module.exports = Array.isArray || function (arr) {
return Object.prototype.toString.call(arr) == '[object Array]';
};
},{}],34:[function(require,module,exports){
/**
* Module dependencies.
*/
var global = require('global');
/**
* Module exports.
*
* Logic borrowed from Modernizr:
*
* - https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cors.js
*/
try {
module.exports = 'XMLHttpRequest' in global &&
'withCredentials' in new global.XMLHttpRequest();
} catch (err) {
// if XMLHttp support is disabled in IE then it will throw
// when trying to create
module.exports = false;
}
},{"global":35}],35:[function(require,module,exports){
/**
* Returns `this`. Execute this without a "context" (i.e. without it being
* attached to an object of the left-hand side), and `this` points to the
* "global" scope of the current JS execution.
*/
module.exports = (function () { return this; })();
},{}],36:[function(require,module,exports){
var indexOf = [].indexOf;
module.exports = function(arr, obj){
if (indexOf) return arr.indexOf(obj);
for (var i = 0; i < arr.length; ++i) {
if (arr[i] === obj) return i;
}
return -1;
};
},{}],37:[function(require,module,exports){
/**
* HOP ref.
*/
var has = Object.prototype.hasOwnProperty;
/**
* Return own keys in `obj`.
*
* @param {Object} obj
* @return {Array}
* @api public
*/
exports.keys = Object.keys || function(obj){
var keys = [];
for (var key in obj) {
if (has.call(obj, key)) {
keys.push(key);
}
}
return keys;
};
/**
* Return own values in `obj`.
*
* @param {Object} obj
* @return {Array}
* @api public
*/
exports.values = function(obj){
var vals = [];
for (var key in obj) {
if (has.call(obj, key)) {
vals.push(obj[key]);
}
}
return vals;
};
/**
* Merge `b` into `a`.
*
* @param {Object} a
* @param {Object} b
* @return {Object} a
* @api public
*/
exports.merge = function(a, b){
for (var key in b) {
if (has.call(b, key)) {
a[key] = b[key];
}
}
return a;
};
/**
* Return length of `obj`.
*
* @param {Object} obj
* @return {Number}
* @api public
*/
exports.length = function(obj){
return exports.keys(obj).length;
};
/**
* Check if `obj` is empty.
*
* @param {Object} obj
* @return {Boolean}
* @api public
*/
exports.isEmpty = function(obj){
return 0 == exports.length(obj);
};
},{}],38:[function(require,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 m = re.exec(str || '')
, uri = {}
, i = 14;
while (i--) {
uri[parts[i]] = m[i] || '';
}
return uri;
};
},{}],39:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/**
* Modle requirements
*/
var isArray = require('isarray');
/**
* Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder.
* Anything with blobs or files should be fed through removeBlobs before coming
* here.
*
* @param {Object} packet - socket.io event packet
* @return {Object} with deconstructed packet and list of buffers
* @api public
*/
exports.deconstructPacket = function(packet) {
var buffers = [];
var packetData = packet.data;
function deconstructBinPackRecursive(data) {
if (!data) return data;
if ((global.Buffer && Buffer.isBuffer(data)) ||
(global.ArrayBuffer && data instanceof ArrayBuffer)) { // replace binary
var placeholder = {_placeholder: true, num: buffers.length};
buffers.push(data);
return placeholder;
} else if (isArray(data)) {
var newData = new Array(data.length);
for (var i = 0; i < data.length; i++) {
newData[i] = deconstructBinPackRecursive(data[i]);
}
return newData;
} else if ('object' == typeof data && !(data instanceof Date)) {
var newData = {};
for (var key in data) {
newData[key] = deconstructBinPackRecursive(data[key]);
}
return newData;
}
return data;
}
var pack = packet;
pack.data = deconstructBinPackRecursive(packetData);
pack.attachments = buffers.length; // number of binary 'attachments'
return {packet: pack, buffers: buffers};
}
/**
* Reconstructs a binary packet from its placeholder packet and buffers
*
* @param {Object} packet - event packet with placeholders
* @param {Array} buffers - binary buffers to put in placeholder positions
* @return {Object} reconstructed packet
* @api public
*/
exports.reconstructPacket = function(packet, buffers) {
var curPlaceHolder = 0;
function reconstructBinPackRecursive(data) {
if (data && data._placeholder) {
var buf = buffers[data.num]; // appropriate buffer (should be natural order anyway)
return buf;
} else if (isArray(data)) {
for (var i = 0; i < data.length; i++) {
data[i] = reconstructBinPackRecursive(data[i]);
}
return data;
} else if (data && 'object' == typeof data) {
for (var key in data) {
data[key] = reconstructBinPackRecursive(data[key]);
}
return data;
}
return data;
}
packet.data = reconstructBinPackRecursive(packet.data);
packet.attachments = undefined; // no longer useful
return packet;
}
/**
* Asynchronously removes Blobs or Files from data via
* FileReader's readAsArrayBuffer method. Used before encoding
* data as msgpack. Calls callback with the blobless data.
*
* @param {Object} data
* @param {Function} callback
* @api private
*/
exports.removeBlobs = function(data, callback) {
function removeBlobsRecursive(obj, curKey, containingObject) {
if (!obj) return obj;
// convert any blob
if ((global.Blob && obj instanceof Blob) ||
(global.File && obj instanceof File)) {
pendingBlobs++;
// async filereader
var fileReader = new FileReader();
fileReader.onload = function() { // this.result == arraybuffer
if (containingObject) {
containingObject[curKey] = this.result;
}
else {
bloblessData = this.result;
}
// if nothing pending its callback time
if(! --pendingBlobs) {
callback(bloblessData);
}
};
fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer
}
if (isArray(obj)) { // handle array
for (var i = 0; i < obj.length; i++) {
removeBlobsRecursive(obj[i], i, obj);
}
} else if (obj && 'object' == typeof obj && !isBuf(obj)) { // and object
for (var key in obj) {
removeBlobsRecursive(obj[key], key, obj);
}
}
}
var pendingBlobs = 0;
var bloblessData = data;
removeBlobsRecursive(bloblessData);
if (!pendingBlobs) {
callback(bloblessData);
}
}
/**
* Returns true if obj is a buffer or an arraybuffer.
*
* @api private
*/
function isBuf(obj) {
return (global.Buffer && Buffer.isBuffer(obj)) ||
(global.ArrayBuffer && obj instanceof ArrayBuffer);
}
},{"isarray":41}],40:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
/**
* Module dependencies.
*/
var debug = require('debug')('socket.io-parser');
var json = require('json3');
var isArray = require('isarray');
var Emitter = require('emitter');
var binary = require('./binary');
/**
* Protocol version.
*
* @api public
*/
exports.protocol = 3;
/**
* Packet types.
*
* @api public
*/
exports.types = [
'CONNECT',
'DISCONNECT',
'EVENT',
'BINARY_EVENT',
'ACK',
'BINARY_ACK',
'ERROR'
];
/**
* Packet type `connect`.
*
* @api public
*/
exports.CONNECT = 0;
/**
* Packet type `disconnect`.
*
* @api public
*/
exports.DISCONNECT = 1;
/**
* Packet type `event`.
*
* @api public
*/
exports.EVENT = 2;
/**
* Packet type `ack`.
*
* @api public
*/
exports.ACK = 3;
/**
* Packet type `error`.
*
* @api public
*/
exports.ERROR = 4;
/**
* Packet type 'binary event'
*
* @api public
*/
exports.BINARY_EVENT = 5;
/**
* Packet type `binary ack`. For acks with binary arguments.
*
* @api public
*/
exports.BINARY_ACK = 6;
exports.Encoder = Encoder
/**
* A socket.io Encoder instance
*
* @api public
*/
function Encoder() {};
/**
* Encode a packet as a single string if non-binary, or as a
* buffer sequence, depending on packet type.
*
* @param {Object} obj - packet object
* @param {Function} callback - function to handle encodings (likely engine.write)
* @return Calls callback with Array of encodings
* @api public
*/
Encoder.prototype.encode = function(obj, callback){
debug('encoding packet %j', obj);
if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) {
encodeAsBinary(obj, callback);
}
else {
var encoding = encodeAsString(obj);
callback([encoding]);
}
};
/**
* Encode packet as string.
*
* @param {Object} packet
* @return {String} encoded
* @api private
*/
function encodeAsString(obj) {
var str = '';
var nsp = false;
// first is type
str += obj.type;
// attachments if we have them
if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) {
str += obj.attachments;
str += '-';
}
// if we have a namespace other than `/`
// we append it followed by a comma `,`
if (obj.nsp && '/' != obj.nsp) {
nsp = true;
str += obj.nsp;
}
// immediately followed by the id
if (null != obj.id) {
if (nsp) {
str += ',';
nsp = false;
}
str += obj.id;
}
// json data
if (null != obj.data) {
if (nsp) str += ',';
str += json.stringify(obj.data);
}
debug('encoded %j as %s', obj, str);
return str;
}
/**
* Encode packet as 'buffer sequence' by removing blobs, and
* deconstructing packet into object with placeholders and
* a list of buffers.
*
* @param {Object} packet
* @return {Buffer} encoded
* @api private
*/
function encodeAsBinary(obj, callback) {
function writeEncoding(bloblessData) {
var deconstruction = binary.deconstructPacket(bloblessData);
var pack = encodeAsString(deconstruction.packet);
var buffers = deconstruction.buffers;
buffers.unshift(pack); // add packet info to beginning of data list
callback(buffers); // write all the buffers
}
binary.removeBlobs(obj, writeEncoding);
}
exports.Decoder = Decoder
/**
* A socket.io Decoder instance
*
* @return {Object} decoder
* @api public
*/
function Decoder() {
this.reconstructor = null;
}
/**
* Mix in `Emitter` with Decoder.
*/
Emitter(Decoder.prototype);
/**
* Decodes an ecoded packet string into packet JSON.
*
* @param {String} obj - encoded packet
* @return {Object} packet
* @api public
*/
Decoder.prototype.add = function(obj) {
var packet;
if ('string' == typeof obj) {
packet = decodeString(obj);
if (exports.BINARY_EVENT == packet.type || exports.BINARY_ACK == packet.type) { // binary packet's json
this.reconstructor = new BinaryReconstructor(packet);
// no attachments, labeled binary but no binary data to follow
if (this.reconstructor.reconPack.attachments == 0) {
this.emit('decoded', packet);
}
} else { // non-binary full packet
this.emit('decoded', packet);
}
}
else if ((global.Buffer && Buffer.isBuffer(obj)) ||
(global.ArrayBuffer && obj instanceof ArrayBuffer) ||
obj.base64) { // raw binary data
if (!this.reconstructor) {
throw new Error('got binary data when not reconstructing a packet');
} else {
packet = this.reconstructor.takeBinaryData(obj);
if (packet) { // received final buffer
this.reconstructor = null;
this.emit('decoded', packet);
}
}
}
else {
throw new Error('Unknown type: ' + obj);
}
}
/**
* Decode a packet String (JSON data)
*
* @param {String} str
* @return {Object} packet
* @api private
*/
function decodeString(str) {
var p = {};
var i = 0;
// look up type
p.type = Number(str.charAt(0));
if (null == exports.types[p.type]) return error();
// look up attachments if type binary
if (exports.BINARY_EVENT == p.type || exports.BINARY_ACK == p.type) {
p.attachments = '';
while (str.charAt(++i) != '-') {
p.attachments += str.charAt(i);
}
p.attachments = Number(p.attachments);
}
// look up namespace (if any)
if ('/' == str.charAt(i + 1)) {
p.nsp = '';
while (++i) {
var c = str.charAt(i);
if (',' == c) break;
p.nsp += c;
if (i + 1 == str.length) break;
}
} else {
p.nsp = '/';
}
// look up id
var next = str.charAt(i + 1);
if ('' != next && Number(next) == next) {
p.id = '';
while (++i) {
var c = str.charAt(i);
if (null == c || Number(c) != c) {
--i;
break;
}
p.id += str.charAt(i);
if (i + 1 == str.length) break;
}
p.id = Number(p.id);
}
// look up json data
if (str.charAt(++i)) {
try {
p.data = json.parse(str.substr(i));
} catch(e){
return error();
}
}
debug('decoded %s as %j', str, p);
return p;
};
/**
* Deallocates a parser's resources
*
* @api public
*/
Decoder.prototype.destroy = function() {
if (this.reconstructor) {
this.reconstructor.finishedReconstruction();
}
}
/**
* A manager of a binary event's 'buffer sequence'. Should
* be constructed whenever a packet of type BINARY_EVENT is
* decoded.
*
* @param {Object} packet
* @return {BinaryReconstructor} initialized reconstructor
* @api private
*/
function BinaryReconstructor(packet) {
this.reconPack = packet;
this.buffers = [];
}
/**
* Method to be called when binary data received from connection
* after a BINARY_EVENT packet.
*
* @param {Buffer | ArrayBuffer} binData - the raw binary data received
* @return {null | Object} returns null if more binary data is expected or
* a reconstructed packet object if all buffers have been received.
* @api private
*/
BinaryReconstructor.prototype.takeBinaryData = function(binData) {
this.buffers.push(binData);
if (this.buffers.length == this.reconPack.attachments) { // done with buffer list
var packet = binary.reconstructPacket(this.reconPack, this.buffers);
this.finishedReconstruction();
return packet;
}
return null;
}
/**
* Cleans up binary packet reconstruction variables.
*
* @api private
*/
BinaryReconstructor.prototype.finishedReconstruction = function() {
this.reconPack = null;
this.buffers = [];
}
function error(data){
return {
type: exports.ERROR,
data: 'parser error'
};
}
},{"./binary":39,"debug":9,"emitter":10,"isarray":41,"json3":42}],41:[function(require,module,exports){
module.exports=require(33)
},{}],42:[function(require,module,exports){
/*! JSON v3.2.6 | http://bestiejs.github.io/json3 | Copyright 2012-2013, Kit Cambridge | http://kit.mit-license.org */
;(function (window) {
// Convenience aliases.
var getClass = {}.toString, isProperty, forEach, undef;
// Detect the `define` function exposed by asynchronous module loaders. The
// strict `define` check is necessary for compatibility with `r.js`.
var isLoader = typeof define === "function" && define.amd;
// Detect native implementations.
var nativeJSON = typeof JSON == "object" && JSON;
// Set up the JSON 3 namespace, preferring the CommonJS `exports` object if
// available.
var JSON3 = typeof exports == "object" && exports && !exports.nodeType && exports;
if (JSON3 && nativeJSON) {
// Explicitly delegate to the native `stringify` and `parse`
// implementations in CommonJS environments.
JSON3.stringify = nativeJSON.stringify;
JSON3.parse = nativeJSON.parse;
} else {
// Export for web browsers, JavaScript engines, and asynchronous module
// loaders, using the global `JSON` object if available.
JSON3 = window.JSON = nativeJSON || {};
}
// Test the `Date#getUTC*` methods. Based on work by @Yaffle.
var isExtended = new Date(-3509827334573292);
try {
// The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
// results for certain dates in Opera >= 10.53.
isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 &&
// Safari < 2.0.2 stores the internal millisecond time value correctly,
// but clips the values returned by the date methods to the range of
// signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
} catch (exception) {}
// Internal: Determines whether the native `JSON.stringify` and `parse`
// implementations are spec-compliant. Based on work by Ken Snyder.
function has(name) {
if (has[name] !== undef) {
// Return cached feature test result.
return has[name];
}
var isSupported;
if (name == "bug-string-char-index") {
// IE <= 7 doesn't support accessing string characters using square
// bracket notation. IE 8 only supports this for primitives.
isSupported = "a"[0] != "a";
} else if (name == "json") {
// Indicates whether both `JSON.stringify` and `JSON.parse` are
// supported.
isSupported = has("json-stringify") && has("json-parse");
} else {
var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';
// Test `JSON.stringify`.
if (name == "json-stringify") {
var stringify = JSON3.stringify, stringifySupported = typeof stringify == "function" && isExtended;
if (stringifySupported) {
// A test function object with a custom `toJSON` method.
(value = function () {
return 1;
}).toJSON = value;
try {
stringifySupported =
// Firefox 3.1b1 and b2 serialize string, number, and boolean
// primitives as object literals.
stringify(0) === "0" &&
// FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
// literals.
stringify(new Number()) === "0" &&
stringify(new String()) == '""' &&
// FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
// does not define a canonical JSON representation (this applies to
// objects with `toJSON` properties as well, *unless* they are nested
// within an object or array).
stringify(getClass) === undef &&
// IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
// FF 3.1b3 pass this test.
stringify(undef) === undef &&
// Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
// respectively, if the value is omitted entirely.
stringify() === undef &&
// FF 3.1b1, 2 throw an error if the given value is not a number,
// string, array, object, Boolean, or `null` literal. This applies to
// objects with custom `toJSON` methods as well, unless they are nested
// inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
// methods entirely.
stringify(value) === "1" &&
stringify([value]) == "[1]" &&
// Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
// `"[null]"`.
stringify([undef]) == "[null]" &&
// YUI 3.0.0b1 fails to serialize `null` literals.
stringify(null) == "null" &&
// FF 3.1b1, 2 halts serialization if an array contains a function:
// `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3
// elides non-JSON values from objects and arrays, unless they
// define custom `toJSON` methods.
stringify([undef, getClass, null]) == "[null,null,null]" &&
// Simple serialization test. FF 3.1b1 uses Unicode escape sequences
// where character escape codes are expected (e.g., `\b` => `\u0008`).
stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized &&
// FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
stringify(null, value) === "1" &&
stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
// JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
// serialize extended years.
stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
// The milliseconds are optional in ES 5, but required in 5.1.
stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
// Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
// four-digit years instead of six-digit years. Credits: @Yaffle.
stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
// Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
// values less than 1000. Credits: @Yaffle.
stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
} catch (exception) {
stringifySupported = false;
}
}
isSupported = stringifySupported;
}
// Test `JSON.parse`.
if (name == "json-parse") {
var parse = JSON3.parse;
if (typeof parse == "function") {
try {
// FF 3.1b1, b2 will throw an exception if a bare literal is provided.
// Conforming implementations should also coerce the initial argument to
// a string prior to parsing.
if (parse("0") === 0 && !parse(false)) {
// Simple parsing test.
value = parse(serialized);
var parseSupported = value["a"].length == 5 && value["a"][0] === 1;
if (parseSupported) {
try {
// Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
parseSupported = !parse('"\t"');
} catch (exception) {}
if (parseSupported) {
try {
// FF 4.0 and 4.0.1 allow leading `+` signs and leading
// decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow
// certain octal literals.
parseSupported = parse("01") !== 1;
} catch (exception) {}
}
if (parseSupported) {
try {
// FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal
// points. These environments, along with FF 3.1b1 and 2,
// also allow trailing commas in JSON objects and arrays.
parseSupported = parse("1.") !== 1;
} catch (exception) {}
}
}
}
} catch (exception) {
parseSupported = false;
}
}
isSupported = parseSupported;
}
}
return has[name] = !!isSupported;
}
if (!has("json")) {
// Common `[[Class]]` name aliases.
var functionClass = "[object Function]";
var dateClass = "[object Date]";
var numberClass = "[object Number]";
var stringClass = "[object String]";
var arrayClass = "[object Array]";
var booleanClass = "[object Boolean]";
// Detect incomplete support for accessing string characters by index.
var charIndexBuggy = has("bug-string-char-index");
// Define additional utility methods if the `Date` methods are buggy.
if (!isExtended) {
var floor = Math.floor;
// A mapping between the months of the year and the number of days between
// January 1st and the first of the respective month.
var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
// Internal: Calculates the number of days between the Unix epoch and the
// first day of the given month.
var getDay = function (year, month) {
return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
};
}
// Internal: Determines if a property is a direct property of the given
// object. Delegates to the native `Object#hasOwnProperty` method.
if (!(isProperty = {}.hasOwnProperty)) {
isProperty = function (property) {
var members = {}, constructor;
if ((members.__proto__ = null, members.__proto__ = {
// The *proto* property cannot be set multiple times in recent
// versions of Firefox and SeaMonkey.
"toString": 1
}, members).toString != getClass) {
// Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
// supports the mutable *proto* property.
isProperty = function (property) {
// Capture and break the object's prototype chain (see section 8.6.2
// of the ES 5.1 spec). The parenthesized expression prevents an
// unsafe transformation by the Closure Compiler.
var original = this.__proto__, result = property in (this.__proto__ = null, this);
// Restore the original prototype chain.
this.__proto__ = original;
return result;
};
} else {
// Capture a reference to the top-level `Object` constructor.
constructor = members.constructor;
// Use the `constructor` property to simulate `Object#hasOwnProperty` in
// other environments.
isProperty = function (property) {
var parent = (this.constructor || constructor).prototype;
return property in this && !(property in parent && this[property] === parent[property]);
};
}
members = null;
return isProperty.call(this, property);
};
}
// Internal: A set of primitive types used by `isHostType`.
var PrimitiveTypes = {
'boolean': 1,
'number': 1,
'string': 1,
'undefined': 1
};
// Internal: Determines if the given object `property` value is a
// non-primitive.
var isHostType = function (object, property) {
var type = typeof object[property];
return type == 'object' ? !!object[property] : !PrimitiveTypes[type];
};
// Internal: Normalizes the `for...in` iteration algorithm across
// environments. Each enumerated key is yielded to a `callback` function.
forEach = function (object, callback) {
var size = 0, Properties, members, property;
// Tests for bugs in the current environment's `for...in` algorithm. The
// `valueOf` property inherits the non-enumerable flag from
// `Object.prototype` in older versions of IE, Netscape, and Mozilla.
(Properties = function () {
this.valueOf = 0;
}).prototype.valueOf = 0;
// Iterate over a new instance of the `Properties` class.
members = new Properties();
for (property in members) {
// Ignore all properties inherited from `Object.prototype`.
if (isProperty.call(members, property)) {
size++;
}
}
Properties = members = null;
// Normalize the iteration algorithm.
if (!size) {
// A list of non-enumerable properties inherited from `Object.prototype`.
members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
// IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
// properties.
forEach = function (object, callback) {
var isFunction = getClass.call(object) == functionClass, property, length;
var hasProperty = !isFunction && typeof object.constructor != 'function' && isHostType(object, 'hasOwnProperty') ? object.hasOwnProperty : isProperty;
for (property in object) {
// Gecko <= 1.0 enumerates the `prototype` property of functions under
// certain conditions; IE does not.
if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) {
callback(property);
}
}
// Manually invoke the callback for each non-enumerable property.
for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property));
};
} else if (size == 2) {
// Safari <= 2.0.4 enumerates shadowed properties twice.
forEach = function (object, callback) {
// Create a set of iterated properties.
var members = {}, isFunction = getClass.call(object) == functionClass, property;
for (property in object) {
// Store each property name to prevent double enumeration. The
// `prototype` property of functions is not enumerated due to cross-
// environment inconsistencies.
if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
callback(property);
}
}
};
} else {
// No bugs detected; use the standard `for...in` algorithm.
forEach = function (object, callback) {
var isFunction = getClass.call(object) == functionClass, property, isConstructor;
for (property in object) {
if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
callback(property);
}
}
// Manually invoke the callback for the `constructor` property due to
// cross-environment inconsistencies.
if (isConstructor || isProperty.call(object, (property = "constructor"))) {
callback(property);
}
};
}
return forEach(object, callback);
};
// Public: Serializes a JavaScript `value` as a JSON string. The optional
// `filter` argument may specify either a function that alters how object and
// array members are serialized, or an array of strings and numbers that
// indicates which properties should be serialized. The optional `width`
// argument may be either a string or number that specifies the indentation
// level of the output.
if (!has("json-stringify")) {
// Internal: A map of control characters and their escaped equivalents.
var Escapes = {
92: "\\\\",
34: '\\"',
8: "\\b",
12: "\\f",
10: "\\n",
13: "\\r",
9: "\\t"
};
// Internal: Converts `value` into a zero-padded string such that its
// length is at least equal to `width`. The `width` must be <= 6.
var leadingZeroes = "000000";
var toPaddedString = function (width, value) {
// The `|| 0` expression is necessary to work around a bug in
// Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
return (leadingZeroes + (value || 0)).slice(-width);
};
// Internal: Double-quotes a string `value`, replacing all ASCII control
// characters (characters with code unit values between 0 and 31) with
// their escaped equivalents. This is an implementation of the
// `Quote(value)` operation defined in ES 5.1 section 15.12.3.
var unicodePrefix = "\\u00";
var quote = function (value) {
var result = '"', index = 0, length = value.length, isLarge = length > 10 && charIndexBuggy, symbols;
if (isLarge) {
symbols = value.split("");
}
for (; index < length; index++) {
var charCode = value.charCodeAt(index);
// If the character is a control character, append its Unicode or
// shorthand escape sequence; otherwise, append the character as-is.
switch (charCode) {
case 8: case 9: case 10: case 12: case 13: case 34: case 92:
result += Escapes[charCode];
break;
default:
if (charCode < 32) {
result += unicodePrefix + toPaddedString(2, charCode.toString(16));
break;
}
result += isLarge ? symbols[index] : charIndexBuggy ? value.charAt(index) : value[index];
}
}
return result + '"';
};
// Internal: Recursively serializes an object. Implements the
// `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
var serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result;
try {
// Necessary for host object support.
value = object[property];
} catch (exception) {}
if (typeof value == "object" && value) {
className = getClass.call(value);
if (className == dateClass && !isProperty.call(value, "toJSON")) {
if (value > -1 / 0 && value < 1 / 0) {
// Dates are serialized according to the `Date#toJSON` method
// specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
// for the ISO 8601 date time string format.
if (getDay) {
// Manually compute the year, month, date, hours, minutes,
// seconds, and milliseconds if the `getUTC*` methods are
// buggy. Adapted from @Yaffle's `date-shim` project.
date = floor(value / 864e5);
for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
date = 1 + date - getDay(year, month);
// The `time` value specifies the time within the day (see ES
// 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
// to compute `A modulo B`, as the `%` operator does not
// correspond to the `modulo` operation for negative numbers.
time = (value % 864e5 + 864e5) % 864e5;
// The hours, minutes, seconds, and milliseconds are obtained by
// decomposing the time within the day. See section 15.9.1.10.
hours = floor(time / 36e5) % 24;
minutes = floor(time / 6e4) % 60;
seconds = floor(time / 1e3) % 60;
milliseconds = time % 1e3;
} else {
year = value.getUTCFullYear();
month = value.getUTCMonth();
date = value.getUTCDate();
hours = value.getUTCHours();
minutes = value.getUTCMinutes();
seconds = value.getUTCSeconds();
milliseconds = value.getUTCMilliseconds();
}
// Serialize extended years correctly.
value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
"-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
// Months, dates, hours, minutes, and seconds should have two
// digits; milliseconds should have three.
"T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
// Milliseconds are optional in ES 5.0, but required in 5.1.
"." + toPaddedString(3, milliseconds) + "Z";
} else {
value = null;
}
} else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) {
// Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
// `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
// ignores all `toJSON` methods on these objects unless they are
// defined directly on an instance.
value = value.toJSON(property);
}
}
if (callback) {
// If a replacement function was provided, call it to obtain the value
// for serialization.
value = callback.call(object, property, value);
}
if (value === null) {
return "null";
}
className = getClass.call(value);
if (className == booleanClass) {
// Booleans are represented literally.
return "" + value;
} else if (className == numberClass) {
// JSON numbers must be finite. `Infinity` and `NaN` are serialized as
// `"null"`.
return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
} else if (className == stringClass) {
// Strings are double-quoted and escaped.
return quote("" + value);
}
// Recursively serialize objects and arrays.
if (typeof value == "object") {
// Check for cyclic structures. This is a linear search; performance
// is inversely proportional to the number of unique nested objects.
for (length = stack.length; length--;) {
if (stack[length] === value) {
// Cyclic structures cannot be serialized by `JSON.stringify`.
throw TypeError();
}
}
// Add the object to the stack of traversed objects.
stack.push(value);
results = [];
// Save the current indentation level and indent one additional level.
prefix = indentation;
indentation += whitespace;
if (className == arrayClass) {
// Recursively serialize array elements.
for (index = 0, length = value.length; index < length; index++) {
element = serialize(index, value, callback, properties, whitespace, indentation, stack);
results.push(element === undef ? "null" : element);
}
result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
} else {
// Recursively serialize object members. Members are selected from
// either a user-specified list of property names, or the object
// itself.
forEach(properties || value, function (property) {
var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
if (element !== undef) {
// According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
// is not the empty string, let `member` {quote(property) + ":"}
// be the concatenation of `member` and the `space` character."
// The "`space` character" refers to the literal space
// character, not the `space` {width} argument provided to
// `JSON.stringify`.
results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
}
});
result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
}
// Remove the object from the traversed object stack.
stack.pop();
return result;
}
};
// Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
JSON3.stringify = function (source, filter, width) {
var whitespace, callback, properties, className;
if (typeof filter == "function" || typeof filter == "object" && filter) {
if ((className = getClass.call(filter)) == functionClass) {
callback = filter;
} else if (className == arrayClass) {
// Convert the property names array into a makeshift set.
properties = {};
for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1));
}
}
if (width) {
if ((className = getClass.call(width)) == numberClass) {
// Convert the `width` to an integer and create a string containing
// `width` number of space characters.
if ((width -= width % 1) > 0) {
for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
}
} else if (className == stringClass) {
whitespace = width.length <= 10 ? width : width.slice(0, 10);
}
}
// Opera <= 7.54u2 discards the values associated with empty string keys
// (`""`) only if they are used directly within an object member list
// (e.g., `!("" in { "": 1})`).
return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
};
}
// Public: Parses a JSON source string.
if (!has("json-parse")) {
var fromCharCode = String.fromCharCode;
// Internal: A map of escaped control characters and their unescaped
// equivalents.
var Unescapes = {
92: "\\",
34: '"',
47: "/",
98: "\b",
116: "\t",
110: "\n",
102: "\f",
114: "\r"
};
// Internal: Stores the parser state.
var Index, Source;
// Internal: Resets the parser state and throws a `SyntaxError`.
var abort = function() {
Index = Source = null;
throw SyntaxError();
};
// Internal: Returns the next token, or `"$"` if the parser has reached
// the end of the source string. A token may be a string, number, `null`
// literal, or Boolean literal.
var lex = function () {
var source = Source, length = source.length, value, begin, position, isSigned, charCode;
while (Index < length) {
charCode = source.charCodeAt(Index);
switch (charCode) {
case 9: case 10: case 13: case 32:
// Skip whitespace tokens, including tabs, carriage returns, line
// feeds, and space characters.
Index++;
break;
case 123: case 125: case 91: case 93: case 58: case 44:
// Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at
// the current position.
value = charIndexBuggy ? source.charAt(Index) : source[Index];
Index++;
return value;
case 34:
// `"` delimits a JSON string; advance to the next character and
// begin parsing the string. String tokens are prefixed with the
// sentinel `@` character to distinguish them from punctuators and
// end-of-string tokens.
for (value = "@", Index++; Index < length;) {
charCode = source.charCodeAt(Index);
if (charCode < 32) {
// Unescaped ASCII control characters (those with a code unit
// less than the space character) are not permitted.
abort();
} else if (charCode == 92) {
// A reverse solidus (`\`) marks the beginning of an escaped
// control character (including `"`, `\`, and `/`) or Unicode
// escape sequence.
charCode = source.charCodeAt(++Index);
switch (charCode) {
case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114:
// Revive escaped control characters.
value += Unescapes[charCode];
Index++;
break;
case 117:
// `\u` marks the beginning of a Unicode escape sequence.
// Advance to the first character and validate the
// four-digit code point.
begin = ++Index;
for (position = Index + 4; Index < position; Index++) {
charCode = source.charCodeAt(Index);
// A valid sequence comprises four hexdigits (case-
// insensitive) that form a single hexadecimal value.
if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) {
// Invalid Unicode escape sequence.
abort();
}
}
// Revive the escaped character.
value += fromCharCode("0x" + source.slice(begin, Index));
break;
default:
// Invalid escape sequence.
abort();
}
} else {
if (charCode == 34) {
// An unescaped double-quote character marks the end of the
// string.
break;
}
charCode = source.charCodeAt(Index);
begin = Index;
// Optimize for the common case where a string is valid.
while (charCode >= 32 && charCode != 92 && charCode != 34) {
charCode = source.charCodeAt(++Index);
}
// Append the string as-is.
value += source.slice(begin, Index);
}
}
if (source.charCodeAt(Index) == 34) {
// Advance to the next character and return the revived string.
Index++;
return value;
}
// Unterminated string.
abort();
default:
// Parse numbers and literals.
begin = Index;
// Advance past the negative sign, if one is specified.
if (charCode == 45) {
isSigned = true;
charCode = source.charCodeAt(++Index);
}
// Parse an integer or floating-point value.
if (charCode >= 48 && charCode <= 57) {
// Leading zeroes are interpreted as octal literals.
if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) {
// Illegal octal literal.
abort();
}
isSigned = false;
// Parse the integer component.
for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++);
// Floats cannot contain a leading decimal point; however, this
// case is already accounted for by the parser.
if (source.charCodeAt(Index) == 46) {
position = ++Index;
// Parse the decimal component.
for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
if (position == Index) {
// Illegal trailing decimal.
abort();
}
Index = position;
}
// Parse exponents. The `e` denoting the exponent is
// case-insensitive.
charCode = source.charCodeAt(Index);
if (charCode == 101 || charCode == 69) {
charCode = source.charCodeAt(++Index);
// Skip past the sign following the exponent, if one is
// specified.
if (charCode == 43 || charCode == 45) {
Index++;
}
// Parse the exponential component.
for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
if (position == Index) {
// Illegal empty exponent.
abort();
}
Index = position;
}
// Coerce the parsed value to a JavaScript number.
return +source.slice(begin, Index);
}
// A negative sign may only precede numbers.
if (isSigned) {
abort();
}
// `true`, `false`, and `null` literals.
if (source.slice(Index, Index + 4) == "true") {
Index += 4;
return true;
} else if (source.slice(Index, Index + 5) == "false") {
Index += 5;
return false;
} else if (source.slice(Index, Index + 4) == "null") {
Index += 4;
return null;
}
// Unrecognized token.
abort();
}
}
// Return the sentinel `$` character if the parser has reached the end
// of the source string.
return "$";
};
// Internal: Parses a JSON `value` token.
var get = function (value) {
var results, hasMembers;
if (value == "$") {
// Unexpected end of input.
abort();
}
if (typeof value == "string") {
if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") {
// Remove the sentinel `@` character.
return value.slice(1);
}
// Parse object and array literals.
if (value == "[") {
// Parses a JSON array, returning a new JavaScript array.
results = [];
for (;; hasMembers || (hasMembers = true)) {
value = lex();
// A closing square bracket marks the end of the array literal.
if (value == "]") {
break;
}
// If the array literal contains elements, the current token
// should be a comma separating the previous element from the
// next.
if (hasMembers) {
if (value == ",") {
value = lex();
if (value == "]") {
// Unexpected trailing `,` in array literal.
abort();
}
} else {
// A `,` must separate each array element.
abort();
}
}
// Elisions and leading commas are not permitted.
if (value == ",") {
abort();
}
results.push(get(value));
}
return results;
} else if (value == "{") {
// Parses a JSON object, returning a new JavaScript object.
results = {};
for (;; hasMembers || (hasMembers = true)) {
value = lex();
// A closing curly brace marks the end of the object literal.
if (value == "}") {
break;
}
// If the object literal contains members, the current token
// should be a comma separator.
if (hasMembers) {
if (value == ",") {
value = lex();
if (value == "}") {
// Unexpected trailing `,` in object literal.
abort();
}
} else {
// A `,` must separate each object member.
abort();
}
}
// Leading commas are not permitted, object property names must be
// double-quoted strings, and a `:` must separate each property
// name and value.
if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") {
abort();
}
results[value.slice(1)] = get(lex());
}
return results;
}
// Unexpected token encountered.
abort();
}
return value;
};
// Internal: Updates a traversed object member.
var update = function(source, property, callback) {
var element = walk(source, property, callback);
if (element === undef) {
delete source[property];
} else {
source[property] = element;
}
};
// Internal: Recursively traverses a parsed JSON object, invoking the
// `callback` function for each value. This is an implementation of the
// `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
var walk = function (source, property, callback) {
var value = source[property], length;
if (typeof value == "object" && value) {
// `forEach` can't be used to traverse an array in Opera <= 8.54
// because its `Object#hasOwnProperty` implementation returns `false`
// for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`).
if (getClass.call(value) == arrayClass) {
for (length = value.length; length--;) {
update(value, length, callback);
}
} else {
forEach(value, function (property) {
update(value, property, callback);
});
}
}
return callback.call(source, property, value);
};
// Public: `JSON.parse`. See ES 5.1 section 15.12.2.
JSON3.parse = function (source, callback) {
var result, value;
Index = 0;
Source = "" + source;
result = get(lex());
// If a JSON string contains multiple tokens, it is invalid.
if (lex() != "$") {
abort();
}
// Reset the parser state.
Index = Source = null;
return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result;
};
}
}
// Export for asynchronous module loaders.
if (isLoader) {
define(function () {
return JSON3;
});
}
}(this));
},{}],43:[function(require,module,exports){
module.exports = toArray
function toArray(list, index) {
var array = []
index = index || 0
for (var i = index || 0; i < list.length; i++) {
array[i - index] = list[i]
}
return array
2014-06-17 16:20:22 +00:00
}
2014-06-20 01:33:49 +00:00
},{}]},{},[1])
(1)
});
;