// Erkennt neue Fassungen der Anwendung und wendet sie an. // // Warum das nötig ist: Eine installierte PWA wird oft wochenlang nicht // vollständig geschlossen. Der Browser lädt zwar den Service Worker im // Hintergrund neu, aber der neue bleibt im Wartezustand, bis alle // Fenster geschlossen sind - und die laufende Seite arbeitet weiter mit // dem alten JavaScript. Für den Nutzer sieht es aus, als käme das // Update nie an. // // Ablauf hier: // 1. Regelmäßig nachfragen, ob eine neue Fassung bereitliegt // 2. Wenn ja: Hinweis einblenden, nicht ungefragt neu laden // 3. Auf Wunsch den Wechsel anfordern und danach EINMAL neu laden "use strict"; import { el } from "./dom.js"; // Wie oft im laufenden Betrieb nachgefragt wird. Der Browser prüft von // sich aus nur bei Navigationen - in einer installierten PWA kann das // sehr lange dauern. const CHECK_INTERVAL_MS = 30 * 60 * 1000; let reloading = false; let banner = null; function showBanner(onApply) { if (banner) return; banner = el("div.update-banner", { role: "status" }, el("span.update-text", {}, "Eine neue Fassung der App ist bereit."), el("button.update-apply", { type: "button", onclick: onApply }, "Jetzt neu laden"), el("button.update-later", { type: "button", "aria-label": "Hinweis ausblenden", title: "Später – die neue Fassung wird beim nächsten Start aktiv", onclick: () => { banner.remove(); banner = null; }, }, "×") ); document.body.append(banner); } /** * Registriert den Service Worker und überwacht Aktualisierungen. * * Gibt nichts zurück; Fehler werden geschluckt, weil eine fehlende * Aktualisierungsprüfung die Anwendung nicht unbenutzbar machen darf. */ export async function watchForUpdates() { if (!("serviceWorker" in navigator)) return; let registration; try { registration = await navigator.serviceWorker.register("/sw.js", { scope: "/" }); } catch (err) { console.warn("Service Worker nicht registriert:", err.message); return; } /** Fordert den wartenden Service Worker auf, zu übernehmen. Das * eigentliche Neuladen löst danach das controllerchange-Ereignis * aus - so wird auch in anderen offenen Fenstern neu geladen. */ function apply() { const waiting = registration.waiting; if (!waiting) { // Sollte nicht vorkommen; dann eben unmittelbar neu laden. window.location.reload(); return; } waiting.postMessage({ type: "SKIP_WAITING" }); } function considerWaiting() { // Ohne controller ist es die Erstinstallation - da gibt es nichts // zu ersetzen, und ein Hinweis wäre verwirrend. if (registration.waiting && navigator.serviceWorker.controller) { showBanner(apply); } } // Beim Start: Liegt schon etwas bereit? considerWaiting(); registration.addEventListener("updatefound", () => { const incoming = registration.installing; if (!incoming) return; incoming.addEventListener("statechange", () => { if (incoming.state === "installed") considerWaiting(); }); }); // Genau einmal neu laden, wenn der neue Service Worker übernimmt. navigator.serviceWorker.addEventListener("controllerchange", () => { if (reloading) return; reloading = true; window.location.reload(); }); const check = () => { // Ohne Verbindung wäre die Abfrage nur ein Fehler im Protokoll. if (!navigator.onLine) return; registration.update().catch(() => {}); }; // Beim Zurückkehren in den Vordergrund - der häufigste Moment, in dem // eine installierte PWA nach Tagen wieder benutzt wird. document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") check(); }); window.addEventListener("online", check); setInterval(check, CHECK_INTERVAL_MS); check(); }