diff options
| author | Fuwn <[email protected]> | 2026-06-02 00:09:50 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-06-02 00:09:50 +0000 |
| commit | b6da8e8474b7a1d0b951b25f4936f039197ff84b (patch) | |
| tree | 7a495d3a5d2098fced9be68818e755ed6d757108 /src | |
| parent | fix(security): sanitize third-party RSS HTML before {@html} (diff) | |
| download | due.moe-b6da8e8474b7a1d0b951b25f4936f039197ff84b.tar.xz due.moe-b6da8e8474b7a1d0b951b25f4936f039197ff84b.zip | |
fix(updates): isolate feed failures so one feed can't break the page
onMount fetched the novel feed then the manga feed, each via
`(await fetch(...)).json()` with no error handling. When a feed endpoint
failed (e.g. all-novels, when wlnupdates returns HTML), `.json()` threw
and aborted onMount before the other feed loaded, leaving the whole page
stuck on skeletons. Load each feed through a fetchJson helper that
returns null on any failure (non-ok, network, or parse error) and set
the feed to null — the existing "Failed to load feed" state — when the
response is missing or malformed. A failing feed now degrades gracefully
while the other still renders.
Diffstat (limited to 'src')
| -rw-r--r-- | src/routes/updates/+page.svelte | 19 |
1 files changed, 15 insertions, 4 deletions
diff --git a/src/routes/updates/+page.svelte b/src/routes/updates/+page.svelte index 0534d0bb..bbcb087d 100644 --- a/src/routes/updates/+page.svelte +++ b/src/routes/updates/+page.svelte @@ -23,6 +23,7 @@ let novelFeed: }[]; }; } + | null | undefined; let startTime: number; let mangaEndTime: number; @@ -32,12 +33,22 @@ let directLink = browser : false; let removeHeightObserver: (() => void) | undefined; +const fetchJson = async (path: string) => { + try { + const response = await fetch(root(path)); + + return response.ok ? await response.json() : null; + } catch { + return null; + } +}; + onMount(async () => { removeHeightObserver = createHeightObserver(false); startTime = performance.now(); - const allNovels = await (await fetch(root("/api/updates/all-novels"))).json(); + const allNovels = await fetchJson("/api/updates/all-novels"); if (allNovels?.data?.items) for (const item of allNovels.data.items) { @@ -45,11 +56,11 @@ onMount(async () => { if (item.series) item.series.name = sanitizeFeedHtml(item.series.name); } - novelFeed = allNovels; + novelFeed = allNovels?.data?.items ? allNovels : null; novelEndTime = performance.now() - startTime; startTime = performance.now(); - const mangaFeed = await (await fetch(root("/api/updates/manga"))).json(); + const mangaFeed = await fetchJson("/api/updates/manga"); if (mangaFeed?.items) for (const item of mangaFeed.items) { @@ -57,7 +68,7 @@ onMount(async () => { item.content = sanitizeFeedHtml(item.content); } - feed = mangaFeed; + feed = mangaFeed?.items ? mangaFeed : null; mangaEndTime = performance.now() - startTime; }); |