1fa035a4140986ac5935f48d6e9b14543e8cbd05
[KiwiIRC.git] / server / websocketrpc.js
1 /*
2 TODO:
3 Create a document explaining the protocol
4 Some way to expire unused callbacks? TTL? expireCallback() function?
5 */
6
7 function WebsocketRpc(eio_socket) {
8 var self = this;
9
10 this._next_id = 0;
11 this._rpc_callbacks = {};
12 this._socket = eio_socket;
13
14 this._mixinEmitter();
15 this._bindSocketListeners();
16 }
17
18
19 WebsocketRpc.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
31 WebsocketRpc.prototype.dispose = function() {
32 if (this._onMessageProxy) {
33 this._socket.removeListener('message', this._onMessageProxy);
34 delete this._onMessageProxy;
35 }
36
37 this.removeAllListeners();
38 };
39
40
41
42
43 /**
44 * The engine.io socket already has an emitter mixin so steal it from there
45 */
46 WebsocketRpc.prototype._mixinEmitter = function() {
47 var funcs = ['on', 'once', 'off', 'removeListener', 'removeAllListeners', 'emit', 'listeners', 'hasListeners'];
48
49 for (var i=0; i<funcs.length; i++) {
50 if (typeof this._socket[funcs[i]] === 'function')
51 this[funcs[i]] = this._socket[funcs[i]];
52 }
53 };
54
55
56 /**
57 * Check if a packet is a valid RPC call
58 */
59 WebsocketRpc.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 */
68 WebsocketRpc.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 */
82 WebsocketRpc.prototype.call = function(method) {
83 var params, callback, packet;
84
85 // Get a normal array of passed in arguments
86 params = Array.prototype.slice.call(arguments, 1, arguments.length);
87
88 // If the last argument is a function, take it as a callback and strip it out
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++;
103 this._rpc_callbacks[packet.id] = callback;
104 }
105
106 this.send(packet);
107 };
108
109
110 /**
111 * Encode the packet into JSON and send it over the websocket
112 */
113 WebsocketRpc.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 */
122 WebsocketRpc.prototype._onMessage = function(message_raw) {
123 var self = this,
124 packet,
125 returnFn,
126 callback;
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)) {
136 // If we have no callback waiting for this response, don't do anything
137 if (typeof this._rpc_callbacks[packet.id] !== 'function')
138 return;
139
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];
143 delete this._rpc_callbacks[packet.id];
144
145 callback.apply(this, packet.response);
146
147 } else if (this._isCall(packet)) {
148 // Calls with an ID may be responded to
149 if (typeof packet.id !== 'undefined') {
150 returnFn = this._createReturnCallFn(packet.id);
151 } else {
152 returnFn = this._noop;
153 }
154
155 this.emit.apply(this, [packet.method, returnFn].concat(packet.params));
156 }
157 };
158
159
160 /**
161 * Returns a function used as a callback when responding to a call
162 */
163 WebsocketRpc.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
180 WebsocketRpc.prototype._noop = function() {};
181
182
183
184
185 // If running a node module, set the exports
186 if (typeof module === 'object' && typeof module.exports !== 'undefined') {
187 module.exports = WebsocketRpc;
188 }