Files
einkaufsapp/web/html/js/update.js

120 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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();
}