Merge pull request #403 from M2Ys4U/nick_trunc_fix
[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
6 var reconnect_delay = 2000;
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
52 if (!planned_disconnect)
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
b42bd8f3
D
110 function WebsocketRpc(eio_socket) {
111 var self = this;
06e30f06 112
b42bd8f3 113 this._next_id = 0;
f621d0ff 114 this._rpc_callbacks = {};
b42bd8f3 115 this._socket = eio_socket;
06e30f06 116
b42bd8f3
D
117 this._mixinEmitter();
118 this._bindSocketListeners();
119 }
06e30f06
D
120
121
b42bd8f3
D
122 WebsocketRpc.prototype._bindSocketListeners = function() {
123 var self = this;
06e30f06 124
b42bd8f3
D
125 // Proxy the onMessage listener
126 this._onMessageProxy = function rpcOnMessageBoundFunction(){
127 self._onMessage.apply(self, arguments);
128 };
129 this._socket.on('message', this._onMessageProxy);
130 };
06e30f06
D
131
132
133
b42bd8f3
D
134 WebsocketRpc.prototype.dispose = function() {
135 if (this._onMessageProxy) {
136 this._socket.removeListener('message', this._onMessageProxy);
137 delete this._onMessageProxy;
138 }
06e30f06 139
b42bd8f3
D
140 this.removeAllListeners();
141 };
06e30f06
D
142
143
144
145
b42bd8f3
D
146 /**
147 * The engine.io socket already has an emitter mixin so steal it from there
148 */
149 WebsocketRpc.prototype._mixinEmitter = function() {
150 var funcs = ['on', 'once', 'off', 'removeListener', 'removeAllListeners', 'emit', 'listeners', 'hasListeners'];
06e30f06 151
b42bd8f3
D
152 for (var i=0; i<funcs.length; i++) {
153 if (typeof this._socket[funcs[i]] === 'function')
154 this[funcs[i]] = this._socket[funcs[i]];
155 }
156 };
06e30f06
D
157
158
b42bd8f3
D
159 /**
160 * Check if a packet is a valid RPC call
161 */
162 WebsocketRpc.prototype._isCall = function(packet) {
163 return (typeof packet.method !== 'undefined' &&
164 typeof packet.params !== 'undefined');
165 };
06e30f06
D
166
167
b42bd8f3
D
168 /**
169 * Check if a packet is a valid RPC response
170 */
171 WebsocketRpc.prototype._isResponse = function(packet) {
172 return (typeof packet.id !== 'undefined' &&
173 typeof packet.response !== 'undefined');
174 };
06e30f06
D
175
176
177
b42bd8f3
D
178 /**
179 * Make an RPC call
180 * First argument must be the method name to call
181 * If the last argument is a function, it is used as a callback
182 * All other arguments are passed to the RPC method
183 * Eg. Rpc.call('namespace.method_name', 1, 2, 3, callbackFn)
184 */
185 WebsocketRpc.prototype.call = function(method) {
186 var params, callback, packet;
06e30f06 187
b42bd8f3
D
188 // Get a normal array of passed in arguments
189 params = Array.prototype.slice.call(arguments, 1, arguments.length);
190
191 // If the last argument is a function, take it as a callback and strip it out
192 if (typeof params[params.length-1] === 'function') {
193 callback = params[params.length-1];
194 params = params.slice(0, params.length-1);
195 }
196
197 packet = {
198 method: method,
199 params: params
200 };
201
202 if (typeof callback === 'function') {
203 packet.id = this._next_id;
204
205 this._next_id++;
f621d0ff 206 this._rpc_callbacks[packet.id] = callback;
b42bd8f3 207 }
06e30f06 208
b42bd8f3
D
209 this.send(packet);
210 };
06e30f06 211
06e30f06 212
b42bd8f3
D
213 /**
214 * Encode the packet into JSON and send it over the websocket
215 */
216 WebsocketRpc.prototype.send = function(packet) {
217 if (this._socket)
218 this._socket.send(JSON.stringify(packet));
219 };
06e30f06 220
06e30f06 221
b42bd8f3
D
222 /**
223 * Handler for the websocket `message` event
224 */
225 WebsocketRpc.prototype._onMessage = function(message_raw) {
226 var self = this,
227 packet,
a6b1c062
D
228 returnFn,
229 callback;
b42bd8f3
D
230
231 try {
232 packet = JSON.parse(message_raw);
233 if (!packet) throw 'Corrupt packet';
234 } catch(err) {
235 return;
236 }
237
238 if (this._isResponse(packet)) {
239 // If we have no callback waiting for this response, don't do anything
f621d0ff 240 if (typeof this._rpc_callbacks[packet.id] !== 'function')
b42bd8f3
D
241 return;
242
a6b1c062
D
243 // Delete the callback before calling it. If any exceptions accur within the callback
244 // we don't have to worry about the delete not happening
245 callback = this._rpc_callbacks[packet.id];
f621d0ff 246 delete this._rpc_callbacks[packet.id];
b42bd8f3 247
a6b1c062
D
248 callback.apply(this, packet.response);
249
b42bd8f3
D
250 } else if (this._isCall(packet)) {
251 // Calls with an ID may be responded to
252 if (typeof packet.id !== 'undefined') {
253 returnFn = this._createReturnCallFn(packet.id);
254 } else {
255 returnFn = this._noop;
256 }
257
258 this.emit.apply(this, [packet.method, returnFn].concat(packet.params));
259 }
260 };
06e30f06
D
261
262
b42bd8f3
D
263 /**
264 * Returns a function used as a callback when responding to a call
265 */
266 WebsocketRpc.prototype._createReturnCallFn = function(packet_id) {
267 var self = this;
06e30f06 268
b42bd8f3
D
269 return function returnCallFn() {
270 var value = Array.prototype.slice.call(arguments, 0);
06e30f06 271
b42bd8f3
D
272 var ret_packet = {
273 id: packet_id,
274 response: value
275 };
06e30f06 276
b42bd8f3
D
277 self.send(ret_packet);
278 };
279 };
06e30f06 280
06e30f06 281
06e30f06 282
b42bd8f3 283 WebsocketRpc.prototype._noop = function() {};
06e30f06
D
284
285
b42bd8f3 286 return WebsocketRpc;
06e30f06
D
287
288 }())
289};