Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/lib/pwa/serviceWorkerRecoveryBootstrap.ts
T

222 lines
6.1 KiB
TypeScript

/**
* Version shared by the early recovery bootstrap and the regular client-side
* registration. Keep it in sync with `public/sw.js`; a regression test enforces
* that boundary.
*/
export const SERVICE_WORKER_VERSION = "v5-2026-07-15-web-push";
/**
* Runs before stylesheet discovery. A healthy worker answers one cheap version
* handshake and performs no update check. A legacy worker gets one update and,
* if it never takes control, one bounded unregister/register fallback.
*/
export const SERVICE_WORKER_RECOVERY_BOOTSTRAP = String.raw`
(function () {
var serviceWorker = navigator.serviceWorker;
var controller = serviceWorker && serviceWorker.controller;
if (!controller) return;
var expectedVersion = "${SERVICE_WORKER_VERSION}";
var expectedScriptPath = "/sw.js?v=" + encodeURIComponent(expectedVersion);
var markerKey = "service-worker-recovery:v4";
var legacyMarkerKey = "service-worker-csp-recovery:v3";
if (
typeof controller.scriptURL === "string" &&
controller.scriptURL === location.origin + expectedScriptPath
) {
try {
sessionStorage.removeItem(markerKey);
sessionStorage.removeItem(legacyMarkerKey);
} catch (error) {}
return;
}
var requestId = Date.now().toString(36) + ":" + Math.random().toString(36).slice(2);
var handshakeTimer;
var lockTimer;
var replacementTimer;
var recoveryStarted = false;
var replacementStarted = false;
var healthy = false;
var reloaded = false;
function clearTimer(timer) {
if (timer) clearTimeout(timer);
}
function removeHandshake() {
clearTimer(handshakeTimer);
try {
serviceWorker.removeEventListener("message", onMessage);
} catch (error) {}
}
function clearMarkers() {
try {
sessionStorage.removeItem(markerKey);
sessionStorage.removeItem(legacyMarkerKey);
} catch (error) {}
}
function cleanup() {
clearTimer(lockTimer);
clearTimer(replacementTimer);
removeHandshake();
try {
serviceWorker.removeEventListener("controllerchange", reloadOnce);
} catch (error) {}
clearMarkers();
}
function reloadOnce() {
if (reloaded || healthy) return;
reloaded = true;
cleanup();
location.reload();
}
function acquireLock() {
try {
var now = Date.now();
var rawMarker = sessionStorage.getItem(markerKey);
if (rawMarker) {
var marker = JSON.parse(rawMarker);
if (marker && typeof marker.expiresAt === "number" && marker.expiresAt > now) {
return marker.expiresAt - now;
}
}
sessionStorage.setItem(markerKey, JSON.stringify({ expiresAt: now + 3000 }));
} catch (error) {}
return 0;
}
function requestSkipWaiting(worker) {
if (!worker) return;
try {
worker.postMessage({ type: "HIVEGRID_SW_SKIP_WAITING" });
} catch (error) {}
}
function finishHealthy() {
if (healthy || reloaded) return;
healthy = true;
cleanup();
}
function probeBeforeReplacement(registration) {
if (healthy || reloaded) return;
clearTimer(handshakeTimer);
requestId = Date.now().toString(36) + ":" + Math.random().toString(36).slice(2);
try {
controller.postMessage({ type: "HIVEGRID_SW_VERSION_REQUEST", requestId: requestId });
handshakeTimer = setTimeout(function () {
replaceRegistration(registration);
}, 1500);
} catch (error) {
replaceRegistration(registration);
}
}
function replaceRegistration(registration) {
if (replacementStarted || reloaded || healthy) return;
replacementStarted = true;
var detached = false;
Promise.resolve()
.then(function () {
return registration ? registration.unregister() : true;
})
.then(
function (unregistered) {
detached = Boolean(unregistered);
},
function () {}
)
.then(function () {
return serviceWorker.register("/sw.js?v=" + encodeURIComponent(expectedVersion), {
scope: "/",
updateViaCache: "none"
});
})
.then(function (nextRegistration) {
if (healthy || reloaded) return;
requestSkipWaiting(nextRegistration.waiting);
replacementTimer = setTimeout(function () {
if (detached) reloadOnce();
else cleanup();
}, 1500);
})
.catch(function () {
if (detached) {
reloadOnce();
return;
}
cleanup();
});
}
function updateRegistration(registration) {
Promise.resolve()
.then(function () {
return registration.update();
})
.then(function () {
if (healthy || reloaded) return;
requestSkipWaiting(registration.waiting);
probeBeforeReplacement(registration);
})
.catch(function () {
probeBeforeReplacement(registration);
});
}
function startRecovery() {
if (recoveryStarted || reloaded || healthy) return;
clearTimer(handshakeTimer);
var lockDelay = acquireLock();
if (lockDelay > 0) {
lockTimer = setTimeout(startRecovery, lockDelay + 10);
return;
}
recoveryStarted = true;
try {
serviceWorker.addEventListener("controllerchange", reloadOnce);
serviceWorker
.getRegistration()
.then(function (registration) {
if (registration) updateRegistration(registration);
else replaceRegistration(null);
})
.catch(function () {
replaceRegistration(null);
});
} catch (error) {
cleanup();
}
}
function onMessage(event) {
var data = event && event.data;
if (!data || data.type !== "HIVEGRID_SW_VERSION_RESPONSE" || data.requestId !== requestId) return;
if (event.source && event.source !== controller) return;
if (data.version === expectedVersion) {
finishHealthy();
return;
}
startRecovery();
}
try {
serviceWorker.addEventListener("message", onMessage);
controller.postMessage({ type: "HIVEGRID_SW_VERSION_REQUEST", requestId: requestId });
handshakeTimer = setTimeout(startRecovery, 250);
} catch (error) {
startRecovery();
}
})();
`
.replace(/\s+/g, " ")
.trim();