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