Merge pull request #3042 from muink/patch-1
[project/luci.git] / modules / luci-base / htdocs / luci-static / resources / rpc.js
1 'use strict';
2
3 var rpcRequestID = 1,
4 rpcSessionID = L.env.sessionid || '00000000000000000000000000000000',
5 rpcBaseURL = L.url('admin/ubus'),
6 rpcInterceptorFns = [];
7
8 return L.Class.extend({
9 call: function(req, cb, nobatch) {
10 var q = '';
11
12 if (Array.isArray(req)) {
13 if (req.length == 0)
14 return Promise.resolve([]);
15
16 for (var i = 0; i < req.length; i++)
17 if (req[i].params)
18 q += '%s%s.%s'.format(
19 q ? ';' : '/',
20 req[i].params[1],
21 req[i].params[2]
22 );
23 }
24 else if (req.params) {
25 q += '/%s.%s'.format(req.params[1], req.params[2]);
26 }
27
28 return L.Request.post(rpcBaseURL + q, req, {
29 timeout: (L.env.rpctimeout || 20) * 1000,
30 nobatch: nobatch,
31 credentials: true
32 }).then(cb, cb);
33 },
34
35 parseCallReply: function(req, res) {
36 var msg = null;
37
38 if (res instanceof Error)
39 return req.reject(res);
40
41 try {
42 if (!res.ok)
43 L.raise('RPCError', 'RPC call to %s/%s failed with HTTP error %d: %s',
44 req.object, req.method, res.status, res.statusText || '?');
45
46 msg = res.json();
47 }
48 catch (e) {
49 return req.reject(e);
50 }
51
52 /*
53 * The interceptor args are intentionally swapped.
54 * Response is passed as first arg to align with Request class interceptors
55 */
56 Promise.all(rpcInterceptorFns.map(function(fn) { return fn(msg, req) }))
57 .then(this.handleCallReply.bind(this, req, msg))
58 .catch(req.reject);
59 },
60
61 handleCallReply: function(req, msg) {
62 var type = Object.prototype.toString,
63 ret = null;
64
65 try {
66 /* verify message frame */
67 if (!L.isObject(msg) || msg.jsonrpc != '2.0')
68 L.raise('RPCError', 'RPC call to %s/%s returned invalid message frame',
69 req.object, req.method);
70
71 /* check error condition */
72 if (L.isObject(msg.error) && msg.error.code && msg.error.message)
73 L.raise('RPCError', 'RPC call to %s/%s failed with error %d: %s',
74 req.object, req.method, msg.error.code, msg.error.message || '?');
75 }
76 catch (e) {
77 return req.reject(e);
78 }
79
80 if (!req.object && !req.method) {
81 ret = msg.result;
82 }
83 else if (Array.isArray(msg.result)) {
84 ret = (msg.result.length > 1) ? msg.result[1] : msg.result[0];
85 }
86
87 if (req.expect) {
88 for (var key in req.expect) {
89 if (ret != null && key != '')
90 ret = ret[key];
91
92 if (ret == null || type.call(ret) != type.call(req.expect[key]))
93 ret = req.expect[key];
94
95 break;
96 }
97 }
98
99 /* apply filter */
100 if (typeof(req.filter) == 'function') {
101 req.priv[0] = ret;
102 req.priv[1] = req.params;
103 ret = req.filter.apply(this, req.priv);
104 }
105
106 req.resolve(ret);
107 },
108
109 list: function() {
110 var msg = {
111 jsonrpc: '2.0',
112 id: rpcRequestID++,
113 method: 'list',
114 params: arguments.length ? this.varargs(arguments) : undefined
115 };
116
117 return new Promise(L.bind(function(resolveFn, rejectFn) {
118 /* store request info */
119 var req = {
120 resolve: resolveFn,
121 reject: rejectFn
122 };
123
124 /* call rpc */
125 this.call(msg, this.parseCallReply.bind(this, req));
126 }, this));
127 },
128
129 declare: function(options) {
130 return Function.prototype.bind.call(function(rpc, options) {
131 var args = this.varargs(arguments, 2);
132 return new Promise(function(resolveFn, rejectFn) {
133 /* build parameter object */
134 var p_off = 0;
135 var params = { };
136 if (Array.isArray(options.params))
137 for (p_off = 0; p_off < options.params.length; p_off++)
138 params[options.params[p_off]] = args[p_off];
139
140 /* all remaining arguments are private args */
141 var priv = [ undefined, undefined ];
142 for (; p_off < args.length; p_off++)
143 priv.push(args[p_off]);
144
145 /* store request info */
146 var req = {
147 expect: options.expect,
148 filter: options.filter,
149 resolve: resolveFn,
150 reject: rejectFn,
151 params: params,
152 priv: priv,
153 object: options.object,
154 method: options.method
155 };
156
157 /* build message object */
158 var msg = {
159 jsonrpc: '2.0',
160 id: rpcRequestID++,
161 method: 'call',
162 params: [
163 rpcSessionID,
164 options.object,
165 options.method,
166 params
167 ]
168 };
169
170 /* call rpc */
171 rpc.call(msg, rpc.parseCallReply.bind(rpc, req), options.nobatch);
172 });
173 }, this, this, options);
174 },
175
176 getSessionID: function() {
177 return rpcSessionID;
178 },
179
180 setSessionID: function(sid) {
181 rpcSessionID = sid;
182 },
183
184 getBaseURL: function() {
185 return rpcBaseURL;
186 },
187
188 setBaseURL: function(url) {
189 rpcBaseURL = url;
190 },
191
192 getStatusText: function(statusCode) {
193 switch (statusCode) {
194 case 0: return _('Command OK');
195 case 1: return _('Invalid command');
196 case 2: return _('Invalid argument');
197 case 3: return _('Method not found');
198 case 4: return _('Resource not found');
199 case 5: return _('No data received');
200 case 6: return _('Permission denied');
201 case 7: return _('Request timeout');
202 case 8: return _('Not supported');
203 case 9: return _('Unspecified error');
204 case 10: return _('Connection lost');
205 default: return _('Unknown error code');
206 }
207 },
208
209 addInterceptor: function(interceptorFn) {
210 if (typeof(interceptorFn) == 'function')
211 rpcInterceptorFns.push(interceptorFn);
212 return interceptorFn;
213 },
214
215 removeInterceptor: function(interceptorFn) {
216 var oldlen = rpcInterceptorFns.length, i = oldlen;
217 while (i--)
218 if (rpcInterceptorFns[i] === interceptorFn)
219 rpcInterceptorFns.splice(i, 1);
220 return (rpcInterceptorFns.length < oldlen);
221 }
222 });