124 lines
3.8 KiB
TypeScript
124 lines
3.8 KiB
TypeScript
export async function unregisterServiceWorkers() {
|
|
if (navigator.serviceWorker) {
|
|
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
for (const registration of registrations) {
|
|
await registration.unregister();
|
|
console.log("Service worker unregistered");
|
|
}
|
|
} else {
|
|
console.error("No service worker found to unregister");
|
|
}
|
|
}
|
|
|
|
/** Returns a versioned worker URL so every release triggers an update check. */
|
|
export function versionedServiceWorkerURL(
|
|
version: string,
|
|
baseURI = document.baseURI,
|
|
): URL {
|
|
const url = new URL("service_worker.js", baseURI);
|
|
url.searchParams.set("v", version);
|
|
return url;
|
|
}
|
|
|
|
function waitForControllerChange(timeoutMs: number): Promise<boolean> {
|
|
return new Promise((resolve) => {
|
|
let settled = false;
|
|
const finish = (didChange: boolean) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
clearTimeout(timeout);
|
|
navigator.serviceWorker.removeEventListener(
|
|
"controllerchange",
|
|
onControllerChange,
|
|
);
|
|
resolve(didChange);
|
|
};
|
|
const onControllerChange = () => finish(true);
|
|
const timeout = setTimeout(() => finish(false), timeoutMs);
|
|
navigator.serviceWorker.addEventListener(
|
|
"controllerchange",
|
|
onControllerChange,
|
|
);
|
|
});
|
|
}
|
|
|
|
function activateWorker(worker: ServiceWorker) {
|
|
if (worker.state === "installed") {
|
|
worker.postMessage({ type: "skip-waiting" });
|
|
return;
|
|
}
|
|
worker.addEventListener("statechange", () => {
|
|
if (worker.state === "installed") {
|
|
worker.postMessage({ type: "skip-waiting" });
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Forces a worker update before reloading. If activation cannot complete, the
|
|
* registration is removed so the reload comes directly from the server. The
|
|
* IndexedDB-backed note cache is deliberately preserved.
|
|
*/
|
|
export async function reloadWithUpdatedServiceWorker(timeoutMs = 5000) {
|
|
if (!navigator.serviceWorker) {
|
|
location.reload();
|
|
return;
|
|
}
|
|
|
|
const registration = await navigator.serviceWorker.getRegistration(
|
|
document.baseURI,
|
|
);
|
|
if (!registration) {
|
|
location.reload();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await registration.update();
|
|
const replacement = registration.waiting ?? registration.installing;
|
|
if (replacement) {
|
|
const controllerChanged = waitForControllerChange(timeoutMs);
|
|
activateWorker(replacement);
|
|
if (!(await controllerChanged)) await registration.unregister();
|
|
} else {
|
|
// The page can still be running an old JS bundle after the newest worker
|
|
// has already activated. A direct reload is the deterministic recovery.
|
|
await registration.unregister();
|
|
}
|
|
} catch (error) {
|
|
console.warn("Service worker update failed, reloading directly", error);
|
|
await registration.unregister();
|
|
}
|
|
location.reload();
|
|
}
|
|
|
|
export function flushCachesAndUnregisterServiceWorker() {
|
|
console.log("Going to flush the caches and unregister the service worker");
|
|
return new Promise<void>((resolve) => {
|
|
if (!navigator.serviceWorker?.controller) {
|
|
console.log("No service worker active");
|
|
return resolve();
|
|
}
|
|
|
|
navigator.serviceWorker.addEventListener("message", (event) => {
|
|
if (event.data.type === "cacheFlushed") {
|
|
console.log("Cache flushed");
|
|
// Then unregister all service workers
|
|
navigator.serviceWorker.getRegistrations().then((registrations) => {
|
|
for (const registration of registrations) {
|
|
registration.unregister();
|
|
console.log("Service worker unregistered");
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// First flush active cache
|
|
navigator.serviceWorker.ready.then((registration) => {
|
|
console.log("Sending request to flush cache");
|
|
registration.active!.postMessage({ type: "flush-cache" });
|
|
});
|
|
});
|
|
}
|