From b79ee99e8a1568be95197dbaf26e2e7de0a70d3c Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Fri, 1 May 2026 11:09:29 -0500 Subject: [PATCH] Fix: Linked CORS Bypass to UI-LOGIN --- Todo | 5 +--- src/api/client.ts | 38 ++++++++++++++++++++++-- src/core/auth.ts | 7 +++-- src/core/cache/imageCache.ts | 15 ++++++++-- src/core/cache/pageCache.ts | 4 +-- src/features/home/components/Home.svelte | 5 +++- src/shared/manga/Thumbnail.svelte | 10 +++++-- vite.config.ts | 2 +- 8 files changed, 66 insertions(+), 20 deletions(-) diff --git a/Todo b/Todo index d2cc7ce..7dc8b13 100644 --- a/Todo +++ b/Todo @@ -35,8 +35,5 @@ In-Progress: - Wire Series-Detail Refresh to Fix Manga-Metadata (MAJOR) - - Add Disable Auto-Completed Feature to Library - - Cap ReaderSettings Zoom (100) - - Fix SeriesDetail Chapter Amount (Link to Scanlator Filtering) - + - UI LOGIN DOES NOT WORK OFFLINE Notes from last time: diff --git a/src/api/client.ts b/src/api/client.ts index b105592..462eeb9 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -1,10 +1,10 @@ import { store } from "@store/state.svelte"; -import { fetchAuthenticated, AuthRequiredError } from "../core/auth"; +import { fetchAuthenticated, AuthRequiredError, uiAuth } from "../core/auth"; import { boot } from "@store/boot.svelte"; const DEFAULT_URL = "http://127.0.0.1:4567"; -function getServerUrl(): string { +export function getServerUrl(): string { const url = store.settings.serverUrl; return typeof url === "string" && url.trim() ? url.replace(/\/$/, "") : DEFAULT_URL; } @@ -12,7 +12,20 @@ function getServerUrl(): string { export function plainThumbUrl(path: string): string { if (!path) return ""; if (path.startsWith("http")) return path; - return `${getServerUrl()}${path}`; + + const base = `${getServerUrl()}${path}`; + const mode = store.settings.serverAuthMode ?? "NONE"; + + if (mode === "UI_LOGIN") { + const token = uiAuth.getToken(); + if (token) { + const url = new URL(base); + url.searchParams.set("authorization", token); + return url.toString(); + } + } + + return base; } export const thumbUrl = plainThumbUrl; @@ -58,6 +71,25 @@ async function fetchWithRetry( throw new Error("unreachable"); } +export async function fetchImage( + path: string, + signal?: AbortSignal, +): Promise<{ src: string; revoke: () => void }> { + if (!path) return { src: "", revoke: () => {} }; + + const url = path.startsWith("http") ? path : `${getServerUrl()}${path}`; + const mode = store.settings.serverAuthMode ?? "NONE"; + + if (mode === "NONE") return { src: url, revoke: () => {} }; + + const res = await fetchWithRetry(url, { method: "GET" }, signal); + if (!res.ok) throw new Error(`Image fetch failed: ${res.status}`); + + const blob = await res.blob(); + const src = URL.createObjectURL(blob); + return { src, revoke: () => URL.revokeObjectURL(src) }; +} + export async function gql( query: string, variables?: Record, diff --git a/src/core/auth.ts b/src/core/auth.ts index 3ba4cdd..0747ec6 100644 --- a/src/core/auth.ts +++ b/src/core/auth.ts @@ -9,12 +9,13 @@ export class AuthRequiredError extends Error { } } -let _accessToken: string | null = null; +const TOKEN_KEY = "moku_access_token"; +let _accessToken: string | null = sessionStorage.getItem(TOKEN_KEY); export const uiAuth = { getToken: () => _accessToken, - setToken: (t: string) => { _accessToken = t; }, - clearToken: () => { _accessToken = null; }, + setToken: (t: string) => { _accessToken = t; sessionStorage.setItem(TOKEN_KEY, t); }, + clearToken: () => { _accessToken = null; sessionStorage.removeItem(TOKEN_KEY); }, }; export const authSession = { diff --git a/src/core/cache/imageCache.ts b/src/core/cache/imageCache.ts index ea7ac97..18a8024 100644 --- a/src/core/cache/imageCache.ts +++ b/src/core/cache/imageCache.ts @@ -1,5 +1,6 @@ import { fetch as tauriFetch } from "@tauri-apps/plugin-http"; import { store } from "@store/state.svelte"; +import { uiAuth } from "@core/auth"; const cache = new Map(); const inflight = new Map>(); @@ -17,9 +18,17 @@ interface QueueEntry { const queue: QueueEntry[] = []; function getAuthHeaders(): Record { - const user = store.settings.serverAuthUser?.trim() ?? ""; - const pass = store.settings.serverAuthPass?.trim() ?? ""; - return user && pass ? { Authorization: `Basic ${btoa(`${user}:${pass}`)}` } : {}; + const mode = store.settings.serverAuthMode ?? "NONE"; + if (mode === "UI_LOGIN") { + const token = uiAuth.getToken(); + return token ? { Authorization: `Bearer ${token}` } : {}; + } + if (mode === "BASIC_AUTH") { + const user = store.settings.serverAuthUser?.trim() ?? ""; + const pass = store.settings.serverAuthPass?.trim() ?? ""; + return user && pass ? { Authorization: `Basic ${btoa(`${user}:${pass}`)}` } : {}; + } + return {}; } async function doFetch(url: string): Promise { diff --git a/src/core/cache/pageCache.ts b/src/core/cache/pageCache.ts index d10e3af..9169a21 100644 --- a/src/core/cache/pageCache.ts +++ b/src/core/cache/pageCache.ts @@ -1,4 +1,4 @@ -import { gql, plainThumbUrl } from "@api/client"; +import { gql, getServerUrl } from "@api/client"; import { getBlobUrl, preloadBlobUrls } from "@core/cache/imageCache"; import { dedupeRequest } from "@core/async/batchRequests"; import { FETCH_CHAPTER_PAGES } from "@api/mutations/chapters"; @@ -29,7 +29,7 @@ export function fetchPages( const p = dedupeRequest(`chapter-pages:${chapterId}`, () => gql<{ fetchChapterPages: { pages: string[] } }>(FETCH_CHAPTER_PAGES, { chapterId }) .then(d => { - const urls = d.fetchChapterPages.pages.map(p => plainThumbUrl(p)); + const urls = d.fetchChapterPages.pages.map(p => p.startsWith("http") ? p : `${getServerUrl()}${p}`); if (useBlob) { if (urls[priorityPage]) getBlobUrl(urls[priorityPage], urls.length + 999); preloadBlobUrls(urls.filter((_, i) => i !== priorityPage), urls.length); diff --git a/src/features/home/components/Home.svelte b/src/features/home/components/Home.svelte index 3534075..1f63872 100644 --- a/src/features/home/components/Home.svelte +++ b/src/features/home/components/Home.svelte @@ -97,7 +97,10 @@ const path = heroThumbSrc; const mode = store.settings.serverAuthMode ?? "NONE"; if (!path) { heroThumb = ""; return; } - if (mode !== "BASIC_AUTH") { heroThumb = thumbUrl(path); return; } + + const needsBlob = mode === "BASIC_AUTH" || mode === "UI_LOGIN"; + if (!needsBlob) { heroThumb = thumbUrl(path); return; } + getBlobUrl(thumbUrl(path)) .then(url => { heroThumb = url; }) .catch(() => { heroThumb = ""; }); diff --git a/src/shared/manga/Thumbnail.svelte b/src/shared/manga/Thumbnail.svelte index 38640c2..c6b0b3d 100644 --- a/src/shared/manga/Thumbnail.svelte +++ b/src/shared/manga/Thumbnail.svelte @@ -1,5 +1,5 @@