luci-base: properly handle promise targets in Request.request()
Under some circumstances, ubus RPC requests may be initiated while LuCI is
still resolving the `rpcBaseURL` value. In this situation, the `target`
argument of the `request()` call will be a pending promise object which
results in an invalid URL when serialized by `expandURL()`, leading to an
`Failed to execute 'open' on 'XMLHttpRequest': Invalid URL` exception.
This commonly occured on the index status page which immediately initiates
ubus RPC calls on load to discover existing status page partials.
Solve the issue by filtering the given `target` argument through
`Promise.resolve()` before expanding the URL and initiating the actual
request.
Fixes: #3747
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
(cherry picked from commit 5663fd596b
)
This commit is contained in:
parent
cc582ebfb3
commit
31a27f3087
1 changed files with 95 additions and 93 deletions
|
@ -695,115 +695,117 @@
|
|||
* The resulting HTTP response.
|
||||
*/
|
||||
request: function(target, options) {
|
||||
var state = { xhr: new XMLHttpRequest(), url: this.expandURL(target), start: Date.now() },
|
||||
opt = Object.assign({}, options, state),
|
||||
content = null,
|
||||
contenttype = null,
|
||||
callback = this.handleReadyStateChange;
|
||||
return Promise.resolve(target).then((function(url) {
|
||||
var state = { xhr: new XMLHttpRequest(), url: this.expandURL(url), start: Date.now() },
|
||||
opt = Object.assign({}, options, state),
|
||||
content = null,
|
||||
contenttype = null,
|
||||
callback = this.handleReadyStateChange;
|
||||
|
||||
return new Promise(function(resolveFn, rejectFn) {
|
||||
opt.xhr.onreadystatechange = callback.bind(opt, resolveFn, rejectFn);
|
||||
opt.method = String(opt.method || 'GET').toUpperCase();
|
||||
return new Promise(function(resolveFn, rejectFn) {
|
||||
opt.xhr.onreadystatechange = callback.bind(opt, resolveFn, rejectFn);
|
||||
opt.method = String(opt.method || 'GET').toUpperCase();
|
||||
|
||||
if ('query' in opt) {
|
||||
var q = (opt.query != null) ? Object.keys(opt.query).map(function(k) {
|
||||
if (opt.query[k] != null) {
|
||||
var v = (typeof(opt.query[k]) == 'object')
|
||||
? JSON.stringify(opt.query[k])
|
||||
: String(opt.query[k]);
|
||||
if ('query' in opt) {
|
||||
var q = (opt.query != null) ? Object.keys(opt.query).map(function(k) {
|
||||
if (opt.query[k] != null) {
|
||||
var v = (typeof(opt.query[k]) == 'object')
|
||||
? JSON.stringify(opt.query[k])
|
||||
: String(opt.query[k]);
|
||||
|
||||
return '%s=%s'.format(encodeURIComponent(k), encodeURIComponent(v));
|
||||
}
|
||||
else {
|
||||
return encodeURIComponent(k);
|
||||
}
|
||||
}).join('&') : '';
|
||||
return '%s=%s'.format(encodeURIComponent(k), encodeURIComponent(v));
|
||||
}
|
||||
else {
|
||||
return encodeURIComponent(k);
|
||||
}
|
||||
}).join('&') : '';
|
||||
|
||||
if (q !== '') {
|
||||
switch (opt.method) {
|
||||
case 'GET':
|
||||
case 'HEAD':
|
||||
case 'OPTIONS':
|
||||
opt.url += ((/\?/).test(opt.url) ? '&' : '?') + q;
|
||||
break;
|
||||
if (q !== '') {
|
||||
switch (opt.method) {
|
||||
case 'GET':
|
||||
case 'HEAD':
|
||||
case 'OPTIONS':
|
||||
opt.url += ((/\?/).test(opt.url) ? '&' : '?') + q;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (content == null) {
|
||||
content = q;
|
||||
contenttype = 'application/x-www-form-urlencoded';
|
||||
default:
|
||||
if (content == null) {
|
||||
content = q;
|
||||
contenttype = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!opt.cache)
|
||||
opt.url += ((/\?/).test(opt.url) ? '&' : '?') + (new Date()).getTime();
|
||||
if (!opt.cache)
|
||||
opt.url += ((/\?/).test(opt.url) ? '&' : '?') + (new Date()).getTime();
|
||||
|
||||
if (isQueueableRequest(opt)) {
|
||||
requestQueue.push([opt, rejectFn, resolveFn]);
|
||||
requestAnimationFrame(flushRequestQueue);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('username' in opt && 'password' in opt)
|
||||
opt.xhr.open(opt.method, opt.url, true, opt.username, opt.password);
|
||||
else
|
||||
opt.xhr.open(opt.method, opt.url, true);
|
||||
|
||||
opt.xhr.responseType = opt.responseType || 'text';
|
||||
|
||||
if ('overrideMimeType' in opt.xhr)
|
||||
opt.xhr.overrideMimeType('application/octet-stream');
|
||||
|
||||
if ('timeout' in opt)
|
||||
opt.xhr.timeout = +opt.timeout;
|
||||
|
||||
if ('credentials' in opt)
|
||||
opt.xhr.withCredentials = !!opt.credentials;
|
||||
|
||||
if (opt.content != null) {
|
||||
switch (typeof(opt.content)) {
|
||||
case 'function':
|
||||
content = opt.content(opt.xhr);
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (!(opt.content instanceof FormData)) {
|
||||
content = JSON.stringify(opt.content);
|
||||
contenttype = 'application/json';
|
||||
}
|
||||
else {
|
||||
content = opt.content;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
content = String(opt.content);
|
||||
if (isQueueableRequest(opt)) {
|
||||
requestQueue.push([opt, rejectFn, resolveFn]);
|
||||
requestAnimationFrame(flushRequestQueue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ('headers' in opt)
|
||||
for (var header in opt.headers)
|
||||
if (opt.headers.hasOwnProperty(header)) {
|
||||
if (header.toLowerCase() != 'content-type')
|
||||
opt.xhr.setRequestHeader(header, opt.headers[header]);
|
||||
else
|
||||
contenttype = opt.headers[header];
|
||||
if ('username' in opt && 'password' in opt)
|
||||
opt.xhr.open(opt.method, opt.url, true, opt.username, opt.password);
|
||||
else
|
||||
opt.xhr.open(opt.method, opt.url, true);
|
||||
|
||||
opt.xhr.responseType = opt.responseType || 'text';
|
||||
|
||||
if ('overrideMimeType' in opt.xhr)
|
||||
opt.xhr.overrideMimeType('application/octet-stream');
|
||||
|
||||
if ('timeout' in opt)
|
||||
opt.xhr.timeout = +opt.timeout;
|
||||
|
||||
if ('credentials' in opt)
|
||||
opt.xhr.withCredentials = !!opt.credentials;
|
||||
|
||||
if (opt.content != null) {
|
||||
switch (typeof(opt.content)) {
|
||||
case 'function':
|
||||
content = opt.content(opt.xhr);
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (!(opt.content instanceof FormData)) {
|
||||
content = JSON.stringify(opt.content);
|
||||
contenttype = 'application/json';
|
||||
}
|
||||
else {
|
||||
content = opt.content;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
content = String(opt.content);
|
||||
}
|
||||
}
|
||||
|
||||
if ('progress' in opt && 'upload' in opt.xhr)
|
||||
opt.xhr.upload.addEventListener('progress', opt.progress);
|
||||
if ('headers' in opt)
|
||||
for (var header in opt.headers)
|
||||
if (opt.headers.hasOwnProperty(header)) {
|
||||
if (header.toLowerCase() != 'content-type')
|
||||
opt.xhr.setRequestHeader(header, opt.headers[header]);
|
||||
else
|
||||
contenttype = opt.headers[header];
|
||||
}
|
||||
|
||||
if (contenttype != null)
|
||||
opt.xhr.setRequestHeader('Content-Type', contenttype);
|
||||
if ('progress' in opt && 'upload' in opt.xhr)
|
||||
opt.xhr.upload.addEventListener('progress', opt.progress);
|
||||
|
||||
try {
|
||||
opt.xhr.send(content);
|
||||
}
|
||||
catch (e) {
|
||||
rejectFn.call(opt, e);
|
||||
}
|
||||
});
|
||||
if (contenttype != null)
|
||||
opt.xhr.setRequestHeader('Content-Type', contenttype);
|
||||
|
||||
try {
|
||||
opt.xhr.send(content);
|
||||
}
|
||||
catch (e) {
|
||||
rejectFn.call(opt, e);
|
||||
}
|
||||
});
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
handleReadyStateChange: function(resolveFn, rejectFn, ev) {
|
||||
|
|
Loading…
Reference in a new issue