Fix: Linked CORS Bypass to UI-LOGIN

This commit is contained in:
Youwes09
2026-05-01 11:09:29 -05:00
parent 80c4b9d9be
commit b79ee99e8a
8 changed files with 66 additions and 20 deletions
+1 -4
View File
@@ -35,8 +35,5 @@ In-Progress:
- Wire Series-Detail Refresh to Fix Manga-Metadata (MAJOR) - Wire Series-Detail Refresh to Fix Manga-Metadata (MAJOR)
- Add Disable Auto-Completed Feature to Library - UI LOGIN DOES NOT WORK OFFLINE
- Cap ReaderSettings Zoom (100)
- Fix SeriesDetail Chapter Amount (Link to Scanlator Filtering)
Notes from last time: Notes from last time:
+35 -3
View File
@@ -1,10 +1,10 @@
import { store } from "@store/state.svelte"; 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"; import { boot } from "@store/boot.svelte";
const DEFAULT_URL = "http://127.0.0.1:4567"; const DEFAULT_URL = "http://127.0.0.1:4567";
function getServerUrl(): string { export function getServerUrl(): string {
const url = store.settings.serverUrl; const url = store.settings.serverUrl;
return typeof url === "string" && url.trim() ? url.replace(/\/$/, "") : DEFAULT_URL; return typeof url === "string" && url.trim() ? url.replace(/\/$/, "") : DEFAULT_URL;
} }
@@ -12,7 +12,20 @@ function getServerUrl(): string {
export function plainThumbUrl(path: string): string { export function plainThumbUrl(path: string): string {
if (!path) return ""; if (!path) return "";
if (path.startsWith("http")) return path; 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; export const thumbUrl = plainThumbUrl;
@@ -58,6 +71,25 @@ async function fetchWithRetry(
throw new Error("unreachable"); 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<T>( export async function gql<T>(
query: string, query: string,
variables?: Record<string, unknown>, variables?: Record<string, unknown>,
+4 -3
View File
@@ -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 = { export const uiAuth = {
getToken: () => _accessToken, getToken: () => _accessToken,
setToken: (t: string) => { _accessToken = t; }, setToken: (t: string) => { _accessToken = t; sessionStorage.setItem(TOKEN_KEY, t); },
clearToken: () => { _accessToken = null; }, clearToken: () => { _accessToken = null; sessionStorage.removeItem(TOKEN_KEY); },
}; };
export const authSession = { export const authSession = {
+9
View File
@@ -1,5 +1,6 @@
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"; import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
import { store } from "@store/state.svelte"; import { store } from "@store/state.svelte";
import { uiAuth } from "@core/auth";
const cache = new Map<string, string>(); const cache = new Map<string, string>();
const inflight = new Map<string, Promise<string>>(); const inflight = new Map<string, Promise<string>>();
@@ -17,9 +18,17 @@ interface QueueEntry {
const queue: QueueEntry[] = []; const queue: QueueEntry[] = [];
function getAuthHeaders(): Record<string, string> { function getAuthHeaders(): Record<string, string> {
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 user = store.settings.serverAuthUser?.trim() ?? "";
const pass = store.settings.serverAuthPass?.trim() ?? ""; const pass = store.settings.serverAuthPass?.trim() ?? "";
return user && pass ? { Authorization: `Basic ${btoa(`${user}:${pass}`)}` } : {}; return user && pass ? { Authorization: `Basic ${btoa(`${user}:${pass}`)}` } : {};
}
return {};
} }
async function doFetch(url: string): Promise<string> { async function doFetch(url: string): Promise<string> {
+2 -2
View File
@@ -1,4 +1,4 @@
import { gql, plainThumbUrl } from "@api/client"; import { gql, getServerUrl } from "@api/client";
import { getBlobUrl, preloadBlobUrls } from "@core/cache/imageCache"; import { getBlobUrl, preloadBlobUrls } from "@core/cache/imageCache";
import { dedupeRequest } from "@core/async/batchRequests"; import { dedupeRequest } from "@core/async/batchRequests";
import { FETCH_CHAPTER_PAGES } from "@api/mutations/chapters"; import { FETCH_CHAPTER_PAGES } from "@api/mutations/chapters";
@@ -29,7 +29,7 @@ export function fetchPages(
const p = dedupeRequest(`chapter-pages:${chapterId}`, () => const p = dedupeRequest(`chapter-pages:${chapterId}`, () =>
gql<{ fetchChapterPages: { pages: string[] } }>(FETCH_CHAPTER_PAGES, { chapterId }) gql<{ fetchChapterPages: { pages: string[] } }>(FETCH_CHAPTER_PAGES, { chapterId })
.then(d => { .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 (useBlob) {
if (urls[priorityPage]) getBlobUrl(urls[priorityPage], urls.length + 999); if (urls[priorityPage]) getBlobUrl(urls[priorityPage], urls.length + 999);
preloadBlobUrls(urls.filter((_, i) => i !== priorityPage), urls.length); preloadBlobUrls(urls.filter((_, i) => i !== priorityPage), urls.length);
+4 -1
View File
@@ -97,7 +97,10 @@
const path = heroThumbSrc; const path = heroThumbSrc;
const mode = store.settings.serverAuthMode ?? "NONE"; const mode = store.settings.serverAuthMode ?? "NONE";
if (!path) { heroThumb = ""; return; } 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)) getBlobUrl(thumbUrl(path))
.then(url => { heroThumb = url; }) .then(url => { heroThumb = url; })
.catch(() => { heroThumb = ""; }); .catch(() => { heroThumb = ""; });
+7 -3
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { thumbUrl, plainThumbUrl } from "@api/client"; import { thumbUrl, getServerUrl } from "@api/client";
import { store } from "@store/state.svelte"; import { store } from "@store/state.svelte";
import { getBlobUrl } from "@core/cache/imageCache"; import { getBlobUrl } from "@core/cache/imageCache";
@@ -23,7 +23,10 @@
[key: string]: any; [key: string]: any;
} = $props(); } = $props();
const isAuth = $derived(store.settings.serverAuthMode === "BASIC_AUTH"); const isAuth = $derived(
store.settings.serverAuthMode === "BASIC_AUTH" ||
store.settings.serverAuthMode === "UI_LOGIN"
);
let blobUrl = $state(""); let blobUrl = $state("");
let reqId = 0; let reqId = 0;
@@ -36,7 +39,8 @@
if (!_isAuth || !_src) { blobUrl = ""; return; } if (!_isAuth || !_src) { blobUrl = ""; return; }
const id = ++reqId; const id = ++reqId;
getBlobUrl(plainThumbUrl(_src), _priority) const bareUrl = _src.startsWith("http") ? _src : `${getServerUrl()}${_src}`;
getBlobUrl(bareUrl, _priority)
.then(u => { if (id === reqId) blobUrl = u; }) .then(u => { if (id === reqId) blobUrl = u; })
.catch(() => { if (id === reqId) blobUrl = ""; }); .catch(() => { if (id === reqId) blobUrl = ""; });
}); });
+1 -1
View File
@@ -27,7 +27,7 @@ export default defineConfig({
envPrefix: ["VITE_", "TAURI_"], envPrefix: ["VITE_", "TAURI_"],
build: { build: {
target: ["es2021", "chrome100", "safari13"], target: ["es2021", "chrome100", "safari13"],
minify: !process.env.TAURI_DEBUG ? "esbuild" : false, minify: !process.env.TAURI_DEBUG ? "oxc" : false,
sourcemap: !!process.env.TAURI_DEBUG, sourcemap: !!process.env.TAURI_DEBUG,
}, },
}); });