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) => {