Commit | Line | Data |
---|---|---|
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 | ||
7 | function 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 | ||
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) { | |
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 | */ | |
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++) { | |
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 | */ | |
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 | ||
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 | */ | |
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, | |
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 | */ | |
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 | ||
d99e7823 D |
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 | } |