ember-cli-ajh
Version:
Command line tool for developing ambitious ember.js apps
2,343 lines (1,943 loc) • 174 kB
JavaScript
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.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(_dereq_,module,exports){
module.exports = _dereq_('./lib/');
},{"./lib/":2}],2:[function(_dereq_,module,exports){
/**
* Module dependencies.
*/
var url = _dereq_('./url');
var parser = _dereq_('socket.io-parser');
var Manager = _dereq_('./manager');
var debug = _dereq_('debug')('socket.io-client');
/**
* Module exports.
*/
module.exports = exports = lookup;
/**
* Managers cache.
*/
var cache = exports.managers = {};
/**
* 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
*/
function lookup(uri, opts) {
if (typeof uri == 'object') {
opts = uri;
uri = undefined;
}
opts = opts || {};
var parsed = url(uri);
var source = parsed.source;
var id = parsed.id;
var io;
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);
}
io = cache[id];
}
return io.socket(parsed.path);
}
/**
* Protocol version.
*
* @api public
*/
exports.protocol = parser.protocol;
/**
* `connect`.
*
* @param {String} uri
* @api public
*/
exports.connect = lookup;
/**
* Expose constructors for standalone build.
*
* @api public
*/
exports.Manager = _dereq_('./manager');
exports.Socket = _dereq_('./socket');
},{"./manager":3,"./socket":5,"./url":6,"debug":10,"socket.io-parser":43}],3:[function(_dereq_,module,exports){
/**
* Module dependencies.
*/
var url = _dereq_('./url');
var eio = _dereq_('engine.io-client-pure');
var Socket = _dereq_('./socket');
var Emitter = _dereq_('component-emitter');
var parser = _dereq_('socket.io-parser');
var on = _dereq_('./on');
var bind = _dereq_('component-bind');
var object = _dereq_('object-component');
var debug = _dereq_('debug')('socket.io-client:manager');
var indexOf = _dereq_('indexof');
var Backoff = _dereq_('backo2');
/**
* Module exports
*/
module.exports = Manager;
/**
* `Manager` constructor.
*
* @param {String} engine instance or engine uri/opts
* @param {Object} options
* @api public
*/
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.randomizationFactor(opts.randomizationFactor || 0.5);
this.backoff = new Backoff({
min: this.reconnectionDelay(),
max: this.reconnectionDelayMax(),
jitter: this.randomizationFactor()
});
this.timeout(null == opts.timeout ? 20000 : opts.timeout);
this.readyState = 'closed';
this.uri = uri;
this.connected = [];
this.encoding = false;
this.packetBuffer = [];
this.encoder = new parser.Encoder();
this.decoder = new parser.Decoder();
this.autoConnect = opts.autoConnect !== false;
if (this.autoConnect) this.open();
}
/**
* Propagate given event to sockets and emit on `this`
*
* @api private
*/
Manager.prototype.emitAll = function() {
this.emit.apply(this, arguments);
for (var nsp in this.nsps) {
this.nsps[nsp].emit.apply(this.nsps[nsp], arguments);
}
};
/**
* Update `socket.id` of all sockets
*
* @api private
*/
Manager.prototype.updateSocketIds = function(){
for (var nsp in this.nsps) {
this.nsps[nsp].id = this.engine.id;
}
};
/**
* Mix in `Emitter`.
*/
Emitter(Manager.prototype);
/**
* Sets the `reconnection` config.
*
* @param {Boolean} true/false if it should automatically reconnect
* @return {Manager} self or value
* @api public
*/
Manager.prototype.reconnection = function(v){
if (!arguments.length) return this._reconnection;
this._reconnection = !!v;
return this;
};
/**
* Sets the reconnection attempts config.
*
* @param {Number} max reconnection attempts before giving up
* @return {Manager} self or value
* @api public
*/
Manager.prototype.reconnectionAttempts = function(v){
if (!arguments.length) return this._reconnectionAttempts;
this._reconnectionAttempts = v;
return this;
};
/**
* Sets the delay between reconnections.
*
* @param {Number} delay
* @return {Manager} self or value
* @api public
*/
Manager.prototype.reconnectionDelay = function(v){
if (!arguments.length) return this._reconnectionDelay;
this._reconnectionDelay = v;
this.backoff && this.backoff.setMin(v);
return this;
};
Manager.prototype.randomizationFactor = function(v){
if (!arguments.length) return this._randomizationFactor;
this._randomizationFactor = v;
this.backoff && this.backoff.setJitter(v);
return this;
};
/**
* Sets the maximum delay between reconnections.
*
* @param {Number} delay
* @return {Manager} self or value
* @api public
*/
Manager.prototype.reconnectionDelayMax = function(v){
if (!arguments.length) return this._reconnectionDelayMax;
this._reconnectionDelayMax = v;
this.backoff && this.backoff.setMax(v);
return this;
};
/**
* Sets the connection timeout. `false` to disable
*
* @return {Manager} self or value
* @api public
*/
Manager.prototype.timeout = function(v){
if (!arguments.length) return this._timeout;
this._timeout = v;
return this;
};
/**
* Starts trying to reconnect if reconnection is enabled and we have not
* started reconnecting yet
*
* @api private
*/
Manager.prototype.maybeReconnectOnOpen = function() {
// Only try to reconnect if it's the first time we're connecting
if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) {
// keeps reconnection from firing twice for the same reconnection loop
this.reconnect();
}
};
/**
* Sets the current transport `socket`.
*
* @param {Function} optional, callback
* @return {Manager} self
* @api public
*/
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';
this.skipReconnect = false;
// 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);
} else {
// Only do this if there is no fn to handle the error
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);
}
});
}
this.subs.push(openSub);
this.subs.push(errorSub);
return this;
};
/**
* Called upon transport open.
*
* @api private
*/
Manager.prototype.onopen = function(){
debug('open');
// clear old subs
this.cleanup();
// mark as open
this.readyState = 'open';
this.emit('open');
// 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')));
};
/**
* Called with data.
*
* @api private
*/
Manager.prototype.ondata = function(data){
this.decoder.add(data);
};
/**
* Called when parser fully decodes a packet.
*
* @api private
*/
Manager.prototype.ondecoded = function(packet) {
this.emit('packet', packet);
};
/**
* Called upon socket error.
*
* @api private
*/
Manager.prototype.onerror = function(err){
debug('error', err);
this.emitAll('error', err);
};
/**
* Creates a new socket for the given `nsp`.
*
* @return {Socket}
* @api public
*/
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(){
socket.id = self.engine.id;
if (!~indexOf(self.connected, socket)) {
self.connected.push(socket);
}
});
}
return socket;
};
/**
* Called upon a socket close.
*
* @param {Socket} socket
*/
Manager.prototype.destroy = function(socket){
var index = indexOf(this.connected, socket);
if (~index) this.connected.splice(index, 1);
if (this.connected.length) return;
this.close();
};
/**
* Writes a packet.
*
* @param {Object} packet
* @api private
*/
Manager.prototype.packet = function(packet){
debug('writing packet %j', packet);
var self = this;
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);
}
};
/**
* If packet buffer is non-empty, begins encoding the
* next packet in line.
*
* @api private
*/
Manager.prototype.processPacketQueue = function() {
if (this.packetBuffer.length > 0 && !this.encoding) {
var pack = this.packetBuffer.shift();
this.packet(pack);
}
};
/**
* Clean up transport subscriptions and packet buffer.
*
* @api private
*/
Manager.prototype.cleanup = function(){
var sub;
while (sub = this.subs.shift()) sub.destroy();
this.packetBuffer = [];
this.encoding = false;
this.decoder.destroy();
};
/**
* Close the current socket.
*
* @api private
*/
Manager.prototype.close =
Manager.prototype.disconnect = function(){
this.skipReconnect = true;
this.backoff.reset();
this.readyState = 'closed';
this.engine && this.engine.close();
};
/**
* Called upon engine close.
*
* @api private
*/
Manager.prototype.onclose = function(reason){
debug('close');
this.cleanup();
this.backoff.reset();
this.readyState = 'closed';
this.emit('close', reason);
if (this._reconnection && !this.skipReconnect) {
this.reconnect();
}
};
/**
* Attempt a reconnection.
*
* @api private
*/
Manager.prototype.reconnect = function(){
if (this.reconnecting || this.skipReconnect) return this;
var self = this;
if (this.backoff.attempts >= this._reconnectionAttempts) {
debug('reconnect failed');
this.backoff.reset();
this.emitAll('reconnect_failed');
this.reconnecting = false;
} else {
var delay = this.backoff.duration();
debug('will wait %dms before reconnect attempt', delay);
this.reconnecting = true;
var timer = setTimeout(function(){
if (self.skipReconnect) return;
debug('attempting reconnect');
self.emitAll('reconnect_attempt', self.backoff.attempts);
self.emitAll('reconnecting', self.backoff.attempts);
// check again for the case socket closed in above events
if (self.skipReconnect) return;
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);
this.subs.push({
destroy: function(){
clearTimeout(timer);
}
});
}
};
/**
* Called upon successful reconnect.
*
* @api private
*/
Manager.prototype.onreconnect = function(){
var attempt = this.backoff.attempts;
this.reconnecting = false;
this.backoff.reset();
this.updateSocketIds();
this.emitAll('reconnect', attempt);
};
},{"./on":4,"./socket":5,"./url":6,"backo2":7,"component-bind":8,"component-emitter":9,"debug":10,"engine.io-client-pure":11,"indexof":39,"object-component":40,"socket.io-parser":43}],4:[function(_dereq_,module,exports){
/**
* Module exports.
*/
module.exports = on;
/**
* Helper for subscriptions.
*
* @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter`
* @param {String} event name
* @param {Function} callback
* @api public
*/
function on(obj, ev, fn) {
obj.on(ev, fn);
return {
destroy: function(){
obj.removeListener(ev, fn);
}
};
}
},{}],5:[function(_dereq_,module,exports){
/**
* Module dependencies.
*/
var parser = _dereq_('socket.io-parser');
var Emitter = _dereq_('component-emitter');
var toArray = _dereq_('to-array');
var on = _dereq_('./on');
var bind = _dereq_('component-bind');
var debug = _dereq_('debug')('socket.io-client:socket');
var hasBin = _dereq_('has-binary');
/**
* Module exports.
*/
module.exports = exports = Socket;
/**
* Internal events (blacklisted).
* These events can't be emitted by the user.
*
* @api private
*/
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
};
/**
* Shortcut to `Emitter#emit`.
*/
var emit = Emitter.prototype.emit;
/**
* `Socket` constructor.
*
* @api public
*/
function Socket(io, nsp){
this.io = io;
this.nsp = nsp;
this.json = this; // compat
this.ids = 0;
this.acks = {};
if (this.io.autoConnect) this.open();
this.receiveBuffer = [];
this.sendBuffer = [];
this.connected = false;
this.disconnected = true;
}
/**
* Mix in `Emitter`.
*/
Emitter(Socket.prototype);
/**
* Subscribe to open, close and packet events
*
* @api private
*/
Socket.prototype.subEvents = function() {
if (this.subs) return;
var io = this.io;
this.subs = [
on(io, 'open', bind(this, 'onopen')),
on(io, 'packet', bind(this, 'onpacket')),
on(io, 'close', bind(this, 'onclose'))
];
};
/**
* "Opens" the socket.
*
* @api public
*/
Socket.prototype.open =
Socket.prototype.connect = function(){
if (this.connected) return this;
this.subEvents();
this.io.open(); // ensure open
if ('open' == this.io.readyState) this.onopen();
return this;
};
/**
* Sends a `message` event.
*
* @return {Socket} self
* @api public
*/
Socket.prototype.send = function(){
var args = toArray(arguments);
args.unshift('message');
this.emit.apply(this, args);
return this;
};
/**
* Override `emit`.
* If the event is in `events`, it's emitted normally.
*
* @param {String} event name
* @return {Socket} self
* @api public
*/
Socket.prototype.emit = function(ev){
if (events.hasOwnProperty(ev)) {
emit.apply(this, arguments);
return this;
}
var args = toArray(arguments);
var parserType = parser.EVENT; // default
if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
var packet = { type: parserType, data: args };
// 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++;
}
if (this.connected) {
this.packet(packet);
} else {
this.sendBuffer.push(packet);
}
return this;
};
/**
* Sends a packet.
*
* @param {Object} packet
* @api private
*/
Socket.prototype.packet = function(packet){
packet.nsp = this.nsp;
this.io.packet(packet);
};
/**
* Called upon engine `open`.
*
* @api private
*/
Socket.prototype.onopen = function(){
debug('transport is open - connecting');
// write connect packet if necessary
if ('/' != this.nsp) {
this.packet({ type: parser.CONNECT });
}
};
/**
* Called upon engine `close`.
*
* @param {String} reason
* @api private
*/
Socket.prototype.onclose = function(reason){
debug('close (%s)', reason);
this.connected = false;
this.disconnected = true;
delete this.id;
this.emit('disconnect', reason);
};
/**
* Called with socket packet.
*
* @param {Object} packet
* @api private
*/
Socket.prototype.onpacket = function(packet){
if (packet.nsp != this.nsp) return;
switch (packet.type) {
case parser.CONNECT:
this.onconnect();
break;
case parser.EVENT:
this.onevent(packet);
break;
case parser.BINARY_EVENT:
this.onevent(packet);
break;
case parser.ACK:
this.onack(packet);
break;
case parser.BINARY_ACK:
this.onack(packet);
break;
case parser.DISCONNECT:
this.ondisconnect();
break;
case parser.ERROR:
this.emit('error', packet.data);
break;
}
};
/**
* Called upon a server event.
*
* @param {Object} packet
* @api private
*/
Socket.prototype.onevent = function(packet){
var args = packet.data || [];
debug('emitting event %j', args);
if (null != packet.id) {
debug('attaching ack callback to event');
args.push(this.ack(packet.id));
}
if (this.connected) {
emit.apply(this, args);
} else {
this.receiveBuffer.push(args);
}
};
/**
* Produces an ack callback to emit with an event.
*
* @api private
*/
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
});
};
};
/**
* Called upon a server acknowlegement.
*
* @param {Object} packet
* @api private
*/
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];
};
/**
* Called upon server connect.
*
* @api private
*/
Socket.prototype.onconnect = function(){
this.connected = true;
this.disconnected = false;
this.emit('connect');
this.emitBuffered();
};
/**
* Emit buffered events (received and emitted).
*
* @api private
*/
Socket.prototype.emitBuffered = function(){
var i;
for (i = 0; i < this.receiveBuffer.length; i++) {
emit.apply(this, this.receiveBuffer[i]);
}
this.receiveBuffer = [];
for (i = 0; i < this.sendBuffer.length; i++) {
this.packet(this.sendBuffer[i]);
}
this.sendBuffer = [];
};
/**
* Called upon server disconnect.
*
* @api private
*/
Socket.prototype.ondisconnect = function(){
debug('server disconnect (%s)', this.nsp);
this.destroy();
this.onclose('io server disconnect');
};
/**
* 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.
*/
Socket.prototype.destroy = function(){
if (this.subs) {
// clean subscriptions to avoid reconnections
for (var i = 0; i < this.subs.length; i++) {
this.subs[i].destroy();
}
this.subs = null;
}
this.io.destroy(this);
};
/**
* Disconnects the socket manually.
*
* @return {Socket} self
* @api public
*/
Socket.prototype.close =
Socket.prototype.disconnect = function(){
if (this.connected) {
debug('performing disconnect (%s)', this.nsp);
this.packet({ type: parser.DISCONNECT });
}
// remove socket from pool
this.destroy();
if (this.connected) {
// fire events
this.onclose('io client disconnect');
}
return this;
};
},{"./on":4,"component-bind":8,"component-emitter":9,"debug":10,"has-binary":37,"socket.io-parser":43,"to-array":47}],6:[function(_dereq_,module,exports){
(function (global){
/**
* Module dependencies.
*/
var parseuri = _dereq_('parseuri');
var debug = _dereq_('debug')('socket.io-client:url');
/**
* Module exports.
*/
module.exports = url;
/**
* URL parser.
*
* @param {String} url
* @param {Object} An object meant to mimic window.location.
* Defaults to window.location.
* @api public
*/
function url(uri, loc){
var obj = uri;
// default to window.location
var loc = loc || global.location;
if (null == uri) uri = loc.protocol + '//' + loc.host;
// relative path support
if ('string' == typeof uri) {
if ('/' == uri.charAt(0)) {
if ('/' == uri.charAt(1)) {
uri = loc.protocol + uri;
} else {
uri = loc.hostname + uri;
}
}
if (!/^(https?|wss?):\/\//.test(uri)) {
debug('protocol-less url %s', uri);
if ('undefined' != typeof loc) {
uri = loc.protocol + '//' + uri;
} else {
uri = 'https://' + uri;
}
}
// parse
debug('parse %s', uri);
obj = parseuri(uri);
}
// 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';
}
}
obj.path = obj.path || '/';
// 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));
return obj;
}
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"debug":10,"parseuri":41}],7:[function(_dereq_,module,exports){
/**
* Expose `Backoff`.
*/
module.exports = Backoff;
/**
* Initialize backoff timer with `opts`.
*
* - `min` initial timeout in milliseconds [100]
* - `max` max timeout [10000]
* - `jitter` [0]
* - `factor` [2]
*
* @param {Object} opts
* @api public
*/
function Backoff(opts) {
opts = opts || {};
this.ms = opts.min || 100;
this.max = opts.max || 10000;
this.factor = opts.factor || 2;
this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0;
this.attempts = 0;
}
/**
* Return the backoff duration.
*
* @return {Number}
* @api public
*/
Backoff.prototype.duration = function(){
var ms = this.ms * Math.pow(this.factor, this.attempts++);
if (this.jitter) {
var rand = Math.random();
var deviation = Math.floor(rand * this.jitter * ms);
ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation;
}
return Math.min(ms, this.max) | 0;
};
/**
* Reset the number of attempts.
*
* @api public
*/
Backoff.prototype.reset = function(){
this.attempts = 0;
};
/**
* Set the minimum duration
*
* @api public
*/
Backoff.prototype.setMin = function(min){
this.ms = min;
};
/**
* Set the maximum duration
*
* @api public
*/
Backoff.prototype.setMax = function(max){
this.max = max;
};
/**
* Set the jitter
*
* @api public
*/
Backoff.prototype.setJitter = function(jitter){
this.jitter = jitter;
};
},{}],8:[function(_dereq_,module,exports){
/**
* Slice reference.
*/
var slice = [].slice;
/**
* Bind `obj` to `fn`.
*
* @param {Object} obj
* @param {Function|String} fn or string
* @return {Function}
* @api public
*/
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)));
}
};
},{}],9:[function(_dereq_,module,exports){
/**
* Expose `Emitter`.
*/
module.exports = Emitter;
/**
* Initialize a new `Emitter`.
*
* @api public
*/
function Emitter(obj) {
if (obj) return mixin(obj);
};
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
function mixin(obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key];
}
return obj;
}
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
(this._callbacks[event] = this._callbacks[event] || [])
.push(fn);
return this;
};
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.once = function(event, fn){
var self = this;
this._callbacks = this._callbacks || {};
function on() {
self.off(event, on);
fn.apply(this, arguments);
}
on.fn = fn;
this.on(event, on);
return this;
};
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
// all
if (0 == arguments.length) {
this._callbacks = {};
return this;
}
// specific event
var callbacks = this._callbacks[event];
if (!callbacks) return this;
// remove all handlers
if (1 == arguments.length) {
delete this._callbacks[event];
return this;
}
// remove specific handler
var cb;
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i];
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1);
break;
}
}
return this;
};
/**
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
*/
Emitter.prototype.emit = function(event){
this._callbacks = this._callbacks || {};
var args = [].slice.call(arguments, 1)
, callbacks = this._callbacks[event];
if (callbacks) {
callbacks = callbacks.slice(0);
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
return this;
};
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
Emitter.prototype.listeners = function(event){
this._callbacks = this._callbacks || {};
return this._callbacks[event] || [];
};
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
Emitter.prototype.hasListeners = function(event){
return !! this.listeners(event).length;
};
},{}],10:[function(_dereq_,module,exports){
/**
* Expose `debug()` as the module.
*/
module.exports = debug;
/**
* Create a debugger with the given `name`.
*
* @param {String} name
* @return {Type}
* @api public
*/
function debug(name) {
if (!debug.enabled(name)) return function(){};
return function(fmt){
fmt = coerce(fmt);
var curr = new Date;
var ms = curr - (debug[name] || curr);
debug[name] = curr;
fmt = name
+ ' '
+ fmt
+ ' +' + debug.humanize(ms);
// This hackery is required for IE8
// where `console.log` doesn't have 'apply'
window.console
&& console.log
&& Function.prototype.apply.call(console.log, console, arguments);
}
}
/**
* The currently active debug mode names.
*/
debug.names = [];
debug.skips = [];
/**
* Enables a debug mode by name. This can include modes
* separated by a colon and wildcards.
*
* @param {String} name
* @api public
*/
debug.enable = function(name) {
try {
localStorage.debug = name;
} catch(e){}
var split = (name || '').split(/[\s,]+/)
, len = split.length;
for (var i = 0; i < len; i++) {
name = split[i].replace('*', '.*?');
if (name[0] === '-') {
debug.skips.push(new RegExp('^' + name.substr(1) + '$'));
}
else {
debug.names.push(new RegExp('^' + name + '$'));
}
}
};
/**
* Disable debug output.
*
* @api public
*/
debug.disable = function(){
debug.enable('');
};
/**
* Humanize the given `ms`.
*
* @param {Number} m
* @return {String}
* @api private
*/
debug.humanize = function(ms) {
var sec = 1000
, min = 60 * 1000
, hour = 60 * min;
if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
if (ms >= min) return (ms / min).toFixed(1) + 'm';
if (ms >= sec) return (ms / sec | 0) + 's';
return ms + 'ms';
};
/**
* Returns true if the given mode name is enabled, false otherwise.
*
* @param {String} name
* @return {Boolean}
* @api public
*/
debug.enabled = function(name) {
for (var i = 0, len = debug.skips.length; i < len; i++) {
if (debug.skips[i].test(name)) {
return false;
}
}
for (var i = 0, len = debug.names.length; i < len; i++) {
if (debug.names[i].test(name)) {
return true;
}
}
return false;
};
/**
* Coerce `val`.
*/
function coerce(val) {
if (val instanceof Error) return val.stack || val.message;
return val;
}
// persist
try {
if (window.localStorage) debug.enable(localStorage.debug);
} catch(e){}
},{}],11:[function(_dereq_,module,exports){
module.exports = _dereq_('./lib/');
},{"./lib/":12}],12:[function(_dereq_,module,exports){
module.exports = _dereq_('./socket');
/**
* Exports parser
*
* @api public
*
*/
module.exports.parser = _dereq_('engine.io-parser');
},{"./socket":13,"engine.io-parser":25}],13:[function(_dereq_,module,exports){
(function (global){
/**
* Module dependencies.
*/
var transports = _dereq_('./transports');
var Emitter = _dereq_('component-emitter');
var debug = _dereq_('debug')('engine.io-client:socket');
var index = _dereq_('indexof');
var parser = _dereq_('engine.io-parser');
var parseuri = _dereq_('parseuri');
var parsejson = _dereq_('parsejson');
var parseqs = _dereq_('parseqs');
/**
* Module exports.
*/
module.exports = Socket;
/**
* Noop function.
*
* @api private
*/
function noop(){}
/**
* Socket constructor.
*
* @param {String|Object} uri or options
* @param {Object} options
* @api public
*/
function Socket(uri, opts){
if (!(this instanceof Socket)) return new Socket(uri, opts);
opts = opts || {};
if (uri && 'object' == typeof uri) {
opts = uri;
uri = null;
}
if (uri) {
uri = parseuri(uri);
opts.host = uri.host;
opts.secure = uri.protocol == 'https' || uri.protocol == 'wss';
opts.port = uri.port;
if (uri.query) opts.query = uri.query;
}
this.secure = null != opts.secure ? opts.secure :
(global.location && 'https:' == location.protocol);
if (opts.host) {
var pieces = opts.host.split(':');
opts.hostname = pieces.shift();
if (pieces.length) {
opts.port = pieces.pop();
} else if (!opts.port) {
// if no port is specified manually, use the protocol default
opts.port = this.secure ? '443' : '80';
}
}
this.agent = opts.agent || false;
this.hostname = opts.hostname ||
(global.location ? location.hostname : 'localhost');
this.port = opts.port || (global.location && location.port ?
location.port :
(this.secure ? 443 : 80));
this.query = opts.query || {};
if ('string' == typeof this.query) this.query = parseqs.decode(this.query);
this.upgrade = false !== opts.upgrade;
this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/';
this.forceJSONP = !!opts.forceJSONP;
this.jsonp = false !== opts.jsonp;
this.forceBase64 = !!opts.forceBase64;
this.enablesXDR = !!opts.enablesXDR;
this.timestampParam = opts.timestampParam || 't';
this.timestampRequests = opts.timestampRequests;
this.transports = opts.transports || ['polling', 'websocket'];
this.readyState = '';
this.writeBuffer = [];
this.callbackBuffer = [];
this.policyPort = opts.policyPort || 843;
this.rememberUpgrade = opts.rememberUpgrade || false;
this.binaryType = null;
this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades;
// SSL options for Node.js client
this.pfx = opts.pfx || null;
this.key = opts.key || null;
this.passphrase = opts.passphrase || null;
this.cert = opts.cert || null;
this.ca = opts.ca || null;
this.ciphers = opts.ciphers || null;
this.rejectUnauthorized = opts.rejectUnauthorized || null;
this.open();
}
Socket.priorWebsocketSuccess = false;
/**
* Mix in `Emitter`.
*/
Emitter(Socket.prototype);
/**
* Protocol version.
*
* @api public
*/
Socket.protocol = parser.protocol; // this is an int
/**
* Expose deps for legacy compatibility
* and standalone browser access.
*/
Socket.Socket = Socket;
Socket.Transport = _dereq_('./transport');
Socket.transports = _dereq_('./transports');
Socket.parser = _dereq_('engine.io-parser');
/**
* Creates transport of the given type.
*
* @param {String} transport name
* @return {Transport}
* @api private
*/
Socket.prototype.createTransport = function (name) {
debug('creating transport "%s"', name);
var query = clone(this.query);
// append engine.io protocol identifier
query.EIO = parser.protocol;
// transport name
query.transport = name;
// session id if we already have one
if (this.id) query.sid = this.id;
var transport = new transports[name]({
agent: this.agent,
hostname: this.hostname,
port: this.port,
secure: this.secure,
path: this.path,
query: query,
forceJSONP: this.forceJSONP,
jsonp: this.jsonp,
forceBase64: this.forceBase64,
enablesXDR: this.enablesXDR,
timestampRequests: this.timestampRequests,
timestampParam: this.timestampParam,
policyPort: this.policyPort,
socket: this,
pfx: this.pfx,
key: this.key,
passphrase: this.passphrase,
cert: this.cert,
ca: this.ca,
ciphers: this.ciphers,
rejectUnauthorized: this.rejectUnauthorized
});
return transport;
};
function clone (obj) {
var o = {};
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
o[i] = obj[i];
}
}
return o;
}
/**
* Initializes transport to use and starts probe.
*
* @api private
*/
Socket.prototype.open = function () {
var transport;
if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) {
transport = 'websocket';
} else if (0 == this.transports.length) {
// Emit error on next tick so it can be listened to
var self = this;
setTimeout(function() {
self.emit('error', 'No transports available');
}, 0);
return;
} else {
transport = this.transports[0];
}
this.readyState = 'opening';
// Retry with the next transport if the transport is disabled (jsonp: false)
var transport;
try {
transport = this.createTransport(transport);
} catch (e) {
this.transports.shift();
this.open();
return;
}
transport.open();
this.setTransport(transport);
};
/**
* Sets the current transport. Disables the existing one (if any).
*
* @api private
*/
Socket.prototype.setTransport = function(transport){
debug('setting transport %s', transport.name);
var self = this;
if (this.transport) {
debug('clearing existing transport %s', this.transport.name);
this.transport.removeAllListeners();
}
// set up transport
this.transport = transport;
// set up transport listeners
transport
.on('drain', function(){
self.onDrain();
})
.on('packet', function(packet){
self.onPacket(packet);
})
.on('error', function(e){
self.onError(e);
})
.on('close', function(){
self.onClose('transport close');
});
};
/**
* Probes a transport.
*
* @param {String} transport name
* @api private
*/
Socket.prototype.probe = function (name) {
debug('probing transport "%s"', name);
var transport = this.createTransport(name, { probe: 1 })
, failed = false
, self = this;
Socket.priorWebsocketSuccess = false;
function onTransportOpen(){
if (self.onlyBinaryUpgrades) {
var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary;
failed = failed || upgradeLosesBinary;
}
if (failed) return;
debug('probe transport "%s" opened', name);
transport.send([{ type: 'ping', data: 'probe' }]);
transport.once('packet', function (msg) {
if (failed) return;
if ('pong' == msg.type && 'probe' == msg.data) {
debug('probe transport "%s" pong', name);
self.upgrading = true;
self.emit('upgrading', transport);
if (!transport) return;
Socket.priorWebsocketSuccess = 'websocket' == transport.name;
debug('pausing current transport "%s"', self.transport.name);
self.transport.pause(function () {
if (failed) return;
if ('closed' == self.readyState) return;
debug('changing transport and sending upgrade packet');
cleanup();
self.setTransport(transport);
transport.send([{ type: 'upgrade' }]);
self.emit('upgrade', transport);
transport = null;
self.upgrading = false;
self.flush();
});
} else {
debug('probe transport "%s" failed', name);
var err = new Error('probe error');
err.transport = transport.name;
self.emit('upgradeError', err);
}
});
}
function freezeTransport() {
if (failed) return;
// Any callback called by transport should be ignored since now
failed = true;
cleanup();
transport.close();
transport = null;
}
//Handle any error that happens while probing
function onerror(err) {
var error = new Error('probe error: ' + err);
error.transport = transport.name;
freezeTransport();
debug('probe transport "%s" failed because of error: %s', name, err);
self.emit('upgradeError', error);
}
function onTransportClose(){
onerror("transport closed");
}
//When the socket is closed while we're probing
function onclose(){
onerror("socket closed");
}
//When the socket is upgraded while we're probing
function onupgrade(to){
if (transport && to.name != transport.name) {
debug('"%s" works - aborting "%s"', to.name, transport.name);
freezeTransport();
}
}
//Remove all listeners on the transport and on self
function cleanup(){
transport.removeListener('open', onTransportOpen);
transport.removeListener('error', onerror);
transport.removeListener('close', onTransportClose);
self.removeListener('close', onclose);
self.removeListener('upgrading', onupgrade);
}
transport.once('open', onTransportOpen);
transport.once('error', onerror);
transport.once('close', onTransportClose);
this.once('close', onclose);
this.once('upgrading', onupgrade);
transport.open();
};
/**
* Called when connection is deemed open.
*
* @api public
*/
Socket.prototype.onOpen = function () {
debug('socket open');
this.readyState = 'open';
Socket.priorWebsocketSuccess = 'websocket' == this.transport.name;
this.emit('open');
this.flush();
// we check for `readyState` in case an `open`
// listener already closed the socket
if ('open' == this.readyState && this.upgrade && this.transport.pause) {
debug('starting upgrade probes');
for (var i = 0, l = this.upgrades.length; i < l; i++) {
this.probe(this.upgrades[i]);
}
}
};
/**
* Handles a packet.
*
* @api private
*/
Socket.prototype.onPacket = function (packet) {
if ('opening' == this.readyState || 'open' == this.readyState) {
debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
this.emit('packet', packet);
// Socket is live - any packet counts
this.emit('heartbeat');
switch (packet.type) {
case 'open':
this.onHandshake(parsejson(packet.data));
break;
case 'pong':
this.setPing();
break;
case 'error':
var err = new Error('server error');
err.code = packet.data;
this.emit('error', err);
break;
case 'message':
this.emit('data', packet.data);
this.emit('message', packet.data);
break;
}
} else {
debug('packet received with socket readyState "%s"', this.readyState);
}
};
/**
* Called upon handshake completion.
*
* @param {Object} handshake obj
* @api private
*/
Socket.prototype.onHandshake = function (data) {
this.emit('handshake', data);
this.id = data.sid;
this.transport.query.sid = data.sid;
this.upgrades = this.filterUpgrades(data.upgrades);
this.pingInterval = data.pingInterval;
this.pingTimeout = data.pingTimeout;
this.onOpen();
// In case open handler closes socket
if ('closed' == this.readyState) return;
this.setPing();
// Prolong liveness of socket on heartbeat
this.removeListener('heartbeat', this.onHeartbeat);
this.on('heartbeat', this.onHeartbeat);
};
/**
* Resets ping timeout.
*
* @api private
*/
Socket.prototype.onHeartbeat = function (timeout) {
clearTimeout(this.pingTimeoutTimer);
var self = this;
self.pingTimeoutTimer = setTimeout(function () {
if ('closed' == self.readyState) return;
self.onClose('ping timeout');
}, timeout || (self.pingInterval + self.pingTimeout));
};
/**
* Pings server every `this.pingInterval` and expects response
* within `this.pingTimeout` or closes connection.
*
* @api private
*/
Socket.prototype.setPing = function () {
var self = this;
clearTimeout(self.pingIntervalTimer);
self.pingIntervalTimer = setTimeout(function () {
debug('writing ping packet - expecting pong within %sms', self.pingTimeout);
self.ping();
self.onHeartbeat(self.pingTimeout);
}, self.pingInterval);
};
/**
* Sends a ping packet.
*
* @api public
*/
Socket.prototype.ping = function () {
this.sendPacket('ping');
};
/**
* Called on `drain` event
*
* @api private
*/
Socket.prototype.onDrain = function() {
for (var i = 0; i < this.prevBufferLen; i++) {
if (this.callbackBuffer[i]) {
this.callbackBuffer[i]();
}
}
this.writeBuffer.splice(0, this.prevBufferLen);
this.callbackBuffer.splice(0, this.prevBufferLen);
// setting prevBufferLen = 0 is very important
// for example, when upgrading, upgrade packet is sent over,
// and a nonzero prevBufferLen could cause problems on `drain`
this.prevBufferLen = 0;
if (this.writeBuffer.length == 0) {
this.emit('drain');
} else {
this.flush();
}
};
/**
* Flush write buffers.
*
* @api private
*/
Socket.prototype.flush = function () {
if ('closed' != this.readyState && this.transport.writable &&
!this.upgrading && this.writeBuffer.length) {
debug('flushing %d packets in socket', this.writeBuffer.length);
this.transport.send(this.writeBuffer);
// keep track of current length of writeBuffer
// splice writeBuffer and callbackBuffer on `drain`
this.prevBufferLen = this.writeBuffer.length;
this.emit('flush');
}
};
/**
* Sends a message.
*
* @param {String} message.
* @param {Function} callback function.
* @return {Socket} for chaining.
* @api public
*/
Socket.prototype.write =
Socket.prototype.send = function (msg, fn) {
this.sendPacket('message', msg, fn);
return this;
};
/**
* Sends a packet.
*
* @param {String} packet type.
* @param {String} data.
* @param {Function} callback function.
* @api private
*/
Socket.prototype.sendPacket = function (type, data, fn) {
if ('closing' == this.readyState || 'closed' == this.readyState) {
return;
}
var packet = { type: type, data: data };
this.emit('packetCreate', packet);
this.writeBuffer.push(packet);
this.callbackBuffer.push(fn);
this.flush();
};
/**
* Closes the connection.
*
* @api private
*/
Socket.prototype.close = function () {
if ('opening' == this.readyState || 'open' == this.readyState) {
this.readyState = 'closing';
var self = this;
function close() {
self.onClose('forced close');
debug('socket closing - telling transport to close');
self.transport.close();
}
function cleanupAndClose() {
self.removeListener('upgrade', cleanupAndClose);
self.removeListener('upgradeError', cleanupAndClose);
close();
}
function waitForUpgrade() {
// wait for upgrade to finish since we can't send packets while pausing a transport
self.once('upgrade', cleanupAndClose);
self.once('upgradeError', cleanupAndClose);
}
if (this.writeBuffer.length) {
this.once('drain', function() {
if (this.upgrading) {
waitForUpgrade();
} else {
close();
}
});
} else if (this.upgrading) {
waitForUpgrade();
} else {
close();
}
}
return this;
};
/**
* Called upon transport error
*
* @api private
*/
Socket.prototype.onError = function (err) {
debug('socket error %j', err);
Socket.priorWebsocketSuccess = false;
this.emit('error', err);
this.onClose('transport error', err);
};
/**
* Called upon transport close.
*
* @api private
*/
Socket.prototype.onClose = function (reason, desc) {
if ('opening' == this.readyState || 'open' == this.readyState || 'closing' == this.readyState) {
debug('socket close with reason: "%s"', reason);
var self = this;
// clear timers
clearTimeout(this.pingIntervalTimer);
clearTimeout(this.pingTimeoutTimer);
// 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);
// stop event from firing again for transport
this.transport.removeAllListeners('close');
// ensure transport won't stay open
this.transport.close();
// ignore further transport communication
this.transport.removeAllListeners();
// set ready state
this.readyState = 'closed';
// clear session id
this.id = null;
// emit close event
this.emit('close', reason, desc);
}
};
/**
* Filters upgrades, returning only those matching client transports.
*
* @param {Array} server upgrades
* @api private
*
*/
Socket.prototype.filterUpgrades = function (upgrades) {
var filteredUpgrades = [];
for (var i = 0, j = upgrades.length; i<j; i++) {
if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]);
}
return filteredUpgrades;
};
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./transport":14,"./transports":15,"component-emitter":9,"debug":22,"engine.io-parser":25,"indexof":39,"parsejson":33,"parseqs":34,"parseuri":35}],14:[function(_dereq_,module,exports){
/**
* Module dependencies.
*/
var parser = _dereq_('engine.io-parser');
var Emitter = _dereq_('component-emitter');
/**
* Module exports.
*/
module.exports = Transport;
/**
* Transport abstract constructor.
*
* @param {Object} options.
* @api private
*/
function Transport (opts) {
this.path = opts.path;
this.hostname = opts.hostname;
this.port = opts.port;
this.secure = opts.secure;
this.query = opts.query;
this.timestampParam = opts.timestampParam;
this.timestampRequests = opts.timestampRequests;
this.readyState = '';
this.agent = opts.agent || false;
this.socket = opts.socket;
this.enablesXDR = opts.enablesXDR;
// SSL options for Node.js client
this.pfx = opts.pfx;
this.key = opts.key;
this.passphrase = opts.passphrase;
this.cert = opts.cert;
this.ca = opts.ca;
this.ciphers = opts.ciphers;
this.rejectUnauthorized = opts.rejectUnauthorized;
}
/**
* Mix in `Emitter`.
*/
Emitter(Transport.prototype);
/**
* A counter used to prevent collisions in the timestamps used
* for cache busting.
*/
Transport.timestamps = 0;
/**
* Emits an error.
*
* @param {String} str
* @return {Transport} for chaining
* @api public
*/
Transport.prototype.onError = function (msg, desc) {
var err = new Error(msg);
err.type = 'TransportError';
err.description = desc;
this.emit('error', err);
return this;
};
/**
* Opens the transport.
*
* @api public
*/
Transport.prototype.open = function () {
if ('closed' == this.readyState || '' == this.readyState) {
this.readyState = 'opening';
this.doOpen();
}
return this;
};
/**
* Closes the transport.
*
* @api private
*/
Transport.prototype.close = function () {
if ('opening' == this.readyState || 'open' == this.readyState) {
this.doClose();
this.onClose();
}
return this;
};
/**
* Sends multiple packets.
*
* @param {Array} packets
* @api private
*/
Transport.prototype.send = function(packets){
if ('open' == this.readyState) {
this.write(pac