2 ReconnectingSocket
: function ReconnectingSocket(server_uri
, socket_options
) {
4 var is_reconnecting
= false;
6 var reconnect_delay
= 4000;
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;
13 var original_disconnect
;
14 var planned_disconnect
= false;
16 var socket
= eio
.apply(eio
, arguments
);
17 socket
.on('open', onOpen
);
18 socket
.on('close', onClose
);
19 socket
.on('error', onError
);
21 original_disconnect
= socket
.close
;
24 // Apply any custom reconnection config
26 if (typeof socket_options
.reconnect_delay
=== 'number')
27 reconnect_delay
= socket_options
.reconnect_delay
;
29 if (typeof socket_options
.reconnect_max_attempts
=== 'number')
30 reconnect_max_attempts
= socket_options
.reconnect_max_attempts
;
32 if (typeof socket_options
.reconnect_delay_exponential
!== 'undefined')
33 reconnect_delay_exponential
= !!socket_options
.reconnect_delay_exponential
;
39 is_reconnecting
= false;
40 planned_disconnect
= false;
43 reconnect_last_delay
= 0;
45 clearTimeout(reconnect_tmr
);
52 if (!planned_disconnect
&& !is_reconnecting
)
58 // This will be called when a reconnect fails
65 planned_disconnect
= true;
66 original_disconnect
.call(socket
);
70 function reconnect() {
71 if (reconnect_step
>= reconnect_max_attempts
) {
72 socket
.emit('reconnecting_failed');
76 var delay
= reconnect_delay_exponential
?
77 (reconnect_last_delay
|| reconnect_delay
/ 2) * 2 :
78 reconnect_delay
* reconnect_step
;
80 is_reconnecting
= true;
82 reconnect_tmr
= setTimeout(function() {
86 reconnect_last_delay
= delay
;
88 socket
.emit('reconnecting', {
89 attempt
: reconnect_step
+ 1,
90 max_attempts
: reconnect_max_attempts
,
106 Create a document explaining the protocol
107 Some way to expire unused callbacks? TTL? expireCallback() function?
111 * Wrapper around creating a new WebsocketRpcCaller
112 * This lets us use the WebsocketRpc object as a function
114 function WebsocketRpc(eio_socket
) {
115 var caller
= new WebsocketRpcCaller(eio_socket
);
116 var ret
= function WebsocketRpcInstance() {
117 return ret
.makeCall
.apply(ret
, arguments
);
120 for(var prop
in caller
){
121 ret
[prop
] = caller
[prop
];
125 ret
._bindSocketListeners();
127 // Keep a reference to the main Rpc object so namespaces can find calling functions
134 function WebsocketRpcCaller(eio_socket
) {
136 this._rpc_callbacks
= {};
137 this._socket
= eio_socket
;
140 this._namespace
= '';
141 this._namespaces
= [];
145 WebsocketRpcCaller
.prototype._bindSocketListeners = function() {
148 // Proxy the onMessage listener
149 this._onMessageProxy
= function rpcOnMessageBoundFunction(){
150 self
._onMessage
.apply(self
, arguments
);
152 this._socket
.on('message', this._onMessageProxy
);
157 WebsocketRpcCaller
.prototype.dispose = function() {
158 if (this._onMessageProxy
) {
159 this._socket
.removeListener('message', this._onMessageProxy
);
160 delete this._onMessageProxy
;
163 // Clean up any namespaces
164 for (var idx
in this._namespaces
) {
165 this._namespaces
[idx
].dispose();
168 this.removeAllListeners();
173 WebsocketRpcCaller
.prototype.namespace = function(namespace_name
) {
174 var complete_namespace
, namespace;
176 if (this._namespace
) {
177 complete_namespace
= this._namespace
+ '.' + namespace_name
;
179 complete_namespace
= namespace_name
;
182 namespace = new this._rpc
.Namespace(this._rpc
, complete_namespace
);
183 this._rpc
._namespaces
.push(namespace);
190 // Find all namespaces that either matches or starts with namespace_name
191 WebsocketRpcCaller
.prototype._findRelevantNamespaces = function(namespace_name
) {
192 var found_namespaces
= [];
194 for(var idx
in this._namespaces
) {
195 if (this._namespaces
[idx
]._namespace
=== namespace_name
) {
196 found_namespaces
.push(this._namespaces
[idx
]);
199 if (this._namespaces
[idx
]._namespace
.indexOf(namespace_name
+ '.') === 0) {
200 found_namespaces
.push(this._namespaces
[idx
]);
204 return found_namespaces
;
210 * The engine.io socket already has an emitter mixin so steal it from there
212 WebsocketRpcCaller
.prototype._mixinEmitter = function(target_obj
) {
213 var funcs
= ['on', 'once', 'off', 'removeListener', 'removeAllListeners', 'emit', 'listeners', 'hasListeners'];
215 target_obj
= target_obj
|| this;
217 for (var i
=0; i
<funcs
.length
; i
++) {
218 if (typeof this._socket
[funcs
[i
]] === 'function')
219 target_obj
[funcs
[i
]] = this._socket
[funcs
[i
]];
225 * Check if a packet is a valid RPC call
227 WebsocketRpcCaller
.prototype._isCall = function(packet
) {
228 return (typeof packet
.method
!== 'undefined' &&
229 typeof packet
.params
!== 'undefined');
234 * Check if a packet is a valid RPC response
236 WebsocketRpcCaller
.prototype._isResponse = function(packet
) {
237 return (typeof packet
.id
!== 'undefined' &&
238 typeof packet
.response
!== 'undefined');
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
248 * Eg. Rpc.makeCall('namespace.method_name', 1, 2, 3, callbackFn)
250 WebsocketRpcCaller
.prototype.makeCall = function(method
) {
251 var params
, callback
, packet
;
253 // Get a normal array of passed in arguments
254 params
= Array
.prototype.slice
.call(arguments
, 1, arguments
.length
);
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);
267 if (typeof callback
=== 'function') {
268 packet
.id
= this._next_id
;
271 this._rpc_callbacks
[packet
.id
] = callback
;
279 * Encode the packet into JSON and send it over the websocket
281 WebsocketRpcCaller
.prototype.send = function(packet
) {
283 this._socket
.send(JSON
.stringify(packet
));
288 * Handler for the websocket `message` event
290 WebsocketRpcCaller
.prototype._onMessage = function(message_raw
) {
295 namespace, namespaces
, idx
;
298 packet
= JSON
.parse(message_raw
);
299 if (!packet
) throw 'Corrupt packet';
304 if (this._isResponse(packet
)) {
305 // If we have no callback waiting for this response, don't do anything
306 if (typeof this._rpc_callbacks
[packet
.id
] !== 'function')
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
];
312 delete this._rpc_callbacks
[packet
.id
];
314 callback
.apply(this, packet
.response
);
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
);
321 returnFn
= this._noop
;
324 this.emit
.apply(this, ['all', packet
.method
, returnFn
].concat(packet
.params
));
325 this.emit
.apply(this, [packet
.method
, returnFn
].concat(packet
.params
));
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
));
340 * Returns a function used as a callback when responding to a call
342 WebsocketRpcCaller
.prototype._createReturnCallFn = function(packet_id
) {
345 return function returnCallFn() {
346 var value
= Array
.prototype.slice
.call(arguments
, 0);
353 self
.send(ret_packet
);
359 WebsocketRpcCaller
.prototype._noop = function() {};
363 WebsocketRpcCaller
.prototype.Namespace = function(rpc
, namespace) {
364 var ret
= function WebsocketRpcNamespaceInstance() {
365 if (typeof arguments
[0] === 'undefined') {
369 arguments
[0] = ret
._namespace
+ '.' + arguments
[0];
370 return ret
._rpc
.apply(ret
._rpc
, arguments
);
374 ret
._namespace
= namespace;
376 ret
.dispose = function() {
377 ret
.removeAllListeners();
381 rpc
._mixinEmitter(ret
);