diff options
| author | Jo-Philipp Wich <jo@mein.io> | 2019-02-07 18:53:25 +0100 |
|---|---|---|
| committer | Jo-Philipp Wich <jo@mein.io> | 2019-07-07 15:25:49 +0200 |
| commit | 1dd910148eaf7b9ed7130d1a067465dd43940da3 (patch) | |
| tree | e7e928433ace878e32719f369879d64ad76011c5 /modules/luci-base/htdocs/luci-static/resources/rpc.js | |
| parent | c89bbd50fdba8b07930c29bae6ca71cc54515e2c (diff) | |
luci-base: add uci.js and rpc.js classes
Add a new rpc.js class which provides low level facilities to exchanges
messages with the ubus rpc endpoint.
Also introduce a new uci.js class which provides client side uci
manipulation routines.
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'modules/luci-base/htdocs/luci-static/resources/rpc.js')
| -rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/rpc.js | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/rpc.js b/modules/luci-base/htdocs/luci-static/resources/rpc.js new file mode 100644 index 0000000000..cc22d0aeb4 --- /dev/null +++ b/modules/luci-base/htdocs/luci-static/resources/rpc.js @@ -0,0 +1,196 @@ +'use strict'; + +var rpcRequestRegistry = {}, + rpcRequestBatch = null, + rpcRequestID = 1, + rpcSessionID = L.env.sessionid || '00000000000000000000000000000000'; + +return L.Class.extend({ + call: function(req, cbFn) { + var cb = cbFn.bind(this, req), + q = ''; + + if (Array.isArray(req)) { + if (req.length == 0) + return Promise.resolve([]); + + for (var i = 0; i < req.length; i++) + q += '%s%s.%s'.format( + q ? ';' : '/', + req[i].params[1], + req[i].params[2] + ); + } + else { + q += '/%s.%s'.format(req.params[1], req.params[2]); + } + + return L.Request.post(L.url('admin/ubus') + q, req, { + timeout: (L.env.rpctimeout || 5) * 1000, + credentials: true + }).then(cb); + }, + + handleListReply: function(req, msg) { + var list = msg.result; + + /* verify message frame */ + if (typeof(msg) != 'object' || msg.jsonrpc != '2.0' || !msg.id || !Array.isArray(list)) + list = [ ]; + + req.resolve(list); + }, + + handleCallReply: function(reqs, res) { + var type = Object.prototype.toString, + data = [], + msg = null; + + if (!res.ok) + L.error('RPCError', 'RPC call failed with HTTP error %d: %s', + res.status, res.statusText || '?'); + + msg = res.json(); + + if (!Array.isArray(reqs)) { + msg = [ msg ]; + reqs = [ reqs ]; + } + + for (var i = 0; i < msg.length; i++) { + /* fetch related request info */ + var req = rpcRequestRegistry[reqs[i].id]; + if (typeof(req) != 'object') + throw 'No related request for JSON response'; + + /* fetch response attribute and verify returned type */ + var ret = undefined; + + /* verify message frame */ + if (typeof(msg[i]) == 'object' && msg[i].jsonrpc == '2.0') { + if (typeof(msg[i].error) == 'object' && msg[i].error.code && msg[i].error.message) + req.reject(new Error('RPC call failed with error %d: %s' + .format(msg[i].error.code, msg[i].error.message || '?'))); + else if (Array.isArray(msg[i].result) && msg[i].result[0] == 0) + ret = (msg[i].result.length > 1) ? msg[i].result[1] : msg[i].result[0]; + } + else { + req.reject(new Error('Invalid message frame received')); + } + + if (req.expect) { + for (var key in req.expect) { + if (ret != null && key != '') + ret = ret[key]; + + if (ret == null || type.call(ret) != type.call(req.expect[key])) + ret = req.expect[key]; + + break; + } + } + + /* apply filter */ + if (typeof(req.filter) == 'function') { + req.priv[0] = ret; + req.priv[1] = req.params; + ret = req.filter.apply(this, req.priv); + } + + req.resolve(ret); + + /* store response data */ + if (typeof(req.index) == 'number') + data[req.index] = ret; + else + data = ret; + + /* delete request object */ + delete rpcRequestRegistry[reqs[i].id]; + } + + return Promise.resolve(data); + }, + + list: function() { + var msg = { + jsonrpc: '2.0', + id: rpcRequestID++, + method: 'list', + params: arguments.length ? this.varargs(arguments) : undefined + }; + + return this.call(msg, this.handleListReply); + }, + + batch: function() { + if (!Array.isArray(rpcRequestBatch)) + rpcRequestBatch = [ ]; + }, + + flush: function() { + if (!Array.isArray(rpcRequestBatch)) + return Promise.resolve([]); + + var req = rpcRequestBatch; + rpcRequestBatch = null; + + /* call rpc */ + return this.call(req, this.handleCallReply); + }, + + declare: function(options) { + return Function.prototype.bind.call(function(rpc, options) { + var args = this.varargs(arguments, 2); + return new Promise(function(resolveFn, rejectFn) { + /* build parameter object */ + var p_off = 0; + var params = { }; + if (Array.isArray(options.params)) + for (p_off = 0; p_off < options.params.length; p_off++) + params[options.params[p_off]] = args[p_off]; + + /* all remaining arguments are private args */ + var priv = [ undefined, undefined ]; + for (; p_off < args.length; p_off++) + priv.push(args[p_off]); + + /* store request info */ + var req = rpcRequestRegistry[rpcRequestID] = { + expect: options.expect, + filter: options.filter, + resolve: resolveFn, + reject: rejectFn, + params: params, + priv: priv + }; + + /* build message object */ + var msg = { + jsonrpc: '2.0', + id: rpcRequestID++, + method: 'call', + params: [ + rpcSessionID, + options.object, + options.method, + params + ] + }; + + /* when a batch is in progress then store index in request data + * and push message object onto the stack */ + if (Array.isArray(rpcRequestBatch)) + req.index = rpcRequestBatch.push(msg) - 1; + + /* call rpc */ + else + rpc.call(msg, rpc.handleCallReply); + }); + }, this, this, options); + }, + + setSessionID: function(sid) { + rpcSessionID = sid; + } +}); |
