104 lines
3.4 KiB
JavaScript
Executable File
104 lines
3.4 KiB
JavaScript
Executable File
export class ApiError extends Error {
|
|
constructor(status, message, requestId = "") {
|
|
super(message);
|
|
this.status = status;
|
|
this.requestId = requestId;
|
|
}
|
|
|
|
get isNetworkError() {
|
|
return this.status === 0;
|
|
}
|
|
}
|
|
|
|
export function describeError(error) {
|
|
const message = error && error.message ? error.message : "Something went wrong.";
|
|
const alreadyReferenced = message.includes("reference");
|
|
if (error instanceof ApiError && error.requestId && error.status >= 500 && !alreadyReferenced) {
|
|
return `${message} (reference ${error.requestId})`;
|
|
}
|
|
return message;
|
|
}
|
|
|
|
const unauthorizedListeners = new Set();
|
|
|
|
export function onUnauthorized(listener) {
|
|
unauthorizedListeners.add(listener);
|
|
}
|
|
|
|
function parseBody(text) {
|
|
if (!text) return null;
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function fallbackMessage(status) {
|
|
if (status === 502 || status === 503 || status === 504) {
|
|
return `The server is not responding right now (${status}). Try again in a moment.`;
|
|
}
|
|
if (status === 413) return "The request was too large.";
|
|
return `Request failed (${status}).`;
|
|
}
|
|
|
|
async function send(path, options) {
|
|
try {
|
|
return await fetch(path, options);
|
|
} catch {
|
|
throw new ApiError(0, "Cannot reach the server. Check your connection.");
|
|
}
|
|
}
|
|
|
|
async function request(method, path, body) {
|
|
const options = { method, credentials: "same-origin", headers: {} };
|
|
if (body !== undefined) {
|
|
options.headers["Content-Type"] = "application/json";
|
|
options.body = JSON.stringify(body);
|
|
}
|
|
|
|
const response = await send(path, options);
|
|
let text = "";
|
|
try {
|
|
text = await response.text();
|
|
} catch {
|
|
throw new ApiError(0, "The connection dropped while reading the server response.");
|
|
}
|
|
const data = parseBody(text);
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401 && path !== "/api/login") {
|
|
unauthorizedListeners.forEach((listener) => listener());
|
|
}
|
|
const message = data && data.error ? data.error : fallbackMessage(response.status);
|
|
const requestId = (data && data.requestId) || response.headers.get("X-Request-ID") || "";
|
|
throw new ApiError(response.status, message, requestId);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function jobPath(id, suffix = "") {
|
|
return `/api/jobs/${encodeURIComponent(id)}${suffix}`;
|
|
}
|
|
|
|
export const api = {
|
|
session: () => request("GET", "/api/session"),
|
|
login: (username, password) => request("POST", "/api/login", { username, password }),
|
|
logout: () => request("POST", "/api/logout", {}),
|
|
libraries: () => request("GET", "/api/libraries"),
|
|
disk: () => request("GET", "/api/disk"),
|
|
settings: () => request("GET", "/api/settings"),
|
|
saveSettings: (settings) => request("POST", "/api/settings", settings),
|
|
jobs: () => request("GET", "/api/jobs"),
|
|
createJob: (payload) => request("POST", "/api/jobs", payload),
|
|
files: (id) => request("GET", jobPath(id, "/files")),
|
|
extract: (id, path) => request("POST", jobPath(id, "/extract"), { path }),
|
|
move: (id, payload) => request("POST", jobPath(id, "/move"), payload),
|
|
pause: (id) => request("POST", jobPath(id, "/pause"), {}),
|
|
resume: (id) => request("POST", jobPath(id, "/resume"), {}),
|
|
reorder: (id, direction) => request("POST", jobPath(id, "/queue"), { direction }),
|
|
remove: (id) => request("DELETE", jobPath(id)),
|
|
history: () => request("GET", "/api/history"),
|
|
clearHistory: () => request("DELETE", "/api/history"),
|
|
};
|