'use strict';
-var rpcRequestRegistry = {},
- rpcRequestBatch = null,
- rpcRequestID = 1,
+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, cbFn) {
- var cb = cbFn.bind(this, req),
- q = '';
+ call: function(req, cb, nobatch) {
+ var 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]
- );
+ if (req[i].params)
+ q += '%s%s.%s'.format(
+ q ? ';' : '/',
+ req[i].params[1],
+ req[i].params[2]
+ );
}
- else {
+ else if (req.params) {
q += '/%s.%s'.format(req.params[1], req.params[2]);
}
return L.Request.post(rpcBaseURL + q, req, {
- timeout: (L.env.rpctimeout || 5) * 1000,
+ timeout: (L.env.rpctimeout || 20) * 1000,
+ nobatch: nobatch,
credentials: true
- }).then(cb);
+ }).then(cb, cb);
},
- handleListReply: function(req, msg) {
- var list = msg.result;
+ parseCallReply: function(req, res) {
+ var msg = null;
- /* verify message frame */
- if (typeof(msg) != 'object' || msg.jsonrpc != '2.0' || !msg.id || !Array.isArray(list))
- list = [ ];
+ if (res instanceof Error)
+ return req.reject(res);
- 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 || '?');
+ 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();
-
- if (!Array.isArray(reqs)) {
- msg = [ msg ];
- reqs = [ reqs ];
+ msg = res.json();
+ }
+ catch (e) {
+ return req.reject(e);
}
- 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';
+ /*
+ * 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);
+ },
- /* fetch response attribute and verify returned type */
- var ret = undefined;
+ handleCallReply: function(req, msg) {
+ var type = Object.prototype.toString,
+ ret = null;
+ try {
/* 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 (!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 || '?');
+ }
+ catch (e) {
+ return req.reject(e);
+ }
- if (req.expect) {
- for (var key in req.expect) {
- if (ret != null && key != '')
- ret = ret[key];
+ if (!req.object && !req.method) {
+ ret = msg.result;
+ }
+ else if (Array.isArray(msg.result)) {
+ ret = (msg.result.length > 1) ? msg.result[1] : msg.result[0];
+ }
- if (ret == null || type.call(ret) != type.call(req.expect[key]))
- ret = req.expect[key];
+ if (req.expect) {
+ for (var key in req.expect) {
+ if (ret != null && key != '')
+ ret = ret[key];
- break;
- }
- }
+ if (ret == null || type.call(ret) != type.call(req.expect[key]))
+ ret = req.expect[key];
- /* apply filter */
- if (typeof(req.filter) == 'function') {
- req.priv[0] = ret;
- req.priv[1] = req.params;
- ret = req.filter.apply(this, req.priv);
+ break;
}
+ }
- 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];
+ /* apply filter */
+ if (typeof(req.filter) == 'function') {
+ req.priv[0] = ret;
+ req.priv[1] = req.params;
+ ret = req.filter.apply(this, req.priv);
}
- return Promise.resolve(data);
+ req.resolve(ret);
},
list: function() {
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([]);
+ return new Promise(L.bind(function(resolveFn, rejectFn) {
+ /* store request info */
+ var req = {
+ resolve: resolveFn,
+ reject: rejectFn
+ };
- var req = rpcRequestBatch;
- rpcRequestBatch = null;
-
- /* call rpc */
- return this.call(req, this.handleCallReply);
+ /* call rpc */
+ this.call(msg, this.parseCallReply.bind(this, req));
+ }, this));
},
declare: function(options) {
priv.push(args[p_off]);
/* store request info */
- var req = rpcRequestRegistry[rpcRequestID] = {
+ var req = {
expect: options.expect,
filter: options.filter,
resolve: resolveFn,
reject: rejectFn,
params: params,
- priv: priv
+ priv: priv,
+ object: options.object,
+ method: options.method
};
/* build message object */
]
};
- /* 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);
+ rpc.call(msg, rpc.parseCallReply.bind(rpc, req), options.nobatch);
});
}, this, this, options);
},
setBaseURL: function(url) {
rpcBaseURL = url;
+ },
+
+ getStatusText: function(statusCode) {
+ switch (statusCode) {
+ case 0: return _('Command OK');
+ case 1: return _('Invalid command');
+ case 2: return _('Invalid argument');
+ case 3: return _('Method not found');
+ case 4: return _('Resource not found');
+ case 5: return _('No data received');
+ case 6: return _('Permission denied');
+ case 7: return _('Request timeout');
+ case 8: return _('Not supported');
+ case 9: return _('Unspecified error');
+ 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);
}
});