Merge branch 'development'
[KiwiIRC.git] / client / assets / libs / engine.io.tools.js
CommitLineData
06e30f06 1var EngineioTools = {
b42bd8f3 2 ReconnectingSocket: function ReconnectingSocket(server_uri, socket_options) {
06e30f06
D
3 var connected = false;
4 var is_reconnecting = false;
5
a37a697f 6 var reconnect_delay = 4000;
06e30f06
D
7 var reconnect_last_delay = 0;
8 var reconnect_delay_exponential = true;
9 var reconnect_max_attempts = 5;
10 var reconnect_step = 0;
11 var reconnect_tmr = null;
12
13 var original_disconnect;
14 var planned_disconnect = false;
15
16 var socket = eio.apply(eio, arguments);
17 socket.on('open', onOpen);
18 socket.on('close', onClose);
19 socket.on('error', onError);
20
21 original_disconnect = socket.close;
22 socket.close = close;
23
24 // Apply any custom reconnection config
25 if (socket_options) {
26 if (typeof socket_options.reconnect_delay === 'number')
27 reconnect_delay = socket_options.reconnect_delay;
28
29 if (typeof socket_options.reconnect_max_attempts === 'number')
30 reconnect_max_attempts = socket_options.reconnect_max_attempts;
31
32 if (typeof socket_options.reconnect_delay_exponential !== 'undefined')
33 reconnect_delay_exponential = !!socket_options.reconnect_delay_exponential;
34 }
35
36
37 function onOpen() {
38 connected = true;
39 is_reconnecting = false;
40 planned_disconnect = false;
41
42 reconnect_step = 0;
43 reconnect_last_delay = 0;
44
45 clearTimeout(reconnect_tmr);
46 }
47
48
49 function onClose() {
50 connected = false;
51
a37a697f 52 if (!planned_disconnect && !is_reconnecting)
06e30f06
D
53 reconnect();
54 }
55
56
57 function onError() {
58 // This will be called when a reconnect fails
59 if (is_reconnecting)
60 reconnect();
61 }
62
63
64 function close() {
65 planned_disconnect = true;
66 original_disconnect.call(socket);
67 }
68
69
70 function reconnect() {
71 if (reconnect_step >= reconnect_max_attempts) {
72 socket.emit('reconnecting_failed');
73 return;
74 }
75
76 var delay = reconnect_delay_exponential ?
77 (reconnect_last_delay || reconnect_delay / 2) * 2 :
78 reconnect_delay * reconnect_step;
79
80 is_reconnecting = true;
81
82 reconnect_tmr = setTimeout(function() {
83 socket.open();
84 }, delay);
85
86 reconnect_last_delay = delay;
87
88 socket.emit('reconnecting', {
89 attempt: reconnect_step + 1,
90 max_attempts: reconnect_max_attempts,
91 delay: delay
92 });
93
94 reconnect_step++;
95 }
96
97 return socket;
98 },
99
100
101
102
103 Rpc: (function(){
b42bd8f3
D
104 /*
105 TODO:
106 Create a document explaining the protocol
107 Some way to expire unused callbacks? TTL? expireCallback() function?
108 */
06e30f06 109
d718a0f9
D
110 /**
111 * Wrapper around creating a new WebsocketRpcCaller
112 * This lets us use the WebsocketRpc object as a function
113 */
b42bd8f3 114 function WebsocketRpc(eio_socket) {
d718a0f9
D
115 var caller = new WebsocketRpcCaller(eio_socket);
116 var ret = function WebsocketRpcInstance() {
117 return ret.makeCall.apply(ret, arguments);
118 };
119
120 for(var prop in caller){
121 ret[prop] = caller[prop];
122 }
06e30f06 123
d718a0f9
D
124 ret._mixinEmitter();
125 ret._bindSocketListeners();
126
c7cb4d54
D
127 // Keep a reference to the main Rpc object so namespaces can find calling functions
128 ret._rpc = ret;
129
d718a0f9
D
130 return ret;
131 }
132
133
134 function WebsocketRpcCaller(eio_socket) {
b42bd8f3 135 this._next_id = 0;
f621d0ff 136 this._rpc_callbacks = {};
b42bd8f3 137 this._socket = eio_socket;
c7cb4d54
D
138
139 this._rpc = this;
140 this._namespace = '';
141 this._namespaces = [];
b42bd8f3 142 }
06e30f06
D
143
144
d718a0f9 145 WebsocketRpcCaller.prototype._bindSocketListeners = function() {
b42bd8f3 146 var self = this;
06e30f06 147
b42bd8f3
D
148 // Proxy the onMessage listener
149 this._onMessageProxy = function rpcOnMessageBoundFunction(){
150 self._onMessage.apply(self, arguments);
151 };
152 this._socket.on('message', this._onMessageProxy);
153 };
06e30f06
D
154
155
156
d718a0f9 157 WebsocketRpcCaller.prototype.dispose = function() {
b42bd8f3
D
158 if (this._onMessageProxy) {
159 this._socket.removeListener('message', this._onMessageProxy);
160 delete this._onMessageProxy;
161 }
06e30f06 162
c7cb4d54
D
163 // Clean up any namespaces
164 for (var idx in this._namespaces) {
165 this._namespaces[idx].dispose();
166 }
167
b42bd8f3
D
168 this.removeAllListeners();
169 };
06e30f06
D
170
171
172
c7cb4d54
D
173 WebsocketRpcCaller.prototype.namespace = function(namespace_name) {
174 var complete_namespace, namespace;
175
176 if (this._namespace) {
177 complete_namespace = this._namespace + '.' + namespace_name;
178 } else {
179 complete_namespace = namespace_name;
180 }
181
182 namespace = new this._rpc.Namespace(this._rpc, complete_namespace);
183 this._rpc._namespaces.push(namespace);
184
185 return namespace;
186 };
187
188
189
190 // Find all namespaces that either matches or starts with namespace_name
191 WebsocketRpcCaller.prototype._findRelevantNamespaces = function(namespace_name) {
192 var found_namespaces = [];
193
194 for(var idx in this._namespaces) {
195 if (this._namespaces[idx]._namespace === namespace_name) {
196 found_namespaces.push(this._namespaces[idx]);
197 }
198
199 if (this._namespaces[idx]._namespace.indexOf(namespace_name + '.') === 0) {
200 found_namespaces.push(this._namespaces[idx]);
201 }
202 }
203
204 return found_namespaces;
205 };
206
207
06e30f06 208
b42bd8f3
D
209 /**
210 * The engine.io socket already has an emitter mixin so steal it from there
211 */
c7cb4d54 212 WebsocketRpcCaller.prototype._mixinEmitter = function(target_obj) {
b42bd8f3 213 var funcs = ['on', 'once', 'off', 'removeListener', 'removeAllListeners', 'emit', 'listeners', 'hasListeners'];
06e30f06 214
c7cb4d54
D
215 target_obj = target_obj || this;
216
b42bd8f3
D
217 for (var i=0; i<funcs.length; i++) {
218 if (typeof this._socket[funcs[i]] === 'function')
c7cb4d54 219 target_obj[funcs[i]] = this._socket[funcs[i]];
b42bd8f3
D
220 }
221 };
06e30f06
D
222
223
b42bd8f3
D
224 /**
225 * Check if a packet is a valid RPC call
226 */
d718a0f9 227 WebsocketRpcCaller.prototype._isCall = function(packet) {
b42bd8f3
D
228 return (typeof packet.method !== 'undefined' &&
229 typeof packet.params !== 'undefined');
230 };
06e30f06
D
231
232
b42bd8f3
D
233 /**
234 * Check if a packet is a valid RPC response
235 */
d718a0f9 236 WebsocketRpcCaller.prototype._isResponse = function(packet) {
b42bd8f3
D
237 return (typeof packet.id !== 'undefined' &&
238 typeof packet.response !== 'undefined');
239 };
06e30f06
D
240
241
242
b42bd8f3
D
243 /**
244 * Make an RPC call
245 * First argument must be the method name to call
246 * If the last argument is a function, it is used as a callback
247 * All other arguments are passed to the RPC method
d718a0f9 248 * Eg. Rpc.makeCall('namespace.method_name', 1, 2, 3, callbackFn)
b42bd8f3 249 */
d718a0f9 250 WebsocketRpcCaller.prototype.makeCall = function(method) {
b42bd8f3 251 var params, callback, packet;
06e30f06 252
b42bd8f3
D
253 // Get a normal array of passed in arguments
254 params = Array.prototype.slice.call(arguments, 1, arguments.length);
255
256 // If the last argument is a function, take it as a callback and strip it out
257 if (typeof params[params.length-1] === 'function') {
258 callback = params[params.length-1];
259 params = params.slice(0, params.length-1);
260 }
261
262 packet = {
263 method: method,
264 params: params
265 };
266
267 if (typeof callback === 'function') {
268 packet.id = this._next_id;
269
270 this._next_id++;
f621d0ff 271 this._rpc_callbacks[packet.id] = callback;
b42bd8f3 272 }
06e30f06 273
b42bd8f3
D
274 this.send(packet);
275 };
06e30f06 276
06e30f06 277
b42bd8f3
D
278 /**
279 * Encode the packet into JSON and send it over the websocket
280 */
d718a0f9 281 WebsocketRpcCaller.prototype.send = function(packet) {
b42bd8f3
D
282 if (this._socket)
283 this._socket.send(JSON.stringify(packet));
284 };
06e30f06 285
06e30f06 286
b42bd8f3
D
287 /**
288 * Handler for the websocket `message` event
289 */
d718a0f9 290 WebsocketRpcCaller.prototype._onMessage = function(message_raw) {
b42bd8f3
D
291 var self = this,
292 packet,
a6b1c062 293 returnFn,
c7cb4d54
D
294 callback,
295 namespace, namespaces, idx;
b42bd8f3
D
296
297 try {
298 packet = JSON.parse(message_raw);
299 if (!packet) throw 'Corrupt packet';
300 } catch(err) {
301 return;
302 }
303
304 if (this._isResponse(packet)) {
305 // If we have no callback waiting for this response, don't do anything
f621d0ff 306 if (typeof this._rpc_callbacks[packet.id] !== 'function')
b42bd8f3
D
307 return;
308
a6b1c062
D
309 // Delete the callback before calling it. If any exceptions accur within the callback
310 // we don't have to worry about the delete not happening
311 callback = this._rpc_callbacks[packet.id];
f621d0ff 312 delete this._rpc_callbacks[packet.id];
b42bd8f3 313
a6b1c062
D
314 callback.apply(this, packet.response);
315
b42bd8f3
D
316 } else if (this._isCall(packet)) {
317 // Calls with an ID may be responded to
318 if (typeof packet.id !== 'undefined') {
319 returnFn = this._createReturnCallFn(packet.id);
320 } else {
321 returnFn = this._noop;
322 }
323
c7cb4d54 324 this.emit.apply(this, ['all', packet.method, returnFn].concat(packet.params));
b42bd8f3 325 this.emit.apply(this, [packet.method, returnFn].concat(packet.params));
c7cb4d54
D
326
327 if (packet.method.indexOf('.') > 0) {
328 namespace = packet.method.substring(0, packet.method.lastIndexOf('.'));
329 namespaces = this._findRelevantNamespaces(namespace);
330 for(idx in namespaces){
331 packet.method = packet.method.replace(namespaces[idx]._namespace + '.', '');
332 namespaces[idx].emit.apply(namespaces[idx], [packet.method, returnFn].concat(packet.params));
333 }
334 }
b42bd8f3
D
335 }
336 };
06e30f06
D
337
338
b42bd8f3
D
339 /**
340 * Returns a function used as a callback when responding to a call
341 */
d718a0f9 342 WebsocketRpcCaller.prototype._createReturnCallFn = function(packet_id) {
b42bd8f3 343 var self = this;
06e30f06 344
b42bd8f3
D
345 return function returnCallFn() {
346 var value = Array.prototype.slice.call(arguments, 0);
06e30f06 347
b42bd8f3
D
348 var ret_packet = {
349 id: packet_id,
350 response: value
351 };
06e30f06 352
b42bd8f3
D
353 self.send(ret_packet);
354 };
355 };
06e30f06 356
06e30f06 357
06e30f06 358
d718a0f9 359 WebsocketRpcCaller.prototype._noop = function() {};
06e30f06
D
360
361
c7cb4d54
D
362
363 WebsocketRpcCaller.prototype.Namespace = function(rpc, namespace) {
364 var ret = function WebsocketRpcNamespaceInstance() {
365 if (typeof arguments[0] === 'undefined') {
366 return;
367 }
368
369 arguments[0] = ret._namespace + '.' + arguments[0];
370 return ret._rpc.apply(ret._rpc, arguments);
371 };
372
373 ret._rpc = rpc;
374 ret._namespace = namespace;
375
376 ret.dispose = function() {
377 ret.removeAllListeners();
378 ret._rpc = null;
379 };
380
381 rpc._mixinEmitter(ret);
382
383 return ret;
384 };
385
386
b42bd8f3 387 return WebsocketRpc;
06e30f06
D
388
389 }())
390};