From f141433f5e38153e9dfbb8b320dfa70db1a47cf2 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Wed, 11 Sep 2019 09:25:11 +0200 Subject: [PATCH] luci-base: rpc.js: revamp error handling, add interceptor support Add two new functions L.rpc.addInterceptor() and L.rpc.removeInterceptor() which allow to register and remove interceptor functions which are invoked before the rpc reply result promise is fulfilled. Signed-off-by: Jo-Philipp Wich --- .../htdocs/luci-static/resources/rpc.js | 76 ++++++++++++++----- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/modules/luci-base/htdocs/luci-static/resources/rpc.js b/modules/luci-base/htdocs/luci-static/resources/rpc.js index e6a1bb24dc..9a0f0164ae 100644 --- a/modules/luci-base/htdocs/luci-static/resources/rpc.js +++ b/modules/luci-base/htdocs/luci-static/resources/rpc.js @@ -2,7 +2,8 @@ var rpcRequestID = 1, rpcSessionID = L.env.sessionid || '00000000000000000000000000000000', - rpcBaseURL = L.url('admin/ubus'); + rpcBaseURL = L.url('admin/ubus'), + rpcInterceptorFns = []; return L.Class.extend({ call: function(req, cb) { @@ -39,29 +40,50 @@ return L.Class.extend({ req.resolve(list); }, - handleCallReply: function(req, res) { - var type = Object.prototype.toString, - msg = null; + parseCallReply: function(req, res) { + var msg = null; - if (!res.ok) - L.error('RPCError', 'RPC call to %s/%s failed with HTTP error %d: %s', - req.object, req.method, res.status, res.statusText || '?'); + try { + if (!res.ok) + L.raise('RPCError', 'RPC call to %s/%s failed with HTTP error %d: %s', + req.object, req.method, res.status, res.statusText || '?'); - msg = res.json(); + msg = res.json(); + } + catch (e) { + return req.reject(e); + } - /* fetch response attribute and verify returned type */ - var ret = undefined; + /* + * The interceptor args are intentionally swapped. + * Response is passed as first arg to align with Request class interceptors + */ + Promise.all(rpcInterceptorFns.map(function(fn) { return fn(msg, req) })) + .then(this.handleCallReply.bind(this, req, msg)) + .catch(req.reject); + }, - /* verify message frame */ - if (typeof(msg) == 'object' && msg.jsonrpc == '2.0') { - if (typeof(msg.error) == 'object' && msg.error.code && msg.error.message) - req.reject(new Error('RPC call to %s/%s failed with error %d: %s' - .format(req.object, req.method, msg.error.code, msg.error.message || '?'))); - else if (Array.isArray(msg.result)) - ret = (msg.result.length > 1) ? msg.result[1] : msg.result[0]; + handleCallReply: function(req, msg) { + var type = Object.prototype.toString, + ret = null; + + try { + /* verify message frame */ + if (!L.isObject(msg) || msg.jsonrpc != '2.0') + L.raise('RPCError', 'RPC call to %s/%s returned invalid message frame', + req.object, req.method); + + /* check error condition */ + if (L.isObject(msg.error) && msg.error.code && msg.error.message) + L.raise('RPCError', 'RPC call to %s/%s failed with error %d: %s', + req.object, req.method, msg.error.code, msg.error.message || '?'); } - else { - req.reject(new Error('Invalid message frame received')); + catch (e) { + return req.reject(e); + } + + if (Array.isArray(msg.result)) { + ret = (msg.result.length > 1) ? msg.result[1] : msg.result[0]; } if (req.expect) { @@ -139,7 +161,7 @@ return L.Class.extend({ }; /* call rpc */ - rpc.call(msg, rpc.handleCallReply.bind(rpc, req)); + rpc.call(msg, rpc.parseCallReply.bind(rpc, req)); }); }, this, this, options); }, @@ -175,5 +197,19 @@ return L.Class.extend({ case 10: return _('Connection lost'); default: return _('Unknown error code'); } + }, + + addInterceptor: function(interceptorFn) { + if (typeof(interceptorFn) == 'function') + rpcInterceptorFns.push(interceptorFn); + return interceptorFn; + }, + + removeInterceptor: function(interceptorFn) { + var oldlen = rpcInterceptorFns.length, i = oldlen; + while (i--) + if (rpcInterceptorFns[i] === interceptorFn) + rpcInterceptorFns.splice(i, 1); + return (rpcInterceptorFns.length < oldlen); } }); -- 2.30.2