From af754dc31f8b9d58d4d29416ff644d0e10682d0a Mon Sep 17 00:00:00 2001 From: commilitia Date: Mon, 15 Jun 2026 23:08:19 +0800 Subject: [PATCH] =?UTF-8?q?web=EF=BC=9Aservice=20worker=20=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E6=8B=A6=E6=88=AA=E5=AF=BC=E8=88=AA=EF=BC=88=E4=BF=AE?= =?UTF-8?q?=20Safari=20OAuth=20=E5=9B=9E=E8=B7=B3=20~2min=20=E5=8D=A1?= =?UTF-8?q?=E9=A1=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SW 之前以 network-first 接管所有导航请求,疑似在 Safari 下令 Casdoor→应用的回跳导航长时间挂起(~2min 似卡在 Casdoor 页)。改为:导航、/api、跨源一律直通网络,SW 只对同源 hash 化静态资源做 stale-while-revalidate。cdrop 离线无意义,放弃导航壳缓存无损失。cache 版本 bump v2 清旧缓存。 --- web/public/sw.js | 63 +++++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 43 deletions(-) diff --git a/web/public/sw.js b/web/public/sw.js index 7b79b41..84514ad 100644 --- a/web/public/sw.js +++ b/web/public/sw.js @@ -1,32 +1,24 @@ -// cdrop service worker — makes the web app installable and caches the static -// shell for instant launch. +// cdrop service worker — makes the web app installable and caches hashed static +// assets for faster repeat loads. // -// HARD RULE: it MUST NOT intercept /api/* . The SSE stream (/api/hub/events), -// clipboard, WebRTC signaling and relay all need a live, unbuffered network -// connection — those requests are passed straight through to the network. Same -// goes for cross-origin requests (fonts / CDN): the browser handles them. +// Deliberately minimal. It does NOT intercept: +// - /api/* — SSE (/api/hub/events), clipboard, signaling, relay need a +// live, unbuffered connection; and the OAuth exchange/refresh +// must not be cached. +// - navigations — page loads / OAuth redirects go straight to the network. +// (A SW that mediates navigations was stalling the Casdoor → +// app redirect for ~2 min in Safari; cdrop is useless offline +// anyway, so there's nothing to gain by caching the shell.) +// - cross-origin — fonts / CDN are left to the browser. // -// cdrop is useless offline (everything needs the backend), so this is a shell -// cache for fast loads + installability, not a real offline app. +// What's left: hashed JS/CSS/image assets, served stale-while-revalidate. -const CACHE = "cdrop-shell-v1"; -const SHELL = [ - "/", - "/site.webmanifest", - "/favicon-96.png", - "/icon-192.png", - "/apple-touch-icon-180.png", - "/logo-mark.png", -]; +const CACHE = "cdrop-assets-v2"; self.addEventListener("install", (event) => { - event.waitUntil( - caches.open(CACHE) - .then((cache) => cache.addAll(SHELL)) - .then(() => self.skipWaiting()) - .catch(() => self.skipWaiting()), - ); + // No precache — assets are cached lazily on first fetch. Activate immediately. + event.waitUntil(self.skipWaiting()); }); self.addEventListener("activate", (event) => @@ -42,29 +34,14 @@ self.addEventListener("fetch", (event) => { const req = event.request; if (req.method !== "GET") { return; } + if (req.mode === "navigate") { return; } // page loads / OAuth redirects → network const url = new URL(req.url); - if (url.origin !== self.location.origin) { return; } // cross-origin (fonts/CDN) → browser default - if (url.pathname.startsWith("/api/")) { return; } // live backend → never intercept + if (url.origin !== self.location.origin) { return; } // cross-origin (fonts/CDN) → browser default + if (url.pathname.startsWith("/api/")) { return; } // live backend → never intercept - // SPA navigations: network-first so a new deploy serves fresh HTML (which - // points at the new hashed assets); fall back to the cached shell offline. - if (req.mode === "navigate") - { - event.respondWith( - fetch(req) - .then((resp) => - { - const copy = resp.clone(); - caches.open(CACHE).then((c) => c.put("/", copy)).catch(() => {}); - return resp; - }) - .catch(() => caches.match("/").then((r) => r || Response.error())), - ); - return; - } - - // Static assets (hashed JS/CSS/images are immutable): stale-while-revalidate. + // Static assets (Vite emits content-hashed, immutable files): serve cache + // immediately, refresh in the background. event.respondWith( caches.match(req).then((cached) => {