From f4d2a7cd618dcf4836563d4d2df719292b108b0b Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Sun, 12 Jul 2026 21:54:18 +0200 Subject: [PATCH] fix(front): unregister cache prio to 0.8.0 this fix issue https://github.com/AtalayaLabs/OxiCloud/issues/560 previous version where caching assets, now sveltekit is fully autonomous, use a sw.js that clears the cache and unregisters it self --- frontend/static/sw.js | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 frontend/static/sw.js diff --git a/frontend/static/sw.js b/frontend/static/sw.js new file mode 100644 index 00000000..ddfe4168 --- /dev/null +++ b/frontend/static/sw.js @@ -0,0 +1,69 @@ +// Self-unregistering stub — replaces the legacy vanilla-frontend +// service worker that shipped with OxiCloud ≤ 0.8.0. +// +// Browsers that installed the old SW keep it registered across upgrades +// and it intercepts every navigation, serving a stale index.html from its +// `oxicloud-cache*` Cache Storage. The stale shell's meta-CSP predates +// the SvelteKit build's inline-script hashes, so hydration is blocked by +// CSP and the app hangs on the spinner. Symptom: infinite loader on +// fresh visits, only cleared by a hard refresh. Ref: issue #560. +// +// SvelteKit itself does NOT register a service worker (no `src/service-worker` +// module exists) — this file exists solely to shepherd upgraders off the +// legacy SW. Browsers on a clean install fetch it, install it, immediately +// unregister it, and the URL stays a 200 for the next visitor with the +// same stale-SW problem. +// +// The install/activate handlers race the browser's normal SW lifecycle; +// `skipWaiting` + `clients.claim` fast-forward through the "waiting" and +// "activating" states so the tab that triggered the update gets reloaded +// with a controller-less document (no SW intercepting fetches) within +// the same page lifetime. + +self.addEventListener('install', (event) => { + event.waitUntil(self.skipWaiting()); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil( + (async () => { + // 1. Drop every Cache Storage bucket the legacy SW may have + // populated. We match the `oxicloud-cache*` prefix the old + // SW used, plus a defensive wildcard clear if that prefix + // was ever changed in a fork/downstream build. + if (self.caches) { + const keys = await self.caches.keys(); + await Promise.all(keys.map((k) => self.caches.delete(k))); + } + + // 2. Unregister this SW. After this the browser will not + // invoke `fetch` handlers from this registration on future + // navigations. + await self.registration.unregister(); + + // 3. Take control of open clients so we can reload them into + // a controller-less state (fresh HTML, matching CSP). + await self.clients.claim(); + const clients = await self.clients.matchAll({ type: 'window' }); + for (const client of clients) { + // `navigate` beats `location.reload()`-in-postMessage because + // it works even if the page's JS is CSP-blocked (the case + // we're fixing). Same URL → same-tab reload without controller. + try { + await client.navigate(client.url); + } catch { + /* opaque redirect / cross-origin — nothing we can do */ + } + } + })() + ); +}); + +// Explicit pass-through fetch handler. Without one, browsers may treat +// the SW as controlling — with an empty handler they short-circuit to +// the network. Belt-and-suspenders: we've already unregistered above, +// but a race between activation and an in-flight navigation could still +// hit this handler. +self.addEventListener('fetch', () => { + /* fall through to network */ +});