luci-base: rpc.js: revamp error handling, add interceptor support
[project/luci.git] / modules / luci-base / htdocs / luci-static / resources / rpc.js
index e6a1bb24dc96e9130fff2e85893c7adbcfe1185b..9a0f0164aeb32a8ecd5f3c90c0ab0ead43958070 100644 (file)
@@ -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);
        }
 });