70 lines
2.9 KiB
JavaScript
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 */
|
||
|
|
});
|