Place to store the code and config used for the next-Iterations live event. https://iterations.space/live/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

390 lines
12 KiB

var EngineioTools = {
ReconnectingSocket: function ReconnectingSocket(server_uri, socket_options) {
var connected = false;
var is_reconnecting = false;
var reconnect_delay = 4000;
var reconnect_last_delay = 0;
var reconnect_delay_exponential = true;
var reconnect_max_attempts = 5;
var reconnect_step = 0;
var reconnect_tmr = null;
var original_disconnect;
var planned_disconnect = false;
var socket = eio.apply(eio, arguments);
socket.on('open', onOpen);
socket.on('close', onClose);
socket.on('error', onError);
original_disconnect = socket.close;
socket.close = close;
// Apply any custom reconnection config
if (socket_options) {
if (typeof socket_options.reconnect_delay === 'number')
reconnect_delay = socket_options.reconnect_delay;
if (typeof socket_options.reconnect_max_attempts === 'number')
reconnect_max_attempts = socket_options.reconnect_max_attempts;
if (typeof socket_options.reconnect_delay_exponential !== 'undefined')
reconnect_delay_exponential = !!socket_options.reconnect_delay_exponential;
}
function onOpen() {
connected = true;
is_reconnecting = false;
planned_disconnect = false;
reconnect_step = 0;
reconnect_last_delay = 0;
clearTimeout(reconnect_tmr);
}
function onClose() {
connected = false;
if (!planned_disconnect && !is_reconnecting)
reconnect();
}
function onError() {
// This will be called when a reconnect fails
if (is_reconnecting)
reconnect();
}
function close() {
planned_disconnect = true;
original_disconnect.call(socket);
}
function reconnect() {
if (reconnect_step >= reconnect_max_attempts) {
socket.emit('reconnecting_failed');
return;
}
var delay = reconnect_delay_exponential ?
(reconnect_last_delay || reconnect_delay / 2) * 2 :
reconnect_delay * reconnect_step;
is_reconnecting = true;
reconnect_tmr = setTimeout(function() {
socket.open();
}, delay);
reconnect_last_delay = delay;
socket.emit('reconnecting', {
attempt: reconnect_step + 1,
max_attempts: reconnect_max_attempts,
delay: delay
});
reconnect_step++;
}
return socket;
},
Rpc: (function(){
/*
TODO:
Create a document explaining the protocol
Some way to expire unused callbacks? TTL? expireCallback() function?
*/
/**
* Wrapper around creating a new WebsocketRpcCaller
* This lets us use the WebsocketRpc object as a function
*/
function WebsocketRpc(eio_socket) {
var caller = new WebsocketRpcCaller(eio_socket);
var ret = function WebsocketRpcInstance() {
return ret.makeCall.apply(ret, arguments);
};
for(var prop in caller){
ret[prop] = caller[prop];
}
ret._mixinEmitter();
ret._bindSocketListeners();
// Keep a reference to the main Rpc object so namespaces can find calling functions
ret._rpc = ret;
return ret;
}
function WebsocketRpcCaller(eio_socket) {
this._next_id = 0;
this._rpc_callbacks = {};
this._socket = eio_socket;
this._rpc = this;
this._namespace = '';
this._namespaces = [];
}
WebsocketRpcCaller.prototype._bindSocketListeners = function() {
var self = this;
// Proxy the onMessage listener
this._onMessageProxy = function rpcOnMessageBoundFunction(){
self._onMessage.apply(self, arguments);
};
this._socket.on('message', this._onMessageProxy);
};
WebsocketRpcCaller.prototype.dispose = function() {
if (this._onMessageProxy) {
this._socket.removeListener('message', this._onMessageProxy);
delete this._onMessageProxy;
}
// Clean up any namespaces
for (var idx in this._namespaces) {
this._namespaces[idx].dispose();
}
this.removeAllListeners();
};
WebsocketRpcCaller.prototype.namespace = function(namespace_name) {
var complete_namespace, namespace;
if (this._namespace) {
complete_namespace = this._namespace + '.' + namespace_name;
} else {
complete_namespace = namespace_name;
}
namespace = new this._rpc.Namespace(this._rpc, complete_namespace);
this._rpc._namespaces.push(namespace);
return namespace;
};
// Find all namespaces that either matches or starts with namespace_name
WebsocketRpcCaller.prototype._findRelevantNamespaces = function(namespace_name) {
var found_namespaces = [];
for(var idx in this._namespaces) {
if (this._namespaces[idx]._namespace === namespace_name) {
found_namespaces.push(this._namespaces[idx]);
}
if (this._namespaces[idx]._namespace.indexOf(namespace_name + '.') === 0) {
found_namespaces.push(this._namespaces[idx]);
}
}
return found_namespaces;
};
/**
* The engine.io socket already has an emitter mixin so steal it from there
*/
WebsocketRpcCaller.prototype._mixinEmitter = function(target_obj) {
var funcs = ['on', 'once', 'off', 'removeListener', 'removeAllListeners', 'emit', 'listeners', 'hasListeners'];
target_obj = target_obj || this;
for (var i=0; i<funcs.length; i++) {
if (typeof this._socket[funcs[i]] === 'function')
target_obj[funcs[i]] = this._socket[funcs[i]];
}
};
/**
* Check if a packet is a valid RPC call
*/
WebsocketRpcCaller.prototype._isCall = function(packet) {
return (typeof packet.method !== 'undefined' &&
typeof packet.params !== 'undefined');
};
/**
* Check if a packet is a valid RPC response
*/
WebsocketRpcCaller.prototype._isResponse = function(packet) {
return (typeof packet.id !== 'undefined' &&
typeof packet.response !== 'undefined');
};
/**
* Make an RPC call
* First argument must be the method name to call
* If the last argument is a function, it is used as a callback
* All other arguments are passed to the RPC method
* Eg. Rpc.makeCall('namespace.method_name', 1, 2, 3, callbackFn)
*/
WebsocketRpcCaller.prototype.makeCall = function(method) {
var params, callback, packet;
// Get a normal array of passed in arguments
params = Array.prototype.slice.call(arguments, 1, arguments.length);
// If the last argument is a function, take it as a callback and strip it out
if (typeof params[params.length-1] === 'function') {
callback = params[params.length-1];
params = params.slice(0, params.length-1);
}
packet = {
method: method,
params: params
};
if (typeof callback === 'function') {
packet.id = this._next_id;
this._next_id++;
this._rpc_callbacks[packet.id] = callback;
}
this.send(packet);
};
/**
* Encode the packet into JSON and send it over the websocket
*/
WebsocketRpcCaller.prototype.send = function(packet) {
if (this._socket)
this._socket.send(JSON.stringify(packet));
};
/**
* Handler for the websocket `message` event
*/
WebsocketRpcCaller.prototype._onMessage = function(message_raw) {
var self = this,
packet,
returnFn,
callback,
namespace, namespaces, idx;
try {
packet = JSON.parse(message_raw);
if (!packet) throw 'Corrupt packet';
} catch(err) {
return;
}
if (this._isResponse(packet)) {
// If we have no callback waiting for this response, don't do anything
if (typeof this._rpc_callbacks[packet.id] !== 'function')
return;
// Delete the callback before calling it. If any exceptions accur within the callback
// we don't have to worry about the delete not happening
callback = this._rpc_callbacks[packet.id];
delete this._rpc_callbacks[packet.id];
callback.apply(this, packet.response);
} else if (this._isCall(packet)) {
// Calls with an ID may be responded to
if (typeof packet.id !== 'undefined') {
returnFn = this._createReturnCallFn(packet.id);
} else {
returnFn = this._noop;
}
this.emit.apply(this, ['all', packet.method, returnFn].concat(packet.params));
this.emit.apply(this, [packet.method, returnFn].concat(packet.params));
if (packet.method.indexOf('.') > 0) {
namespace = packet.method.substring(0, packet.method.lastIndexOf('.'));
namespaces = this._findRelevantNamespaces(namespace);
for(idx in namespaces){
packet.method = packet.method.replace(namespaces[idx]._namespace + '.', '');
namespaces[idx].emit.apply(namespaces[idx], [packet.method, returnFn].concat(packet.params));
}
}
}
};
/**
* Returns a function used as a callback when responding to a call
*/
WebsocketRpcCaller.prototype._createReturnCallFn = function(packet_id) {
var self = this;
return function returnCallFn() {
var value = Array.prototype.slice.call(arguments, 0);
var ret_packet = {
id: packet_id,
response: value
};
self.send(ret_packet);
};
};
WebsocketRpcCaller.prototype._noop = function() {};
WebsocketRpcCaller.prototype.Namespace = function(rpc, namespace) {
var ret = function WebsocketRpcNamespaceInstance() {
if (typeof arguments[0] === 'undefined') {
return;
}
arguments[0] = ret._namespace + '.' + arguments[0];
return ret._rpc.apply(ret._rpc, arguments);
};
ret._rpc = rpc;
ret._namespace = namespace;
ret.dispose = function() {
ret.removeAllListeners();
ret._rpc = null;
};
rpc._mixinEmitter(ret);
return ret;
};
return WebsocketRpc;
}())
};