import { store } from "../store/state.svelte"; const DEFAULT_URL = "http://127.0.0.1:4567"; function getServerUrl(): string { const url = store.settings.serverUrl; return typeof url === "string" && url.trim() ? url.replace(/\/$/, "") : DEFAULT_URL; } function getAuthHeader(): Record { const s = store.settings; if (!s.serverAuthEnabled) return {}; const user = typeof s.serverAuthUser === "string" ? s.serverAuthUser.trim() : ""; const pass = typeof s.serverAuthPass === "string" ? s.serverAuthPass.trim() : ""; if (user && pass) return { Authorization: `Basic ${btoa(`${user}:${pass}`)}` }; return {}; } function gqlUrl(): string { return `${getServerUrl()}/api/graphql`; } export function thumbUrl(path: string): string { if (!path) return ""; if (path.startsWith("http")) return path; return `${getServerUrl()}${path}`; } interface GQLResponse { data: T; errors?: { message: string }[]; } function abortableSleep(ms: number, signal?: AbortSignal): Promise { return new Promise((resolve, reject) => { if (signal?.aborted) { reject(new DOMException("Aborted", "AbortError")); return; } const timer = setTimeout(resolve, ms); signal?.addEventListener("abort", () => { clearTimeout(timer); reject(new DOMException("Aborted", "AbortError")); }, { once: true }); }); } async function fetchWithRetry( url: string, init: RequestInit, signal?: AbortSignal, retries = 3, delayMs = 300, ): Promise { if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); for (let i = 0; i < retries; i++) { if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); try { const res = await fetch(url, { ...init, signal }); if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); return res; } catch (e: any) { const isAbort = e?.name === "AbortError" || signal?.aborted; if (isAbort) throw new DOMException("Aborted", "AbortError"); if (i === retries - 1) throw e; await abortableSleep(delayMs * Math.pow(1.5, i), signal); } } throw new Error("unreachable"); } export async function gql( query: string, variables?: Record, signal?: AbortSignal, ): Promise { const res = await fetchWithRetry(gqlUrl(), { method: "POST", headers: { "Content-Type": "application/json", ...getAuthHeader() }, body: JSON.stringify({ query, variables }), }, signal); if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); if (!res.ok) throw new Error(`Suwayomi HTTP ${res.status}`); const json: GQLResponse = await res.json(); if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); if (json.errors?.length) throw new Error(json.errors[0].message); return json.data; }