Files
Oxicloud/frontend/static/sw.js
T
Edouard Vanbelle f4d2a7cd61 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
2026-07-12 22:02:11 +02:00

70 lines
2.9 KiB
JavaScript

// 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 */
});