From 093b395cc18b6a9ccc43038884e487dd82766c31 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Sun, 3 May 2026 11:47:18 -0500 Subject: [PATCH] Fix: Re-Try UI Login Token + GQL Wait --- src/api/client.ts | 57 ++++++++++++++++++++++++++-------------- src/store/boot.svelte.ts | 2 ++ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/api/client.ts b/src/api/client.ts index 462eeb9..4adab2d 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -4,6 +4,19 @@ import { boot } from "@store/boot.svelte"; const DEFAULT_URL = "http://127.0.0.1:4567"; +type ReauthResolver = () => void; +let _reauthQueue: ReauthResolver[] = []; + +export function notifyReauthSuccess() { + const queue = _reauthQueue; + _reauthQueue = []; + queue.forEach(resolve => resolve()); +} + +function waitForReauth(): Promise { + return new Promise(resolve => { _reauthQueue.push(resolve); }); +} + export function getServerUrl(): string { const url = store.settings.serverUrl; return typeof url === "string" && url.trim() ? url.replace(/\/$/, "") : DEFAULT_URL; @@ -95,24 +108,30 @@ export async function gql( variables?: Record, signal?: AbortSignal, ): Promise { - const res = await fetchWithRetry( - `${getServerUrl()}/api/graphql`, - { method: "POST", headers: { "Content-Type": "application/json" }, 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) { - const isAuthError = json.errors.some(e => /unauthorized|unauthenticated/i.test(e.message)); - if (isAuthError && !boot.skipped) { - boot.sessionExpired = true; - boot.loginRequired = true; - boot.loginUser = store.settings.serverAuthUser ?? ""; - throw new AuthRequiredError(json.errors[0].message); + const attempt = async (): Promise => { + const res = await fetchWithRetry( + `${getServerUrl()}/api/graphql`, + { method: "POST", headers: { "Content-Type": "application/json" }, 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) { + const isAuthError = json.errors.some(e => /unauthorized|unauthenticated/i.test(e.message)); + if (isAuthError && !boot.skipped) { + boot.sessionExpired = true; + boot.loginRequired = true; + boot.loginUser = store.settings.serverAuthUser ?? ""; + await waitForReauth(); + if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); + return attempt(); + } + throw new Error(json.errors[0].message); } - throw new Error(json.errors[0].message); - } - return json.data; + return json.data; + }; + + return attempt(); } \ No newline at end of file diff --git a/src/store/boot.svelte.ts b/src/store/boot.svelte.ts index 6ffbe6a..893dba1 100644 --- a/src/store/boot.svelte.ts +++ b/src/store/boot.svelte.ts @@ -2,6 +2,7 @@ import { store } from "@store/state.svelte"; import { probeServer, loginBasic, loginUI } from "@core/auth"; import { trackingState } from "@features/tracking/store/trackingState.svelte"; import { loadAllStores } from "@core/persistence/persist"; +import { notifyReauthSuccess } from "@api/client"; const MAX_ATTEMPTS = 40; @@ -107,6 +108,7 @@ export async function submitLogin(onSuccess: () => void): Promise { boot.skipped = false; boot.loginPass = ""; boot.loginError = null; + notifyReauthSuccess(); trackingState.bootSync().catch(() => {}); onSuccess(); } catch (e: any) {