2020-03-25 17:05:25 +01:00
|
|
|
const ajax = (function() { "use strict";
|
|
|
|
|
2020-03-26 16:01:09 +01:00
|
|
|
class AjaxError extends Error {
|
|
|
|
constructor (message, response, ...rest) {
|
|
|
|
super(message, ...rest);
|
|
|
|
this.response = response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class NetworkError extends AjaxError {}
|
|
|
|
class NotOkError extends AjaxError {}
|
|
|
|
class UnexpectedMimeError extends AjaxError {}
|
|
|
|
class InvalidJsonError extends AjaxError {}
|
2020-03-25 17:05:25 +01:00
|
|
|
|
|
|
|
const MIME_JSON = "application/json";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrap a function so that it receives `this` as first argument.
|
|
|
|
*/
|
|
|
|
function thisToArg(f) {
|
|
|
|
return function(...rest) {
|
|
|
|
return f(this, ...rest);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function xhrInitForPromise(resolve, reject, url, method, headers) {
|
|
|
|
const t_resolve = thisToArg(resolve),
|
|
|
|
t_reject = thisToArg(reject);
|
|
|
|
|
|
|
|
let xhr = new XMLHttpRequest();
|
|
|
|
xhr.addEventListener("load", t_resolve);
|
|
|
|
xhr.addEventListener("error", t_resolve);
|
|
|
|
xhr.addEventListener("abort", t_reject);
|
|
|
|
xhr.addEventListener("timeout", t_reject);
|
|
|
|
xhr.open(method, url);
|
|
|
|
for (let header of Object.getOwnPropertyNames(headers)) {
|
|
|
|
xhr.setRequestHeader(header, headers[header]);
|
|
|
|
}
|
|
|
|
return xhr;
|
|
|
|
}
|
|
|
|
|
|
|
|
function xhrParseJsonResponse(xhr) {
|
2020-03-26 16:01:09 +01:00
|
|
|
xhr.responseJson = null;
|
|
|
|
xhr.ok = false;
|
2020-03-25 17:05:25 +01:00
|
|
|
|
2020-03-26 16:01:09 +01:00
|
|
|
if (xhr.responseText) {
|
2020-03-25 17:05:25 +01:00
|
|
|
const contentType = xhr.getResponseHeader("Content-Type");
|
|
|
|
if (contentType != MIME_JSON) {
|
2020-03-26 16:01:09 +01:00
|
|
|
throw new UnexpectedMimeError(`Server did not reply with JSON but with ${contentType}`, xhr);
|
2020-03-25 17:05:25 +01:00
|
|
|
}
|
|
|
|
try {
|
2020-03-26 16:01:09 +01:00
|
|
|
xhr.responseJson = JSON.parse(xhr.responseText);
|
2020-03-25 17:05:25 +01:00
|
|
|
} catch(e) {
|
2020-03-26 16:01:09 +01:00
|
|
|
throw new InvalidJsonError("Server replied with JSON that we couldn't parse", xhr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xhr.ok = 200 <= xhr.status && xhr.status < 300;
|
|
|
|
if (!xhr.ok) {
|
|
|
|
console.error(xhr);
|
|
|
|
if (xhr.status === 0) {
|
|
|
|
throw new NetworkError("Failed to connect to server. Developer console may have more information", xhr);
|
|
|
|
} else {
|
|
|
|
throw new NotOkError(xhr.statusText, xhr);
|
2020-03-25 17:05:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-26 16:01:09 +01:00
|
|
|
return xhr;
|
2020-03-25 17:05:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function getJson(url, options={}) {
|
|
|
|
if (!options.headers) options.headers = {};
|
|
|
|
options.headers["Accept"] = MIME_JSON;
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let xhr = xhrInitForPromise(resolve, reject, url, "GET", options.headers);
|
|
|
|
xhr.send();
|
|
|
|
}).then(xhrParseJsonResponse);
|
|
|
|
}
|
|
|
|
|
2020-03-26 16:01:09 +01:00
|
|
|
function postJson(url, data=undefined, options={}) {
|
2020-03-25 17:05:25 +01:00
|
|
|
if (!options.headers) options.headers = {};
|
2020-03-26 16:35:33 +01:00
|
|
|
options.headers["Content-Type"] = MIME_JSON;
|
|
|
|
options.headers["Accept"] = MIME_JSON;
|
2020-03-25 17:05:25 +01:00
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let xhr = xhrInitForPromise(resolve, reject, url, "POST", options.headers);
|
2020-03-26 16:01:09 +01:00
|
|
|
if (data === undefined) {
|
|
|
|
xhr.send();
|
|
|
|
} else {
|
|
|
|
xhr.send(JSON.stringify(data));
|
|
|
|
}
|
2020-03-25 17:05:25 +01:00
|
|
|
}).then(xhrParseJsonResponse);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2020-03-26 16:01:09 +01:00
|
|
|
NetworkError, NotOkError, UnexpectedMimeError, InvalidJsonError,
|
2020-03-25 17:05:25 +01:00
|
|
|
getJson, postJson
|
|
|
|
};
|
|
|
|
|
|
|
|
})();
|