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"), };