diff --git a/README.md b/README.md
index 9067849..c8775d0 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,8 @@ An elegant web & terminal interface for Unitech/PM2.
# Features
-- Curses-like dashboard
+- Curses-like dashboard.
+- Remoting monitor / web control.
- All the heartbeats (no matter **monitor** or **tail (logs)**) are automatic destroyed.
- The `PM2` processes are watched by a subscribed emitter.
- Communicated with `PM2` through **RPC** socket directly.
@@ -28,10 +29,10 @@ An elegant web & terminal interface for Unitech/PM2.
- Monitor CPU and Memory usage of server in a real-time.
- Monitor `PM2` processes in a real-time.
- PM2 *restart/stop/delete*.
- - *stopWatch* files before *restart/stop/delete*
- - *restartWatch* files before *restart*
+ - *stopWatch* files before *restart/stop/delete*.
+ - *restartWatch* files before *restart*.
- Supports [ANSI color codes](#ss_logs) by [ansi-html](https://github.com/Tjatse/ansi-html).
-- High performance. In my case, there are near one hundred processes, but `pm2-gui` works fine.
+- High performance. In my case, there are near one hundred processes, but `pm2-gui` works without any suck.
# Cautions
diff --git a/lib/blessed-widget/layout.js b/lib/blessed-widget/layout.js
index f102937..50c1402 100644
--- a/lib/blessed-widget/layout.js
+++ b/lib/blessed-widget/layout.js
@@ -29,10 +29,6 @@ function Layout(options) {
this.options = options;
this._eles = {};
this._procCount = 0;
- /*
- Log({
- level: 1000
- });*/
};
/**
@@ -54,13 +50,22 @@ Layout.prototype.render = function (monitor) {
namespace: conf.NSP[ns]
}, options);
- monitor.connect(opts, function (err, socket) {
- if (err) {
- console.error('Failed due to', err.message, 'when connecting to', socket.nsp);
+ monitor.connect(opts, function (socket) {
+ console.info('Connected to', socket.nsp);
+ !next._called && next(null, socket);
+ next._called = true;
+ }, function (err, socket) {
+ console.log(err);
+ if (!next._called) {
+ next(err, socket);
+ next._called = true;
} else {
- console.info('Connected to', socket.nsp);
+ //Log(options.log);
+ }
+ console.error('Failed due to', err.message, 'when connecting to', socket.nsp);
+ if (next._called) {
+ process.exit(0);
}
- next(err, socket);
});
}
});
@@ -69,6 +74,9 @@ Layout.prototype.render = function (monitor) {
if (err) {
return process.exit(0);
}
+ Log({
+ level: 1000
+ });
self.sockets = _.extend(res, options.sockets);
delete options.sockets;
diff --git a/lib/monitor.js b/lib/monitor.js
index ee27a40..68ea60e 100644
--- a/lib/monitor.js
+++ b/lib/monitor.js
@@ -5,6 +5,7 @@ var fs = require('fs'),
ansiHTML = require('ansi-html'),
totalmem = require('os').totalmem(),
pidusage = require('pidusage'),
+ url = require('url'),
socketIOClient = require('socket.io-client'),
pm = require('./pm'),
stat = require('./stat'),
@@ -33,25 +34,6 @@ Monitor.ACCEPT_KEYS = ['pm2', 'refresh', 'daemonize', 'max_restarts', 'port', 'l
Monitor.DEF_CONF_FILE = 'pm2-gui.ini';
Monitor.PM2_DAEMON_PROPS = ['DAEMON_RPC_PORT', 'DAEMON_PUB_PORT', 'PM2_LOG_FILE_PATH'];
-/**
- * Resolve home path.
- * @param {String} pm2Home
- * @returns {*}
- * @private
- */
-Monitor.prototype._resolveHome = function (pm2Home) {
- if (pm2Home && pm2Home.indexOf('~/') == 0) {
- // Get root directory of PM2.
- pm2Home = process.env.PM2_HOME || path.resolve(process.env.HOME || process.env.HOMEPATH, pm2Home.substr(2));
-
- // Make sure exist.
- if (!pm2Home || !fs.existsSync(pm2Home)) {
- throw new Error('PM2 root can not be located, try to initialize PM2 by executing `pm2 ls` or set environment variable vi `export PM2_HOME=[ROOT]`.');
- }
- }
- return pm2Home;
-}
-
/**
* Run socket.io server.
*/
@@ -88,25 +70,47 @@ Monitor.prototype.quit = function () {
/**
* Connect to socket.io server.
* @param {String} ns the namespace.
- * @param {Function} callback
+ * @param {Function} success
+ * @param {Function} failure
*/
-Monitor.prototype.connect = function (options, callback) {
- if (!options.port || !options.namespace) {
- throw new Error('Port and namespace are both required!');
+Monitor.prototype.connect = function (options, success, failure) {
+ if (!options.port) {
+ throw new Error('Port is required!');
}
- var serverUri = (options.protocol || 'http:') + '//' + (options.hostname || '127.0.0.1') + ':' + options.port + (options.path || '') + options.namespace;
+ var auth,
+ serverUri = Monitor.toConnectionString(options);
+
console.info('Connecting to', serverUri);
var socket = socketIOClient(serverUri);
socket.on('connect', function () {
- !callback.__called && callback(null, socket);
- callback.__called = true;
+ !success._called && success(socket);
+ success._called = true;
});
- socket.on('connect_error', function (err) {
- !callback.__called && callback(err, socket);
- callback.__called = true;
+
+ socket.on('error', function (err) {
+ !failure._called && failure(err, socket);
+ failure._called = true;
});
};
+/**
+ * Resolve home path.
+ * @param {String} pm2Home
+ * @returns {*}
+ * @private
+ */
+Monitor.prototype._resolveHome = function (pm2Home) {
+ if (pm2Home && pm2Home.indexOf('~/') == 0) {
+ // Get root directory of PM2.
+ pm2Home = process.env.PM2_HOME || path.resolve(process.env.HOME || process.env.HOMEPATH, pm2Home.substr(2));
+
+ // Make sure exist.
+ if (!pm2Home || !fs.existsSync(pm2Home)) {
+ throw new Error('PM2 root can not be located, try to initialize PM2 by executing `pm2 ls` or set environment variable vi `export PM2_HOME=[ROOT]`.');
+ }
+ }
+ return pm2Home;
+};
/**
* Initialize options and configurations.
@@ -177,13 +181,13 @@ Monitor.prototype._connectSysSock = function (socket) {
// Trigger actions of process.
socket.on('action', function (action, id) {
- console.info('[' + id + ']', action, 'sending to pm2 daemon...');
+ console.debug('[pm2:' + id + ']', action, 'sending to pm2 daemon...');
pm.action(self.options.pm2Conf.DAEMON_RPC_PORT, action, id, function (err, forceRefresh) {
if (err) {
console.error(action, err.message);
return socket.emit('action', id, err.message);
}
- console.info('[' + id + ']', action, 'completed!');
+ console.debug('[pm2:' + id + ']', action, 'completed!');
forceRefresh && self._throttleRefresh();
});
});
@@ -252,7 +256,7 @@ Monitor.prototype._connectLogSock = function (socket) {
return emitError(err, pm_id, keepANSI);
}
- console.info('[' + pm_id + ']', 'tail starting...');
+ console.info('[pm2:' + pm_id + ']', 'tail starting...');
self._tails[pm_id] = tails;
});
}
@@ -282,18 +286,19 @@ Monitor.prototype._connectProcSock = function (socket) {
function killObserver() {
var socks = self._sockio.of(conf.NSP.PROC).sockets,
canNotBeDeleted = {};
- if (socks && socks.length > 0) {
+
+ if (Array.isArray(socks) && socks.length > 0) {
socks.forEach(function (sock) {
canNotBeDeleted[sock.pid.toString()] = 1;
});
}
- for (var pid in this._usages) {
+ for (var pid in self._usages) {
var timer;
- if (!canNotBeDeleted[pid] && (timer = this._usages[pid])) {
+ if (!canNotBeDeleted[pid] && (timer = self._usages[pid])) {
clearInterval(timer);
- delete this._usages[pid];
- console.info('[' + pid + ']', 'cpu and memory observer destroyed!');
+ delete self._usages[pid];
+ console.debug('[pid:' + pid + ']', 'cpu and memory observer destroyed!');
}
}
}
@@ -306,7 +311,7 @@ Monitor.prototype._connectProcSock = function (socket) {
return;
}
- console.info('[' + pidStr + ']', 'cpu and memory observer is running...');
+ console.debug('[pid:' + pidStr + ']', 'cpu and memory observer is running...');
function runTimer() {
pidusage.stat(pid, function (err, stat) {
@@ -493,7 +498,7 @@ Monitor.prototype._killTailProcess = function (pm_id) {
} catch (err) {}
});
delete self._tails[id];
- console.info('[' + id + ']', 'tail destroyed!');
+ console.info('[pm2:' + id + ']', 'tail destroyed!');
}
if (!isNaN(pm_id)) {
return killTail(pm_id);
@@ -529,10 +534,113 @@ Monitor.prototype._listeningSocketIO = function () {
console.info('Listening connection event on', nsp.toLowerCase());
}
+ var auth;
+ if (!(this.options.agent && (auth = this.options.agent.authorization))) {
+ return;
+ }
this._sockio.use(function (socket, next) {
- // console.log(socket.handshake);
+ if (auth !== socket.handshake.query.auth) {
+ return next(new Error('unauthorized'));
+ }
next();
- })
+ });
+};
+
+/**
+ * List all available monitors.
+ * @param {Object} options
+ * @return {Object}
+ */
+Monitor.available = function (options) {
+ options.agent = options.agent || {};
+ var remotable = options.remotes && _.keys(options.remotes).length > 0;
+
+ if (options.agent.offline && !remotable) {
+ return null;
+ }
+
+ options.port = options.port || 8088;
+
+ if (!remotable) {
+ return options;
+ }
+ var q = {
+ name: 'socket_server',
+ message: 'Which socket server would you wanna connect to',
+ type: 'list',
+ choices: []
+ },
+ maxShortLength = 0;
+ for (var remote in options.remotes) {
+ var connectionString = options.remotes[remote];
+ q.choices.push({
+ value: connectionString,
+ short: remote
+ });
+ maxShortLength = Math.max(maxShortLength, remote.length);
+ }
+ if (!options.agent.offline) {
+ var short = 'localhost',
+ connectionString = (options.agent && options.agent.authorization ? options.agent.authorization + '@' : '') + '127.0.0.1:' + options.port;
+ q.choices.push({
+ value: connectionString,
+ short: short
+ });
+ maxShortLength = Math.max(maxShortLength, short.length);
+ }
+
+ if (q.choices.length > 1) {
+ q.choices.forEach(function (c) {
+ c.name = '[' + c.short + Array(maxShortLength - c.short.length + 1).join(' ') + '] ' + c.value;
+ });
+ }
+
+ return q;
+};
+
+/**
+ * Convert connection object to string.
+ * @param {Object} connection
+ * @return {String}
+ */
+Monitor.toConnectionString = function (connection) {
+ var uri = (connection.protocol || 'http:') + '//' + (connection.hostname || '127.0.0.1') + ':' + connection.port +
+ (connection.path || '') + (connection.namespace || '');
+
+ if (connection.authorization) {
+ uri += (uri.indexOf('?') > 0 ? '&' : '?') + 'auth=' + connection.authorization;
+ }
+ return uri;
+};
+
+/**
+ * Parse connection string to an uri object.
+ * @param {String} connectionString
+ * @return {Object}
+ */
+Monitor.parseConnectionString = function (connectionString) {
+ var connection = {
+ port: 8088,
+ hostname: '127.0.0.1',
+ authorization: ''
+ };
+ var lastAt = connectionString.lastIndexOf('@');
+ if (lastAt >= 0) {
+ connection.authorization = connectionString.slice(0, lastAt);
+ connectionString = connectionString.slice(lastAt + 1);
+ }
+ if (!/^http(s)?:\/\//i.test(connectionString)) {
+ connectionString = 'http://' + connectionString;
+ }
+
+ if (connectionString) {
+ connectionString = url.parse(connectionString);
+ connection.hostname = connectionString.hostname;
+ connection.port = connectionString.port;
+ connection.path = _.trimLeft(connectionString.path, '/');
+ connection.protocol = connectionString.protocol;
+ }
+ return connection;
};
Object.defineProperty(Monitor.prototype, 'sockio', {
diff --git a/pm2-gui.ini b/pm2-gui.ini
index 66cac0c..847a6f2 100644
--- a/pm2-gui.ini
+++ b/pm2-gui.ini
@@ -31,17 +31,17 @@ date = false
;
; Log level, one of debug, log, info, warn, error.
;
-level = log
+level = debug
[agent]
;
; This authorization will be used to authorize socket / web connections if it's set.
;
-; authorization = AuTh
+authorization = AuTh
;
; A value indicates whether agent offline or not.
;
-; offline = false
+; offline = true
[remotes]
;
; the dashboard and web server will use this section to connect remoting socket server
@@ -51,3 +51,4 @@ level = log
; pm2@172 = 192.168.1.172:9001
; pm2@173 = 192.168.1.173:9000
;
+pm2@138 = AuTh107@192.168.100.138:8088
diff --git a/pm2-gui.js b/pm2-gui.js
index 8877af0..2c161b0 100644
--- a/pm2-gui.js
+++ b/pm2-gui.js
@@ -2,7 +2,6 @@ var chalk = require('chalk'),
path = require('path'),
fs = require('fs'),
_ = require('lodash'),
- url = require('url'),
socketIO = require('socket.io'),
inquirer = require("inquirer"),
conf = require('./lib/util/conf'),
@@ -93,59 +92,28 @@ function dashboard(confFile) {
var monitor = slave({
confFile: confFile
}),
- options = _.clone(monitor.options);
+ options = _.clone(monitor.options),
+ q = Monitor.available(options);
- options.agent = options.agent || {};
- var remotable = options.remotes && _.keys(options.remotes).length > 0;
-
- if (options.agent.offline && remotable) {
+ if (!q) {
console.error('No agent is online, can not start it.');
return process.exit(0);
}
- options.port = options.port || 8088;
-
- if (!remotable) {
- return _connectToDashboard(monitor, options);
+ var ql = q.choices.length;
+ if (ql == 1) {
+ console.info('There is just one remoting server online, try to connect it.')
+ return _connectToDashboard(monitor, options, Monitor.parseConnectionString(q.choices[0].value));
}
+
+ q.choices.splice(ql - 1, 0, new inquirer.Separator());
+
console.info('Remoting servers are online, choose one you are intrested in.')
- var q = {
- name: 'socket_server',
- message: 'Which socket server would you wanna connect to',
- type: 'list',
- choices: []
- },
- maxShortLength = 0;
- for (var remote in options.remotes) {
- var connectionString = options.remotes[remote];
- q.choices.push({
- value: connectionString,
- short: remote
- });
- maxShortLength = Math.max(maxShortLength, remote.length);
- }
- if (!options.agent.offline) {
- q.choices.push(new inquirer.Separator());
- var short = 'localhost',
- connectionString = (options.agent && options.agent.authorization ? options.agent.authorization + '@' : '') + '127.0.0.1:' + options.port;
- q.choices.push({
- value: connectionString,
- short: short
- });
- maxShortLength = Math.max(maxShortLength, short.length);
- }
-
- q.choices.forEach(function (c) {
- if (c.type != 'separator') {
- c.name = '[' + c.short + Array(maxShortLength - c.short.length + 1).join(' ') + '] ' + c.value;
- }
- });
-
console.log('');
inquirer.prompt(q, function (answers) {
console.log('');
- _connectToDashboard(monitor, options, _parseConnectionString(answers.socket_server));
+ _connectToDashboard(monitor, options, Monitor.parseConnectionString(answers.socket_server));
});
}
@@ -233,51 +201,24 @@ function slave(options) {
}
function _connectToDashboard(monitor, options, connection) {
- if (!connection || !!~['127.0.0.1', '0.0.0.0', 'localhost'].indexOf(connection.hostname)) {
- return monitor.connect(_.extend({
- namespace: conf.NSP.SYS
- }, options), function (err, socket) {
- if (err || !socket) {
- console.warn('Agent is offline, try to start it.');
- var sockio = socketIO();
- sockio.listen(options.port);
- monitor.sockio = sockio;
- monitor.run();
- } else {
- console.info('Agent is online, try to connect it in dashboard directly.');
- options.sockets = {};
- var ns = _.trimLeft(conf.NSP.SYS, '/');
- options.sockets[ns] = socket;
+ connection = _.extend({}, options, connection);
+ if (!!~['127.0.0.1', '0.0.0.0', 'localhost'].indexOf(connection.hostname)) {
+ return monitor.connect(connection, function (socket) {
+ console.info('Agent is online, try to connect it in dashboard directly.');
+ layout(connection).render(monitor);
+ }, function (err, socket) {
+ if (err == 'unauthorized') {
+ console.error('There was an error with the authentication:', err);
+ return process.exit(0);
}
-
- layout(options).render(monitor);
+ console.warn('Agent is offline, try to start it.');
+ var sockio = socketIO();
+ sockio.listen(connection.port);
+ monitor.sockio = sockio;
+ monitor.run();
+ layout(connection).render(monitor);
});
}
layout(connection).render(monitor);
-}
-
-function _parseConnectionString(connectionString) {
- var connection = {
- port: 8088,
- hostname: '127.0.0.1',
- authorization: ''
- };
- var lastAt = connectionString.lastIndexOf('@');
- if (lastAt >= 0) {
- connection.authorization = connectionString.slice(0, lastAt);
- connectionString = connectionString.slice(lastAt + 1);
- }
- if (!/^http(s)?:\/\//i.test(connectionString)) {
- connectionString = 'http://' + connectionString;
- }
-
- if (connectionString) {
- connectionString = url.parse(connectionString);
- connection.hostname = connectionString.hostname;
- connection.port = connectionString.port;
- connection.path = _.trimLeft(connectionString.path, '/');
- connection.protocol = connectionString.protocol;
- }
- return connection;
-}
+}
\ No newline at end of file
diff --git a/web/public/js/index.html.js b/web/public/js/index.html.js
index c71f275..bbb80ad 100644
--- a/web/public/js/index.html.js
+++ b/web/public/js/index.html.js
@@ -19,21 +19,6 @@ var sysStat,
popupProc,
scrolled;
-/**
- * Initialization.
- */
-$(window).ready(function () {
-
- prepareDOM();
-
- initFullPage();
-
- listenSocket();
-
- renderFanavi();
-
-});
-
/**
* Prepare DOM, cache elements, templates...
*/
@@ -98,11 +83,11 @@ function initFullPage() {
/**
* Set fullPage enable or disable.
* @param {Boolean} enable
- * @param {Boolean} exceptScroll
+ * @param {Boolean} unscrollable
*/
-function setFPEnable(enable, exceptScroll) {
+function setFPEnable(enable, unscrollable) {
$.fn.fullpage.setAllowScrolling(enable);
- if (!exceptScroll) {
+ if (!unscrollable) {
$.fn.fullpage.setKeyboardScrolling(enable);
eles.fpNav[enable ? 'fadeIn' : 'fadeOut']();
}
@@ -112,15 +97,40 @@ function setFPEnable(enable, exceptScroll) {
* Connect to socket server.
*/
function connectSocketServer(ns) {
- var socket = io('127.0.0.1:8088' + ns + '?token=abc');
- socket.on('error', info);
+ var uri = GUI.connection.value,
+ index = uri.indexOf('?'),
+ query = '';
+ if (index > 0) {
+ query = uri.slice(index);
+ uri = uri.slice(0, index);
+ }
+ console.log('before', uri, query, ns);
+
+ uri = _.trimRight(uri, '/') + (ns || '') + query;
+ if (!ns) {
+ return io.connect(uri).on('error', onError);
+ }
+ var socket = io.connect(uri);
+ socket.on('error', onError);
return socket;
}
+/**
+ * Fires on error.
+ * @param {String} err
+ */
+function onError(err) {
+ if (err == 'unauthorized') {
+ err = 'There was an error with the authentication: ' + err;
+ }
+ info(err);
+}
+
/**
* Initialize socket.io client and add listeners.
*/
function listenSocket() {
+ connectSocketServer();
sockets.sys = connectSocketServer(NSP.SYS);
// information from server.
sockets.sys.on('info', info);
diff --git a/web/public/js/socket.io.js b/web/public/js/socket.io.js
index eed35cc..fb6968c 100644
--- a/web/public/js/socket.io.js
+++ b/web/public/js/socket.io.js
@@ -1,350 +1,6988 @@
-!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;o0&&!this.encoding){var pack=this.packetBuffer.shift();this.packet(pack);}};Manager.prototype.cleanup=function(){var sub;while(sub=this.subs.shift())sub.destroy();this.packetBuffer=[];this.encoding=false;this.decoder.destroy();};Manager.prototype.close=Manager.prototype.disconnect=function(){this.skipReconnect=true;this.readyState='closed';this.engine&&this.engine.close();};Manager.prototype.onclose=function(reason){debug('close');this.cleanup();this.readyState='closed';this.emit('close',reason);if(this._reconnection&&!this.skipReconnect){this.reconnect();}};Manager.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var self=this;this.attempts++;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);this.reconnecting=true;var timer=setTimeout(function(){if(self.skipReconnect)return;debug('attempting reconnect');self.emitAll('reconnect_attempt',self.attempts);self.emitAll('reconnecting',self.attempts);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);}});}};Manager.prototype.onreconnect=function(){var attempt=this.attempts;this.attempts=0;this.reconnecting=false;this.emitAll('reconnect',attempt);};},{"./on":4,"./socket":5,"./url":6,"component-bind":7,"component-emitter":8,"debug":9,"engine.io-client":10,"indexof":36,"object-component":37,"socket.io-parser":40}],4:[function(_dereq_,module,exports){module.exports=on;function on(obj,ev,fn){obj.on(ev,fn);return{destroy:function(){obj.removeListener(ev,fn);}};}},{}],5:[function(_dereq_,module,exports){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=exports=Socket;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};var emit=Emitter.prototype.emit;function Socket(io,nsp){this.io=io;this.nsp=nsp;this.json=this;this.ids=0;this.acks={};if(this.io.autoConnect)this.open();this.receiveBuffer=[];this.sendBuffer=[];this.connected=false;this.disconnected=true;}
-Emitter(Socket.prototype);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'))];};Socket.prototype.open=Socket.prototype.connect=function(){if(this.connected)return this;this.subEvents();this.io.open();if('open'==this.io.readyState)this.onopen();return this;};Socket.prototype.send=function(){var args=toArray(arguments);args.unshift('message');this.emit.apply(this,args);return this;};Socket.prototype.emit=function(ev){if(events.hasOwnProperty(ev)){emit.apply(this,arguments);return this;}
-var args=toArray(arguments);var parserType=parser.EVENT;if(hasBin(args)){parserType=parser.BINARY_EVENT;}
-var packet={type:parserType,data:args};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;};Socket.prototype.packet=function(packet){packet.nsp=this.nsp;this.io.packet(packet);};Socket.prototype.onopen=function(){debug('transport is open - connecting');if('/'!=this.nsp){this.packet({type:parser.CONNECT});}};Socket.prototype.onclose=function(reason){debug('close (%s)',reason);this.connected=false;this.disconnected=true;this.emit('disconnect',reason);};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;}};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);}};Socket.prototype.ack=function(id){var self=this;var sent=false;return function(){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});};};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];};Socket.prototype.onconnect=function(){this.connected=true;this.disconnected=false;this.emit('connect');this.emitBuffered();};Socket.prototype.emitBuffered=function(){var i;for(i=0;i=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';};debug.enabled=function(name){for(var i=0,len=debug.skips.length;i';iframe=document.createElement(html);}catch(e){iframe=document.createElement('iframe');iframe.name=self.iframeId;iframe.src='javascript:0';}
-iframe.id=self.iframeId;self.form.appendChild(iframe);self.iframe=iframe;}
-initIframe();data=data.replace(rEscapedNewline,'\\\n');this.area.value=data.replace(rNewline,'\\n');try{this.form.submit();}catch(e){}
-if(this.iframe.attachEvent){this.iframe.onreadystatechange=function(){if(self.iframe.readyState=='complete'){complete();}};}else{this.iframe.onload=complete;}};}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./polling":17,"component-inherit":20}],16:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_('xmlhttprequest');var Polling=_dereq_('./polling');var Emitter=_dereq_('component-emitter');var inherit=_dereq_('component-inherit');var debug=_dereq_('debug')('engine.io-client:polling-xhr');module.exports=XHR;module.exports.Request=Request;function empty(){}
-function XHR(opts){Polling.call(this,opts);if(global.location){var isSSL='https:'==location.protocol;var port=location.port;if(!port){port=isSSL?443:80;}
-this.xd=opts.hostname!=global.location.hostname||port!=opts.port;this.xs=opts.secure!=isSSL;}}
-inherit(XHR,Polling);XHR.prototype.supportsBinary=true;XHR.prototype.request=function(opts){opts=opts||{};opts.uri=this.uri();opts.xd=this.xd;opts.xs=this.xs;opts.agent=this.agent||false;opts.supportsBinary=this.supportsBinary;opts.enablesXDR=this.enablesXDR;return new Request(opts);};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;};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;};function Request(opts){this.method=opts.method||'GET';this.uri=opts.uri;this.xd=!!opts.xd;this.xs=!!opts.xs;this.async=false!==opts.async;this.data=undefined!=opts.data?opts.data:null;this.agent=opts.agent;this.isBinary=opts.isBinary;this.supportsBinary=opts.supportsBinary;this.enablesXDR=opts.enablesXDR;this.create();}
-Emitter(Request.prototype);Request.prototype.create=function(){var xhr=this.xhr=new XMLHttpRequest({agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR});var self=this;try{debug('xhr open %s: %s',this.method,this.uri);xhr.open(this.method,this.uri,this.async);if(this.supportsBinary){xhr.responseType='arraybuffer';}
-if('POST'==this.method){try{if(this.isBinary){xhr.setRequestHeader('Content-type','application/octet-stream');}else{xhr.setRequestHeader('Content-type','text/plain;charset=UTF-8');}}catch(e){}}
-if('withCredentials'in xhr){xhr.withCredentials=true;}
-if(this.hasXDR()){xhr.onload=function(){self.onLoad();};xhr.onerror=function(){self.onError(xhr.responseText);};}else{xhr.onreadystatechange=function(){if(4!=xhr.readyState)return;if(200==xhr.status||1223==xhr.status){self.onLoad();}else{setTimeout(function(){self.onError(xhr.status);},0);}};}
-debug('xhr data %s',this.data);xhr.send(this.data);}catch(e){setTimeout(function(){self.onError(e);},0);return;}
-if(global.document){this.index=Request.requestsCount++;Request.requests[this.index]=this;}};Request.prototype.onSuccess=function(){this.emit('success');this.cleanup();};Request.prototype.onData=function(data){this.emit('data',data);this.onSuccess();};Request.prototype.onError=function(err){this.emit('error',err);this.cleanup();};Request.prototype.cleanup=function(){if('undefined'==typeof this.xhr||null===this.xhr){return;}
-if(this.hasXDR()){this.xhr.onload=this.xhr.onerror=empty;}else{this.xhr.onreadystatechange=empty;}
-try{this.xhr.abort();}catch(e){}
-if(global.document){delete Request.requests[this.index];}
-this.xhr=null;};Request.prototype.onLoad=function(){var data;try{var contentType;try{contentType=this.xhr.getResponseHeader('Content-Type').split(';')[0];}catch(e){}
-if(contentType==='application/octet-stream'){data=this.xhr.response;}else{if(!this.supportsBinary){data=this.xhr.responseText;}else{data='ok';}}}catch(e){this.onError(e);}
-if(null!=data){this.onData(data);}};Request.prototype.hasXDR=function(){return'undefined'!==typeof global.XDomainRequest&&!this.xs&&this.enablesXDR;};Request.prototype.abort=function(){this.cleanup();};if(global.document){Request.requestsCount=0;Request.requests={};if(global.attachEvent){global.attachEvent('onunload',unloadHandler);}else if(global.addEventListener){global.addEventListener('beforeunload',unloadHandler);}}
-function unloadHandler(){for(var i in Request.requests){if(Request.requests.hasOwnProperty(i)){Request.requests[i].abort();}}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./polling":17,"component-emitter":8,"component-inherit":20,"debug":9,"xmlhttprequest":19}],17:[function(_dereq_,module,exports){var Transport=_dereq_('../transport');var parseqs=_dereq_('parseqs');var parser=_dereq_('engine.io-parser');var inherit=_dereq_('component-inherit');var debug=_dereq_('debug')('engine.io-client:polling');module.exports=Polling;var hasXHR2=(function(){var XMLHttpRequest=_dereq_('xmlhttprequest');var xhr=new XMLHttpRequest({xdomain:false});return null!=xhr.responseType;})();function Polling(opts){var forceBase64=(opts&&opts.forceBase64);if(!hasXHR2||forceBase64){this.supportsBinary=false;}
-Transport.call(this,opts);}
-inherit(Polling,Transport);Polling.prototype.name='polling';Polling.prototype.doOpen=function(){this.poll();};Polling.prototype.pause=function(onPause){var pending=0;var self=this;this.readyState='pausing';function pause(){debug('paused');self.readyState='paused';onPause();}
-if(this.polling||!this.writable){var total=0;if(this.polling){debug('we are currently polling - waiting to pause');total++;this.once('pollComplete',function(){debug('pre-pause polling complete');--total||pause();});}
-if(!this.writable){debug('we are currently writing - waiting to pause');total++;this.once('drain',function(){debug('pre-pause writing complete');--total||pause();});}}else{pause();}};Polling.prototype.poll=function(){debug('polling');this.polling=true;this.doPoll();this.emit('poll');};Polling.prototype.onData=function(data){var self=this;debug('polling got data %s',data);var callback=function(packet,index,total){if('opening'==self.readyState){self.onOpen();}
-if('close'==packet.type){self.onClose();return false;}
-self.onPacket(packet);};parser.decodePayload(data,this.socket.binaryType,callback);if('closed'!=this.readyState){this.polling=false;this.emit('pollComplete');if('open'==this.readyState){this.poll();}else{debug('ignoring poll - transport state "%s"',this.readyState);}}};Polling.prototype.doClose=function(){var self=this;function close(){debug('writing close packet');self.write([{type:'close'}]);}
-if('open'==this.readyState){debug('transport open - closing');close();}else{debug('transport not open - deferring close');this.once('open',close);}};Polling.prototype.write=function(packets){var self=this;this.writable=false;var callbackfn=function(){self.writable=true;self.emit('drain');};var self=this;parser.encodePayload(packets,this.supportsBinary,function(data){self.doWrite(data,callbackfn);});};Polling.prototype.uri=function(){var query=this.query||{};var schema=this.secure?'https':'http';var port='';if(false!==this.timestampRequests){query[this.timestampParam]=+new Date+'-'+Transport.timestamps++;}
-if(!this.supportsBinary&&!query.sid){query.b64=1;}
-query=parseqs.encode(query);if(this.port&&(('https'==schema&&this.port!=443)||('http'==schema&&this.port!=80))){port=':'+this.port;}
-if(query.length){query='?'+query;}
-return schema+'://'+this.hostname+port+this.path+query;};},{"../transport":13,"component-inherit":20,"debug":9,"engine.io-parser":21,"parseqs":29,"xmlhttprequest":19}],18:[function(_dereq_,module,exports){var Transport=_dereq_('../transport');var parser=_dereq_('engine.io-parser');var parseqs=_dereq_('parseqs');var inherit=_dereq_('component-inherit');var debug=_dereq_('debug')('engine.io-client:websocket');var WebSocket=_dereq_('ws');module.exports=WS;function WS(opts){var forceBase64=(opts&&opts.forceBase64);if(forceBase64){this.supportsBinary=false;}
-Transport.call(this,opts);}
-inherit(WS,Transport);WS.prototype.name='websocket';WS.prototype.supportsBinary=true;WS.prototype.doOpen=function(){if(!this.check()){return;}
-var self=this;var uri=this.uri();var protocols=void(0);var opts={agent:this.agent};this.ws=new WebSocket(uri,protocols,opts);if(this.ws.binaryType===undefined){this.supportsBinary=false;}
-this.ws.binaryType='arraybuffer';this.addEventListeners();};WS.prototype.addEventListeners=function(){var self=this;this.ws.onopen=function(){self.onOpen();};this.ws.onclose=function(){self.onClose();};this.ws.onmessage=function(ev){self.onData(ev.data);};this.ws.onerror=function(e){self.onError('websocket error',e);};};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);};}
-WS.prototype.write=function(packets){var self=this;this.writable=false;for(var i=0,l=packets.length;i1){return{type:packetslist[type],data:data.substring(1)};}else{return{type:packetslist[type]};}}
-var asArray=new Uint8Array(data);var type=asArray[0];var rest=sliceBuffer(data,1);if(Blob&&binaryType==='blob'){rest=new Blob([rest]);}
-return{type:packetslist[type],data:rest};};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};};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,true,function(message){doneCallback(null,setLengthHeader(message));});}
-map(packets,encodeOne,function(err,results){return callback(results.join(''));});};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;i0){var tailArray=new Uint8Array(bufferTail);var isString=tailArray[0]===0;var msgLength='';for(var i=1;;i++){if(tailArray[i]==255)break;if(msgLength.length>310){numberTooLong=true;break;}
-msgLength+=tailArray[i];}
-if(numberTooLong)return callback(err,0,1);bufferTail=sliceBuffer(bufferTail,2+msgLength.length);msgLength=parseInt(msgLength);var msg=sliceBuffer(bufferTail,0,msgLength);if(isString){try{msg=String.fromCharCode.apply(null,new Uint8Array(msg));}catch(e){var typed=new Uint8Array(msg);msg='';for(var i=0;ibytes){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>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>4);bytes[p++]=((encoded2&15)<<4)|(encoded3>>2);bytes[p++]=((encoded3&3)<<6)|(encoded4&63);}
-return arraybuffer;};})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");},{}],26:[function(_dereq_,module,exports){(function(global){var BlobBuilder=global.BlobBuilder||global.WebKitBlobBuilder||global.MSBlobBuilder||global.MozBlobBuilder;var blobSupported=(function(){try{var b=new Blob(['hi']);return b.size==2;}catch(e){return false;}})();var blobBuilderSupported=BlobBuilder&&BlobBuilder.prototype.append&&BlobBuilder.prototype.getBlob;function BlobBuilderConstructor(ary,options){options=options||{};var bb=new BlobBuilder();for(var i=0;i=0xD800&&value<=0xDBFF&&counter0xFFFF){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){return stringFromCharCode(codePoint);}
-var symbol='';if((codePoint&0xFFFFF800)==0){symbol=stringFromCharCode(((codePoint>>6)&0x1F)|0xC0);}
-else if((codePoint&0xFFFF0000)==0){symbol=stringFromCharCode(((codePoint>>12)&0x0F)|0xE0);symbol+=createByte(codePoint,6);}
-else if((codePoint&0xFFE00000)==0){symbol=stringFromCharCode(((codePoint>>18)&0x07)|0xF0);symbol+=createByte(codePoint,12);symbol+=createByte(codePoint,6);}
-symbol+=stringFromCharCode((codePoint&0x3F)|0x80);return symbol;}
-function utf8encode(string){var codePoints=ucs2decode(string);var length=codePoints.length;var index=-1;var codePoint;var byteString='';while(++index=byteCount){throw Error('Invalid byte index');}
-var continuationByte=byteArray[byteIndex]&0xFF;byteIndex++;if((continuationByte&0xC0)==0x80){return continuationByte&0x3F;}
-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;}
-byte1=byteArray[byteIndex]&0xFF;byteIndex++;if((byte1&0x80)==0){return byte1;}
-if((byte1&0xE0)==0xC0){var byte2=readContinuationByte();codePoint=((byte1&0x1F)<<6)|byte2;if(codePoint>=0x80){return codePoint;}else{throw Error('Invalid continuation byte');}}
-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');}}
-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};if(typeof define=='function'&&typeof define.amd=='object'&&define.amd){define(function(){return utf8;});}else if(freeExports&&!freeExports.nodeType){if(freeModule){freeModule.exports=utf8;}else{var object={};var hasOwnProperty=object.hasOwnProperty;for(var key in utf8){hasOwnProperty.call(utf8,key)&&(freeExports[key]=utf8[key]);}}}else{root.utf8=utf8;}}(this));}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],28:[function(_dereq_,module,exports){(function(global){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,'');if(global.JSON&&JSON.parse){return JSON.parse(data);}
-if(rvalidchars.test(data.replace(rvalidescape,'@').replace(rvalidtokens,']').replace(rvalidbraces,''))){return(new Function('return '+data))();}};}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],29:[function(_dereq_,module,exports){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;};exports.decode=function(qs){var qry={};var pairs=qs.split('&');for(var i=0,l=pairs.length;i1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400);};}
-if(!(isProperty={}.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={"toString":1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result;};}else{constructor=members.constructor;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);};}
-var PrimitiveTypes={'boolean':1,'number':1,'string':1,'undefined':1};var isHostType=function(object,property){var type=typeof object[property];return type=='object'?!!object[property]:!PrimitiveTypes[type];};forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0;}).prototype.valueOf=0;members=new Properties();for(property in members){if(isProperty.call(members,property)){size++;}}
-Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];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){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property);}}
-for(length=members.length;property=members[--length];hasProperty.call(object,property)&&callback(property));};}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property);}}};}else{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);}}
-if(isConstructor||isProperty.call(object,(property="constructor"))){callback(property);}};}
-return forEach(object,callback);};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width);};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-1/0&&value<1/0){if(getDay){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);time=(value%864e5+864e5)%864e5;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();}
-value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z";}else{value=null;}}else if(typeof value.toJSON=="function"&&((className!=numberClass&&className!=stringClass&&className!=arrayClass)||isProperty.call(value,"toJSON"))){value=value.toJSON(property);}}
-if(callback){value=callback.call(object,property,value);}
-if(value===null){return"null";}
-className=getClass.call(value);if(className==booleanClass){return""+value;}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null";}else if(className==stringClass){return quote(""+value);}
-if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError();}}
-stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index0){for(whitespace="",width>10&&(width=10);whitespace.length=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort();}}
-value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort();}}else{if(charCode==34){break;}
-charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index);}
-value+=source.slice(begin,Index);}}
-if(source.charCodeAt(Index)==34){Index++;return value;}
-abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index);}
-if(charCode>=48&&charCode<=57){if(charCode==48&&((charCode=source.charCodeAt(Index+1)),charCode>=48&&charCode<=57)){abort();}
-isSigned=false;for(;Index=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position=48&&charCode<=57);position++);if(position==Index){abort();}
-Index=position;}
-charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++;}
-for(position=Index;position=48&&charCode<=57);position++);if(position==Index){abort();}
-Index=position;}
-return+source.slice(begin,Index);}
-if(isSigned){abort();}
-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;}
-abort();}}
-return"$";};var get=function(value){var results,hasMembers;if(value=="$"){abort();}
-if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1);}
-if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break;}
-if(hasMembers){if(value==","){value=lex();if(value=="]"){abort();}}else{abort();}}
-if(value==","){abort();}
-results.push(get(value));}
-return results;}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break;}
-if(hasMembers){if(value==","){value=lex();if(value=="}"){abort();}}else{abort();}}
-if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort();}
-results[value.slice(1)]=get(lex());}
-return results;}
-abort();}
-return value;};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property];}else{source[property]=element;}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){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);};JSON3.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort();}
-Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result;};}}
-if(isLoader){define(function(){return JSON3;});}}(this));},{}],44:[function(_dereq_,module,exports){module.exports=toArray
-function toArray(list,index){var array=[]
-index=index||0
-for(var i=index||0;i 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":11,"indexof":40,"object-component":41,"socket.io-parser":44}],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":36,"socket.io-parser":44,"to-array":48}],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":42}],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';
+ iframe = document.createElement(html);
+ } catch (e) {
+ iframe = document.createElement('iframe');
+ iframe.name = self.iframeId;
+ iframe.src = 'javascript:0';
+ }
+
+ iframe.id = self.iframeId;
+
+ self.form.appendChild(iframe);
+ self.iframe = iframe;
+ }
+
+ initIframe();
+
+ // escape \n to prevent it from being converted into \r\n by some UAs
+ // double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side
+ data = data.replace(rEscapedNewline, '\\\n');
+ this.area.value = data.replace(rNewline, '\\n');
+
+ try {
+ this.form.submit();
+ } catch(e) {}
+
+ if (this.iframe.attachEvent) {
+ this.iframe.onreadystatechange = function(){
+ if (self.iframe.readyState == 'complete') {
+ complete();
+ }
+ };
+ } else {
+ this.iframe.onload = complete;
+ }
+};
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./polling":18,"component-inherit":21}],17:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Module requirements.
+ */
+
+var XMLHttpRequest = _dereq_('xmlhttprequest');
+var Polling = _dereq_('./polling');
+var Emitter = _dereq_('component-emitter');
+var inherit = _dereq_('component-inherit');
+var debug = _dereq_('debug')('engine.io-client:polling-xhr');
+
+/**
+ * Module exports.
+ */
+
+module.exports = XHR;
+module.exports.Request = Request;
+
+/**
+ * Empty function
+ */
+
+function empty(){}
+
+/**
+ * XHR Polling constructor.
+ *
+ * @param {Object} opts
+ * @api public
+ */
+
+function XHR(opts){
+ Polling.call(this, opts);
+
+ if (global.location) {
+ var isSSL = 'https:' == location.protocol;
+ var port = location.port;
+
+ // some user agents have empty `location.port`
+ if (!port) {
+ port = isSSL ? 443 : 80;
+ }
+
+ this.xd = opts.hostname != global.location.hostname ||
+ port != opts.port;
+ this.xs = opts.secure != isSSL;
+ }
+}
+
+/**
+ * Inherits from Polling.
+ */
+
+inherit(XHR, Polling);
+
+/**
+ * XHR supports binary
+ */
+
+XHR.prototype.supportsBinary = true;
+
+/**
+ * Creates a request.
+ *
+ * @param {String} method
+ * @api private
+ */
+
+XHR.prototype.request = function(opts){
+ opts = opts || {};
+ opts.uri = this.uri();
+ opts.xd = this.xd;
+ opts.xs = this.xs;
+ opts.agent = this.agent || false;
+ opts.supportsBinary = this.supportsBinary;
+ opts.enablesXDR = this.enablesXDR;
+
+ // SSL options for Node.js client
+ opts.pfx = this.pfx;
+ opts.key = this.key;
+ opts.passphrase = this.passphrase;
+ opts.cert = this.cert;
+ opts.ca = this.ca;
+ opts.ciphers = this.ciphers;
+ opts.rejectUnauthorized = this.rejectUnauthorized;
+
+ return new Request(opts);
+};
+
+/**
+ * Sends data.
+ *
+ * @param {String} data to send.
+ * @param {Function} called upon flush.
+ * @api private
+ */
+
+XHR.prototype.doWrite = function(data, fn){
+ var isBinary = typeof data !== 'string' && data !== undefined;
+ var req = this.request({ method: 'POST', data: data, isBinary: isBinary });
+ var self = this;
+ req.on('success', fn);
+ req.on('error', function(err){
+ self.onError('xhr post error', err);
+ });
+ this.sendXhr = req;
+};
+
+/**
+ * Starts a poll cycle.
+ *
+ * @api private
+ */
+
+XHR.prototype.doPoll = function(){
+ debug('xhr poll');
+ var req = this.request();
+ var self = this;
+ req.on('data', function(data){
+ self.onData(data);
+ });
+ req.on('error', function(err){
+ self.onError('xhr poll error', err);
+ });
+ this.pollXhr = req;
+};
+
+/**
+ * Request constructor
+ *
+ * @param {Object} options
+ * @api public
+ */
+
+function Request(opts){
+ this.method = opts.method || 'GET';
+ this.uri = opts.uri;
+ this.xd = !!opts.xd;
+ this.xs = !!opts.xs;
+ this.async = false !== opts.async;
+ this.data = undefined != opts.data ? opts.data : null;
+ this.agent = opts.agent;
+ this.isBinary = opts.isBinary;
+ this.supportsBinary = opts.supportsBinary;
+ this.enablesXDR = opts.enablesXDR;
+
+ // SSL options for Node.js client
+ this.pfx = opts.pfx;
+ this.key = opts.key;
+ this.passphrase = opts.passphrase;
+ this.cert = opts.cert;
+ this.ca = opts.ca;
+ this.ciphers = opts.ciphers;
+ this.rejectUnauthorized = opts.rejectUnauthorized;
+
+ this.create();
+}
+
+/**
+ * Mix in `Emitter`.
+ */
+
+Emitter(Request.prototype);
+
+/**
+ * Creates the XHR object and sends the request.
+ *
+ * @api private
+ */
+
+Request.prototype.create = function(){
+ var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };
+
+ // SSL options for Node.js client
+ opts.pfx = this.pfx;
+ opts.key = this.key;
+ opts.passphrase = this.passphrase;
+ opts.cert = this.cert;
+ opts.ca = this.ca;
+ opts.ciphers = this.ciphers;
+ opts.rejectUnauthorized = this.rejectUnauthorized;
+
+ var xhr = this.xhr = new XMLHttpRequest(opts);
+ var self = this;
+
+ try {
+ debug('xhr open %s: %s', this.method, this.uri);
+ xhr.open(this.method, this.uri, this.async);
+ if (this.supportsBinary) {
+ // This has to be done after open because Firefox is stupid
+ // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension
+ xhr.responseType = 'arraybuffer';
+ }
+
+ if ('POST' == this.method) {
+ try {
+ if (this.isBinary) {
+ xhr.setRequestHeader('Content-type', 'application/octet-stream');
+ } else {
+ xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
+ }
+ } catch (e) {}
+ }
+
+ // ie6 check
+ if ('withCredentials' in xhr) {
+ xhr.withCredentials = true;
+ }
+
+ if (this.hasXDR()) {
+ xhr.onload = function(){
+ self.onLoad();
+ };
+ xhr.onerror = function(){
+ self.onError(xhr.responseText);
+ };
+ } else {
+ xhr.onreadystatechange = function(){
+ if (4 != xhr.readyState) return;
+ if (200 == xhr.status || 1223 == xhr.status) {
+ self.onLoad();
+ } else {
+ // make sure the `error` event handler that's user-set
+ // does not throw in the same tick and gets caught here
+ setTimeout(function(){
+ self.onError(xhr.status);
+ }, 0);
+ }
+ };
+ }
+
+ debug('xhr data %s', this.data);
+ xhr.send(this.data);
+ } catch (e) {
+ // Need to defer since .create() is called directly fhrom the constructor
+ // and thus the 'error' event can only be only bound *after* this exception
+ // occurs. Therefore, also, we cannot throw here at all.
+ setTimeout(function() {
+ self.onError(e);
+ }, 0);
+ return;
+ }
+
+ if (global.document) {
+ this.index = Request.requestsCount++;
+ Request.requests[this.index] = this;
+ }
+};
+
+/**
+ * Called upon successful response.
+ *
+ * @api private
+ */
+
+Request.prototype.onSuccess = function(){
+ this.emit('success');
+ this.cleanup();
+};
+
+/**
+ * Called if we have data.
+ *
+ * @api private
+ */
+
+Request.prototype.onData = function(data){
+ this.emit('data', data);
+ this.onSuccess();
+};
+
+/**
+ * Called upon error.
+ *
+ * @api private
+ */
+
+Request.prototype.onError = function(err){
+ this.emit('error', err);
+ this.cleanup(true);
+};
+
+/**
+ * Cleans up house.
+ *
+ * @api private
+ */
+
+Request.prototype.cleanup = function(fromError){
+ if ('undefined' == typeof this.xhr || null === this.xhr) {
+ return;
+ }
+ // xmlhttprequest
+ if (this.hasXDR()) {
+ this.xhr.onload = this.xhr.onerror = empty;
+ } else {
+ this.xhr.onreadystatechange = empty;
+ }
+
+ if (fromError) {
+ try {
+ this.xhr.abort();
+ } catch(e) {}
+ }
+
+ if (global.document) {
+ delete Request.requests[this.index];
+ }
+
+ this.xhr = null;
+};
+
+/**
+ * Called upon load.
+ *
+ * @api private
+ */
+
+Request.prototype.onLoad = function(){
+ var data;
+ try {
+ var contentType;
+ try {
+ contentType = this.xhr.getResponseHeader('Content-Type').split(';')[0];
+ } catch (e) {}
+ if (contentType === 'application/octet-stream') {
+ data = this.xhr.response;
+ } else {
+ if (!this.supportsBinary) {
+ data = this.xhr.responseText;
+ } else {
+ data = 'ok';
+ }
+ }
+ } catch (e) {
+ this.onError(e);
+ }
+ if (null != data) {
+ this.onData(data);
+ }
+};
+
+/**
+ * Check if it has XDomainRequest.
+ *
+ * @api private
+ */
+
+Request.prototype.hasXDR = function(){
+ return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR;
+};
+
+/**
+ * Aborts the request.
+ *
+ * @api public
+ */
+
+Request.prototype.abort = function(){
+ this.cleanup();
+};
+
+/**
+ * Aborts pending requests when unloading the window. This is needed to prevent
+ * memory leaks (e.g. when using IE) and to ensure that no spurious error is
+ * emitted.
+ */
+
+if (global.document) {
+ Request.requestsCount = 0;
+ Request.requests = {};
+ if (global.attachEvent) {
+ global.attachEvent('onunload', unloadHandler);
+ } else if (global.addEventListener) {
+ global.addEventListener('beforeunload', unloadHandler, false);
+ }
+}
+
+function unloadHandler() {
+ for (var i in Request.requests) {
+ if (Request.requests.hasOwnProperty(i)) {
+ Request.requests[i].abort();
+ }
+ }
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./polling":18,"component-emitter":9,"component-inherit":21,"debug":22,"xmlhttprequest":20}],18:[function(_dereq_,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var Transport = _dereq_('../transport');
+var parseqs = _dereq_('parseqs');
+var parser = _dereq_('engine.io-parser');
+var inherit = _dereq_('component-inherit');
+var debug = _dereq_('debug')('engine.io-client:polling');
+
+/**
+ * Module exports.
+ */
+
+module.exports = Polling;
+
+/**
+ * Is XHR2 supported?
+ */
+
+var hasXHR2 = (function() {
+ var XMLHttpRequest = _dereq_('xmlhttprequest');
+ var xhr = new XMLHttpRequest({ xdomain: false });
+ return null != xhr.responseType;
+})();
+
+/**
+ * Polling interface.
+ *
+ * @param {Object} opts
+ * @api private
+ */
+
+function Polling(opts){
+ var forceBase64 = (opts && opts.forceBase64);
+ if (!hasXHR2 || forceBase64) {
+ this.supportsBinary = false;
+ }
+ Transport.call(this, opts);
+}
+
+/**
+ * Inherits from Transport.
+ */
+
+inherit(Polling, Transport);
+
+/**
+ * Transport name.
+ */
+
+Polling.prototype.name = 'polling';
+
+/**
+ * Opens the socket (triggers polling). We write a PING message to determine
+ * when the transport is open.
+ *
+ * @api private
+ */
+
+Polling.prototype.doOpen = function(){
+ this.poll();
+};
+
+/**
+ * Pauses polling.
+ *
+ * @param {Function} callback upon buffers are flushed and transport is paused
+ * @api private
+ */
+
+Polling.prototype.pause = function(onPause){
+ var pending = 0;
+ var self = this;
+
+ this.readyState = 'pausing';
+
+ function pause(){
+ debug('paused');
+ self.readyState = 'paused';
+ onPause();
+ }
+
+ if (this.polling || !this.writable) {
+ var total = 0;
+
+ if (this.polling) {
+ debug('we are currently polling - waiting to pause');
+ total++;
+ this.once('pollComplete', function(){
+ debug('pre-pause polling complete');
+ --total || pause();
+ });
+ }
+
+ if (!this.writable) {
+ debug('we are currently writing - waiting to pause');
+ total++;
+ this.once('drain', function(){
+ debug('pre-pause writing complete');
+ --total || pause();
+ });
+ }
+ } else {
+ pause();
+ }
+};
+
+/**
+ * Starts polling cycle.
+ *
+ * @api public
+ */
+
+Polling.prototype.poll = function(){
+ debug('polling');
+ this.polling = true;
+ this.doPoll();
+ this.emit('poll');
+};
+
+/**
+ * Overloads onData to detect payloads.
+ *
+ * @api private
+ */
+
+Polling.prototype.onData = function(data){
+ var self = this;
+ debug('polling got data %s', data);
+ var callback = function(packet, index, total) {
+ // if its the first message we consider the transport open
+ if ('opening' == self.readyState) {
+ self.onOpen();
+ }
+
+ // if its a close packet, we close the ongoing requests
+ if ('close' == packet.type) {
+ self.onClose();
+ return false;
+ }
+
+ // otherwise bypass onData and handle the message
+ self.onPacket(packet);
+ };
+
+ // decode payload
+ parser.decodePayload(data, this.socket.binaryType, callback);
+
+ // if an event did not trigger closing
+ if ('closed' != this.readyState) {
+ // if we got data we're not polling
+ this.polling = false;
+ this.emit('pollComplete');
+
+ if ('open' == this.readyState) {
+ this.poll();
+ } else {
+ debug('ignoring poll - transport state "%s"', this.readyState);
+ }
+ }
+};
+
+/**
+ * For polling, send a close packet.
+ *
+ * @api private
+ */
+
+Polling.prototype.doClose = function(){
+ var self = this;
+
+ function close(){
+ debug('writing close packet');
+ self.write([{ type: 'close' }]);
+ }
+
+ if ('open' == this.readyState) {
+ debug('transport open - closing');
+ close();
+ } else {
+ // in case we're trying to close while
+ // handshaking is in progress (GH-164)
+ debug('transport not open - deferring close');
+ this.once('open', close);
+ }
+};
+
+/**
+ * Writes a packets payload.
+ *
+ * @param {Array} data packets
+ * @param {Function} drain callback
+ * @api private
+ */
+
+Polling.prototype.write = function(packets){
+ var self = this;
+ this.writable = false;
+ var callbackfn = function() {
+ self.writable = true;
+ self.emit('drain');
+ };
+
+ var self = this;
+ parser.encodePayload(packets, this.supportsBinary, function(data) {
+ self.doWrite(data, callbackfn);
+ });
+};
+
+/**
+ * Generates uri for connection.
+ *
+ * @api private
+ */
+
+Polling.prototype.uri = function(){
+ var query = this.query || {};
+ var schema = this.secure ? 'https' : 'http';
+ var port = '';
+
+ // cache busting is forced
+ if (false !== this.timestampRequests) {
+ query[this.timestampParam] = +new Date + '-' + Transport.timestamps++;
+ }
+
+ if (!this.supportsBinary && !query.sid) {
+ query.b64 = 1;
+ }
+
+ query = parseqs.encode(query);
+
+ // avoid port if default for schema
+ if (this.port && (('https' == schema && this.port != 443) ||
+ ('http' == schema && this.port != 80))) {
+ port = ':' + this.port;
+ }
+
+ // prepend ? to query
+ if (query.length) {
+ query = '?' + query;
+ }
+
+ return schema + '://' + this.hostname + port + this.path + query;
+};
+
+},{"../transport":14,"component-inherit":21,"debug":22,"engine.io-parser":25,"parseqs":33,"xmlhttprequest":20}],19:[function(_dereq_,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var Transport = _dereq_('../transport');
+var parser = _dereq_('engine.io-parser');
+var parseqs = _dereq_('parseqs');
+var inherit = _dereq_('component-inherit');
+var debug = _dereq_('debug')('engine.io-client:websocket');
+
+/**
+ * `ws` exposes a WebSocket-compatible interface in
+ * Node, or the `WebSocket` or `MozWebSocket` globals
+ * in the browser.
+ */
+
+var WebSocket = _dereq_('ws');
+
+/**
+ * Module exports.
+ */
+
+module.exports = WS;
+
+/**
+ * WebSocket transport constructor.
+ *
+ * @api {Object} connection options
+ * @api public
+ */
+
+function WS(opts){
+ var forceBase64 = (opts && opts.forceBase64);
+ if (forceBase64) {
+ this.supportsBinary = false;
+ }
+ Transport.call(this, opts);
+}
+
+/**
+ * Inherits from Transport.
+ */
+
+inherit(WS, Transport);
+
+/**
+ * Transport name.
+ *
+ * @api public
+ */
+
+WS.prototype.name = 'websocket';
+
+/*
+ * WebSockets support binary
+ */
+
+WS.prototype.supportsBinary = true;
+
+/**
+ * Opens socket.
+ *
+ * @api private
+ */
+
+WS.prototype.doOpen = function(){
+ if (!this.check()) {
+ // let probe timeout
+ return;
+ }
+
+ var self = this;
+ var uri = this.uri();
+ var protocols = void(0);
+ var opts = { agent: this.agent };
+
+ // SSL options for Node.js client
+ opts.pfx = this.pfx;
+ opts.key = this.key;
+ opts.passphrase = this.passphrase;
+ opts.cert = this.cert;
+ opts.ca = this.ca;
+ opts.ciphers = this.ciphers;
+ opts.rejectUnauthorized = this.rejectUnauthorized;
+
+ this.ws = new WebSocket(uri, protocols, opts);
+
+ if (this.ws.binaryType === undefined) {
+ this.supportsBinary = false;
+ }
+
+ this.ws.binaryType = 'arraybuffer';
+ this.addEventListeners();
+};
+
+/**
+ * Adds event listeners to the socket
+ *
+ * @api private
+ */
+
+WS.prototype.addEventListeners = function(){
+ var self = this;
+
+ this.ws.onopen = function(){
+ self.onOpen();
+ };
+ this.ws.onclose = function(){
+ self.onClose();
+ };
+ this.ws.onmessage = function(ev){
+ self.onData(ev.data);
+ };
+ this.ws.onerror = function(e){
+ self.onError('websocket error', e);
+ };
+};
+
+/**
+ * Override `onData` to use a timer on iOS.
+ * See: https://gist.github.com/mloughran/2052006
+ *
+ * @api private
+ */
+
+if ('undefined' != typeof navigator
+ && /iPad|iPhone|iPod/i.test(navigator.userAgent)) {
+ WS.prototype.onData = function(data){
+ var self = this;
+ setTimeout(function(){
+ Transport.prototype.onData.call(self, data);
+ }, 0);
+ };
+}
+
+/**
+ * Writes data to socket.
+ *
+ * @param {Array} array of packets.
+ * @api private
+ */
+
+WS.prototype.write = function(packets){
+ var self = this;
+ this.writable = false;
+ // 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');
+ }
+ });
+ }
+
+ function ondrain() {
+ self.writable = true;
+ self.emit('drain');
+ }
+ // fake drain
+ // defer to next tick to allow Socket to clear writeBuffer
+ setTimeout(ondrain, 0);
+};
+
+/**
+ * Called upon close
+ *
+ * @api private
+ */
+
+WS.prototype.onClose = function(){
+ Transport.prototype.onClose.call(this);
+};
+
+/**
+ * Closes socket.
+ *
+ * @api private
+ */
+
+WS.prototype.doClose = function(){
+ if (typeof this.ws !== 'undefined') {
+ this.ws.close();
+ }
+};
+
+/**
+ * Generates uri for connection.
+ *
+ * @api private
+ */
+
+WS.prototype.uri = function(){
+ var query = this.query || {};
+ var schema = this.secure ? 'wss' : 'ws';
+ var port = '';
+
+ // avoid port if default for schema
+ if (this.port && (('wss' == schema && this.port != 443)
+ || ('ws' == schema && this.port != 80))) {
+ port = ':' + this.port;
+ }
+
+ // append timestamp to URI
+ if (this.timestampRequests) {
+ query[this.timestampParam] = +new Date;
+ }
+
+ // communicate binary support capabilities
+ if (!this.supportsBinary) {
+ query.b64 = 1;
+ }
+
+ query = parseqs.encode(query);
+
+ // prepend ? to query
+ if (query.length) {
+ query = '?' + query;
+ }
+
+ return schema + '://' + this.hostname + port + this.path + query;
+};
+
+/**
+ * Feature detection for WebSocket.
+ *
+ * @return {Boolean} whether this transport is available.
+ * @api public
+ */
+
+WS.prototype.check = function(){
+ return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name);
+};
+
+},{"../transport":14,"component-inherit":21,"debug":22,"engine.io-parser":25,"parseqs":33,"ws":35}],20:[function(_dereq_,module,exports){
+// browser shim for xmlhttprequest module
+var hasCORS = _dereq_('has-cors');
+
+module.exports = function(opts) {
+ var xdomain = opts.xdomain;
+
+ // scheme must be same when usign XDomainRequest
+ // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
+ var xscheme = opts.xscheme;
+
+ // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default.
+ // https://github.com/Automattic/engine.io-client/pull/217
+ var enablesXDR = opts.enablesXDR;
+
+ // XMLHttpRequest can be disabled on IE
+ try {
+ if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) {
+ return new XMLHttpRequest();
+ }
+ } catch (e) { }
+
+ // Use XDomainRequest for IE8 if enablesXDR is true
+ // because loading bar keeps flashing when using jsonp-polling
+ // https://github.com/yujiosaka/socke.io-ie8-loading-example
+ try {
+ if ('undefined' != typeof XDomainRequest && !xscheme && enablesXDR) {
+ return new XDomainRequest();
+ }
+ } catch (e) { }
+
+ if (!xdomain) {
+ try {
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ } catch(e) { }
+ }
+}
+
+},{"has-cors":38}],21:[function(_dereq_,module,exports){
+
+module.exports = function(a, b){
+ var fn = function(){};
+ fn.prototype = b.prototype;
+ a.prototype = new fn;
+ a.prototype.constructor = a;
+};
+},{}],22:[function(_dereq_,module,exports){
+
+/**
+ * This is the web browser implementation of `debug()`.
+ *
+ * Expose `debug()` as the module.
+ */
+
+exports = module.exports = _dereq_('./debug');
+exports.log = log;
+exports.formatArgs = formatArgs;
+exports.save = save;
+exports.load = load;
+exports.useColors = useColors;
+
+/**
+ * Colors.
+ */
+
+exports.colors = [
+ 'lightseagreen',
+ 'forestgreen',
+ 'goldenrod',
+ 'dodgerblue',
+ 'darkorchid',
+ 'crimson'
+];
+
+/**
+ * Currently only WebKit-based Web Inspectors, Firefox >= v31,
+ * and the Firebug extension (any Firefox version) are known
+ * to support "%c" CSS customizations.
+ *
+ * TODO: add a `localStorage` variable to explicitly enable/disable colors
+ */
+
+function useColors() {
+ // is webkit? http://stackoverflow.com/a/16459606/376773
+ return ('WebkitAppearance' in document.documentElement.style) ||
+ // is firebug? http://stackoverflow.com/a/398120/376773
+ (window.console && (console.firebug || (console.exception && console.table))) ||
+ // is firefox >= v31?
+ // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
+ (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31);
+}
+
+/**
+ * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
+ */
+
+exports.formatters.j = function(v) {
+ return JSON.stringify(v);
+};
+
+
+/**
+ * Colorize log arguments if enabled.
+ *
+ * @api public
+ */
+
+function formatArgs() {
+ var args = arguments;
+ var useColors = this.useColors;
+
+ args[0] = (useColors ? '%c' : '')
+ + this.namespace
+ + (useColors ? ' %c' : ' ')
+ + args[0]
+ + (useColors ? '%c ' : ' ')
+ + '+' + exports.humanize(this.diff);
+
+ if (!useColors) return args;
+
+ var c = 'color: ' + this.color;
+ args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));
+
+ // the final "%c" is somewhat tricky, because there could be other
+ // arguments passed either before or after the %c, so we need to
+ // figure out the correct index to insert the CSS into
+ var index = 0;
+ var lastC = 0;
+ args[0].replace(/%[a-z%]/g, function(match) {
+ if ('%%' === match) return;
+ index++;
+ if ('%c' === match) {
+ // we only are interested in the *last* %c
+ // (the user may have provided their own)
+ lastC = index;
+ }
+ });
+
+ args.splice(lastC, 0, c);
+ return args;
+}
+
+/**
+ * Invokes `console.log()` when available.
+ * No-op when `console.log` is not a "function".
+ *
+ * @api public
+ */
+
+function log() {
+ // This hackery is required for IE8,
+ // where the `console.log` function doesn't have 'apply'
+ return 'object' == typeof console
+ && 'function' == typeof console.log
+ && Function.prototype.apply.call(console.log, console, arguments);
+}
+
+/**
+ * Save `namespaces`.
+ *
+ * @param {String} namespaces
+ * @api private
+ */
+
+function save(namespaces) {
+ try {
+ if (null == namespaces) {
+ localStorage.removeItem('debug');
+ } else {
+ localStorage.debug = namespaces;
+ }
+ } catch(e) {}
+}
+
+/**
+ * Load `namespaces`.
+ *
+ * @return {String} returns the previously persisted debug modes
+ * @api private
+ */
+
+function load() {
+ var r;
+ try {
+ r = localStorage.debug;
+ } catch(e) {}
+ return r;
+}
+
+/**
+ * Enable namespaces listed in `localStorage.debug` initially.
+ */
+
+exports.enable(load());
+
+},{"./debug":23}],23:[function(_dereq_,module,exports){
+
+/**
+ * This is the common logic for both the Node.js and web browser
+ * implementations of `debug()`.
+ *
+ * Expose `debug()` as the module.
+ */
+
+exports = module.exports = debug;
+exports.coerce = coerce;
+exports.disable = disable;
+exports.enable = enable;
+exports.enabled = enabled;
+exports.humanize = _dereq_('ms');
+
+/**
+ * The currently active debug mode names, and names to skip.
+ */
+
+exports.names = [];
+exports.skips = [];
+
+/**
+ * Map of special "%n" handling functions, for the debug "format" argument.
+ *
+ * Valid key names are a single, lowercased letter, i.e. "n".
+ */
+
+exports.formatters = {};
+
+/**
+ * Previously assigned color.
+ */
+
+var prevColor = 0;
+
+/**
+ * Previous log timestamp.
+ */
+
+var prevTime;
+
+/**
+ * Select a color.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+function selectColor() {
+ return exports.colors[prevColor++ % exports.colors.length];
+}
+
+/**
+ * Create a debugger with the given `namespace`.
+ *
+ * @param {String} namespace
+ * @return {Function}
+ * @api public
+ */
+
+function debug(namespace) {
+
+ // define the `disabled` version
+ function disabled() {
+ }
+ disabled.enabled = false;
+
+ // define the `enabled` version
+ function enabled() {
+
+ var self = enabled;
+
+ // set `diff` timestamp
+ var curr = +new Date();
+ var ms = curr - (prevTime || curr);
+ self.diff = ms;
+ self.prev = prevTime;
+ self.curr = curr;
+ prevTime = curr;
+
+ // add the `color` if not set
+ if (null == self.useColors) self.useColors = exports.useColors();
+ if (null == self.color && self.useColors) self.color = selectColor();
+
+ var args = Array.prototype.slice.call(arguments);
+
+ args[0] = exports.coerce(args[0]);
+
+ if ('string' !== typeof args[0]) {
+ // anything else let's inspect with %o
+ args = ['%o'].concat(args);
+ }
+
+ // apply any `formatters` transformations
+ var index = 0;
+ args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
+ // if we encounter an escaped % then don't increase the array index
+ if (match === '%%') return match;
+ index++;
+ var formatter = exports.formatters[format];
+ if ('function' === typeof formatter) {
+ var val = args[index];
+ match = formatter.call(self, val);
+
+ // now we need to remove `args[index]` since it's inlined in the `format`
+ args.splice(index, 1);
+ index--;
+ }
+ return match;
+ });
+
+ if ('function' === typeof exports.formatArgs) {
+ args = exports.formatArgs.apply(self, args);
+ }
+ var logFn = enabled.log || exports.log || console.log.bind(console);
+ logFn.apply(self, args);
+ }
+ enabled.enabled = true;
+
+ var fn = exports.enabled(namespace) ? enabled : disabled;
+
+ fn.namespace = namespace;
+
+ return fn;
+}
+
+/**
+ * Enables a debug mode by namespaces. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} namespaces
+ * @api public
+ */
+
+function enable(namespaces) {
+ exports.save(namespaces);
+
+ var split = (namespaces || '').split(/[\s,]+/);
+ var len = split.length;
+
+ for (var i = 0; i < len; i++) {
+ if (!split[i]) continue; // ignore empty strings
+ namespaces = split[i].replace(/\*/g, '.*?');
+ if (namespaces[0] === '-') {
+ exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
+ } else {
+ exports.names.push(new RegExp('^' + namespaces + '$'));
+ }
+ }
+}
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+function disable() {
+ exports.enable('');
+}
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+function enabled(name) {
+ var i, len;
+ for (i = 0, len = exports.skips.length; i < len; i++) {
+ if (exports.skips[i].test(name)) {
+ return false;
+ }
+ }
+ for (i = 0, len = exports.names.length; i < len; i++) {
+ if (exports.names[i].test(name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Coerce `val`.
+ *
+ * @param {Mixed} val
+ * @return {Mixed}
+ * @api private
+ */
+
+function coerce(val) {
+ if (val instanceof Error) return val.stack || val.message;
+ return val;
+}
+
+},{"ms":24}],24:[function(_dereq_,module,exports){
+/**
+ * Helpers.
+ */
+
+var s = 1000;
+var m = s * 60;
+var h = m * 60;
+var d = h * 24;
+var y = d * 365.25;
+
+/**
+ * Parse or format the given `val`.
+ *
+ * Options:
+ *
+ * - `long` verbose formatting [false]
+ *
+ * @param {String|Number} val
+ * @param {Object} options
+ * @return {String|Number}
+ * @api public
+ */
+
+module.exports = function(val, options){
+ options = options || {};
+ if ('string' == typeof val) return parse(val);
+ return options.long
+ ? long(val)
+ : short(val);
+};
+
+/**
+ * Parse the given `str` and return milliseconds.
+ *
+ * @param {String} str
+ * @return {Number}
+ * @api private
+ */
+
+function parse(str) {
+ var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str);
+ if (!match) return;
+ var n = parseFloat(match[1]);
+ var type = (match[2] || 'ms').toLowerCase();
+ switch (type) {
+ case 'years':
+ case 'year':
+ case 'y':
+ return n * y;
+ case 'days':
+ case 'day':
+ case 'd':
+ return n * d;
+ case 'hours':
+ case 'hour':
+ case 'h':
+ return n * h;
+ case 'minutes':
+ case 'minute':
+ case 'm':
+ return n * m;
+ case 'seconds':
+ case 'second':
+ case 's':
+ return n * s;
+ case 'ms':
+ return n;
+ }
+}
+
+/**
+ * Short format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function short(ms) {
+ if (ms >= d) return Math.round(ms / d) + 'd';
+ if (ms >= h) return Math.round(ms / h) + 'h';
+ if (ms >= m) return Math.round(ms / m) + 'm';
+ if (ms >= s) return Math.round(ms / s) + 's';
+ return ms + 'ms';
+}
+
+/**
+ * Long format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function long(ms) {
+ return plural(ms, d, 'day')
+ || plural(ms, h, 'hour')
+ || plural(ms, m, 'minute')
+ || plural(ms, s, 'second')
+ || ms + ' ms';
+}
+
+/**
+ * Pluralization helper.
+ */
+
+function plural(ms, n, name) {
+ if (ms < n) return;
+ if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
+ return Math.ceil(ms / n) + ' ' + name + 's';
+}
+
+},{}],25:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Module dependencies.
+ */
+
+var keys = _dereq_('./keys');
+var hasBinary = _dereq_('has-binary');
+var sliceBuffer = _dereq_('arraybuffer.slice');
+var base64encoder = _dereq_('base64-arraybuffer');
+var after = _dereq_('after');
+var utf8 = _dereq_('utf8');
+
+/**
+ * Check if we are running an android browser. That requires us to use
+ * ArrayBuffer with polling transports...
+ *
+ * http://ghinda.net/jpeg-blob-ajax-android/
+ */
+
+var isAndroid = navigator.userAgent.match(/Android/i);
+
+/**
+ * Check if we are running in PhantomJS.
+ * Uploading a Blob with PhantomJS does not work correctly, as reported here:
+ * https://github.com/ariya/phantomjs/issues/11395
+ * @type boolean
+ */
+var isPhantomJS = /PhantomJS/i.test(navigator.userAgent);
+
+/**
+ * When true, avoids using Blobs to encode payloads.
+ * @type boolean
+ */
+var dontSendBlobs = isAndroid || isPhantomJS;
+
+/**
+ * Current protocol version.
+ */
+
+exports.protocol = 3;
+
+/**
+ * Packet types.
+ */
+
+var packets = exports.packets = {
+ open: 0 // non-ws
+ , close: 1 // non-ws
+ , ping: 2
+ , pong: 3
+ , message: 4
+ , upgrade: 5
+ , noop: 6
+};
+
+var packetslist = keys(packets);
+
+/**
+ * Premade error packet.
+ */
+
+var err = { type: 'error', data: 'parser error' };
+
+/**
+ * Create a blob api even for blob builder when vendor prefixes exist
+ */
+
+var Blob = _dereq_('blob');
+
+/**
+ * Encodes a packet.
+ *
+ * [ ]
+ *
+ * Example:
+ *
+ * 5hello world
+ * 3
+ * 4
+ *
+ * Binary is encoded in an identical principle
+ *
+ * @api private
+ */
+
+exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
+ if ('function' == typeof supportsBinary) {
+ callback = supportsBinary;
+ supportsBinary = false;
+ }
+
+ if ('function' == typeof utf8encode) {
+ callback = utf8encode;
+ utf8encode = null;
+ }
+
+ var data = (packet.data === undefined)
+ ? undefined
+ : packet.data.buffer || packet.data;
+
+ if (global.ArrayBuffer && data instanceof ArrayBuffer) {
+ return encodeArrayBuffer(packet, supportsBinary, callback);
+ } else if (Blob && data instanceof global.Blob) {
+ return encodeBlob(packet, supportsBinary, callback);
+ }
+
+ // might be an object with { base64: true, data: dataAsBase64String }
+ if (data && data.base64) {
+ return encodeBase64Object(packet, callback);
+ }
+
+ // Sending data as a utf-8 string
+ var encoded = packets[packet.type];
+
+ // data fragment is optional
+ if (undefined !== packet.data) {
+ encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data);
+ }
+
+ return callback('' + encoded);
+
+};
+
+function encodeBase64Object(packet, callback) {
+ // packet data is an object { base64: true, data: dataAsBase64String }
+ var message = 'b' + exports.packets[packet.type] + packet.data.data;
+ return callback(message);
+}
+
+/**
+ * Encode packet helpers for binary types
+ */
+
+function encodeArrayBuffer(packet, supportsBinary, callback) {
+ if (!supportsBinary) {
+ return exports.encodeBase64Packet(packet, callback);
+ }
+
+ var data = packet.data;
+ var contentArray = new Uint8Array(data);
+ var resultBuffer = new Uint8Array(1 + data.byteLength);
+
+ resultBuffer[0] = packets[packet.type];
+ for (var i = 0; i < contentArray.length; i++) {
+ resultBuffer[i+1] = contentArray[i];
+ }
+
+ return callback(resultBuffer.buffer);
+}
+
+function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) {
+ if (!supportsBinary) {
+ return exports.encodeBase64Packet(packet, callback);
+ }
+
+ var fr = new FileReader();
+ fr.onload = function() {
+ packet.data = fr.result;
+ exports.encodePacket(packet, supportsBinary, true, callback);
+ };
+ return fr.readAsArrayBuffer(packet.data);
+}
+
+function encodeBlob(packet, supportsBinary, callback) {
+ if (!supportsBinary) {
+ return exports.encodeBase64Packet(packet, callback);
+ }
+
+ if (dontSendBlobs) {
+ return encodeBlobAsArrayBuffer(packet, supportsBinary, callback);
+ }
+
+ var length = new Uint8Array(1);
+ length[0] = packets[packet.type];
+ var blob = new Blob([length.buffer, packet.data]);
+
+ return callback(blob);
+}
+
+/**
+ * Encodes a packet with binary data in a base64 string
+ *
+ * @param {Object} packet, has `type` and `data`
+ * @return {String} base64 encoded message
+ */
+
+exports.encodeBase64Packet = function(packet, callback) {
+ var message = 'b' + exports.packets[packet.type];
+ if (Blob && packet.data instanceof Blob) {
+ var fr = new FileReader();
+ fr.onload = function() {
+ var b64 = fr.result.split(',')[1];
+ callback(message + b64);
+ };
+ return fr.readAsDataURL(packet.data);
+ }
+
+ var b64data;
+ try {
+ b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data));
+ } catch (e) {
+ // iPhone Safari doesn't let you apply with typed arrays
+ var typed = new Uint8Array(packet.data);
+ var basic = new Array(typed.length);
+ for (var i = 0; i < typed.length; i++) {
+ basic[i] = typed[i];
+ }
+ b64data = String.fromCharCode.apply(null, basic);
+ }
+ message += global.btoa(b64data);
+ return callback(message);
+};
+
+/**
+ * Decodes a packet. Changes format to Blob if requested.
+ *
+ * @return {Object} with `type` and `data` (if any)
+ * @api private
+ */
+
+exports.decodePacket = function (data, binaryType, utf8decode) {
+ // String data
+ if (typeof data == 'string' || data === undefined) {
+ if (data.charAt(0) == 'b') {
+ return exports.decodeBase64Packet(data.substr(1), binaryType);
+ }
+
+ if (utf8decode) {
+ try {
+ data = utf8.decode(data);
+ } catch (e) {
+ return err;
+ }
+ }
+ var type = data.charAt(0);
+
+ if (Number(type) != type || !packetslist[type]) {
+ return err;
+ }
+
+ if (data.length > 1) {
+ return { type: packetslist[type], data: data.substring(1) };
+ } else {
+ return { type: packetslist[type] };
+ }
+ }
+
+ var asArray = new Uint8Array(data);
+ var type = asArray[0];
+ var rest = sliceBuffer(data, 1);
+ if (Blob && binaryType === 'blob') {
+ rest = new Blob([rest]);
+ }
+ return { type: packetslist[type], data: rest };
+};
+
+/**
+ * Decodes a packet encoded in a base64 string
+ *
+ * @param {String} base64 encoded message
+ * @return {Object} with `type` and `data` (if any)
+ */
+
+exports.decodeBase64Packet = function(msg, binaryType) {
+ var type = packetslist[msg.charAt(0)];
+ if (!global.ArrayBuffer) {
+ return { type: type, data: { base64: true, data: msg.substr(1) } };
+ }
+
+ var data = base64encoder.decode(msg.substr(1));
+
+ if (binaryType === 'blob' && Blob) {
+ data = new Blob([data]);
+ }
+
+ return { type: type, data: data };
+};
+
+/**
+ * Encodes multiple messages (payload).
+ *
+ * :data
+ *
+ * Example:
+ *
+ * 11:hello world2:hi
+ *
+ * If any contents are binary, they will be encoded as base64 strings. Base64
+ * encoded strings are marked with a b before the length specifier
+ *
+ * @param {Array} packets
+ * @api private
+ */
+
+exports.encodePayload = function (packets, supportsBinary, callback) {
+ if (typeof supportsBinary == 'function') {
+ callback = supportsBinary;
+ supportsBinary = null;
+ }
+
+ var isBinary = hasBinary(packets);
+
+ if (supportsBinary && isBinary) {
+ if (Blob && !dontSendBlobs) {
+ return exports.encodePayloadAsBlob(packets, callback);
+ }
+
+ return exports.encodePayloadAsArrayBuffer(packets, callback);
+ }
+
+ if (!packets.length) {
+ return callback('0:');
+ }
+
+ function setLengthHeader(message) {
+ return message.length + ':' + message;
+ }
+
+ function encodeOne(packet, doneCallback) {
+ exports.encodePacket(packet, !isBinary ? false : supportsBinary, true, function(message) {
+ doneCallback(null, setLengthHeader(message));
+ });
+ }
+
+ map(packets, encodeOne, function(err, results) {
+ return callback(results.join(''));
+ });
+};
+
+/**
+ * Async array map using after
+ */
+
+function map(ary, each, done) {
+ var result = new Array(ary.length);
+ var next = after(ary.length, done);
+
+ var eachWithIndex = function(i, el, cb) {
+ each(el, function(error, msg) {
+ result[i] = msg;
+ cb(error, result);
+ });
+ };
+
+ for (var i = 0; i < ary.length; i++) {
+ eachWithIndex(i, ary[i], next);
+ }
+}
+
+/*
+ * Decodes data when a payload is maybe expected. Possible binary contents are
+ * decoded from their base64 representation
+ *
+ * @param {String} data, callback method
+ * @api public
+ */
+
+exports.decodePayload = function (data, binaryType, callback) {
+ if (typeof data != 'string') {
+ return exports.decodePayloadAsBinary(data, binaryType, callback);
+ }
+
+ if (typeof binaryType === 'function') {
+ callback = binaryType;
+ binaryType = null;
+ }
+
+ var packet;
+ if (data == '') {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ var length = ''
+ , n, msg;
+
+ for (var i = 0, l = data.length; i < l; i++) {
+ var chr = data.charAt(i);
+
+ if (':' != chr) {
+ length += chr;
+ } else {
+ if ('' == length || (length != (n = Number(length)))) {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ msg = data.substr(i + 1, n);
+
+ if (length != msg.length) {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ if (msg.length) {
+ packet = exports.decodePacket(msg, binaryType, true);
+
+ if (err.type == packet.type && err.data == packet.data) {
+ // parser error in individual packet - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ var ret = callback(packet, i + n, l);
+ if (false === ret) return;
+ }
+
+ // advance cursor
+ i += n;
+ length = '';
+ }
+ }
+
+ if (length != '') {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+};
+
+/**
+ * Encodes multiple messages (payload) as binary.
+ *
+ * <1 = binary, 0 = string>[...]
+ *
+ * Example:
+ * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
+ *
+ * @param {Array} packets
+ * @return {ArrayBuffer} encoded payload
+ * @api private
+ */
+
+exports.encodePayloadAsArrayBuffer = function(packets, callback) {
+ if (!packets.length) {
+ return callback(new ArrayBuffer(0));
+ }
+
+ function encodeOne(packet, doneCallback) {
+ exports.encodePacket(packet, true, true, function(data) {
+ return doneCallback(null, data);
+ });
+ }
+
+ map(packets, encodeOne, function(err, encodedPackets) {
+ var totalLength = encodedPackets.reduce(function(acc, p) {
+ var len;
+ if (typeof p === 'string'){
+ len = p.length;
+ } else {
+ len = p.byteLength;
+ }
+ return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2
+ }, 0);
+
+ var resultArray = new Uint8Array(totalLength);
+
+ var bufferIndex = 0;
+ encodedPackets.forEach(function(p) {
+ var isString = typeof p === 'string';
+ var ab = p;
+ if (isString) {
+ var view = new Uint8Array(p.length);
+ for (var i = 0; i < p.length; i++) {
+ view[i] = p.charCodeAt(i);
+ }
+ ab = view.buffer;
+ }
+
+ if (isString) { // not true binary
+ resultArray[bufferIndex++] = 0;
+ } else { // true binary
+ resultArray[bufferIndex++] = 1;
+ }
+
+ var lenStr = ab.byteLength.toString();
+ for (var i = 0; i < lenStr.length; i++) {
+ resultArray[bufferIndex++] = parseInt(lenStr[i]);
+ }
+ resultArray[bufferIndex++] = 255;
+
+ var view = new Uint8Array(ab);
+ for (var i = 0; i < view.length; i++) {
+ resultArray[bufferIndex++] = view[i];
+ }
+ });
+
+ return callback(resultArray.buffer);
+ });
+};
+
+/**
+ * Encode as Blob
+ */
+
+exports.encodePayloadAsBlob = function(packets, callback) {
+ function encodeOne(packet, doneCallback) {
+ exports.encodePacket(packet, true, true, function(encoded) {
+ var binaryIdentifier = new Uint8Array(1);
+ binaryIdentifier[0] = 1;
+ if (typeof encoded === 'string') {
+ var view = new Uint8Array(encoded.length);
+ for (var i = 0; i < encoded.length; i++) {
+ view[i] = encoded.charCodeAt(i);
+ }
+ encoded = view.buffer;
+ binaryIdentifier[0] = 0;
+ }
+
+ var len = (encoded instanceof ArrayBuffer)
+ ? encoded.byteLength
+ : encoded.size;
+
+ var lenStr = len.toString();
+ var lengthAry = new Uint8Array(lenStr.length + 1);
+ for (var i = 0; i < lenStr.length; i++) {
+ lengthAry[i] = parseInt(lenStr[i]);
+ }
+ lengthAry[lenStr.length] = 255;
+
+ if (Blob) {
+ var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]);
+ doneCallback(null, blob);
+ }
+ });
+ }
+
+ map(packets, encodeOne, function(err, results) {
+ return callback(new Blob(results));
+ });
+};
+
+/*
+ * Decodes data when a payload is maybe expected. Strings are decoded by
+ * interpreting each byte as a key code for entries marked to start with 0. See
+ * description of encodePayloadAsBinary
+ *
+ * @param {ArrayBuffer} data, callback method
+ * @api public
+ */
+
+exports.decodePayloadAsBinary = function (data, binaryType, callback) {
+ if (typeof binaryType === 'function') {
+ callback = binaryType;
+ binaryType = null;
+ }
+
+ var bufferTail = data;
+ var buffers = [];
+
+ var numberTooLong = false;
+ while (bufferTail.byteLength > 0) {
+ var tailArray = new Uint8Array(bufferTail);
+ var isString = tailArray[0] === 0;
+ var msgLength = '';
+
+ for (var i = 1; ; i++) {
+ if (tailArray[i] == 255) break;
+
+ if (msgLength.length > 310) {
+ numberTooLong = true;
+ break;
+ }
+
+ msgLength += tailArray[i];
+ }
+
+ if(numberTooLong) return callback(err, 0, 1);
+
+ bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length);
+ msgLength = parseInt(msgLength);
+
+ var msg = sliceBuffer(bufferTail, 0, msgLength);
+ if (isString) {
+ try {
+ msg = String.fromCharCode.apply(null, new Uint8Array(msg));
+ } catch (e) {
+ // iPhone Safari doesn't let you apply to typed arrays
+ var typed = new Uint8Array(msg);
+ msg = '';
+ for (var i = 0; i < typed.length; i++) {
+ msg += String.fromCharCode(typed[i]);
+ }
+ }
+ }
+
+ buffers.push(msg);
+ bufferTail = sliceBuffer(bufferTail, msgLength);
+ }
+
+ var total = buffers.length;
+ buffers.forEach(function(buffer, i) {
+ callback(exports.decodePacket(buffer, binaryType, true), i, total);
+ });
+};
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./keys":26,"after":27,"arraybuffer.slice":28,"base64-arraybuffer":29,"blob":30,"has-binary":36,"utf8":31}],26:[function(_dereq_,module,exports){
+
+/**
+ * Gets the keys for an object.
+ *
+ * @return {Array} keys
+ * @api private
+ */
+
+module.exports = Object.keys || function keys (obj){
+ var arr = [];
+ var has = Object.prototype.hasOwnProperty;
+
+ for (var i in obj) {
+ if (has.call(obj, i)) {
+ arr.push(i);
+ }
+ }
+ return arr;
+};
+
+},{}],27:[function(_dereq_,module,exports){
+module.exports = after
+
+function after(count, callback, err_cb) {
+ var bail = false
+ err_cb = err_cb || noop
+ proxy.count = count
+
+ return (count === 0) ? callback() : proxy
+
+ function proxy(err, result) {
+ if (proxy.count <= 0) {
+ throw new Error('after called too many times')
+ }
+ --proxy.count
+
+ // after first error, rest are passed to err_cb
+ if (err) {
+ bail = true
+ callback(err)
+ // future error callbacks will go to error handler
+ callback = err_cb
+ } else if (proxy.count === 0 && !bail) {
+ callback(null, result)
+ }
+ }
+}
+
+function noop() {}
+
+},{}],28:[function(_dereq_,module,exports){
+/**
+ * An abstraction for slicing an arraybuffer even when
+ * ArrayBuffer.prototype.slice is not supported
+ *
+ * @api public
+ */
+
+module.exports = function(arraybuffer, start, end) {
+ var bytes = arraybuffer.byteLength;
+ start = start || 0;
+ end = end || bytes;
+
+ if (arraybuffer.slice) { return arraybuffer.slice(start, end); }
+
+ if (start < 0) { start += bytes; }
+ if (end < 0) { end += bytes; }
+ if (end > bytes) { end = bytes; }
+
+ if (start >= bytes || start >= end || bytes === 0) {
+ return new ArrayBuffer(0);
+ }
+
+ var abv = new Uint8Array(arraybuffer);
+ var result = new Uint8Array(end - start);
+ for (var i = start, ii = 0; i < end; i++, ii++) {
+ result[ii] = abv[i];
+ }
+ return result.buffer;
+};
+
+},{}],29:[function(_dereq_,module,exports){
+/*
+ * base64-arraybuffer
+ * https://github.com/niklasvh/base64-arraybuffer
+ *
+ * Copyright (c) 2012 Niklas von Hertzen
+ * Licensed under the MIT license.
+ */
+(function(chars){
+ "use strict";
+
+ exports.encode = function(arraybuffer) {
+ var bytes = new Uint8Array(arraybuffer),
+ i, len = bytes.length, base64 = "";
+
+ for (i = 0; i < len; i+=3) {
+ base64 += chars[bytes[i] >> 2];
+ base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
+ base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
+ base64 += chars[bytes[i + 2] & 63];
+ }
+
+ if ((len % 3) === 2) {
+ base64 = base64.substring(0, base64.length - 1) + "=";
+ } else if (len % 3 === 1) {
+ base64 = base64.substring(0, base64.length - 2) + "==";
+ }
+
+ return base64;
+ };
+
+ exports.decode = function(base64) {
+ var bufferLength = base64.length * 0.75,
+ len = base64.length, i, p = 0,
+ encoded1, encoded2, encoded3, encoded4;
+
+ if (base64[base64.length - 1] === "=") {
+ bufferLength--;
+ if (base64[base64.length - 2] === "=") {
+ bufferLength--;
+ }
+ }
+
+ var arraybuffer = new ArrayBuffer(bufferLength),
+ bytes = new Uint8Array(arraybuffer);
+
+ for (i = 0; i < len; i+=4) {
+ encoded1 = chars.indexOf(base64[i]);
+ encoded2 = chars.indexOf(base64[i+1]);
+ encoded3 = chars.indexOf(base64[i+2]);
+ encoded4 = chars.indexOf(base64[i+3]);
+
+ bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
+ bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
+ bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
+ }
+
+ return arraybuffer;
+ };
+})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
+
+},{}],30:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Create a blob builder even when vendor prefixes exist
+ */
+
+var BlobBuilder = global.BlobBuilder
+ || global.WebKitBlobBuilder
+ || global.MSBlobBuilder
+ || global.MozBlobBuilder;
+
+/**
+ * Check if Blob constructor is supported
+ */
+
+var blobSupported = (function() {
+ try {
+ var a = new Blob(['hi']);
+ return a.size === 2;
+ } catch(e) {
+ return false;
+ }
+})();
+
+/**
+ * Check if Blob constructor supports ArrayBufferViews
+ * Fails in Safari 6, so we need to map to ArrayBuffers there.
+ */
+
+var blobSupportsArrayBufferView = blobSupported && (function() {
+ try {
+ var b = new Blob([new Uint8Array([1,2])]);
+ return b.size === 2;
+ } catch(e) {
+ return false;
+ }
+})();
+
+/**
+ * Check if BlobBuilder is supported
+ */
+
+var blobBuilderSupported = BlobBuilder
+ && BlobBuilder.prototype.append
+ && BlobBuilder.prototype.getBlob;
+
+/**
+ * Helper function that maps ArrayBufferViews to ArrayBuffers
+ * Used by BlobBuilder constructor and old browsers that didn't
+ * support it in the Blob constructor.
+ */
+
+function mapArrayBufferViews(ary) {
+ for (var i = 0; i < ary.length; i++) {
+ var chunk = ary[i];
+ if (chunk.buffer instanceof ArrayBuffer) {
+ var buf = chunk.buffer;
+
+ // if this is a subarray, make a copy so we only
+ // include the subarray region from the underlying buffer
+ if (chunk.byteLength !== buf.byteLength) {
+ var copy = new Uint8Array(chunk.byteLength);
+ copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength));
+ buf = copy.buffer;
+ }
+
+ ary[i] = buf;
+ }
+ }
+}
+
+function BlobBuilderConstructor(ary, options) {
+ options = options || {};
+
+ var bb = new BlobBuilder();
+ mapArrayBufferViews(ary);
+
+ for (var i = 0; i < ary.length; i++) {
+ bb.append(ary[i]);
+ }
+
+ return (options.type) ? bb.getBlob(options.type) : bb.getBlob();
+};
+
+function BlobConstructor(ary, options) {
+ mapArrayBufferViews(ary);
+ return new Blob(ary, options || {});
+};
+
+module.exports = (function() {
+ if (blobSupported) {
+ return blobSupportsArrayBufferView ? global.Blob : BlobConstructor;
+ } else if (blobBuilderSupported) {
+ return BlobBuilderConstructor;
+ } else {
+ return undefined;
+ }
+})();
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],31:[function(_dereq_,module,exports){
+(function (global){
+/*! https://mths.be/utf8js v2.0.0 by @mathias */
+;(function(root) {
+
+ // Detect free variables `exports`
+ var freeExports = typeof exports == 'object' && exports;
+
+ // Detect free variable `module`
+ var freeModule = typeof module == 'object' && module &&
+ module.exports == freeExports && module;
+
+ // Detect free variable `global`, from Node.js or Browserified code,
+ // and use it as `root`
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var stringFromCharCode = String.fromCharCode;
+
+ // Taken from https://mths.be/punycode
+ function ucs2decode(string) {
+ var output = [];
+ var counter = 0;
+ var length = string.length;
+ var value;
+ var extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ // Taken from https://mths.be/punycode
+ function ucs2encode(array) {
+ var length = array.length;
+ var index = -1;
+ var value;
+ var output = '';
+ while (++index < length) {
+ value = array[index];
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ }
+ return output;
+ }
+
+ function checkScalarValue(codePoint) {
+ if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
+ throw Error(
+ 'Lone surrogate U+' + codePoint.toString(16).toUpperCase() +
+ ' is not a scalar value'
+ );
+ }
+ }
+ /*--------------------------------------------------------------------------*/
+
+ function createByte(codePoint, shift) {
+ return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80);
+ }
+
+ function encodeCodePoint(codePoint) {
+ if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence
+ return stringFromCharCode(codePoint);
+ }
+ var symbol = '';
+ if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence
+ symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0);
+ }
+ else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence
+ checkScalarValue(codePoint);
+ symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0);
+ symbol += createByte(codePoint, 6);
+ }
+ else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence
+ symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0);
+ symbol += createByte(codePoint, 12);
+ symbol += createByte(codePoint, 6);
+ }
+ symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);
+ return symbol;
+ }
+
+ function utf8encode(string) {
+ var codePoints = ucs2decode(string);
+ var length = codePoints.length;
+ var index = -1;
+ var codePoint;
+ var byteString = '';
+ while (++index < length) {
+ codePoint = codePoints[index];
+ byteString += encodeCodePoint(codePoint);
+ }
+ return byteString;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ function readContinuationByte() {
+ if (byteIndex >= byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ var continuationByte = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ if ((continuationByte & 0xC0) == 0x80) {
+ return continuationByte & 0x3F;
+ }
+
+ // If we end up here, it’s not a continuation byte
+ throw Error('Invalid continuation byte');
+ }
+
+ function decodeSymbol() {
+ var byte1;
+ var byte2;
+ var byte3;
+ var byte4;
+ var codePoint;
+
+ if (byteIndex > byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ if (byteIndex == byteCount) {
+ return false;
+ }
+
+ // Read first byte
+ byte1 = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ // 1-byte sequence (no continuation bytes)
+ if ((byte1 & 0x80) == 0) {
+ return byte1;
+ }
+
+ // 2-byte sequence
+ if ((byte1 & 0xE0) == 0xC0) {
+ var byte2 = readContinuationByte();
+ codePoint = ((byte1 & 0x1F) << 6) | byte2;
+ if (codePoint >= 0x80) {
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 3-byte sequence (may include unpaired surrogates)
+ if ((byte1 & 0xF0) == 0xE0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
+ if (codePoint >= 0x0800) {
+ checkScalarValue(codePoint);
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 4-byte sequence
+ if ((byte1 & 0xF8) == 0xF0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ byte4 = readContinuationByte();
+ codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) |
+ (byte3 << 0x06) | byte4;
+ if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
+ return codePoint;
+ }
+ }
+
+ throw Error('Invalid UTF-8 detected');
+ }
+
+ var byteArray;
+ var byteCount;
+ var byteIndex;
+ function utf8decode(byteString) {
+ byteArray = ucs2decode(byteString);
+ byteCount = byteArray.length;
+ byteIndex = 0;
+ var codePoints = [];
+ var tmp;
+ while ((tmp = decodeSymbol()) !== false) {
+ codePoints.push(tmp);
+ }
+ return ucs2encode(codePoints);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var utf8 = {
+ 'version': '2.0.0',
+ 'encode': utf8encode,
+ 'decode': utf8decode
+ };
+
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ typeof define == 'function' &&
+ typeof define.amd == 'object' &&
+ define.amd
+ ) {
+ define(function() {
+ return utf8;
+ });
+ } else if (freeExports && !freeExports.nodeType) {
+ if (freeModule) { // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = utf8;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ var object = {};
+ var hasOwnProperty = object.hasOwnProperty;
+ for (var key in utf8) {
+ hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.utf8 = utf8;
+ }
+
+}(this));
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],32:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * JSON parse.
+ *
+ * @see Based on jQuery#parseJSON (MIT) and JSON2
+ * @api private
+ */
+
+var rvalidchars = /^[\],:{}\s]*$/;
+var rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
+var rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
+var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
+var rtrimLeft = /^\s+/;
+var rtrimRight = /\s+$/;
+
+module.exports = function parsejson(data) {
+ if ('string' != typeof data || !data) {
+ return null;
+ }
+
+ data = data.replace(rtrimLeft, '').replace(rtrimRight, '');
+
+ // Attempt to parse using the native JSON parser first
+ if (global.JSON && JSON.parse) {
+ return JSON.parse(data);
+ }
+
+ if (rvalidchars.test(data.replace(rvalidescape, '@')
+ .replace(rvalidtokens, ']')
+ .replace(rvalidbraces, ''))) {
+ return (new Function('return ' + data))();
+ }
+};
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],33:[function(_dereq_,module,exports){
+/**
+ * Compiles a querystring
+ * Returns string representation of the object
+ *
+ * @param {Object}
+ * @api private
+ */
+
+exports.encode = function (obj) {
+ var str = '';
+
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ if (str.length) str += '&';
+ str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);
+ }
+ }
+
+ return str;
+};
+
+/**
+ * Parses a simple querystring into an object
+ *
+ * @param {String} qs
+ * @api private
+ */
+
+exports.decode = function(qs){
+ var qry = {};
+ var pairs = qs.split('&');
+ for (var i = 0, l = pairs.length; i < l; i++) {
+ var pair = pairs[i].split('=');
+ qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
+ }
+ return qry;
+};
+
+},{}],34:[function(_dereq_,module,exports){
+/**
+ * Parses an URI
+ *
+ * @author Steven Levithan (MIT license)
+ * @api private
+ */
+
+var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
+
+var parts = [
+ 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'
+];
+
+module.exports = function parseuri(str) {
+ var src = str,
+ b = str.indexOf('['),
+ e = str.indexOf(']');
+
+ if (b != -1 && e != -1) {
+ str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length);
+ }
+
+ var m = re.exec(str || ''),
+ uri = {},
+ i = 14;
+
+ while (i--) {
+ uri[parts[i]] = m[i] || '';
+ }
+
+ if (b != -1 && e != -1) {
+ uri.source = src;
+ uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':');
+ uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':');
+ uri.ipv6uri = true;
+ }
+
+ return uri;
+};
+
+},{}],35:[function(_dereq_,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;
+
+},{}],36:[function(_dereq_,module,exports){
+(function (global){
+
+/*
+ * Module requirements.
+ */
+
+var isArray = _dereq_('isarray');
+
+/**
+ * Module exports.
+ */
+
+module.exports = hasBinary;
+
+/**
+ * Checks for binary data.
+ *
+ * Right now only Buffer and ArrayBuffer are supported..
+ *
+ * @param {Object} anything
+ * @api public
+ */
+
+function hasBinary(data) {
+
+ function _hasBinary(obj) {
+ if (!obj) return false;
+
+ if ( (global.Buffer && global.Buffer.isBuffer(obj)) ||
+ (global.ArrayBuffer && obj instanceof ArrayBuffer) ||
+ (global.Blob && obj instanceof Blob) ||
+ (global.File && obj instanceof File)
+ ) {
+ return true;
+ }
+
+ if (isArray(obj)) {
+ for (var i = 0; i < obj.length; i++) {
+ if (_hasBinary(obj[i])) {
+ return true;
+ }
+ }
+ } else if (obj && 'object' == typeof obj) {
+ if (obj.toJSON) {
+ obj = obj.toJSON();
+ }
+
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key) && _hasBinary(obj[key])) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ return _hasBinary(data);
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"isarray":37}],37:[function(_dereq_,module,exports){
+module.exports = Array.isArray || function (arr) {
+ return Object.prototype.toString.call(arr) == '[object Array]';
+};
+
+},{}],38:[function(_dereq_,module,exports){
+
+/**
+ * Module dependencies.
+ */
+
+var global = _dereq_('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":39}],39:[function(_dereq_,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; })();
+
+},{}],40:[function(_dereq_,module,exports){
+
+var indexOf = [].indexOf;
+
+module.exports = function(arr, obj){
+ if (indexOf) return arr.indexOf(obj);
+ for (var i = 0; i < arr.length; ++i) {
+ if (arr[i] === obj) return i;
+ }
+ return -1;
+};
+},{}],41:[function(_dereq_,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);
+};
+},{}],42:[function(_dereq_,module,exports){
+/**
+ * Parses an URI
+ *
+ * @author Steven Levithan (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;
+};
+
+},{}],43:[function(_dereq_,module,exports){
+(function (global){
+/*global Blob,File*/
+
+/**
+ * Module requirements
+ */
+
+var isArray = _dereq_('isarray');
+var isBuf = _dereq_('./is-buffer');
+
+/**
+ * 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 _deconstructPacket(data) {
+ if (!data) return data;
+
+ if (isBuf(data)) {
+ 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] = _deconstructPacket(data[i]);
+ }
+ return newData;
+ } else if ('object' == typeof data && !(data instanceof Date)) {
+ var newData = {};
+ for (var key in data) {
+ newData[key] = _deconstructPacket(data[key]);
+ }
+ return newData;
+ }
+ return data;
+ }
+
+ var pack = packet;
+ pack.data = _deconstructPacket(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 _reconstructPacket(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] = _reconstructPacket(data[i]);
+ }
+ return data;
+ } else if (data && 'object' == typeof data) {
+ for (var key in data) {
+ data[key] = _reconstructPacket(data[key]);
+ }
+ return data;
+ }
+ return data;
+ }
+
+ packet.data = _reconstructPacket(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 _removeBlobs(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
+ } else if (isArray(obj)) { // handle array
+ for (var i = 0; i < obj.length; i++) {
+ _removeBlobs(obj[i], i, obj);
+ }
+ } else if (obj && 'object' == typeof obj && !isBuf(obj)) { // and object
+ for (var key in obj) {
+ _removeBlobs(obj[key], key, obj);
+ }
+ }
+ }
+
+ var pendingBlobs = 0;
+ var bloblessData = data;
+ _removeBlobs(bloblessData);
+ if (!pendingBlobs) {
+ callback(bloblessData);
+ }
+};
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./is-buffer":45,"isarray":46}],44:[function(_dereq_,module,exports){
+
+/**
+ * Module dependencies.
+ */
+
+var debug = _dereq_('debug')('socket.io-parser');
+var json = _dereq_('json3');
+var isArray = _dereq_('isarray');
+var Emitter = _dereq_('component-emitter');
+var binary = _dereq_('./binary');
+var isBuf = _dereq_('./is-buffer');
+
+/**
+ * Protocol version.
+ *
+ * @api public
+ */
+
+exports.protocol = 4;
+
+/**
+ * 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;
+
+/**
+ * Encoder constructor.
+ *
+ * @api public
+ */
+
+exports.Encoder = Encoder;
+
+/**
+ * Decoder constructor.
+ *
+ * @api public
+ */
+
+exports.Decoder = Decoder;
+
+/**
+ * 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);
+}
+
+/**
+ * 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 (isBuf(obj) || 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) {
+ var buf = '';
+ while (str.charAt(++i) != '-') {
+ buf += str.charAt(i);
+ if (i == str.length) break;
+ }
+ if (buf != Number(buf) || str.charAt(i) != '-') {
+ throw new Error('Illegal attachments');
+ }
+ p.attachments = Number(buf);
+ }
+
+ // 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 == 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 == 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":43,"./is-buffer":45,"component-emitter":9,"debug":10,"isarray":46,"json3":47}],45:[function(_dereq_,module,exports){
+(function (global){
+
+module.exports = isBuf;
+
+/**
+ * Returns true if obj is a buffer or an arraybuffer.
+ *
+ * @api private
+ */
+
+function isBuf(obj) {
+ return (global.Buffer && global.Buffer.isBuffer(obj)) ||
+ (global.ArrayBuffer && obj instanceof ArrayBuffer);
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],46:[function(_dereq_,module,exports){
+module.exports=_dereq_(37)
+},{}],47:[function(_dereq_,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));
+
+},{}],48:[function(_dereq_,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
+}
+
+},{}]},{},[1])
+(1)
+});
\ No newline at end of file
diff --git a/web/routes/index.js b/web/routes/index.js
index 8cbaad9..0000a1e 100644
--- a/web/routes/index.js
+++ b/web/routes/index.js
@@ -1,31 +1,54 @@
+var Monitor = require('../../lib/monitor');
+
// Authorization
-action(function auth(req, res){/*
- if (!req._config.password || (req._config.password === req.session['password'])) {
+action(function auth(req, res) {
+ if (!req._config.agent || (req._config.agent.authorization === req.session['authorization'])) {
return res.redirect('/');
- }*/
- res.render('auth', {title: 'Authorization'});
+ }
+ res.render('auth', {
+ title: 'Authorization'
+ });
});
// Index
-action(function(req, res){
- if (req._config.agent && (req._config.agent.authorization !== req.session['authorization'])) {
+action(function (req, res) {
+ var auth;
+ if (req._config.agent && ((auth = req._config.agent.authorization) !== req.session['authorization'])) {
return res.redirect('/auth');
}
- res.render('index', {title: 'Monitor'});
+ var q = Monitor.available(req._config),
+ connections = [];
+
+ q.choices.forEach(function (c) {
+ c.value = Monitor.toConnectionString(Monitor.parseConnectionString(c.value));
+ connections.push(c);
+ });
+ res.render('index', {
+ title: 'Monitor',
+ connections: connections
+ });
});
// API
-action(function auth_api(req, res){
- if(!req._config.agent || !req._config.agent.authorization){
- return res.json({error: 'Can not found agent[.authorization] config, no need to authorize!'});
+action(function auth_api(req, res) {
+ if (!req._config.agent || !req._config.agent.authorization) {
+ return res.json({
+ error: 'Can not found agent[.authorization] config, no need to authorize!'
+ });
}
if (!req.query || !req.query.authorization) {
- return res.json({error: 'Authorization is required!'});
+ return res.json({
+ error: 'Authorization is required!'
+ });
}
if (req._config.agent && req.query.authorization === req._config.agent.authorization) {
req.session['authorization'] = req.query.authorization;
- return res.json({status: 200});
+ return res.json({
+ status: 200
+ });
}
- return res.json({error: 'Failed, authorization is incorrect.'});
-});
\ No newline at end of file
+ return res.json({
+ error: 'Failed, authorization is incorrect.'
+ });
+});
diff --git a/web/templates/views/index.jade b/web/templates/views/index.jade
index 21bb94d..5698aa3 100644
--- a/web/templates/views/index.jade
+++ b/web/templates/views/index.jade
@@ -27,7 +27,7 @@ block content
div
div
div
-
+ input#authorization(type="hidden")= authorization
include ../partials/tmpls
block js
@@ -36,3 +36,18 @@ block js
script(src='js/jquery.fullPage.min.js')
script(src='js/fanavi.min.js')
script(src='js/index.html.js')
+ script.
+ var GUI = {};
+ GUI.connections = !{JSON.stringify(connections)};
+ $(window).ready(function () {
+ if (!Array.isArray(GUI.connections) || GUI.connections.length == 0) {
+ info('No agent is online, can not start it.');
+ } else {
+ GUI.connection = GUI.connections[GUI.connections.length - 1];
+ }
+ prepareDOM();
+ initFullPage();
+ listenSocket();
+ renderFanavi();
+ });
+