BattleSim/client/vendor/js/primus.js

4323 lines
128 KiB
JavaScript

(function UMDish(name, context, definition) { context[name] = definition.call(context); if (typeof module !== "undefined" && module.exports) { module.exports = context[name]; } else if (typeof define == "function" && define.amd) { define(function reference() { return context[name]; }); }})("Primus", this, function Primus() {/*globals require, define */
'use strict';
/**
* Representation of a single EventEmitter function.
*
* @param {Function} fn Event handler to be called.
* @param {Mixed} context Context for function execution.
* @param {Boolean} once Only emit once
* @api private
*/
function EE(fn, context, once) {
this.fn = fn;
this.context = context;
this.once = once || false;
}
/**
* Minimal EventEmitter interface that is molded against the Node.js
* EventEmitter interface.
*
* @constructor
* @api public
*/
function EventEmitter() { /* Nothing to set */ }
/**
* Holds the assigned EventEmitters by name.
*
* @type {Object}
* @private
*/
EventEmitter.prototype._events = undefined;
/**
* Return a list of assigned event listeners.
*
* @param {String} event The events that should be listed.
* @returns {Array}
* @api public
*/
EventEmitter.prototype.listeners = function listeners(event) {
if (!this._events || !this._events[event]) return [];
for (var i = 0, l = this._events[event].length, ee = []; i < l; i++) {
ee.push(this._events[event][i].fn);
}
return ee;
};
/**
* Emit an event to all registered event listeners.
*
* @param {String} event The name of the event.
* @returns {Boolean} Indication if we've emitted an event.
* @api public
*/
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
if (!this._events || !this._events[event]) return false;
var listeners = this._events[event]
, length = listeners.length
, len = arguments.length
, ee = listeners[0]
, args
, i, j;
if (1 === length) {
if (ee.once) this.removeListener(event, ee.fn, true);
switch (len) {
case 1: return ee.fn.call(ee.context), true;
case 2: return ee.fn.call(ee.context, a1), true;
case 3: return ee.fn.call(ee.context, a1, a2), true;
case 4: return ee.fn.call(ee.context, a1, a2, a3), true;
case 5: return ee.fn.call(ee.context, a1, a2, a3, a4), true;
case 6: return ee.fn.call(ee.context, a1, a2, a3, a4, a5), true;
}
for (i = 1, args = new Array(len -1); i < len; i++) {
args[i - 1] = arguments[i];
}
ee.fn.apply(ee.context, args);
} else {
for (i = 0; i < length; i++) {
if (listeners[i].once) this.removeListener(event, listeners[i].fn, true);
switch (len) {
case 1: listeners[i].fn.call(listeners[i].context); break;
case 2: listeners[i].fn.call(listeners[i].context, a1); break;
case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
default:
if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {
args[j - 1] = arguments[j];
}
listeners[i].fn.apply(listeners[i].context, args);
}
}
}
return true;
};
/**
* Register a new EventListener for the given event.
*
* @param {String} event Name of the event.
* @param {Functon} fn Callback function.
* @param {Mixed} context The context of the function.
* @api public
*/
EventEmitter.prototype.on = function on(event, fn, context) {
if (!this._events) this._events = {};
if (!this._events[event]) this._events[event] = [];
this._events[event].push(new EE( fn, context || this ));
return this;
};
/**
* Add an EventListener that's only called once.
*
* @param {String} event Name of the event.
* @param {Function} fn Callback function.
* @param {Mixed} context The context of the function.
* @api public
*/
EventEmitter.prototype.once = function once(event, fn, context) {
if (!this._events) this._events = {};
if (!this._events[event]) this._events[event] = [];
this._events[event].push(new EE(fn, context || this, true ));
return this;
};
/**
* Remove event listeners.
*
* @param {String} event The event we want to remove.
* @param {Function} fn The listener that we need to find.
* @param {Boolean} once Only remove once listeners.
* @api public
*/
EventEmitter.prototype.removeListener = function removeListener(event, fn, once) {
if (!this._events || !this._events[event]) return this;
var listeners = this._events[event]
, events = [];
if (fn) for (var i = 0, length = listeners.length; i < length; i++) {
if (listeners[i].fn !== fn && listeners[i].once !== once) {
events.push(listeners[i]);
}
}
//
// Reset the array, or remove it completely if we have no more listeners.
//
if (events.length) this._events[event] = events;
else this._events[event] = null;
return this;
};
/**
* Remove all listeners or only the listeners for the specified event.
*
* @param {String} event The event want to remove all listeners for.
* @api public
*/
EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
if (!this._events) return this;
if (event) this._events[event] = null;
else this._events = {};
return this;
};
//
// Alias methods names because people roll like that.
//
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
//
// This function doesn't apply anymore.
//
EventEmitter.prototype.setMaxListeners = function setMaxListeners() {
return this;
};
/**
* Context assertion, ensure that some of our public Primus methods are called
* with the correct context to ensure that
*
* @param {Primus} self The context of the function.
* @param {String} method The method name.
* @api private
*/
function context(self, method) {
if (self instanceof Primus) return;
var failure = new Error('Primus#'+ method + '\'s context should called with a Primus instance');
if ('function' !== typeof self.listeners || !self.listeners('error').length) {
throw failure;
}
self.emit('error', failure);
}
//
// Sets the default connection URL, it uses the default origin of the browser
// when supported but degrades for older browsers. In Node.js, we cannot guess
// where the user wants to connect to, so we just default to localhost.
//
var defaultUrl;
try {
if (location.origin) {
defaultUrl = location.origin;
} else {
defaultUrl = location.protocol +'//'+ location.hostname + (location.port ? ':'+ location.port : '');
}
} catch (e) {
defaultUrl = 'http://127.0.0.1';
}
/**
* Primus in a real-time library agnostic framework for establishing real-time
* connections with servers.
*
* Options:
* - reconnect, configuration for the reconnect process.
* - manual, don't automatically call `.open` to start the connection.
* - websockets, force the use of WebSockets, even when you should avoid them.
* - timeout, connect timeout, server didn't respond in a timely manner.
* - ping, The heartbeat interval for sending a ping packet to the server.
* - pong, The heartbeat timeout for receiving a response to the ping.
* - network, Use network events as leading method for network connection drops.
* - strategy, Reconnection strategies.
* - transport, Transport options.
* - url, uri, The URL to use connect with the server.
*
* @constructor
* @param {String} url The URL of your server.
* @param {Object} options The configuration.
* @api public
*/
function Primus(url, options) {
if (!(this instanceof Primus)) return new Primus(url, options);
if ('function' !== typeof this.client) {
var message = 'The client library has not been compiled correctly, ' +
'see https://github.com/primus/primus#client-library for more details';
return this.critical(new Error(message));
}
if ('object' === typeof url) {
options = url;
url = options.url || options.uri || defaultUrl;
} else {
options = options || {};
}
var primus = this;
// The maximum number of messages that can be placed in queue.
options.queueSize = 'queueSize' in options ? options.queueSize : Infinity;
// Connection timeout duration.
options.timeout = 'timeout' in options ? options.timeout : 10e3;
// Stores the back off configuration.
options.reconnect = 'reconnect' in options ? options.reconnect : {};
// Heartbeat ping interval.
options.ping = 'ping' in options ? options.ping : 25000;
// Heartbeat pong response timeout.
options.pong = 'pong' in options ? options.pong : 10e3;
// Reconnect strategies.
options.strategy = 'strategy' in options ? options.strategy : [];
// Custom transport options.
options.transport = 'transport' in options ? options.transport : {};
primus.buffer = []; // Stores premature send data.
primus.writable = true; // Silly stream compatibility.
primus.readable = true; // Silly stream compatibility.
primus.url = primus.parse(url || defaultUrl); // Parse the URL to a readable format.
primus.readyState = Primus.CLOSED; // The readyState of the connection.
primus.options = options; // Reference to the supplied options.
primus.timers = {}; // Contains all our timers.
primus.attempt = null; // Current back off attempt.
primus.socket = null; // Reference to the internal connection.
primus.latency = 0; // Latency between messages.
primus.stamps = 0; // Counter to make timestamps unqiue.
primus.disconnect = false; // Did we receive a disconnect packet?
primus.transport = options.transport; // Transport options.
primus.transformers = { // Message transformers.
outgoing: [],
incoming: []
};
//
// Parse the reconnection strategy. It can have the following strategies:
//
// - timeout: Reconnect when we have a network timeout.
// - disconnect: Reconnect when we have an unexpected disconnect.
// - online: Reconnect when we're back online.
//
if ('string' === typeof options.strategy) {
options.strategy = options.strategy.split(/\s?\,\s?/g);
}
if (false === options.strategy) {
//
// Strategies are disabled, but we still need an empty array to join it in
// to nothing.
//
options.strategy = [];
} else if (!options.strategy.length) {
options.strategy.push('disconnect', 'online');
//
// Timeout based reconnection should only be enabled conditionally. When
// authorization is enabled it could trigger.
//
if (!this.authorization) options.strategy.push('timeout');
}
options.strategy = options.strategy.join(',').toLowerCase();
//
// Only initialise the EventEmitter interface if we're running in a plain
// browser environment. The Stream interface is inherited differently when it
// runs on browserify and on Node.js.
//
if (!Stream) EventEmitter.call(primus);
//
// Force the use of WebSockets, even when we've detected some potential
// broken WebSocket implementation.
//
if ('websockets' in options) {
primus.AVOID_WEBSOCKETS = !options.websockets;
}
//
// Force or disable the use of NETWORK events as leading client side
// disconnection detection.
//
if ('network' in options) {
primus.NETWORK_EVENTS = options.network;
}
//
// Check if the user wants to manually initialise a connection. If they don't,
// we want to do it after a really small timeout so we give the users enough
// time to listen for `error` events etc.
//
if (!options.manual) primus.timers.open = setTimeout(function open() {
primus.clearTimeout('open').open();
}, 0);
primus.initialise(options);
}
/**
* Simple require wrapper to make browserify, node and require.js play nice.
*
* @param {String} name The module to require.
* @returns {Object|Undefined} The module that we required.
* @api private
*/
Primus.require = function requires(name) {
if ('function' !== typeof require) return undefined;
return !('function' === typeof define && define.amd)
? require(name)
: undefined;
};
//
// It's possible that we're running in Node.js or in a Node.js compatible
// environment such as browserify. In these cases we want to use some build in
// libraries to minimize our dependence on the DOM.
//
var Stream, parse;
try {
Primus.Stream = Stream = Primus.require('stream');
parse = Primus.require('url').parse;
//
// Normally inheritance is done in the same way as we do in our catch
// statement. But due to changes to the EventEmitter interface in Node 0.10
// this will trigger annoying memory leak warnings and other potential issues
// outlined in the issue linked below.
//
// @see https://github.com/joyent/node/issues/4971
//
Primus.require('util').inherits(Primus, Stream);
} catch (e) {
Primus.Stream = EventEmitter;
Primus.prototype = new EventEmitter();
//
// In the browsers we can leverage the DOM to parse the URL for us. It will
// automatically default to host of the current server when we supply it path
// etc.
//
parse = function parse(url) {
var a = document.createElement('a')
, data = {}
, key;
a.href = url;
//
// Transform it from a readOnly object to a read/writable object so we can
// change some parsed values. This is required if we ever want to override
// a port number etc. (as browsers remove port 443 and 80 from the URL's).
//
for (key in a) {
if ('string' === typeof a[key] || 'number' === typeof a[key]) {
data[key] = a[key];
}
}
//
// If we don't obtain a port number (e.g. when using zombie) then try
// and guess at a value from the 'href' value.
//
if (!data.port) {
var splits = (data.href || '').split('/');
if (splits.length > 2) {
var host = splits[2]
, atSignIndex = host.lastIndexOf('@');
if (~atSignIndex) host = host.slice(atSignIndex + 1);
splits = host.split(':');
if (splits.length === 2) data.port = splits[1];
}
}
//
// IE quirk: The `protocol` is parsed as ":" when a protocol agnostic URL
// is used. In this case we extract the value from the `href` value.
//
if (':' === data.protocol) {
data.protocol = data.href.substr(0, data.href.indexOf(':') + 1);
}
//
// Safari 5.1.7 (windows) quirk: When parsing a URL without a port number
// the `port` in the data object will default to "0" instead of the expected
// "". We're going to do an explicit check on "0" and force it to "".
//
if ('0' === data.port) data.port = '';
//
// Browsers do not parse authorization information, so we need to extract
// that from the URL.
//
if (~data.href.indexOf('@') && !data.auth) {
var start = data.protocol.length + 2;
data.auth = data.href.slice(start, data.href.indexOf(data.pathname, start)).split('@')[0];
}
return data;
};
}
/**
* Primus readyStates, used internally to set the correct ready state.
*
* @type {Number}
* @private
*/
Primus.OPENING = 1; // We're opening the connection.
Primus.CLOSED = 2; // No active connection.
Primus.OPEN = 3; // The connection is open.
/**
* Are we working with a potentially broken WebSockets implementation? This
* boolean can be used by transformers to remove `WebSockets` from their
* supported transports.
*
* @type {Boolean}
* @private
*/
Primus.prototype.AVOID_WEBSOCKETS = false;
/**
* Some browsers support registering emitting `online` and `offline` events when
* the connection has been dropped on the client. We're going to detect it in
* a simple `try {} catch (e) {}` statement so we don't have to do complicated
* feature detection.
*
* @type {Boolean}
* @private
*/
Primus.prototype.NETWORK_EVENTS = false;
Primus.prototype.online = true;
try {
if (
Primus.prototype.NETWORK_EVENTS = 'onLine' in navigator
&& (window.addEventListener || document.body.attachEvent)
) {
if (!navigator.onLine) {
Primus.prototype.online = false;
}
}
} catch (e) { }
/**
* The Ark contains all our plugins definitions. It's namespaced by
* name => plugin.
*
* @type {Object}
* @private
*/
Primus.prototype.ark = {};
/**
* Return the given plugin.
*
* @param {String} name The name of the plugin.
* @returns {Object|undefined} The plugin or undefined.
* @api public
*/
Primus.prototype.plugin = function plugin(name) {
context(this, 'plugin');
if (name) return this.ark[name];
var plugins = {};
for (name in this.ark) {
plugins[name] = this.ark[name];
}
return plugins;
};
/**
* Checks if the given event is an emitted event by Primus.
*
* @param {String} evt The event name.
* @returns {Boolean} Indication of the event is reserved for internal use.
* @api public
*/
Primus.prototype.reserved = function reserved(evt) {
return (/^(incoming|outgoing)::/).test(evt)
|| evt in this.reserved.events;
};
/**
* The actual events that are used by the client.
*
* @type {Object}
* @public
*/
Primus.prototype.reserved.events = {
readyStateChange: 1,
reconnecting: 1,
reconnected: 1,
reconnect: 1,
offline: 1,
timeout: 1,
online: 1,
error: 1,
close: 1,
open: 1,
data: 1,
end: 1
};
/**
* Initialise the Primus and setup all parsers and internal listeners.
*
* @param {Object} options The original options object.
* @returns {Primus}
* @api private
*/
Primus.prototype.initialise = function initialise(options) {
var primus = this
, start;
primus.on('outgoing::open', function opening() {
var readyState = primus.readyState;
primus.readyState = Primus.OPENING;
if (readyState !== primus.readyState) {
primus.emit('readyStateChange', 'opening');
}
start = +new Date();
});
primus.on('incoming::open', function opened() {
var readyState = primus.readyState
, reconnect = primus.attempt;
if (primus.attempt) primus.attempt = null;
//
// The connection has been openend so we should set our state to
// (writ|read)able so our stream compatibility works as intended.
//
primus.writable = true;
primus.readable = true;
//
// Make sure we are flagged as `online` as we've successfully opened the
// connection.
//
if (!primus.online) {
primus.online = true;
primus.emit('online');
}
primus.readyState = Primus.OPEN;
if (readyState !== primus.readyState) {
primus.emit('readyStateChange', 'open');
}
primus.latency = +new Date() - start;
primus.emit('open');
if (reconnect) primus.emit('reconnected');
primus.clearTimeout('ping', 'pong').heartbeat();
if (primus.buffer.length) {
var data = primus.buffer.slice()
, length = data.length
, i = 0;
primus.buffer.length = 0;
for (; i < length; i++) {
primus._write(data[i]);
}
}
});
primus.on('incoming::pong', function pong(time) {
primus.online = true;
primus.clearTimeout('pong').heartbeat();
primus.latency = (+new Date()) - time;
});
primus.on('incoming::error', function error(e) {
var connect = primus.timers.connect
, err = e;
//
// We're still doing a reconnect attempt, it could be that we failed to
// connect because the server was down. Failing connect attempts should
// always emit an `error` event instead of a `open` event.
//
if (primus.attempt) return primus.reconnect();
//
// When the error is not an Error instance we try to normalize it.
//
if ('string' === typeof e) {
err = new Error(e);
} else if (!(e instanceof Error) && 'object' === typeof e) {
//
// BrowserChannel and SockJS returns an object which contains some
// details of the error. In order to have a proper error we "copy" the
// details in an Error instance.
//
err = new Error(e.message || e.reason);
for (var key in e) {
if (e.hasOwnProperty(key)) err[key] = e[key];
}
}
if (primus.listeners('error').length) primus.emit('error', err);
//
// We received an error while connecting, this most likely the result of an
// unauthorized access to the server. But this something that is only
// triggered for Node based connections. Browsers trigger the error event.
//
if (connect) {
if (~primus.options.strategy.indexOf('timeout')) primus.reconnect();
else primus.end();
}
});
primus.on('incoming::data', function message(raw) {
primus.decoder(raw, function decoding(err, data) {
//
// Do a "save" emit('error') when we fail to parse a message. We don't
// want to throw here as listening to errors should be optional.
//
if (err) return primus.listeners('error').length && primus.emit('error', err);
//
// Handle all "primus::" prefixed protocol messages.
//
if (primus.protocol(data)) return;
primus.transforms(primus, primus, 'incoming', data, raw);
});
});
primus.on('incoming::end', function end() {
var readyState = primus.readyState;
//
// This `end` started with the receiving of a primus::server::close packet
// which indicated that the user/developer on the server closed the
// connection and it was not a result of a network disruption. So we should
// kill the connection without doing a reconnect.
//
if (primus.disconnect) {
primus.disconnect = false;
return primus.end();
}
//
// Always set the readyState to closed, and if we're still connecting, close
// the connection so we're sure that everything after this if statement block
// is only executed because our readyState is set to `open`.
//
primus.readyState = Primus.CLOSED;
if (readyState !== primus.readyState) {
primus.emit('readyStateChange', 'end');
}
if (primus.timers.connect) primus.end();
if (readyState !== Primus.OPEN) return;
this.writable = false;
this.readable = false;
//
// Clear all timers in case we're not going to reconnect.
//
for (var timeout in this.timers) {
this.clearTimeout(timeout);
}
//
// Fire the `close` event as an indication of connection disruption.
// This is also fired by `primus#end` so it is emitted in all cases.
//
primus.emit('close');
//
// The disconnect was unintentional, probably because the server has
// shutdown, so if the reconnection is enabled start a reconnect procedure.
//
if (~primus.options.strategy.indexOf('disconnect')) {
return primus.reconnect();
}
primus.emit('outgoing::end');
primus.emit('end');
});
//
// Setup the real-time client.
//
primus.client();
//
// Process the potential plugins.
//
for (var plugin in primus.ark) {
primus.ark[plugin].call(primus, primus, options);
}
//
// NOTE: The following code is only required if we're supporting network
// events as it requires access to browser globals.
//
if (!primus.NETWORK_EVENTS) return primus;
/**
* Handler for offline notifications.
*
* @api private
*/
function offline() {
if (!primus.online) return; // Already or still offline, bailout.
primus.online = false;
primus.emit('offline');
primus.end();
}
/**
* Handler for online notifications.
*
* @api private
*/
function online() {
if (primus.online) return; // Already or still online, bailout
primus.online = true;
primus.emit('online');
if (~primus.options.strategy.indexOf('online')) primus.reconnect();
}
if (window.addEventListener) {
window.addEventListener('offline', offline, false);
window.addEventListener('online', online, false);
} else if (document.body.attachEvent){
document.body.attachEvent('onoffline', offline);
document.body.attachEvent('ononline', online);
}
return primus;
};
/**
* Really dead simple protocol parser. We simply assume that every message that
* is prefixed with `primus::` could be used as some sort of protocol definition
* for Primus.
*
* @param {String} msg The data.
* @returns {Boolean} Is a protocol message.
* @api private
*/
Primus.prototype.protocol = function protocol(msg) {
if (
'string' !== typeof msg
|| msg.indexOf('primus::') !== 0
) return false;
var last = msg.indexOf(':', 8)
, value = msg.slice(last + 2);
switch (msg.slice(8, last)) {
case 'pong':
this.emit('incoming::pong', value);
break;
case 'server':
//
// The server is closing the connection, forcefully disconnect so we don't
// reconnect again.
//
if ('close' === value) {
this.disconnect = true;
}
break;
case 'id':
this.emit('incoming::id', value);
break;
//
// Unknown protocol, somebody is probably sending `primus::` prefixed
// messages.
//
default:
return false;
}
return true;
};
/**
* Execute the set of message transformers from Primus on the incoming or
* outgoing message.
* This function and it's content should be in sync with Spark#transforms in
* spark.js.
*
* @param {Primus} primus Reference to the Primus instance with message transformers.
* @param {Spark|Primus} connection Connection that receives or sends data.
* @param {String} type The type of message, 'incoming' or 'outgoing'.
* @param {Mixed} data The data to send or that has been received.
* @param {String} raw The raw encoded data.
* @returns {Primus}
* @api public
*/
Primus.prototype.transforms = function transforms(primus, connection, type, data, raw) {
var packet = { data: data }
, fns = primus.transformers[type];
//
// Iterate in series over the message transformers so we can allow optional
// asynchronous execution of message transformers which could for example
// retrieve additional data from the server, do extra decoding or even
// message validation.
//
(function transform(index, done) {
var transformer = fns[index++];
if (!transformer) return done();
if (1 === transformer.length) {
if (false === transformer.call(connection, packet)) {
//
// When false is returned by an incoming transformer it means that's
// being handled by the transformer and we should not emit the `data`
// event.
//
return;
}
return transform(index, done);
}
transformer.call(connection, packet, function finished(err, arg) {
if (err) return connection.emit('error', err);
if (false === arg) return;
transform(index, done);
});
}(0, function done() {
//
// We always emit 2 arguments for the data event, the first argument is the
// parsed data and the second argument is the raw string that we received.
// This allows you, for example, to do some validation on the parsed data
// and then save the raw string in your database without the stringify
// overhead.
//
if ('incoming' === type) return connection.emit('data', packet.data, raw);
connection._write(packet.data);
}));
return this;
};
/**
* Retrieve the current id from the server.
*
* @param {Function} fn Callback function.
* @returns {Primus}
* @api public
*/
Primus.prototype.id = function id(fn) {
if (this.socket && this.socket.id) return fn(this.socket.id);
this.write('primus::id::');
return this.once('incoming::id', fn);
};
/**
* Establish a connection with the server. When this function is called we
* assume that we don't have any open connections. If you do call it when you
* have a connection open, it could cause duplicate connections.
*
* @returns {Primus}
* @api public
*/
Primus.prototype.open = function open() {
context(this, 'open');
//
// Only start a `connection timeout` procedure if we're not reconnecting as
// that shouldn't count as an initial connection. This should be started
// before the connection is opened to capture failing connections and kill the
// timeout.
//
if (!this.attempt && this.options.timeout) this.timeout();
this.emit('outgoing::open');
return this;
};
/**
* Send a new message.
*
* @param {Mixed} data The data that needs to be written.
* @returns {Boolean} Always returns true as we don't support back pressure.
* @api public
*/
Primus.prototype.write = function write(data) {
context(this, 'write');
this.transforms(this, this, 'outgoing', data);
return true;
};
/**
* The actual message writer.
*
* @param {Mixed} data The message that needs to be written.
* @returns {Boolean} Successful write to the underlaying transport.
* @api private
*/
Primus.prototype._write = function write(data) {
var primus = this;
//
// The connection is closed, normally this would already be done in the
// `spark.write` method, but as `_write` is used internally, we should also
// add the same check here to prevent potential crashes by writing to a dead
// socket.
//
if (Primus.OPEN !== primus.readyState) {
//
// If the buffer is at capacity, remove the first item.
//
if (this.buffer.length === this.options.queueSize) {
this.buffer.splice(0, 1);
}
this.buffer.push(data);
return false;
}
primus.encoder(data, function encoded(err, packet) {
//
// Do a "save" emit('error') when we fail to parse a message. We don't
// want to throw here as listening to errors should be optional.
//
if (err) return primus.listeners('error').length && primus.emit('error', err);
primus.emit('outgoing::data', packet);
});
return true;
};
/**
* Send a new heartbeat over the connection to ensure that we're still
* connected and our internet connection didn't drop. We cannot use server side
* heartbeats for this unfortunately.
*
* @returns {Primus}
* @api private
*/
Primus.prototype.heartbeat = function heartbeat() {
var primus = this;
if (!primus.options.ping) return primus;
/**
* Exterminate the connection as we've timed out.
*
* @api private
*/
function pong() {
primus.clearTimeout('pong');
//
// The network events already captured the offline event.
//
if (!primus.online) return;
primus.online = false;
primus.emit('offline');
primus.emit('incoming::end');
}
/**
* We should send a ping message to the server.
*
* @api private
*/
function ping() {
var value = +new Date();
primus.clearTimeout('ping').write('primus::ping::'+ value);
primus.emit('outgoing::ping', value);
primus.timers.pong = setTimeout(pong, primus.options.pong);
}
primus.timers.ping = setTimeout(ping, primus.options.ping);
return this;
};
/**
* Start a connection timeout.
*
* @returns {Primus}
* @api private
*/
Primus.prototype.timeout = function timeout() {
var primus = this;
/**
* Remove all references to the timeout listener as we've received an event
* that can be used to determine state.
*
* @api private
*/
function remove() {
primus.removeListener('error', remove)
.removeListener('open', remove)
.removeListener('end', remove)
.clearTimeout('connect');
}
primus.timers.connect = setTimeout(function expired() {
remove(); // Clean up old references.
if (primus.readyState === Primus.OPEN || primus.attempt) return;
primus.emit('timeout');
//
// We failed to connect to the server.
//
if (~primus.options.strategy.indexOf('timeout')) primus.reconnect();
else primus.end();
}, primus.options.timeout);
return primus.on('error', remove)
.on('open', remove)
.on('end', remove);
};
/**
* Properly clean up all `setTimeout` references.
*
* @param {String} ..args.. The names of the timeout's we need clear.
* @returns {Primus}
* @api private
*/
Primus.prototype.clearTimeout = function clear() {
for (var args = arguments, i = 0, l = args.length; i < l; i++) {
if (this.timers[args[i]]) clearTimeout(this.timers[args[i]]);
delete this.timers[args[i]];
}
return this;
};
/**
* Exponential back off algorithm for retry operations. It uses an randomized
* retry so we don't DDOS our server when it goes down under pressure.
*
* @param {Function} callback Callback to be called after the timeout.
* @param {Object} opts Options for configuring the timeout.
* @returns {Primus}
* @api private
*/
Primus.prototype.backoff = function backoff(callback, opts) {
opts = opts || {};
var primus = this;
//
// Bailout when we already have a backoff process running. We shouldn't call
// the callback then as it might cause an unexpected `end` event as another
// reconnect process is already running.
//
if (opts.backoff) return primus;
opts.maxDelay = 'maxDelay' in opts ? opts.maxDelay : Infinity; // Maximum delay.
opts.minDelay = 'minDelay' in opts ? opts.minDelay : 500; // Minimum delay.
opts.retries = 'retries' in opts ? opts.retries : 10; // Allowed retries.
opts.attempt = (+opts.attempt || 0) + 1; // Current attempt.
opts.factor = 'factor' in opts ? opts.factor : 2; // Back off factor.
//
// Bailout if we are about to make to much attempts. Please note that we use
// `>` because we already incremented the value above.
//
if (opts.attempt > opts.retries) {
callback(new Error('Unable to retry'), opts);
return primus;
}
//
// Prevent duplicate back off attempts using the same options object.
//
opts.backoff = true;
//
// Calculate the timeout, but make it randomly so we don't retry connections
// at the same interval and defeat the purpose. This exponential back off is
// based on the work of:
//
// http://dthain.blogspot.nl/2009/02/exponential-backoff-in-distributed.html
//
opts.timeout = opts.attempt !== 1
? Math.min(Math.round(
(Math.random() + 1) * opts.minDelay * Math.pow(opts.factor, opts.attempt)
), opts.maxDelay)
: opts.minDelay;
primus.timers.reconnect = setTimeout(function delay() {
opts.backoff = false;
primus.clearTimeout('reconnect');
callback(undefined, opts);
}, opts.timeout);
//
// Emit a `reconnecting` event with current reconnect options. This allows
// them to update the UI and provide their users with feedback.
//
primus.emit('reconnecting', opts);
return primus;
};
/**
* Start a new reconnect procedure.
*
* @returns {Primus}
* @api private
*/
Primus.prototype.reconnect = function reconnect() {
var primus = this;
//
// Try to re-use the existing attempt.
//
primus.attempt = primus.attempt || primus.clone(primus.options.reconnect);
primus.backoff(function attempt(fail, backoff) {
if (fail) {
primus.attempt = null;
return primus.emit('end');
}
//
// Try to re-open the connection again.
//
primus.emit('reconnect', backoff);
primus.emit('outgoing::reconnect');
}, primus.attempt);
return primus;
};
/**
* Close the connection completely.
*
* @param {Mixed} data last packet of data.
* @returns {Primus}
* @api public
*/
Primus.prototype.end = function end(data) {
context(this, 'end');
if (this.readyState === Primus.CLOSED && !this.timers.connect) {
//
// If we are reconnecting stop the reconnection procedure.
//
if (this.timers.reconnect) {
this.clearTimeout('reconnect');
this.attempt = null;
this.emit('end');
}
return this;
}
if (data !== undefined) this.write(data);
this.writable = false;
this.readable = false;
var readyState = this.readyState;
this.readyState = Primus.CLOSED;
if (readyState !== this.readyState) {
this.emit('readyStateChange', 'end');
}
for (var timeout in this.timers) {
this.clearTimeout(timeout);
}
this.emit('outgoing::end');
this.emit('close');
this.emit('end');
return this;
};
/**
* Create a shallow clone of a given object.
*
* @param {Object} obj The object that needs to be cloned.
* @returns {Object} Copy.
* @api private
*/
Primus.prototype.clone = function clone(obj) {
return this.merge({}, obj);
};
/**
* Merge different objects in to one target object.
*
* @param {Object} target The object where everything should be merged in.
* @returns {Object} Original target with all merged objects.
* @api private
*/
Primus.prototype.merge = function merge(target) {
var args = Array.prototype.slice.call(arguments, 1);
for (var i = 0, l = args.length, key, obj; i < l; i++) {
obj = args[i];
for (key in obj) {
if (obj.hasOwnProperty(key)) target[key] = obj[key];
}
}
return target;
};
/**
* Parse the connection string.
*
* @type {Function}
* @param {String} url Connection URL.
* @returns {Object} Parsed connection.
* @api private
*/
Primus.prototype.parse = parse;
/**
* Parse a query string.
*
* @param {String} query The query string that needs to be parsed.
* @returns {Object} Parsed query string.
* @api private
*/
Primus.prototype.querystring = function querystring(query) {
var parser = /([^=?&]+)=([^&]*)/g
, result = {}
, part;
//
// Little nifty parsing hack, leverage the fact that RegExp.exec increments
// the lastIndex property so we can continue executing this loop until we've
// parsed all results.
//
for (;
part = parser.exec(query);
result[decodeURIComponent(part[1])] = decodeURIComponent(part[2])
);
return result;
};
/**
* Transform a query string object back in to string equiv.
*
* @param {Object} obj The query string object.
* @returns {String}
* @api private
*/
Primus.prototype.querystringify = function querystringify(obj) {
var pairs = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
pairs.push(encodeURIComponent(key) +'='+ encodeURIComponent(obj[key]));
}
}
return pairs.join('&');
};
/**
* Generates a connection URI.
*
* @param {String} protocol The protocol that should used to crate the URI.
* @returns {String|options} The URL.
* @api private
*/
Primus.prototype.uri = function uri(options) {
var url = this.url
, server = []
, qsa = false;
//
// Query strings are only allowed when we've received clearance for it.
//
if (options.query) qsa = true;
options = options || {};
options.protocol = 'protocol' in options ? options.protocol : 'http';
options.query = url.search && 'query' in options ? (url.search.charAt(0) === '?' ? url.search.slice(1) : url.search) : false;
options.secure = 'secure' in options ? options.secure : (url.protocol === 'https:' || url.protocol === 'wss:');
options.auth = 'auth' in options ? options.auth : url.auth;
options.pathname = 'pathname' in options ? options.pathname : this.pathname.slice(1);
options.port = 'port' in options ? +options.port : +url.port || (options.secure ? 443 : 80);
options.host = 'host' in options ? options.host : url.hostname || url.host.replace(':'+ url.port, '');
//
// Allow transformation of the options before we construct a full URL from it.
//
this.emit('outgoing::url', options);
//
// `url.host` might be undefined (e.g. when using zombie) so we use the
// hostname and port defined above.
//
var host = (443 !== options.port && 80 !== options.port)
? options.host +':'+ options.port
: options.host;
//
// We need to make sure that we create a unique connection URL every time to
// prevent bfcache back forward cache of becoming an issue. We're doing this
// by forcing an cache busting query string in to the URL.
//
var querystring = this.querystring(options.query || '');
querystring._primuscb = +new Date() +'-'+ this.stamps++;
options.query = this.querystringify(querystring);
//
// Automatically suffix the protocol so we can supply `ws` and `http` and it gets
// transformed correctly.
//
server.push(options.secure ? options.protocol +'s:' : options.protocol +':', '');
if (options.auth) server.push(options.auth +'@'+ host);
else server.push(host);
//
// Pathnames are optional as some Transformers would just use the pathname
// directly.
//
if (options.pathname) server.push(options.pathname);
//
// Optionally add a search query, again, not supported by all Transformers.
// SockJS is known to throw errors when a query string is included.
//
if (qsa) server.push('?'+ options.query);
else delete options.query;
if (options.object) return options;
return server.join('/');
};
/**
* Simple emit wrapper that returns a function that emits an event once it's
* called. This makes it easier for transports to emit specific events. The
* scope of this function is limited as it will only emit one single argument.
*
* @param {String} event Name of the event that we should emit.
* @param {Function} parser Argument parser.
* @returns {Function} The wrapped function that will emit events when called.
* @api public
*/
Primus.prototype.emits = function emits(event, parser) {
var primus = this;
return function emit(arg) {
var data = parser ? parser.apply(primus, arguments) : arg;
//
// Timeout is required to prevent crashes on WebSockets connections on
// mobile devices. We need to handle these edge cases in our own library
// as we cannot be certain that all frameworks fix these issues.
//
setTimeout(function timeout() {
primus.emit('incoming::'+ event, data);
}, 0);
};
};
/**
* Register a new message transformer. This allows you to easily manipulate incoming
* and outgoing data which is particularity handy for plugins that want to send
* meta data together with the messages.
*
* @param {String} type Incoming or outgoing
* @param {Function} fn A new message transformer.
* @returns {Primus}
* @api public
*/
Primus.prototype.transform = function transform(type, fn) {
context(this, 'transform');
if (!(type in this.transformers)) {
return this.critical(new Error('Invalid transformer type'));
}
this.transformers[type].push(fn);
return this;
};
/**
* A critical error has occurred, if we have an `error` listener, emit it there.
* If not, throw it, so we get a stack trace + proper error message.
*
* @param {Error} err The critical error.
* @returns {Primus}
* @api private
*/
Primus.prototype.critical = function critical(err) {
if (this.listeners('error').length) {
this.emit('error', err);
return this;
}
throw err;
};
/**
* Syntax sugar, adopt a Socket.IO like API.
*
* @param {String} url The URL we want to connect to.
* @param {Object} options Connection options.
* @returns {Primus}
* @api public
*/
Primus.connect = function connect(url, options) {
return new Primus(url, options);
};
//
// Expose the EventEmitter so it can be re-used by wrapping libraries we're also
// exposing the Stream interface.
//
Primus.EventEmitter = EventEmitter;
//
// These libraries are automatically are automatically inserted at the
// server-side using the Primus#library method.
//
Primus.prototype.client = function client() {
var primus = this
, socket;
//
// Selects an available Engine.IO factory.
//
var Factory = (function Factory() {
if ('undefined' !== typeof SockJS) return SockJS;
try { return Primus.require('sockjs-client-node'); }
catch (e) {}
return undefined;
})();
if (!Factory) return primus.critical(new Error('Missing required `sockjs-client-node` module. Please run `npm install --save sockjs-client-node`'));
//
// Connect to the given URL.
//
primus.on('outgoing::open', function opening() {
primus.emit('outgoing::end');
primus.socket = socket = new Factory(
primus.uri({ protocol: 'http' }),
null,
primus.merge(primus.transport, {
info: {
websocket: !primus.AVOID_WEBSOCKETS, // Prevent WebSocket crashes
cookie_needed: true // Disables xdomainrequest bugs
}
}));
//
// Setup the Event handlers.
//
socket.onopen = primus.emits('open');
socket.onerror = primus.emits('error');
socket.onclose = function (e) {
//
// The timeout replicates the behaviour of primus.emits so we're not
// affected by any timing bugs.
//
setTimeout(function timeout() {
if (e && e.code > 1000) primus.emit('incoming::error', e);
primus.emit('incoming::end');
}, 0);
};
socket.onmessage = primus.emits('data', function parse(evt) {
return evt.data;
});
});
//
// We need to write a new message to the socket.
//
primus.on('outgoing::data', function write(message) {
if (socket) socket.send(message);
});
//
// Attempt to reconnect the socket. It assumes that the `outgoing::end` event is
// called if it failed to disconnect.
//
primus.on('outgoing::reconnect', function reconnect() {
primus.emit('outgoing::end');
primus.emit('outgoing::open');
});
//
// We need to close the socket.
//
primus.on('outgoing::end', function close() {
if (socket) {
socket.onerror = socket.onopen = socket.onclose = socket.onmessage = function () {};
socket.close();
socket = null;
}
});
};
Primus.prototype.authorization = false;
Primus.prototype.pathname = "/primus";
Primus.prototype.encoder = function encoder(data, fn) {
var err;
try { data = JSON.stringify(data); }
catch (e) { err = e; }
fn(err, data);
};
Primus.prototype.decoder = function decoder(data, fn) {
var err;
if ('string' !== typeof data) return fn(err, data);
try { data = JSON.parse(data); }
catch (e) { err = e; }
fn(err, data);
};
Primus.prototype.version = "2.4.6";
//
// Hack 1: \u2028 and \u2029 are allowed inside string in JSON. But JavaScript
// defines them as newline separators. Because no literal newlines are allowed
// in a string this causes a ParseError. We work around this issue by replacing
// these characters with a properly escaped version for those chars. This can
// cause errors with JSONP requests or if the string is just evaluated.
//
// This could have been solved by replacing the data during the "outgoing::data"
// event. But as it affects the JSON encoding in general I've opted for a global
// patch instead so all JSON.stringify operations are save.
//
if (
'object' === typeof JSON
&& 'function' === typeof JSON.stringify
&& JSON.stringify(['\u2028\u2029']) === '["\u2028\u2029"]'
) {
JSON.stringify = function replace(stringify) {
var u2028 = /\u2028/g
, u2029 = /\u2029/g;
return function patched(value, replacer, spaces) {
var result = stringify.call(this, value, replacer, spaces);
//
// Replace the bad chars.
//
if (result) {
if (~result.indexOf('\u2028')) result = result.replace(u2028, '\\u2028');
if (~result.indexOf('\u2029')) result = result.replace(u2029, '\\u2029');
}
return result;
};
}(JSON.stringify);
}
if (
'undefined' !== typeof document
&& 'undefined' !== typeof navigator
) {
//
// Hack 2: If you press ESC in FireFox it will close all active connections.
// Normally this makes sense, when your page is still loading. But versions
// before FireFox 22 will close all connections including WebSocket connections
// after page load. One way to prevent this is to do a `preventDefault()` and
// cancel the operation before it bubbles up to the browsers default handler.
// It needs to be added as `keydown` event, if it's added keyup it will not be
// able to prevent the connection from being closed.
//
if (document.addEventListener) {
document.addEventListener('keydown', function keydown(e) {
if (e.keyCode !== 27 || !e.preventDefault) return;
e.preventDefault();
}, false);
}
//
// Hack 3: This is a Mac/Apple bug only, when you're behind a reverse proxy or
// have you network settings set to `automatic proxy discovery` the safari
// browser will crash when the WebSocket constructor is initialised. There is
// no way to detect the usage of these proxies available in JavaScript so we
// need to do some nasty browser sniffing. This only affects Safari versions
// lower then 5.1.4
//
var ua = (navigator.userAgent || '').toLowerCase()
, parsed = ua.match(/.+(?:rv|it|ra|ie)[\/: ](\d+)\.(\d+)(?:\.(\d+))?/) || []
, version = +[parsed[1], parsed[2]].join('.');
if (
!~ua.indexOf('chrome')
&& ~ua.indexOf('safari')
&& version < 534.54
) {
Primus.prototype.AVOID_WEBSOCKETS = true;
}
}
Primus.prototype.ark["emitter"] = function (){};
return Primus; });(function PrimusLibraryWrap(Primus) {/* SockJS client, version 0.3.4, http://sockjs.org, MIT License
Copyright (c) 2011-2012 VMware, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// JSON2 by Douglas Crockford (minified).
var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function f(a){return a<10?"0"+a:a}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")})}()
// [*] Including lib/index.js
// Public object
SockJS = (function(){
var _document = document;
var _window = window;
var utils = {};
// [*] Including lib/reventtarget.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
/* Simplified implementation of DOM2 EventTarget.
* http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget
*/
var REventTarget = function() {};
REventTarget.prototype.addEventListener = function (eventType, listener) {
if(!this._listeners) {
this._listeners = {};
}
if(!(eventType in this._listeners)) {
this._listeners[eventType] = [];
}
var arr = this._listeners[eventType];
if(utils.arrIndexOf(arr, listener) === -1) {
arr.push(listener);
}
return;
};
REventTarget.prototype.removeEventListener = function (eventType, listener) {
if(!(this._listeners && (eventType in this._listeners))) {
return;
}
var arr = this._listeners[eventType];
var idx = utils.arrIndexOf(arr, listener);
if (idx !== -1) {
if(arr.length > 1) {
this._listeners[eventType] = arr.slice(0, idx).concat( arr.slice(idx+1) );
} else {
delete this._listeners[eventType];
}
return;
}
return;
};
REventTarget.prototype.dispatchEvent = function (event) {
var t = event.type;
var args = Array.prototype.slice.call(arguments, 0);
if (this['on'+t]) {
this['on'+t].apply(this, args);
}
if (this._listeners && t in this._listeners) {
for(var i=0; i < this._listeners[t].length; i++) {
this._listeners[t][i].apply(this, args);
}
}
};
// [*] End of lib/reventtarget.js
// [*] Including lib/simpleevent.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var SimpleEvent = function(type, obj) {
this.type = type;
if (typeof obj !== 'undefined') {
for(var k in obj) {
if (!obj.hasOwnProperty(k)) continue;
this[k] = obj[k];
}
}
};
SimpleEvent.prototype.toString = function() {
var r = [];
for(var k in this) {
if (!this.hasOwnProperty(k)) continue;
var v = this[k];
if (typeof v === 'function') v = '[function]';
r.push(k + '=' + v);
}
return 'SimpleEvent(' + r.join(', ') + ')';
};
// [*] End of lib/simpleevent.js
// [*] Including lib/eventemitter.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var EventEmitter = function(events) {
var that = this;
that._events = events || [];
that._listeners = {};
};
EventEmitter.prototype.emit = function(type) {
var that = this;
that._verifyType(type);
if (that._nuked) return;
var args = Array.prototype.slice.call(arguments, 1);
if (that['on'+type]) {
that['on'+type].apply(that, args);
}
if (type in that._listeners) {
for(var i = 0; i < that._listeners[type].length; i++) {
that._listeners[type][i].apply(that, args);
}
}
};
EventEmitter.prototype.on = function(type, callback) {
var that = this;
that._verifyType(type);
if (that._nuked) return;
if (!(type in that._listeners)) {
that._listeners[type] = [];
}
that._listeners[type].push(callback);
};
EventEmitter.prototype._verifyType = function(type) {
var that = this;
if (utils.arrIndexOf(that._events, type) === -1) {
utils.log('Event ' + JSON.stringify(type) +
' not listed ' + JSON.stringify(that._events) +
' in ' + that);
}
};
EventEmitter.prototype.nuke = function() {
var that = this;
that._nuked = true;
for(var i=0; i<that._events.length; i++) {
delete that[that._events[i]];
}
that._listeners = {};
};
// [*] End of lib/eventemitter.js
// [*] Including lib/utils.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var random_string_chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
utils.random_string = function(length, max) {
max = max || random_string_chars.length;
var i, ret = [];
for(i=0; i < length; i++) {
ret.push( random_string_chars.substr(Math.floor(Math.random() * max),1) );
}
return ret.join('');
};
utils.random_number = function(max) {
return Math.floor(Math.random() * max);
};
utils.random_number_string = function(max) {
var t = (''+(max - 1)).length;
var p = Array(t+1).join('0');
return (p + utils.random_number(max)).slice(-t);
};
utils.getOrigin = function(url) {
if (url.match(/^file:\/\//)) {
// no origin when using file protocol
return null;
}
var parts = url.split('/');
var protocol = parts[0];
var host = parts[2];
var atSignIndex = host.lastIndexOf('@');
var hostname;
var port;
if (~atSignIndex) host = host.slice(atSignIndex + 1);
parts = host.split(':');
hostname = parts[0];
port = parts[1];
if (!port) port = (protocol === 'https:') ? 443 : 80;
return protocol + '//' + hostname + ':' + port;
};
utils.isSameOriginUrl = function(url_a, url_b) {
// location.origin would do, but it's not always available.
if (!url_b) url_b = _window.location.href;
return utils.getOrigin(url_a) === utils.getOrigin(url_b);
};
utils.getParentDomain = function(url) {
// ipv4 ip address
if (/^[0-9.]*$/.test(url)) return url;
// ipv6 ip address
if (/^\[/.test(url)) return url;
// no dots
if (!(/[.]/.test(url))) return url;
var parts = url.split('.').slice(1);
return parts.join('.');
};
utils.objectExtend = function(dst, src) {
for(var k in src) {
if (src.hasOwnProperty(k)) {
dst[k] = src[k];
}
}
return dst;
};
var WPrefix = '_jp';
utils.polluteGlobalNamespace = function() {
if (!(WPrefix in _window)) {
_window[WPrefix] = {};
}
};
utils.closeFrame = function (code, reason) {
return 'c'+JSON.stringify([code, reason]);
};
utils.userSetCode = function (code) {
return code === 1000 || (code >= 3000 && code <= 4999);
};
// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/
// and RFC 2988.
utils.countRTO = function (rtt) {
// In a local environment, when using IE8/9 and the `jsonp-polling`
// transport the time needed to establish a connection (the time that pass
// from the opening of the transport to the call of `_dispatchOpen`) is
// around 200ms (the lower bound used in the official client) and this
// causes spurious timeouts. For this reason we calculate a value slightly
// larger than that used in official client.
if (rtt > 100) return 4 * rtt; // rto > 400ms
return 300 + rtt; // 300ms < rto <= 400ms
}
utils.log = function() {
if (_window.console && console.log && console.log.apply) {
console.log.apply(console, arguments);
}
};
utils.bind = function(fun, that) {
if (fun.bind) {
return fun.bind(that);
} else {
return function() {
return fun.apply(that, arguments);
};
}
};
utils.flatUrl = function(url) {
return url.indexOf('?') === -1 && url.indexOf('#') === -1;
};
utils.amendUrl = function(url) {
var dl = _document.location;
if (!url) {
throw new Error('Wrong url for SockJS');
}
if (!utils.flatUrl(url)) {
throw new Error('Only basic urls are supported in SockJS');
}
// '//abc' --> 'http://abc'
if (url.indexOf('//') === 0) {
url = dl.protocol + url;
}
// '/abc' --> 'http://localhost:80/abc'
if (url.indexOf('/') === 0) {
url = dl.protocol + '//' + dl.host + url;
}
// strip trailing slashes
url = url.replace(/[/]+$/,'');
return url;
};
// IE doesn't support [].indexOf.
utils.arrIndexOf = function(arr, obj){
for(var i=0; i < arr.length; i++){
if(arr[i] === obj){
return i;
}
}
return -1;
};
utils.arrSkip = function(arr, obj) {
var idx = utils.arrIndexOf(arr, obj);
if (idx === -1) {
return arr.slice();
} else {
var dst = arr.slice(0, idx);
return dst.concat(arr.slice(idx+1));
}
};
// Via: https://gist.github.com/1133122/2121c601c5549155483f50be3da5305e83b8c5df
utils.isArray = Array.isArray || function(value) {
return {}.toString.call(value).indexOf('Array') >= 0
};
utils.delay = function(t, fun) {
if(typeof t === 'function') {
fun = t;
t = 0;
}
return setTimeout(fun, t);
};
// Chars worth escaping, as defined by Douglas Crockford:
// https://github.com/douglascrockford/JSON-js/blob/47a9882cddeb1e8529e07af9736218075372b8ac/json2.js#L196
var json_escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
json_lookup = {
"\u0000":"\\u0000","\u0001":"\\u0001","\u0002":"\\u0002","\u0003":"\\u0003",
"\u0004":"\\u0004","\u0005":"\\u0005","\u0006":"\\u0006","\u0007":"\\u0007",
"\b":"\\b","\t":"\\t","\n":"\\n","\u000b":"\\u000b","\f":"\\f","\r":"\\r",
"\u000e":"\\u000e","\u000f":"\\u000f","\u0010":"\\u0010","\u0011":"\\u0011",
"\u0012":"\\u0012","\u0013":"\\u0013","\u0014":"\\u0014","\u0015":"\\u0015",
"\u0016":"\\u0016","\u0017":"\\u0017","\u0018":"\\u0018","\u0019":"\\u0019",
"\u001a":"\\u001a","\u001b":"\\u001b","\u001c":"\\u001c","\u001d":"\\u001d",
"\u001e":"\\u001e","\u001f":"\\u001f","\"":"\\\"","\\":"\\\\",
"\u007f":"\\u007f","\u0080":"\\u0080","\u0081":"\\u0081","\u0082":"\\u0082",
"\u0083":"\\u0083","\u0084":"\\u0084","\u0085":"\\u0085","\u0086":"\\u0086",
"\u0087":"\\u0087","\u0088":"\\u0088","\u0089":"\\u0089","\u008a":"\\u008a",
"\u008b":"\\u008b","\u008c":"\\u008c","\u008d":"\\u008d","\u008e":"\\u008e",
"\u008f":"\\u008f","\u0090":"\\u0090","\u0091":"\\u0091","\u0092":"\\u0092",
"\u0093":"\\u0093","\u0094":"\\u0094","\u0095":"\\u0095","\u0096":"\\u0096",
"\u0097":"\\u0097","\u0098":"\\u0098","\u0099":"\\u0099","\u009a":"\\u009a",
"\u009b":"\\u009b","\u009c":"\\u009c","\u009d":"\\u009d","\u009e":"\\u009e",
"\u009f":"\\u009f","\u00ad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601",
"\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f",
"\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d",
"\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029",
"\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d",
"\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061",
"\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065",
"\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069",
"\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d",
"\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0",
"\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4",
"\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8",
"\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc",
"\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"};
// Some extra characters that Chrome gets wrong, and substitutes with
// something else on the wire.
var extra_escapable = /[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g,
extra_lookup;
// JSON Quote string. Use native implementation when possible.
var JSONQuote = (JSON && JSON.stringify) || function(string) {
json_escapable.lastIndex = 0;
if (json_escapable.test(string)) {
string = string.replace(json_escapable, function(a) {
return json_lookup[a];
});
}
return '"' + string + '"';
};
// This may be quite slow, so let's delay until user actually uses bad
// characters.
var unroll_lookup = function(escapable) {
var i;
var unrolled = {}
var c = []
for(i=0; i<65536; i++) {
c.push( String.fromCharCode(i) );
}
escapable.lastIndex = 0;
c.join('').replace(escapable, function (a) {
unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
return '';
});
escapable.lastIndex = 0;
return unrolled;
};
// Quote string, also taking care of unicode characters that browsers
// often break. Especially, take care of unicode surrogates:
// http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates
utils.quote = function(string) {
var quoted = JSONQuote(string);
// In most cases this should be very fast and good enough.
extra_escapable.lastIndex = 0;
if(!extra_escapable.test(quoted)) {
return quoted;
}
if(!extra_lookup) extra_lookup = unroll_lookup(extra_escapable);
return quoted.replace(extra_escapable, function(a) {
return extra_lookup[a];
});
}
var _all_protocols = ['websocket',
'xdr-streaming',
'xhr-streaming',
'iframe-eventsource',
'iframe-htmlfile',
'xdr-polling',
'xhr-polling',
'iframe-xhr-polling',
'jsonp-polling'];
utils.probeProtocols = function() {
var probed = {};
for(var i=0; i<_all_protocols.length; i++) {
var protocol = _all_protocols[i];
// User can have a typo in protocol name.
probed[protocol] = SockJS[protocol] &&
SockJS[protocol].enabled();
}
return probed;
};
utils.detectProtocols = function(probed, protocols_whitelist, info) {
var pe = {},
protocols = [];
if (!protocols_whitelist) protocols_whitelist = _all_protocols;
for(var i=0; i<protocols_whitelist.length; i++) {
var protocol = protocols_whitelist[i];
pe[protocol] = probed[protocol];
}
var maybe_push = function(protos) {
var proto = protos.shift();
if (pe[proto]) {
protocols.push(proto);
} else {
if (protos.length > 0) {
maybe_push(protos);
}
}
}
// 1. Websocket
if (info.websocket !== false) {
maybe_push(['websocket']);
}
// 2. Streaming
if (pe['xhr-streaming'] && !info.null_origin) {
protocols.push('xhr-streaming');
} else {
if (pe['xdr-streaming'] && !info.cookie_needed && !info.null_origin) {
protocols.push('xdr-streaming');
} else {
maybe_push(['iframe-eventsource',
'iframe-htmlfile']);
}
}
// 3. Polling
if (pe['xhr-polling'] && !info.null_origin) {
protocols.push('xhr-polling');
} else {
if (pe['xdr-polling'] && !info.cookie_needed && !info.null_origin) {
protocols.push('xdr-polling');
} else {
maybe_push(['iframe-xhr-polling',
'jsonp-polling']);
}
}
return protocols;
}
// [*] End of lib/utils.js
// [*] Including lib/dom.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
// May be used by htmlfile jsonp and transports.
var MPrefix = '_sockjs_global';
utils.createHook = function() {
var window_id = 'a' + utils.random_string(8);
if (!(MPrefix in _window)) {
var map = {};
_window[MPrefix] = function(window_id) {
if (!(window_id in map)) {
map[window_id] = {
id: window_id,
del: function() {delete map[window_id];}
};
}
return map[window_id];
}
}
return _window[MPrefix](window_id);
};
utils.attachMessage = function(listener) {
utils.attachEvent('message', listener);
};
utils.attachEvent = function(event, listener) {
if (typeof _window.addEventListener !== 'undefined') {
_window.addEventListener(event, listener, false);
} else {
// IE quirks.
// According to: http://stevesouders.com/misc/test-postmessage.php
// the message gets delivered only to 'document', not 'window'.
_document.attachEvent("on" + event, listener);
// I get 'window' for ie8.
_window.attachEvent("on" + event, listener);
}
};
utils.detachMessage = function(listener) {
utils.detachEvent('message', listener);
};
utils.detachEvent = function(event, listener) {
if (typeof _window.addEventListener !== 'undefined') {
_window.removeEventListener(event, listener, false);
} else {
_document.detachEvent("on" + event, listener);
_window.detachEvent("on" + event, listener);
}
};
var on_unload = {};
// Things registered after beforeunload are to be called immediately.
var after_unload = false;
var trigger_unload_callbacks = function() {
for(var ref in on_unload) {
on_unload[ref]();
delete on_unload[ref];
};
};
var unload_triggered = function() {
if(after_unload) return;
after_unload = true;
trigger_unload_callbacks();
};
// 'unload' alone is not reliable in opera within an iframe, but we
// can't use `beforeunload` as IE fires it on javascript: links.
utils.attachEvent('unload', unload_triggered);
utils.unload_add = function(listener) {
var ref = utils.random_string(8);
on_unload[ref] = listener;
if (after_unload) {
utils.delay(trigger_unload_callbacks);
}
return ref;
};
utils.unload_del = function(ref) {
if (ref in on_unload)
delete on_unload[ref];
};
utils.createIframe = function (iframe_url, error_callback) {
var iframe = _document.createElement('iframe');
var tref, unload_ref;
var unattach = function() {
clearTimeout(tref);
// Explorer had problems with that.
try {iframe.onload = null;} catch (x) {}
iframe.onerror = null;
};
var cleanup = function() {
if (iframe) {
unattach();
// This timeout makes chrome fire onbeforeunload event
// within iframe. Without the timeout it goes straight to
// onunload.
setTimeout(function() {
if(iframe) {
iframe.parentNode.removeChild(iframe);
}
iframe = null;
}, 0);
utils.unload_del(unload_ref);
}
};
var onerror = function(r) {
if (iframe) {
cleanup();
error_callback(r);
}
};
var post = function(msg, origin) {
try {
// When the iframe is not loaded, IE raises an exception
// on 'contentWindow'.
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(msg, origin);
}
} catch (x) {};
};
iframe.src = iframe_url;
iframe.style.display = 'none';
iframe.style.position = 'absolute';
iframe.onerror = function(){onerror('onerror');};
iframe.onload = function() {
// `onload` is triggered before scripts on the iframe are
// executed. Give it few seconds to actually load stuff.
clearTimeout(tref);
tref = setTimeout(function(){onerror('onload timeout');}, 2000);
};
_document.body.appendChild(iframe);
tref = setTimeout(function(){onerror('timeout');}, 15000);
unload_ref = utils.unload_add(cleanup);
return {
post: post,
cleanup: cleanup,
loaded: unattach
};
};
utils.createHtmlfile = function (iframe_url, error_callback) {
var doc = new ActiveXObject('htmlfile');
var tref, unload_ref;
var iframe;
var unattach = function() {
clearTimeout(tref);
};
var cleanup = function() {
if (doc) {
unattach();
utils.unload_del(unload_ref);
iframe.parentNode.removeChild(iframe);
iframe = doc = null;
CollectGarbage();
}
};
var onerror = function(r) {
if (doc) {
cleanup();
error_callback(r);
}
};
var post = function(msg, origin) {
try {
// When the iframe is not loaded, IE raises an exception
// on 'contentWindow'.
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(msg, origin);
}
} catch (x) {};
};
doc.open();
doc.write('<html><s' + 'cript>' +
'document.domain="' + document.domain + '";' +
'</s' + 'cript></html>');
doc.close();
doc.parentWindow[WPrefix] = _window[WPrefix];
var c = doc.createElement('div');
doc.body.appendChild(c);
iframe = doc.createElement('iframe');
c.appendChild(iframe);
iframe.src = iframe_url;
tref = setTimeout(function(){onerror('timeout');}, 15000);
unload_ref = utils.unload_add(cleanup);
return {
post: post,
cleanup: cleanup,
loaded: unattach
};
};
// [*] End of lib/dom.js
// [*] Including lib/dom2.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var AbstractXHRObject = function(){};
AbstractXHRObject.prototype = new EventEmitter(['chunk', 'finish']);
AbstractXHRObject.prototype._start = function(method, url, payload, opts) {
var that = this;
try {
that.xhr = new XMLHttpRequest();
} catch(x) {};
if (!that.xhr) {
try {
that.xhr = new _window.ActiveXObject('Microsoft.XMLHTTP');
} catch(x) {};
}
if (_window.ActiveXObject || _window.XDomainRequest) {
// IE8 caches even POSTs
url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date);
}
// Explorer tends to keep connection open, even after the
// tab gets closed: http://bugs.jquery.com/ticket/5280
that.unload_ref = utils.unload_add(function(){that._cleanup(true);});
try {
that.xhr.open(method, url, true);
} catch(e) {
// IE raises an exception on wrong port.
that.emit('finish', 0, '');
that._cleanup();
return;
};
if (!opts || !opts.no_credentials) {
// Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest :
// "This never affects same-site requests."
that.xhr.withCredentials = 'true';
}
if (opts && opts.headers) {
for(var key in opts.headers) {
that.xhr.setRequestHeader(key, opts.headers[key]);
}
}
that.xhr.onreadystatechange = function() {
if (that.xhr) {
var x = that.xhr;
switch (x.readyState) {
case 3:
// IE doesn't like peeking into responseText or status
// on Microsoft.XMLHTTP and readystate=3
try {
var status = x.status;
var text = x.responseText;
} catch (x) {};
// IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
if (status === 1223) status = 204;
// IE does return readystate == 3 for 404 answers.
if (text && text.length > 0) {
that.emit('chunk', status, text);
}
break;
case 4:
var status = x.status;
// IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
if (status === 1223) status = 204;
that.emit('finish', status, x.responseText);
that._cleanup(false);
break;
}
}
};
that.xhr.send(payload);
};
AbstractXHRObject.prototype._cleanup = function(abort) {
var that = this;
if (!that.xhr) return;
utils.unload_del(that.unload_ref);
// IE needs this field to be a function
that.xhr.onreadystatechange = function(){};
if (abort) {
try {
that.xhr.abort();
} catch(x) {};
}
that.unload_ref = that.xhr = null;
};
AbstractXHRObject.prototype.close = function() {
var that = this;
that.nuke();
that._cleanup(true);
};
var XHRCorsObject = utils.XHRCorsObject = function() {
var that = this, args = arguments;
utils.delay(function(){that._start.apply(that, args);});
};
XHRCorsObject.prototype = new AbstractXHRObject();
var XHRLocalObject = utils.XHRLocalObject = function(method, url, payload) {
var that = this;
utils.delay(function(){
that._start(method, url, payload);
});
};
XHRLocalObject.prototype = new AbstractXHRObject();
// References:
// http://ajaxian.com/archives/100-line-ajax-wrapper
// http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx
var XDRObject = utils.XDRObject = function(method, url, payload) {
var that = this;
utils.delay(function(){that._start(method, url, payload);});
};
XDRObject.prototype = new EventEmitter(['chunk', 'finish']);
XDRObject.prototype._start = function(method, url, payload) {
var that = this;
var xdr = new XDomainRequest();
// IE caches even POSTs
url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date);
var onerror = xdr.ontimeout = xdr.onerror = function() {
that.emit('finish', 0, '');
that._cleanup(false);
};
xdr.onprogress = function() {
that.emit('chunk', 200, xdr.responseText);
};
xdr.onload = function() {
that.emit('finish', 200, xdr.responseText);
that._cleanup(false);
};
that.xdr = xdr;
that.unload_ref = utils.unload_add(function(){that._cleanup(true);});
try {
// Fails with AccessDenied if port number is bogus
that.xdr.open(method, url);
that.xdr.send(payload);
} catch(x) {
onerror();
}
};
XDRObject.prototype._cleanup = function(abort) {
var that = this;
if (!that.xdr) return;
utils.unload_del(that.unload_ref);
that.xdr.ontimeout = that.xdr.onerror = that.xdr.onprogress =
that.xdr.onload = null;
if (abort) {
try {
that.xdr.abort();
} catch(x) {};
}
that.unload_ref = that.xdr = null;
};
XDRObject.prototype.close = function() {
var that = this;
that.nuke();
that._cleanup(true);
};
// 1. Is natively via XHR
// 2. Is natively via XDR
// 3. Nope, but postMessage is there so it should work via the Iframe.
// 4. Nope, sorry.
utils.isXHRCorsCapable = function() {
// CORS doesn't work if page is served from file://
if (_document.domain) {
if (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()) {
return 1;
}
if (_window.XDomainRequest) {
return 2;
}
}
if (IframeTransport.enabled()) {
return 3;
}
return 4;
};
// [*] End of lib/dom2.js
// [*] Including lib/sockjs.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var SockJS = function(url, dep_protocols_whitelist, options) {
if (this === _window) {
// makes `new` optional
return new SockJS(url, dep_protocols_whitelist, options);
}
var that = this, protocols_whitelist;
that._options = {devel: false, debug: false, protocols_whitelist: [],
info: undefined, rtt: undefined};
if (options) {
utils.objectExtend(that._options, options);
}
that._base_url = utils.amendUrl(url);
that._server = that._options.server || utils.random_number_string(1000);
if (that._options.protocols_whitelist &&
that._options.protocols_whitelist.length) {
protocols_whitelist = that._options.protocols_whitelist;
} else {
// Deprecated API
if (typeof dep_protocols_whitelist === 'string' &&
dep_protocols_whitelist.length > 0) {
protocols_whitelist = [dep_protocols_whitelist];
} else if (utils.isArray(dep_protocols_whitelist)) {
protocols_whitelist = dep_protocols_whitelist
} else {
protocols_whitelist = null;
}
if (protocols_whitelist) {
that._debug('Deprecated API: Use "protocols_whitelist" option ' +
'instead of supplying protocol list as a second ' +
'parameter to SockJS constructor.');
}
}
that._protocols = [];
that.protocol = null;
that.readyState = SockJS.CONNECTING;
that._ir = createInfoReceiver(that._base_url);
that._ir.onfinish = function(info, rtt) {
that._ir = null;
if (info) {
if (that._options.info) {
// Override if user supplies the option
info = utils.objectExtend(info, that._options.info);
}
if (that._options.rtt) {
rtt = that._options.rtt;
}
that._applyInfo(info, rtt, protocols_whitelist);
that._didClose();
} else {
that._didClose(1002, 'Can\'t connect to server', true);
}
};
};
// Inheritance
SockJS.prototype = new REventTarget();
SockJS.version = "0.3.4";
SockJS.CONNECTING = 0;
SockJS.OPEN = 1;
SockJS.CLOSING = 2;
SockJS.CLOSED = 3;
SockJS.prototype._debug = function() {
if (this._options.debug)
utils.log.apply(utils, arguments);
};
SockJS.prototype._dispatchOpen = function() {
var that = this;
if (that.readyState === SockJS.CONNECTING) {
if (that._transport_tref) {
clearTimeout(that._transport_tref);
that._transport_tref = null;
}
that.readyState = SockJS.OPEN;
that.dispatchEvent(new SimpleEvent("open"));
} else {
// The server might have been restarted, and lost track of our
// connection.
that._didClose(1006, "Server lost session");
}
};
SockJS.prototype._dispatchMessage = function(data) {
var that = this;
if (that.readyState !== SockJS.OPEN)
return;
that.dispatchEvent(new SimpleEvent("message", {data: data}));
};
SockJS.prototype._dispatchHeartbeat = function(data) {
var that = this;
if (that.readyState !== SockJS.OPEN)
return;
that.dispatchEvent(new SimpleEvent('heartbeat', {}));
};
SockJS.prototype._didClose = function(code, reason, force) {
var that = this;
if (that.readyState !== SockJS.CONNECTING &&
that.readyState !== SockJS.OPEN &&
that.readyState !== SockJS.CLOSING)
throw new Error('INVALID_STATE_ERR');
if (that._ir) {
that._ir.nuke();
that._ir = null;
}
if (that._transport) {
that._transport.doCleanup();
that._transport = null;
}
var close_event = new SimpleEvent("close", {
code: code,
reason: reason,
wasClean: utils.userSetCode(code)});
if (!utils.userSetCode(code) &&
that.readyState === SockJS.CONNECTING && !force) {
if (that._try_next_protocol(close_event)) {
return;
}
close_event = new SimpleEvent("close", {code: 2000,
reason: "All transports failed",
wasClean: false,
last_event: close_event});
}
that.readyState = SockJS.CLOSED;
utils.delay(function() {
that.dispatchEvent(close_event);
});
};
SockJS.prototype._didMessage = function(data) {
var that = this;
var type = data.slice(0, 1);
switch(type) {
case 'o':
that._dispatchOpen();
break;
case 'a':
var payload = JSON.parse(data.slice(1) || '[]');
for(var i=0; i < payload.length; i++){
that._dispatchMessage(payload[i]);
}
break;
case 'm':
var payload = JSON.parse(data.slice(1) || 'null');
that._dispatchMessage(payload);
break;
case 'c':
var payload = JSON.parse(data.slice(1) || '[]');
that._didClose(payload[0], payload[1]);
break;
case 'h':
that._dispatchHeartbeat();
break;
}
};
SockJS.prototype._try_next_protocol = function(close_event) {
var that = this;
if (that.protocol) {
that._debug('Closed transport:', that.protocol, ''+close_event);
that.protocol = null;
}
if (that._transport_tref) {
clearTimeout(that._transport_tref);
that._transport_tref = null;
}
while(1) {
var protocol = that.protocol = that._protocols.shift();
if (!protocol) {
return false;
}
// Some protocols require access to `body`, what if were in
// the `head`?
if (SockJS[protocol] &&
SockJS[protocol].need_body === true &&
(!_document.body ||
(typeof _document.readyState !== 'undefined'
&& _document.readyState !== 'complete'))) {
that._protocols.unshift(protocol);
that.protocol = 'waiting-for-load';
utils.attachEvent('load', function(){
that._try_next_protocol();
});
return true;
}
if (!SockJS[protocol] ||
!SockJS[protocol].enabled(that._options)) {
that._debug('Skipping transport:', protocol);
} else {
var roundTrips = SockJS[protocol].roundTrips || 1;
var to = ((that._options.rto || 0) * roundTrips) || 5000;
that._transport_tref = utils.delay(to, function() {
if (that.readyState === SockJS.CONNECTING) {
// I can't understand how it is possible to run
// this timer, when the state is CLOSED, but
// apparently in IE everythin is possible.
that._didClose(2007, "Transport timeouted");
}
});
var connid = utils.random_string(8);
var trans_url = that._base_url + '/' + that._server + '/' + connid;
that._debug('Opening transport:', protocol, ' url:'+trans_url,
' RTO:'+that._options.rto);
that._transport = new SockJS[protocol](that, trans_url,
that._base_url);
return true;
}
}
};
SockJS.prototype.close = function(code, reason) {
var that = this;
if (code && !utils.userSetCode(code))
throw new Error("INVALID_ACCESS_ERR");
if(that.readyState !== SockJS.CONNECTING &&
that.readyState !== SockJS.OPEN) {
return false;
}
that.readyState = SockJS.CLOSING;
that._didClose(code || 1000, reason || "Normal closure");
return true;
};
SockJS.prototype.send = function(data) {
var that = this;
if (that.readyState === SockJS.CONNECTING)
throw new Error('INVALID_STATE_ERR');
if (that.readyState === SockJS.OPEN) {
that._transport.doSend(utils.quote('' + data));
}
return true;
};
SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) {
var that = this;
that._options.info = info;
that._options.rtt = rtt;
that._options.rto = utils.countRTO(rtt);
that._options.info.null_origin = !_document.domain;
var probed = utils.probeProtocols();
that._protocols = utils.detectProtocols(probed, protocols_whitelist, info);
};
// [*] End of lib/sockjs.js
// [*] Including lib/trans-websocket.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var WebSocketTransport = SockJS.websocket = function(ri, trans_url) {
var that = this;
var url = trans_url + '/websocket';
if (url.slice(0, 5) === 'https') {
url = 'wss' + url.slice(5);
} else {
url = 'ws' + url.slice(4);
}
that.ri = ri;
that.url = url;
var Constructor = _window.WebSocket || _window.MozWebSocket;
that.ws = new Constructor(that.url);
that.ws.onmessage = function(e) {
that.ri._didMessage(e.data);
};
that.ws.onerror = function() {
ri._didMessage(utils.closeFrame(1006, "WebSocket connection broken"));
};
// Firefox has an interesting bug. If a websocket connection is
// created after onunload, it stays alive even when user
// navigates away from the page. In such situation let's lie -
// let's not open the ws connection at all. See:
// https://github.com/sockjs/sockjs-client/issues/28
// https://bugzilla.mozilla.org/show_bug.cgi?id=696085
that.unload_ref = utils.unload_add(function(){that.ws.close()});
that.ws.onclose = function() {
that.ri._didMessage(utils.closeFrame(1006, "WebSocket connection broken"));
};
};
WebSocketTransport.prototype.doSend = function(data) {
this.ws.send('[' + data + ']');
};
WebSocketTransport.prototype.doCleanup = function() {
var that = this;
var ws = that.ws;
if (ws) {
ws.onmessage = ws.onclose = ws.onerror = null;
ws.close();
utils.unload_del(that.unload_ref);
that.unload_ref = that.ri = that.ws = null;
}
};
WebSocketTransport.enabled = function() {
return !!(_window.WebSocket || _window.MozWebSocket);
};
// In theory, ws should require 1 round trip. But in chrome, this is
// not very stable over SSL. Most likely a ws connection requires a
// separate SSL connection, in which case 2 round trips are an
// absolute minumum.
WebSocketTransport.roundTrips = 2;
// [*] End of lib/trans-websocket.js
// [*] Including lib/trans-sender.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var BufferedSender = function() {};
BufferedSender.prototype.send_constructor = function(sender) {
var that = this;
that.send_buffer = [];
that.sender = sender;
};
BufferedSender.prototype.doSend = function(message) {
var that = this;
that.send_buffer.push(message);
if (!that.send_stop) {
that.send_schedule();
}
};
// For polling transports in a situation when in the message callback,
// new message is being send. If the sending connection was started
// before receiving one, it is possible to saturate the network and
// timeout due to the lack of receiving socket. To avoid that we delay
// sending messages by some small time, in order to let receiving
// connection be started beforehand. This is only a halfmeasure and
// does not fix the big problem, but it does make the tests go more
// stable on slow networks.
BufferedSender.prototype.send_schedule_wait = function() {
var that = this;
var tref;
that.send_stop = function() {
that.send_stop = null;
clearTimeout(tref);
};
tref = utils.delay(25, function() {
that.send_stop = null;
that.send_schedule();
});
};
BufferedSender.prototype.send_schedule = function() {
var that = this;
if (that.send_buffer.length > 0) {
var payload = '[' + that.send_buffer.join(',') + ']';
that.send_stop = that.sender(that.trans_url, payload, function(success, abort_reason) {
that.send_stop = null;
if (success === false) {
that.ri._didClose(1006, 'Sending error ' + abort_reason);
} else {
that.send_schedule_wait();
}
});
that.send_buffer = [];
}
};
BufferedSender.prototype.send_destructor = function() {
var that = this;
if (that._send_stop) {
that._send_stop();
}
that._send_stop = null;
};
var jsonPGenericSender = function(url, payload, callback) {
var that = this;
if (!('_send_form' in that)) {
var form = that._send_form = _document.createElement('form');
var area = that._send_area = _document.createElement('textarea');
area.name = 'd';
form.style.display = 'none';
form.style.position = 'absolute';
form.method = 'POST';
form.enctype = 'application/x-www-form-urlencoded';
form.acceptCharset = "UTF-8";
form.appendChild(area);
_document.body.appendChild(form);
}
var form = that._send_form;
var area = that._send_area;
var id = 'a' + utils.random_string(8);
form.target = id;
form.action = url + '/jsonp_send?i=' + id;
var iframe;
try {
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
iframe = _document.createElement('<iframe name="'+ id +'">');
} catch(x) {
iframe = _document.createElement('iframe');
iframe.name = id;
}
iframe.id = id;
form.appendChild(iframe);
iframe.style.display = 'none';
try {
area.value = payload;
} catch(e) {
utils.log('Your browser is seriously broken. Go home! ' + e.message);
}
form.submit();
var completed = function(e) {
if (!iframe.onerror) return;
iframe.onreadystatechange = iframe.onerror = iframe.onload = null;
// Opera mini doesn't like if we GC iframe
// immediately, thus this timeout.
utils.delay(500, function() {
iframe.parentNode.removeChild(iframe);
iframe = null;
});
area.value = '';
// It is not possible to detect if the iframe succeeded or
// failed to submit our form.
callback(true);
};
iframe.onerror = iframe.onload = completed;
iframe.onreadystatechange = function(e) {
if (iframe.readyState == 'complete') completed();
};
return completed;
};
var createAjaxSender = function(AjaxObject) {
return function(url, payload, callback) {
var xo = new AjaxObject('POST', url + '/xhr_send', payload);
xo.onfinish = function(status, text) {
callback(status === 200 || status === 204,
'http status ' + status);
};
return function(abort_reason) {
callback(false, abort_reason);
};
};
};
// [*] End of lib/trans-sender.js
// [*] Including lib/trans-jsonp-receiver.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
// Parts derived from Socket.io:
// https://github.com/LearnBoost/socket.io/blob/0.6.17/lib/socket.io/transports/jsonp-polling.js
// and jQuery-JSONP:
// https://code.google.com/p/jquery-jsonp/source/browse/trunk/core/jquery.jsonp.js
var jsonPGenericReceiver = function(url, callback) {
var tref;
var script = _document.createElement('script');
var script2; // Opera synchronous load trick.
var close_script = function(frame) {
if (script2) {
script2.parentNode.removeChild(script2);
script2 = null;
}
if (script) {
clearTimeout(tref);
// Unfortunately, you can't really abort script loading of
// the script.
script.parentNode.removeChild(script);
script.onreadystatechange = script.onerror =
script.onload = script.onclick = null;
script = null;
callback(frame);
callback = null;
}
};
// IE9 fires 'error' event after orsc or before, in random order.
var loaded_okay = false;
var error_timer = null;
script.id = 'a' + utils.random_string(8);
script.src = url;
script.type = 'text/javascript';
script.charset = 'UTF-8';
script.onerror = function(e) {
if (!error_timer) {
// Delay firing close_script.
error_timer = setTimeout(function() {
if (!loaded_okay) {
close_script(utils.closeFrame(
1006,
"JSONP script loaded abnormally (onerror)"));
}
}, 1000);
}
};
script.onload = function(e) {
close_script(utils.closeFrame(1006, "JSONP script loaded abnormally (onload)"));
};
script.onreadystatechange = function(e) {
if (/loaded|closed/.test(script.readyState)) {
if (script && script.htmlFor && script.onclick) {
loaded_okay = true;
try {
// In IE, actually execute the script.
script.onclick();
} catch (x) {}
}
if (script) {
close_script(utils.closeFrame(1006, "JSONP script loaded abnormally (onreadystatechange)"));
}
}
};
// IE: event/htmlFor/onclick trick.
// One can't rely on proper order for onreadystatechange. In order to
// make sure, set a 'htmlFor' and 'event' properties, so that
// script code will be installed as 'onclick' handler for the
// script object. Later, onreadystatechange, manually execute this
// code. FF and Chrome doesn't work with 'event' and 'htmlFor'
// set. For reference see:
// http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
// Also, read on that about script ordering:
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
if (typeof script.async === 'undefined' && _document.attachEvent) {
// According to mozilla docs, in recent browsers script.async defaults
// to 'true', so we may use it to detect a good browser:
// https://developer.mozilla.org/en/HTML/Element/script
if (!/opera/i.test(navigator.userAgent)) {
// Naively assume we're in IE
try {
script.htmlFor = script.id;
script.event = "onclick";
} catch (x) {}
script.async = true;
} else {
// Opera, second sync script hack
script2 = _document.createElement('script');
script2.text = "try{var a = document.getElementById('"+script.id+"'); if(a)a.onerror();}catch(x){};";
script.async = script2.async = false;
}
}
if (typeof script.async !== 'undefined') {
script.async = true;
}
// Fallback mostly for Konqueror - stupid timer, 35 seconds shall be plenty.
tref = setTimeout(function() {
close_script(utils.closeFrame(1006, "JSONP script loaded abnormally (timeout)"));
}, 35000);
var head = _document.getElementsByTagName('head')[0];
head.insertBefore(script, head.firstChild);
if (script2) {
head.insertBefore(script2, head.firstChild);
}
return close_script;
};
// [*] End of lib/trans-jsonp-receiver.js
// [*] Including lib/trans-jsonp-polling.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
// The simplest and most robust transport, using the well-know cross
// domain hack - JSONP. This transport is quite inefficient - one
// mssage could use up to one http request. But at least it works almost
// everywhere.
// Known limitations:
// o you will get a spinning cursor
// o for Konqueror a dumb timer is needed to detect errors
var JsonPTransport = SockJS['jsonp-polling'] = function(ri, trans_url) {
utils.polluteGlobalNamespace();
var that = this;
that.ri = ri;
that.trans_url = trans_url;
that.send_constructor(jsonPGenericSender);
that._schedule_recv();
};
// Inheritnace
JsonPTransport.prototype = new BufferedSender();
JsonPTransport.prototype._schedule_recv = function() {
var that = this;
var callback = function(data) {
that._recv_stop = null;
if (data) {
// no data - heartbeat;
if (!that._is_closing) {
that.ri._didMessage(data);
}
}
// The message can be a close message, and change is_closing state.
if (!that._is_closing) {
that._schedule_recv();
}
};
that._recv_stop = jsonPReceiverWrapper(that.trans_url + '/jsonp',
jsonPGenericReceiver, callback);
};
JsonPTransport.enabled = function() {
return true;
};
JsonPTransport.need_body = true;
JsonPTransport.prototype.doCleanup = function() {
var that = this;
that._is_closing = true;
if (that._recv_stop) {
that._recv_stop();
}
that.ri = that._recv_stop = null;
that.send_destructor();
};
// Abstract away code that handles global namespace pollution.
var jsonPReceiverWrapper = function(url, constructReceiver, user_callback) {
var id = 'a' + utils.random_string(6);
var url_id = url + '?c=' + escape(WPrefix + '.' + id);
// Unfortunately it is not possible to abort loading of the
// script. We need to keep track of frake close frames.
var aborting = 0;
// Callback will be called exactly once.
var callback = function(frame) {
switch(aborting) {
case 0:
// Normal behaviour - delete hook _and_ emit message.
delete _window[WPrefix][id];
user_callback(frame);
break;
case 1:
// Fake close frame - emit but don't delete hook.
user_callback(frame);
aborting = 2;
break;
case 2:
// Got frame after connection was closed, delete hook, don't emit.
delete _window[WPrefix][id];
break;
}
};
var close_script = constructReceiver(url_id, callback);
_window[WPrefix][id] = close_script;
var stop = function() {
if (_window[WPrefix][id]) {
aborting = 1;
_window[WPrefix][id](utils.closeFrame(1000, "JSONP user aborted read"));
}
};
return stop;
};
// [*] End of lib/trans-jsonp-polling.js
// [*] Including lib/trans-xhr.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var AjaxBasedTransport = function() {};
AjaxBasedTransport.prototype = new BufferedSender();
AjaxBasedTransport.prototype.run = function(ri, trans_url,
url_suffix, Receiver, AjaxObject) {
var that = this;
that.ri = ri;
that.trans_url = trans_url;
that.send_constructor(createAjaxSender(AjaxObject));
that.poll = new Polling(ri, Receiver,
trans_url + url_suffix, AjaxObject);
};
AjaxBasedTransport.prototype.doCleanup = function() {
var that = this;
if (that.poll) {
that.poll.abort();
that.poll = null;
}
};
// xhr-streaming
var XhrStreamingTransport = SockJS['xhr-streaming'] = function(ri, trans_url) {
this.run(ri, trans_url, '/xhr_streaming', XhrReceiver, utils.XHRCorsObject);
};
XhrStreamingTransport.prototype = new AjaxBasedTransport();
XhrStreamingTransport.enabled = function() {
// Support for CORS Ajax aka Ajax2? Opera 12 claims CORS but
// doesn't do streaming.
return (_window.XMLHttpRequest &&
'withCredentials' in new XMLHttpRequest() &&
(!/opera/i.test(navigator.userAgent)));
};
XhrStreamingTransport.roundTrips = 2; // preflight, ajax
// Safari gets confused when a streaming ajax request is started
// before onload. This causes the load indicator to spin indefinetely.
XhrStreamingTransport.need_body = true;
// According to:
// http://stackoverflow.com/questions/1641507/detect-browser-support-for-cross-domain-xmlhttprequests
// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
// xdr-streaming
var XdrStreamingTransport = SockJS['xdr-streaming'] = function(ri, trans_url) {
this.run(ri, trans_url, '/xhr_streaming', XhrReceiver, utils.XDRObject);
};
XdrStreamingTransport.prototype = new AjaxBasedTransport();
XdrStreamingTransport.enabled = function() {
return !!_window.XDomainRequest;
};
XdrStreamingTransport.roundTrips = 2; // preflight, ajax
// xhr-polling
var XhrPollingTransport = SockJS['xhr-polling'] = function(ri, trans_url) {
this.run(ri, trans_url, '/xhr', XhrReceiver, utils.XHRCorsObject);
};
XhrPollingTransport.prototype = new AjaxBasedTransport();
XhrPollingTransport.enabled = XhrStreamingTransport.enabled;
XhrPollingTransport.roundTrips = 2; // preflight, ajax
// xdr-polling
var XdrPollingTransport = SockJS['xdr-polling'] = function(ri, trans_url) {
this.run(ri, trans_url, '/xhr', XhrReceiver, utils.XDRObject);
};
XdrPollingTransport.prototype = new AjaxBasedTransport();
XdrPollingTransport.enabled = XdrStreamingTransport.enabled;
XdrPollingTransport.roundTrips = 2; // preflight, ajax
// [*] End of lib/trans-xhr.js
// [*] Including lib/trans-iframe.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
// Few cool transports do work only for same-origin. In order to make
// them working cross-domain we shall use iframe, served form the
// remote domain. New browsers, have capabilities to communicate with
// cross domain iframe, using postMessage(). In IE it was implemented
// from IE 8+, but of course, IE got some details wrong:
// http://msdn.microsoft.com/en-us/library/cc197015(v=VS.85).aspx
// http://stevesouders.com/misc/test-postmessage.php
var IframeTransport = function() {};
IframeTransport.prototype.i_constructor = function(ri, trans_url, base_url) {
var that = this;
that.ri = ri;
that.origin = utils.getOrigin(base_url);
that.base_url = base_url;
that.trans_url = trans_url;
var iframe_url = base_url + '/iframe.html';
if (that.ri._options.devel) {
iframe_url += '?t=' + (+new Date);
}
that.window_id = utils.random_string(8);
iframe_url += '#' + that.window_id;
that.iframeObj = utils.createIframe(iframe_url, function(r) {
that.ri._didClose(1006, "Unable to load an iframe (" + r + ")");
});
that.onmessage_cb = utils.bind(that.onmessage, that);
utils.attachMessage(that.onmessage_cb);
};
IframeTransport.prototype.doCleanup = function() {
var that = this;
if (that.iframeObj) {
utils.detachMessage(that.onmessage_cb);
try {
// When the iframe is not loaded, IE raises an exception
// on 'contentWindow'.
if (that.iframeObj.iframe.contentWindow) {
that.postMessage('c');
}
} catch (x) {}
that.iframeObj.cleanup();
that.iframeObj = null;
that.onmessage_cb = that.iframeObj = null;
}
};
IframeTransport.prototype.onmessage = function(e) {
var that = this;
if (!utils.isSameOriginUrl(e.origin, that.origin)) return;
var window_id = e.data.slice(0, 8);
var type = e.data.slice(8, 9);
var data = e.data.slice(9);
if (window_id !== that.window_id) return;
switch(type) {
case 's':
that.iframeObj.loaded();
that.postMessage('s', JSON.stringify([SockJS.version, that.protocol, that.trans_url, that.base_url]));
break;
case 't':
that.ri._didMessage(data);
break;
}
};
IframeTransport.prototype.postMessage = function(type, data) {
var that = this;
that.iframeObj.post(that.window_id + type + (data || ''), that.origin);
};
IframeTransport.prototype.doSend = function (message) {
this.postMessage('m', message);
};
IframeTransport.enabled = function() {
// postMessage misbehaves in konqueror 4.6.5 - the messages are delivered with
// huge delay, or not at all.
var konqueror = navigator && navigator.userAgent && navigator.userAgent.indexOf('Konqueror') !== -1;
return ((typeof _window.postMessage === 'function' ||
typeof _window.postMessage === 'object') && (!konqueror));
};
// [*] End of lib/trans-iframe.js
// [*] Including lib/trans-iframe-within.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var curr_window_id;
var postMessage = function (type, data) {
if(parent !== _window) {
parent.postMessage(curr_window_id + type + (data || ''), '*');
} else {
utils.log("Can't postMessage, no parent window.", type, data);
}
};
var FacadeJS = function() {};
FacadeJS.prototype._didClose = function (code, reason) {
postMessage('t', utils.closeFrame(code, reason));
};
FacadeJS.prototype._didMessage = function (frame) {
postMessage('t', frame);
};
FacadeJS.prototype._doSend = function (data) {
this._transport.doSend(data);
};
FacadeJS.prototype._doCleanup = function () {
this._transport.doCleanup();
};
utils.parent_origin = undefined;
SockJS.bootstrap_iframe = function() {
var facade;
curr_window_id = _document.location.hash.slice(1);
var onMessage = function(e) {
if(e.source !== parent) return;
if(typeof utils.parent_origin === 'undefined')
utils.parent_origin = e.origin;
if (e.origin !== utils.parent_origin) return;
var window_id = e.data.slice(0, 8);
var type = e.data.slice(8, 9);
var data = e.data.slice(9);
if (window_id !== curr_window_id) return;
switch(type) {
case 's':
var p = JSON.parse(data);
var version = p[0];
var protocol = p[1];
var trans_url = p[2];
var base_url = p[3];
if (version !== SockJS.version) {
utils.log("Incompatibile SockJS! Main site uses:" +
" \"" + version + "\", the iframe:" +
" \"" + SockJS.version + "\".");
}
if (!utils.flatUrl(trans_url) || !utils.flatUrl(base_url)) {
utils.log("Only basic urls are supported in SockJS");
return;
}
if (!utils.isSameOriginUrl(trans_url) ||
!utils.isSameOriginUrl(base_url)) {
utils.log("Can't connect to different domain from within an " +
"iframe. (" + JSON.stringify([_window.location.href, trans_url, base_url]) +
")");
return;
}
facade = new FacadeJS();
facade._transport = new FacadeJS[protocol](facade, trans_url, base_url);
break;
case 'm':
facade._doSend(data);
break;
case 'c':
if (facade)
facade._doCleanup();
facade = null;
break;
}
};
// alert('test ticker');
// facade = new FacadeJS();
// facade._transport = new FacadeJS['w-iframe-xhr-polling'](facade, 'http://host.com:9999/ticker/12/basd');
utils.attachMessage(onMessage);
// Start
postMessage('s');
};
// [*] End of lib/trans-iframe-within.js
// [*] Including lib/info.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var InfoReceiver = function(base_url, AjaxObject) {
var that = this;
utils.delay(function(){that.doXhr(base_url, AjaxObject);});
};
InfoReceiver.prototype = new EventEmitter(['finish']);
InfoReceiver.prototype.doXhr = function(base_url, AjaxObject) {
var that = this;
var t0 = (new Date()).getTime();
var xo = new AjaxObject('GET', base_url + '/info');
var tref = utils.delay(8000,
function(){xo.ontimeout();});
xo.onfinish = function(status, text) {
clearTimeout(tref);
tref = null;
if (status === 200) {
var rtt = (new Date()).getTime() - t0;
var info = JSON.parse(text);
if (typeof info !== 'object') info = {};
that.emit('finish', info, rtt);
} else {
that.emit('finish');
}
};
xo.ontimeout = function() {
xo.close();
that.emit('finish');
};
};
var InfoReceiverIframe = function(base_url) {
var that = this;
var go = function() {
var ifr = new IframeTransport();
ifr.protocol = 'w-iframe-info-receiver';
var fun = function(r) {
if (typeof r === 'string' && r.substr(0,1) === 'm') {
var d = JSON.parse(r.substr(1));
var info = d[0], rtt = d[1];
that.emit('finish', info, rtt);
} else {
that.emit('finish');
}
ifr.doCleanup();
ifr = null;
};
var mock_ri = {
_options: {},
_didClose: fun,
_didMessage: fun
};
ifr.i_constructor(mock_ri, base_url, base_url);
}
if(!_document.body) {
utils.attachEvent('load', go);
} else {
go();
}
};
InfoReceiverIframe.prototype = new EventEmitter(['finish']);
var InfoReceiverFake = function() {
// It may not be possible to do cross domain AJAX to get the info
// data, for example for IE7. But we want to run JSONP, so let's
// fake the response, with rtt=2s (rto=6s).
var that = this;
utils.delay(function() {
that.emit('finish', {}, 2000);
});
};
InfoReceiverFake.prototype = new EventEmitter(['finish']);
var createInfoReceiver = function(base_url) {
if (utils.isSameOriginUrl(base_url)) {
// If, for some reason, we have SockJS locally - there's no
// need to start up the complex machinery. Just use ajax.
return new InfoReceiver(base_url, utils.XHRLocalObject);
}
switch (utils.isXHRCorsCapable()) {
case 1:
return new InfoReceiver(base_url, utils.XHRLocalObject);
case 2:
return new InfoReceiver(base_url, utils.XDRObject);
case 3:
// Opera
return new InfoReceiverIframe(base_url);
default:
// IE 7
return new InfoReceiverFake();
};
};
var WInfoReceiverIframe = FacadeJS['w-iframe-info-receiver'] = function(ri, _trans_url, base_url) {
var ir = new InfoReceiver(base_url, utils.XHRLocalObject);
ir.onfinish = function(info, rtt) {
ri._didMessage('m'+JSON.stringify([info, rtt]));
ri._didClose();
}
};
WInfoReceiverIframe.prototype.doCleanup = function() {};
// [*] End of lib/info.js
// [*] Including lib/trans-iframe-eventsource.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var EventSourceIframeTransport = SockJS['iframe-eventsource'] = function () {
var that = this;
that.protocol = 'w-iframe-eventsource';
that.i_constructor.apply(that, arguments);
};
EventSourceIframeTransport.prototype = new IframeTransport();
EventSourceIframeTransport.enabled = function () {
return ('EventSource' in _window) && IframeTransport.enabled();
};
EventSourceIframeTransport.need_body = true;
EventSourceIframeTransport.roundTrips = 3; // html, javascript, eventsource
// w-iframe-eventsource
var EventSourceTransport = FacadeJS['w-iframe-eventsource'] = function(ri, trans_url) {
this.run(ri, trans_url, '/eventsource', EventSourceReceiver, utils.XHRLocalObject);
}
EventSourceTransport.prototype = new AjaxBasedTransport();
// [*] End of lib/trans-iframe-eventsource.js
// [*] Including lib/trans-iframe-xhr-polling.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var XhrPollingIframeTransport = SockJS['iframe-xhr-polling'] = function () {
var that = this;
that.protocol = 'w-iframe-xhr-polling';
that.i_constructor.apply(that, arguments);
};
XhrPollingIframeTransport.prototype = new IframeTransport();
XhrPollingIframeTransport.enabled = function () {
return _window.XMLHttpRequest && IframeTransport.enabled();
};
XhrPollingIframeTransport.need_body = true;
XhrPollingIframeTransport.roundTrips = 3; // html, javascript, xhr
// w-iframe-xhr-polling
var XhrPollingITransport = FacadeJS['w-iframe-xhr-polling'] = function(ri, trans_url) {
this.run(ri, trans_url, '/xhr', XhrReceiver, utils.XHRLocalObject);
};
XhrPollingITransport.prototype = new AjaxBasedTransport();
// [*] End of lib/trans-iframe-xhr-polling.js
// [*] Including lib/trans-iframe-htmlfile.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
// This transport generally works in any browser, but will cause a
// spinning cursor to appear in any browser other than IE.
// We may test this transport in all browsers - why not, but in
// production it should be only run in IE.
var HtmlFileIframeTransport = SockJS['iframe-htmlfile'] = function () {
var that = this;
that.protocol = 'w-iframe-htmlfile';
that.i_constructor.apply(that, arguments);
};
// Inheritance.
HtmlFileIframeTransport.prototype = new IframeTransport();
HtmlFileIframeTransport.enabled = function() {
return IframeTransport.enabled();
};
HtmlFileIframeTransport.need_body = true;
HtmlFileIframeTransport.roundTrips = 3; // html, javascript, htmlfile
// w-iframe-htmlfile
var HtmlFileTransport = FacadeJS['w-iframe-htmlfile'] = function(ri, trans_url) {
this.run(ri, trans_url, '/htmlfile', HtmlfileReceiver, utils.XHRLocalObject);
};
HtmlFileTransport.prototype = new AjaxBasedTransport();
// [*] End of lib/trans-iframe-htmlfile.js
// [*] Including lib/trans-polling.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var Polling = function(ri, Receiver, recv_url, AjaxObject) {
var that = this;
that.ri = ri;
that.Receiver = Receiver;
that.recv_url = recv_url;
that.AjaxObject = AjaxObject;
that._scheduleRecv();
};
Polling.prototype._scheduleRecv = function() {
var that = this;
var poll = that.poll = new that.Receiver(that.recv_url, that.AjaxObject);
var msg_counter = 0;
poll.onmessage = function(e) {
msg_counter += 1;
that.ri._didMessage(e.data);
};
poll.onclose = function(e) {
that.poll = poll = poll.onmessage = poll.onclose = null;
if (!that.poll_is_closing) {
if (e.reason === 'permanent') {
that.ri._didClose(1006, 'Polling error (' + e.reason + ')');
} else {
that._scheduleRecv();
}
}
};
};
Polling.prototype.abort = function() {
var that = this;
that.poll_is_closing = true;
if (that.poll) {
that.poll.abort();
}
};
// [*] End of lib/trans-polling.js
// [*] Including lib/trans-receiver-eventsource.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var EventSourceReceiver = function(url) {
var that = this;
var es = new EventSource(url);
es.onmessage = function(e) {
that.dispatchEvent(new SimpleEvent('message',
{'data': unescape(e.data)}));
};
that.es_close = es.onerror = function(e, abort_reason) {
// ES on reconnection has readyState = 0 or 1.
// on network error it's CLOSED = 2
var reason = abort_reason ? 'user' :
(es.readyState !== 2 ? 'network' : 'permanent');
that.es_close = es.onmessage = es.onerror = null;
// EventSource reconnects automatically.
es.close();
es = null;
// Safari and chrome < 15 crash if we close window before
// waiting for ES cleanup. See:
// https://code.google.com/p/chromium/issues/detail?id=89155
utils.delay(200, function() {
that.dispatchEvent(new SimpleEvent('close', {reason: reason}));
});
};
};
EventSourceReceiver.prototype = new REventTarget();
EventSourceReceiver.prototype.abort = function() {
var that = this;
if (that.es_close) {
that.es_close({}, true);
}
};
// [*] End of lib/trans-receiver-eventsource.js
// [*] Including lib/trans-receiver-htmlfile.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var _is_ie_htmlfile_capable;
var isIeHtmlfileCapable = function() {
if (_is_ie_htmlfile_capable === undefined) {
if ('ActiveXObject' in _window) {
try {
_is_ie_htmlfile_capable = !!new ActiveXObject('htmlfile');
} catch (x) {}
} else {
_is_ie_htmlfile_capable = false;
}
}
return _is_ie_htmlfile_capable;
};
var HtmlfileReceiver = function(url) {
var that = this;
utils.polluteGlobalNamespace();
that.id = 'a' + utils.random_string(6, 26);
url += ((url.indexOf('?') === -1) ? '?' : '&') +
'c=' + escape(WPrefix + '.' + that.id);
var constructor = isIeHtmlfileCapable() ?
utils.createHtmlfile : utils.createIframe;
var iframeObj;
_window[WPrefix][that.id] = {
start: function () {
iframeObj.loaded();
},
message: function (data) {
that.dispatchEvent(new SimpleEvent('message', {'data': data}));
},
stop: function () {
that.iframe_close({}, 'network');
}
};
that.iframe_close = function(e, abort_reason) {
iframeObj.cleanup();
that.iframe_close = iframeObj = null;
delete _window[WPrefix][that.id];
that.dispatchEvent(new SimpleEvent('close', {reason: abort_reason}));
};
iframeObj = constructor(url, function(e) {
that.iframe_close({}, 'permanent');
});
};
HtmlfileReceiver.prototype = new REventTarget();
HtmlfileReceiver.prototype.abort = function() {
var that = this;
if (that.iframe_close) {
that.iframe_close({}, 'user');
}
};
// [*] End of lib/trans-receiver-htmlfile.js
// [*] Including lib/trans-receiver-xhr.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
var XhrReceiver = function(url, AjaxObject) {
var that = this;
var buf_pos = 0;
that.xo = new AjaxObject('POST', url, null);
that.xo.onchunk = function(status, text) {
if (status !== 200) return;
while (1) {
var buf = text.slice(buf_pos);
var p = buf.indexOf('\n');
if (p === -1) break;
buf_pos += p+1;
var msg = buf.slice(0, p);
that.dispatchEvent(new SimpleEvent('message', {data: msg}));
}
};
that.xo.onfinish = function(status, text) {
that.xo.onchunk(status, text);
that.xo = null;
var reason = status === 200 ? 'network' : 'permanent';
that.dispatchEvent(new SimpleEvent('close', {reason: reason}));
}
};
XhrReceiver.prototype = new REventTarget();
XhrReceiver.prototype.abort = function() {
var that = this;
if (that.xo) {
that.xo.close();
that.dispatchEvent(new SimpleEvent('close', {reason: 'user'}));
that.xo = null;
}
};
// [*] End of lib/trans-receiver-xhr.js
// [*] Including lib/test-hooks.js
/*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2011-2012 VMware, Inc.
*
* For the license see COPYING.
* ***** END LICENSE BLOCK *****
*/
// For testing
SockJS.getUtils = function(){
return utils;
};
SockJS.getIframeTransport = function(){
return IframeTransport;
};
// [*] End of lib/test-hooks.js
return SockJS;
})();
if ('_sockjs_onload' in window) setTimeout(_sockjs_onload, 1);
// AMD compliance
if (typeof define === 'function' && define.amd) {
define('sockjs', [], function(){return SockJS;});
}
// [*] End of lib/index.js
// [*] End of lib/all.js
})(this["Primus"]);
(function PrimusLibraryWrap(Primus) {;(function (Primus, undefined) {
function spark(Spark, Emitter) {
'use strict';
/**
* `Primus#initialise` reference.
*/
var initialise = Spark.prototype.initialise;
/**
* Initialise the Primus and setup all
* parsers and internal listeners.
*
* @api private
*/
Spark.prototype.initialise = function init() {
if (!this.emitter) this.emitter = new Emitter(this);
if (!this.__initialise) initialise.apply(this, arguments);
};
// Extend the Spark to add the send method if Spark.readable
// is not supported then we set the method on the prototype instead.
if (!Spark.readable) return Spark.prototype.send = send;
if (!Spark.prototype.send) Spark.readable('send', send);
/**
* Emits to this Spark.
*
* @param {String} ev The event.
* @param {Mixed} [data] The data to broadcast.
* @param {Function} [fn] The callback function.
* @return {Socket} this
* @api public
*/
function send(ev, data, fn) {
// ignore newListener event to avoid this error in node 0.8
// https://github.com/cayasso/primus-emitter/issues/3
if (/^(newListener|removeListener)/.test(ev)) return this;
this.emitter.send.apply(this.emitter, arguments);
return this;
}
}
function emitter() {
'use strict';
/**
* Event packets.
*/
var packets = {
EVENT: 0,
ACK: 1
};
// shortcut to slice
var slice = [].slice;
/**
* Initialize a new `Emitter`.
*
* @api public
*/
function Emitter(conn) {
if (!(this instanceof Emitter)) return new Emitter(conn);
this.ids = 1;
this.acks = {};
this.conn = conn;
if (this.conn) this.bind();
}
/**
* Bind `Emitter` events.
*
* @return {Emitter} self
* @api private
*/
Emitter.prototype.bind = function bind() {
var em = this;
this.conn.on('data', function ondata(data) {
em.ondata.call(em, data);
});
return this;
};
/**
* Called with incoming transport data.
*
* @param {Object} packet
* @return {Emitter} self
* @api private
*/
Emitter.prototype.ondata = function ondata(packet) {
switch (packet.type) {
case packets.EVENT:
this.onevent(packet);
break;
case packets.ACK:
this.onack(packet);
break;
}
};
/**
* Send a message to client.
*
* @return {Emitter} self
* @api public
*/
Emitter.prototype.send = function send() {
var args = slice.call(arguments);
this.conn.write(this.packet(args));
return this;
};
/**
* Prepare packet for emitting.
*
* @param {Array} arguments
* @return {Object} packet
* @api private
*/
Emitter.prototype.packet = function pack(args) {
var packet = { type: packets.EVENT, data: args };
// access last argument to see if it's an ACK callback
if ('function' === typeof args[args.length - 1]) {
var id = this.ids++;
if (this.acks) {
this.acks[id] = args.pop();
packet.id = id;
}
}
return packet;
};
/**
* Called upon event packet.
*
* @param {Object} packet object
* @api private
*/
Emitter.prototype.onevent = function onevent(packet) {
var args = packet.data || [];
if (packet.id) {
args.push(this.ack(packet.id));
}
if (this.conn.reserved(args[0])) return this;
this.conn.emit.apply(this.conn, args);
return this;
};
/**
* Produces an ack callback to emit with an event.
*
* @param {Number} packet id
* @return {Function}
* @api private
*/
Emitter.prototype.ack = function ack(id) {
var conn = this.conn;
var sent = false;
return function(){
if (sent) return; // prevent double callbacks
conn.write({
id: id,
type: packets.ACK,
data: slice.call(arguments)
});
};
};
/**
* Called upon ack packet.
*
* @return {Emitter} self
* @api private
*/
Emitter.prototype.onack = function onack(packet) {
var ack = this.acks[packet.id];
if ('function' === typeof ack) {
ack.apply(this, packet.data);
delete this.acks[packet.id];
} else {
console.log('bad ack %s', packet.id);
}
return this;
};
// Expose packets
Emitter.packets = packets;
return Emitter;
}
if (undefined === Primus) return;
Primus.$ = Primus.$ || {};
Primus.$.emitter = {};
Primus.$.emitter.spark = spark;
Primus.$.emitter.emitter = emitter;
spark(Primus, emitter());
})(Primus);})(this["Primus"]);