diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 7735cbb..4e07197 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1,70 +1,39 @@ -// Paliad service worker — minimal cache-first for /assets/* and /icons/*, -// network-first for /api/*, network passthrough for everything else. -// Bumping CACHE_VERSION purges the previous cache on activation. -const CACHE_VERSION = "paliad-v1"; -const STATIC_CACHE = `${CACHE_VERSION}-static`; +// Paliad service worker — KILL-SWITCH (t-paliad-043). +// The previous SW (t-paliad-042 paliad-v1) cached a broken /assets/projects.js +// bundle that crashes on init ("d is not a function"), making /projects appear +// empty. Mobile Safari users have no devtools to unregister manually, so this +// SW is shipped specifically to self-destruct on activation: +// 1. unregister itself +// 2. delete every cache it ever opened +// 3. force every open client to reload from the network +// +// Browsers re-fetch sw.js on every navigation (we send Cache-Control: no-cache +// from the Go handler), so this propagates the moment a user opens any page. +// A proper versioned SW will land in a follow-up commit (Step 2) once the +// underlying bundle bug is fixed. -self.addEventListener("install", (event) => { - // Activate the new SW as soon as it is installed; matters when - // CACHE_VERSION changes so users don't keep stale assets. +self.addEventListener("install", () => { self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( (async () => { - const keys = await caches.keys(); - await Promise.all( - keys - .filter((k) => k !== STATIC_CACHE && k.startsWith("paliad-")) - .map((k) => caches.delete(k)), - ); - await self.clients.claim(); + const cacheNames = await caches.keys(); + await Promise.all(cacheNames.map((n) => caches.delete(n))); + await self.registration.unregister(); + const clientsList = await self.clients.matchAll({ type: "window" }); + for (const client of clientsList) { + try { + client.navigate(client.url); + } catch { + // Some browsers reject navigate() on cross-origin or detached clients — + // safe to ignore; next manual reload will pick up the unregistered state. + } + } })(), ); }); -self.addEventListener("fetch", (event) => { - const req = event.request; - if (req.method !== "GET") return; - - const url = new URL(req.url); - if (url.origin !== self.location.origin) return; - - if (url.pathname.startsWith("/assets/") || url.pathname.startsWith("/icons/")) { - event.respondWith(cacheFirst(req)); - return; - } - - if (url.pathname.startsWith("/api/")) { - event.respondWith(networkFirst(req)); - return; - } - - // HTML navigations + everything else: pass through to the network. -}); - -async function cacheFirst(req) { - const cache = await caches.open(STATIC_CACHE); - const cached = await cache.match(req); - if (cached) return cached; - try { - const res = await fetch(req); - if (res && res.ok) cache.put(req, res.clone()); - return res; - } catch (err) { - if (cached) return cached; - throw err; - } -} - -async function networkFirst(req) { - try { - return await fetch(req); - } catch (err) { - const cache = await caches.open(STATIC_CACHE); - const cached = await cache.match(req); - if (cached) return cached; - throw err; - } -} +// Fetch handler intentionally absent: with no cache and pending unregister, +// every request goes straight to the network.