ca068f31b61e06a626cc073e63d3f7e0370735c8
[project/luci.git] / docs / jsapi / rpc.js.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>JSDoc: Source: rpc.js</title>
6
7 <script src="scripts/prettify/prettify.js"> </script>
8 <script src="scripts/prettify/lang-css.js"> </script>
9 <!--[if lt IE 9]>
10 <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
11 <![endif]-->
12 <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
13 <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
14 </head>
15
16 <body>
17
18 <div id="main">
19
20 <h1 class="page-title">Source: rpc.js</h1>
21
22
23
24
25
26
27 <section>
28 <article>
29 <pre class="prettyprint source linenums"><code>'use strict';
30
31 var rpcRequestID = 1,
32 rpcSessionID = L.env.sessionid || '00000000000000000000000000000000',
33 rpcBaseURL = L.url('admin/ubus'),
34 rpcInterceptorFns = [];
35
36 /**
37 * @class rpc
38 * @memberof LuCI
39 * @hideconstructor
40 * @classdesc
41 *
42 * The `LuCI.rpc` class provides high level ubus JSON-RPC abstractions
43 * and means for listing and invoking remove RPC methods.
44 */
45 return L.Class.extend(/** @lends LuCI.rpc.prototype */ {
46 /* privates */
47 call: function(req, cb, nobatch) {
48 var q = '';
49
50 if (Array.isArray(req)) {
51 if (req.length == 0)
52 return Promise.resolve([]);
53
54 for (var i = 0; i &lt; req.length; i++)
55 if (req[i].params)
56 q += '%s%s.%s'.format(
57 q ? ';' : '/',
58 req[i].params[1],
59 req[i].params[2]
60 );
61 }
62 else if (req.params) {
63 q += '/%s.%s'.format(req.params[1], req.params[2]);
64 }
65
66 return L.Request.post(rpcBaseURL + q, req, {
67 timeout: (L.env.rpctimeout || 20) * 1000,
68 nobatch: nobatch,
69 credentials: true
70 }).then(cb, cb);
71 },
72
73 parseCallReply: function(req, res) {
74 var msg = null;
75
76 if (res instanceof Error)
77 return req.reject(res);
78
79 try {
80 if (!res.ok)
81 L.raise('RPCError', 'RPC call to %s/%s failed with HTTP error %d: %s',
82 req.object, req.method, res.status, res.statusText || '?');
83
84 msg = res.json();
85 }
86 catch (e) {
87 return req.reject(e);
88 }
89
90 /*
91 * The interceptor args are intentionally swapped.
92 * Response is passed as first arg to align with Request class interceptors
93 */
94 Promise.all(rpcInterceptorFns.map(function(fn) { return fn(msg, req) }))
95 .then(this.handleCallReply.bind(this, req, msg))
96 .catch(req.reject);
97 },
98
99 handleCallReply: function(req, msg) {
100 var type = Object.prototype.toString,
101 ret = null;
102
103 try {
104 /* verify message frame */
105 if (!L.isObject(msg) || msg.jsonrpc != '2.0')
106 L.raise('RPCError', 'RPC call to %s/%s returned invalid message frame',
107 req.object, req.method);
108
109 /* check error condition */
110 if (L.isObject(msg.error) &amp;&amp; msg.error.code &amp;&amp; msg.error.message)
111 L.raise('RPCError', 'RPC call to %s/%s failed with error %d: %s',
112 req.object, req.method, msg.error.code, msg.error.message || '?');
113 }
114 catch (e) {
115 return req.reject(e);
116 }
117
118 if (!req.object &amp;&amp; !req.method) {
119 ret = msg.result;
120 }
121 else if (Array.isArray(msg.result)) {
122 ret = (msg.result.length > 1) ? msg.result[1] : msg.result[0];
123 }
124
125 if (req.expect) {
126 for (var key in req.expect) {
127 if (ret != null &amp;&amp; key != '')
128 ret = ret[key];
129
130 if (ret == null || type.call(ret) != type.call(req.expect[key]))
131 ret = req.expect[key];
132
133 break;
134 }
135 }
136
137 /* apply filter */
138 if (typeof(req.filter) == 'function') {
139 req.priv[0] = ret;
140 req.priv[1] = req.params;
141 ret = req.filter.apply(this, req.priv);
142 }
143
144 req.resolve(ret);
145 },
146
147 /**
148 * Lists available remote ubus objects or the method signatures of
149 * specific objects.
150 *
151 * This function has two signatures and is sensitive to the number of
152 * arguments passed to it:
153 * - `list()` -
154 * Returns an array containing the names of all remote `ubus` objects
155 * - `list("objname", ...)`
156 * Returns method signatures for each given `ubus` object name.
157 *
158 * @param {...string} [objectNames]
159 * If any object names are given, this function will return the method
160 * signatures of each given object.
161 *
162 * @returns {Promise&lt;Array&lt;string>|Object&lt;string, Object&lt;string, Object&lt;string, string>>>>}
163 * When invoked without arguments, this function will return a promise
164 * resolving to an array of `ubus` object names. When invoked with one or
165 * more arguments, a promise resolving to an object describing the method
166 * signatures of each requested `ubus` object name will be returned.
167 */
168 list: function() {
169 var msg = {
170 jsonrpc: '2.0',
171 id: rpcRequestID++,
172 method: 'list',
173 params: arguments.length ? this.varargs(arguments) : undefined
174 };
175
176 return new Promise(L.bind(function(resolveFn, rejectFn) {
177 /* store request info */
178 var req = {
179 resolve: resolveFn,
180 reject: rejectFn
181 };
182
183 /* call rpc */
184 this.call(msg, this.parseCallReply.bind(this, req));
185 }, this));
186 },
187
188 /**
189 * @typedef {Object} DeclareOptions
190 * @memberof LuCI.rpc
191 *
192 * @property {string} object
193 * The name of the remote `ubus` object to invoke.
194 *
195 * @property {string} method
196 * The name of the remote `ubus` method to invoke.
197 *
198 * @property {string[]} [params]
199 * Lists the named parameters expected by the remote `ubus` RPC method.
200 * The arguments passed to the resulting generated method call function
201 * will be mapped to named parameters in the order they appear in this
202 * array.
203 *
204 * Extraneous parameters passed to the generated function will not be
205 * sent to the remote procedure but are passed to the
206 * {@link LuCI.rpc~filterFn filter function} if one is specified.
207 *
208 * Examples:
209 * - `params: [ "foo", "bar" ]` -
210 * When the resulting call function is invoked with `fn(true, false)`,
211 * the corresponding args object sent to the remote procedure will be
212 * `{ foo: true, bar: false }`.
213 * - `params: [ "test" ], filter: function(reply, args, extra) { ... }` -
214 * When the resultung generated function is invoked with
215 * `fn("foo", "bar", "baz")` then `{ "test": "foo" }` will be sent as
216 * argument to the remote procedure and the filter function will be
217 * invoked with `filterFn(reply, [ "foo" ], "bar", "baz")`
218 *
219 * @property {Object&lt;string,*>} [expect]
220 * Describes the expected return data structure. The given object is
221 * supposed to contain a single key selecting the value to use from
222 * the returned `ubus` reply object. The value of the sole key within
223 * the `expect` object is used to infer the expected type of the received
224 * `ubus` reply data.
225 *
226 * If the received data does not contain `expect`'s key, or if the
227 * type of the data differs from the type of the value in the expect
228 * object, the expect object's value is returned as default instead.
229 *
230 * The key in the `expect` object may be an empty string (`''`) in which
231 * case the entire reply object is selected instead of one of its subkeys.
232 *
233 * If the `expect` option is omitted, the received reply will be returned
234 * as-is, regardless of its format or type.
235 *
236 * Examples:
237 * - `expect: { '': { error: 'Invalid response' } }` -
238 * This requires the entire `ubus` reply to be a plain JavaScript
239 * object. If the reply isn't an object but e.g. an array or a numeric
240 * error code instead, it will get replaced with
241 * `{ error: 'Invalid response' }` instead.
242 * - `expect: { results: [] }` -
243 * This requires the received `ubus` reply to be an object containing
244 * a key `results` with an array as value. If the received reply does
245 * not contain such a key, or if `reply.results` points to a non-array
246 * value, the empty array (`[]`) will be used instead.
247 * - `expect: { success: false }` -
248 * This requires the received `ubus` reply to be an object containing
249 * a key `success` with a boolean value. If the reply does not contain
250 * `success` or if `reply.success` is not a boolean value, `false` will
251 * be returned as default instead.
252 *
253 * @property {LuCI.rpc~filterFn} [filter]
254 * Specfies an optional filter function which is invoked to transform the
255 * received reply data before it is returned to the caller.
256 *
257 */
258
259 /**
260 * The filter function is invoked to transform a received `ubus` RPC call
261 * reply before returning it to the caller.
262 *
263 * @callback LuCI.rpc~filterFn
264 *
265 * @param {*} data
266 * The received `ubus` reply data or a subset of it as described in the
267 * `expect` option of the RPC call declaration. In case of remote call
268 * errors, `data` is numeric `ubus` error code instead.
269 *
270 * @param {Array&lt;*>} args
271 * The arguments the RPC method has been invoked with.
272 *
273 * @param {...*} extraArgs
274 * All extraneous arguments passed to the RPC method exceeding the number
275 * of arguments describes in the RPC call declaration.
276 *
277 * @return {*}
278 * The return value of the filter function will be returned to the caller
279 * of the RPC method as-is.
280 */
281
282 /**
283 * The generated invocation function is returned by
284 * {@link LuCI.rpc#declare rpc.declare()} and encapsulates a single
285 * RPC method call.
286 *
287 * Calling this function will execute a remote `ubus` HTTP call request
288 * using the arguments passed to it as arguments and return a promise
289 * resolving to the received reply values.
290 *
291 * @callback LuCI.rpc~invokeFn
292 *
293 * @param {...*} params
294 * The parameters to pass to the remote procedure call. The given
295 * positional arguments will be named to named RPC parameters according
296 * to the names specified in the `params` array of the method declaration.
297 *
298 * Any additional parameters exceeding the amount of arguments in the
299 * `params` declaration are passed as private extra arguments to the
300 * declared filter function.
301 *
302 * @return {Promise&lt;*>}
303 * Returns a promise resolving to the result data of the remote `ubus`
304 * RPC method invocation, optionally substituted and filtered according
305 * to the `expect` and `filter` declarations.
306 */
307
308 /**
309 * Describes a remote RPC call procedure and returns a function
310 * implementing it.
311 *
312 * @param {LuCI.rpc.DeclareOptions} options
313 * If any object names are given, this function will return the method
314 * signatures of each given object.
315 *
316 * @returns {LuCI.rpc~invokeFn}
317 * Returns a new function implementing the method call described in
318 * `options`.
319 */
320 declare: function(options) {
321 return Function.prototype.bind.call(function(rpc, options) {
322 var args = this.varargs(arguments, 2);
323 return new Promise(function(resolveFn, rejectFn) {
324 /* build parameter object */
325 var p_off = 0;
326 var params = { };
327 if (Array.isArray(options.params))
328 for (p_off = 0; p_off &lt; options.params.length; p_off++)
329 params[options.params[p_off]] = args[p_off];
330
331 /* all remaining arguments are private args */
332 var priv = [ undefined, undefined ];
333 for (; p_off &lt; args.length; p_off++)
334 priv.push(args[p_off]);
335
336 /* store request info */
337 var req = {
338 expect: options.expect,
339 filter: options.filter,
340 resolve: resolveFn,
341 reject: rejectFn,
342 params: params,
343 priv: priv,
344 object: options.object,
345 method: options.method
346 };
347
348 /* build message object */
349 var msg = {
350 jsonrpc: '2.0',
351 id: rpcRequestID++,
352 method: 'call',
353 params: [
354 rpcSessionID,
355 options.object,
356 options.method,
357 params
358 ]
359 };
360
361 /* call rpc */
362 rpc.call(msg, rpc.parseCallReply.bind(rpc, req), options.nobatch);
363 });
364 }, this, this, options);
365 },
366
367 /**
368 * Returns the current RPC session id.
369 *
370 * @returns {string}
371 * Returns the 32 byte session ID string used for authenticating remote
372 * requests.
373 */
374 getSessionID: function() {
375 return rpcSessionID;
376 },
377
378 /**
379 * Set the RPC session id to use.
380 *
381 * @param {string} sid
382 * Sets the 32 byte session ID string used for authenticating remote
383 * requests.
384 */
385 setSessionID: function(sid) {
386 rpcSessionID = sid;
387 },
388
389 /**
390 * Returns the current RPC base URL.
391 *
392 * @returns {string}
393 * Returns the RPC URL endpoint to issue requests against.
394 */
395 getBaseURL: function() {
396 return rpcBaseURL;
397 },
398
399 /**
400 * Set the RPC base URL to use.
401 *
402 * @param {string} sid
403 * Sets the RPC URL endpoint to issue requests against.
404 */
405 setBaseURL: function(url) {
406 rpcBaseURL = url;
407 },
408
409 /**
410 * Translates a numeric `ubus` error code into a human readable
411 * description.
412 *
413 * @param {number} statusCode
414 * The numeric status code.
415 *
416 * @returns {string}
417 * Returns the textual description of the code.
418 */
419 getStatusText: function(statusCode) {
420 switch (statusCode) {
421 case 0: return _('Command OK');
422 case 1: return _('Invalid command');
423 case 2: return _('Invalid argument');
424 case 3: return _('Method not found');
425 case 4: return _('Resource not found');
426 case 5: return _('No data received');
427 case 6: return _('Permission denied');
428 case 7: return _('Request timeout');
429 case 8: return _('Not supported');
430 case 9: return _('Unspecified error');
431 case 10: return _('Connection lost');
432 default: return _('Unknown error code');
433 }
434 },
435
436 /**
437 * Registered interceptor functions are invoked before the standard reply
438 * parsing and handling logic.
439 *
440 * By returning rejected promises, interceptor functions can cause the
441 * invocation function to fail, regardless of the received reply.
442 *
443 * Interceptors may also modify their message argument in-place to
444 * rewrite received replies before they're processed by the standard
445 * response handling code.
446 *
447 * A common use case for such functions is to detect failing RPC replies
448 * due to expired authentication in order to trigger a new login.
449 *
450 * @callback LuCI.rpc~interceptorFn
451 *
452 * @param {*} msg
453 * The unprocessed, JSON decoded remote RPC method call reply.
454 *
455 * Since interceptors run before the standard parsing logic, the reply
456 * data is not verified for correctness or filtered according to
457 * `expect` and `filter` specifications in the declarations.
458 *
459 * @param {Object} req
460 * The related request object which is an extended variant of the
461 * declaration object, allowing access to internals of the invocation
462 * function such as `filter`, `expect` or `params` values.
463 *
464 * @return {Promise&lt;*>|*}
465 * Interceptor functions may return a promise to defer response
466 * processing until some delayed work completed. Any values the returned
467 * promise resolves to are ignored.
468 *
469 * When the returned promise rejects with an error, the invocation
470 * function will fail too, forwarding the error to the caller.
471 */
472
473 /**
474 * Registers a new interceptor function.
475 *
476 * @param {LuCI.rpc~interceptorFn} interceptorFn
477 * The inteceptor function to register.
478 *
479 * @returns {LuCI.rpc~interceptorFn}
480 * Returns the given function value.
481 */
482 addInterceptor: function(interceptorFn) {
483 if (typeof(interceptorFn) == 'function')
484 rpcInterceptorFns.push(interceptorFn);
485 return interceptorFn;
486 },
487
488 /**
489 * Removes a registered interceptor function.
490 *
491 * @param {LuCI.rpc~interceptorFn} interceptorFn
492 * The inteceptor function to remove.
493 *
494 * @returns {boolean}
495 * Returns `true` if the given function has been removed or `false`
496 * if it has not been found.
497 */
498 removeInterceptor: function(interceptorFn) {
499 var oldlen = rpcInterceptorFns.length, i = oldlen;
500 while (i--)
501 if (rpcInterceptorFns[i] === interceptorFn)
502 rpcInterceptorFns.splice(i, 1);
503 return (rpcInterceptorFns.length &lt; oldlen);
504 }
505 });
506 </code></pre>
507 </article>
508 </section>
509
510
511
512
513 </div>
514
515 <nav>
516 <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="LuCI.html">LuCI</a></li><li><a href="LuCI.Class.html">Class</a></li><li><a href="LuCI.dom.html">dom</a></li><li><a href="LuCI.fs.html">fs</a></li><li><a href="LuCI.Headers.html">Headers</a></li><li><a href="LuCI.Network.html">Network</a></li><li><a href="LuCI.Network.Device.html">Device</a></li><li><a href="LuCI.Network.Hosts.html">Hosts</a></li><li><a href="LuCI.Network.Protocol.html">Protocol</a></li><li><a href="LuCI.Network.WifiDevice.html">WifiDevice</a></li><li><a href="LuCI.Network.WifiNetwork.html">WifiNetwork</a></li><li><a href="LuCI.Poll.html">Poll</a></li><li><a href="LuCI.Request.html">Request</a></li><li><a href="LuCI.Request.poll.html">poll</a></li><li><a href="LuCI.Response.html">Response</a></li><li><a href="LuCI.rpc.html">rpc</a></li><li><a href="LuCI.uci.html">uci</a></li><li><a href="LuCI.view.html">view</a></li><li><a href="LuCI.XHR.html">XHR</a></li></ul>
517 </nav>
518
519 <br class="clear">
520
521 <footer>
522 Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a> on Tue Nov 05 2019 09:33:05 GMT+0100 (Central European Standard Time)
523 </footer>
524
525 <script> prettyPrint(); </script>
526 <script src="scripts/linenumber.js"> </script>
527 </body>
528 </html>