IRCd reconnection only when registered for 10+ secs
[KiwiIRC.git] / server / websocketrpc.js
CommitLineData
d99e7823 1/*
b42bd8f3 2 TODO:
d99e7823 3 Create a document explaining the protocol
b42bd8f3 4 Some way to expire unused callbacks? TTL? expireCallback() function?
d99e7823
D
5*/
6
7function WebsocketRpc(eio_socket) {
8 var self = this;
9
10 this._next_id = 0;
f621d0ff 11 this._rpc_callbacks = {};
d99e7823
D
12 this._socket = eio_socket;
13
14 this._mixinEmitter();
15 this._bindSocketListeners();
16}
17
18
19WebsocketRpc.prototype._bindSocketListeners = function() {
20 var self = this;
21
22 // Proxy the onMessage listener
23 this._onMessageProxy = function rpcOnMessageBoundFunction(){
24 self._onMessage.apply(self, arguments);
25 };
26 this._socket.on('message', this._onMessageProxy);
27};
28
29
30
31WebsocketRpc.prototype.dispose = function() {
32 if (this._onMessageProxy) {
0fe7c000 33 this._socket.removeListener('message', this._onMessageProxy);
d99e7823
D
34 delete this._onMessageProxy;
35 }
0fe7c000
D
36
37 this.removeAllListeners();
d99e7823
D
38};
39
40
41
42
43/**
44 * The engine.io socket already has an emitter mixin so steal it from there
45 */
46WebsocketRpc.prototype._mixinEmitter = function() {
47 var funcs = ['on', 'once', 'off', 'removeListener', 'removeAllListeners', 'emit', 'listeners', 'hasListeners'];
48
49 for (var i=0; i<funcs.length; i++) {
a9cd4622
D
50 if (typeof this._socket[funcs[i]] === 'function')
51 this[funcs[i]] = this._socket[funcs[i]];
d99e7823
D
52 }
53};
54
55
56/**
57 * Check if a packet is a valid RPC call
58 */
59WebsocketRpc.prototype._isCall = function(packet) {
60 return (typeof packet.method !== 'undefined' &&
61 typeof packet.params !== 'undefined');
62};
63
64
65/**
66 * Check if a packet is a valid RPC response
67 */
68WebsocketRpc.prototype._isResponse = function(packet) {
69 return (typeof packet.id !== 'undefined' &&
70 typeof packet.response !== 'undefined');
71};
72
73
74
75/**
76 * Make an RPC call
77 * First argument must be the method name to call
78 * If the last argument is a function, it is used as a callback
79 * All other arguments are passed to the RPC method
80 * Eg. Rpc.call('namespace.method_name', 1, 2, 3, callbackFn)
81 */
82WebsocketRpc.prototype.call = function(method) {
83 var params, callback, packet;
84
b42bd8f3 85 // Get a normal array of passed in arguments
d99e7823
D
86 params = Array.prototype.slice.call(arguments, 1, arguments.length);
87
b42bd8f3 88 // If the last argument is a function, take it as a callback and strip it out
d99e7823
D
89 if (typeof params[params.length-1] === 'function') {
90 callback = params[params.length-1];
91 params = params.slice(0, params.length-1);
92 }
93
94 packet = {
95 method: method,
96 params: params
97 };
98
99 if (typeof callback === 'function') {
100 packet.id = this._next_id;
101
102 this._next_id++;
f621d0ff 103 this._rpc_callbacks[packet.id] = callback;
d99e7823
D
104 }
105
106 this.send(packet);
107};
108
109
110/**
111 * Encode the packet into JSON and send it over the websocket
112 */
113WebsocketRpc.prototype.send = function(packet) {
114 if (this._socket)
115 this._socket.send(JSON.stringify(packet));
116};
117
118
119/**
120 * Handler for the websocket `message` event
121 */
122WebsocketRpc.prototype._onMessage = function(message_raw) {
123 var self = this,
124 packet,
a6b1c062
D
125 returnFn,
126 callback;
d99e7823
D
127
128 try {
129 packet = JSON.parse(message_raw);
130 if (!packet) throw 'Corrupt packet';
131 } catch(err) {
132 return;
133 }
134
135 if (this._isResponse(packet)) {
b42bd8f3 136 // If we have no callback waiting for this response, don't do anything
f621d0ff 137 if (typeof this._rpc_callbacks[packet.id] !== 'function')
b42bd8f3
D
138 return;
139
a6b1c062
D
140 // Delete the callback before calling it. If any exceptions accur within the callback
141 // we don't have to worry about the delete not happening
142 callback = this._rpc_callbacks[packet.id];
f621d0ff 143 delete this._rpc_callbacks[packet.id];
d99e7823 144
a6b1c062
D
145 callback.apply(this, packet.response);
146
d99e7823
D
147 } else if (this._isCall(packet)) {
148 // Calls with an ID may be responded to
149 if (typeof packet.id !== 'undefined') {
b42bd8f3 150 returnFn = this._createReturnCallFn(packet.id);
d99e7823 151 } else {
b42bd8f3 152 returnFn = this._noop;
d99e7823
D
153 }
154
155 this.emit.apply(this, [packet.method, returnFn].concat(packet.params));
156 }
157};
158
159
b42bd8f3
D
160/**
161 * Returns a function used as a callback when responding to a call
162 */
163WebsocketRpc.prototype._createReturnCallFn = function(packet_id) {
164 var self = this;
165
166 return function returnCallFn() {
167 var value = Array.prototype.slice.call(arguments, 0);
168
169 var ret_packet = {
170 id: packet_id,
171 response: value
172 };
173
174 self.send(ret_packet);
175 };
176};
177
178
179
180WebsocketRpc.prototype._noop = function() {};
181
182
d99e7823
D
183
184
185// If running a node module, set the exports
186if (typeof module === 'object' && typeof module.exports !== 'undefined') {
187 module.exports = WebsocketRpc;
188}