Optimierung automatische Updates der Webanwendung bei Aktualisierung der Anwendung auf dem Server
This commit is contained in:
@@ -24,6 +24,7 @@ import { manageView } from "./views/manage.js";
|
||||
import { closeMenu } from "./appbar.js";
|
||||
import * as db from "./db.js";
|
||||
import { installTriggers, stopWatching } from "./sync.js";
|
||||
import { watchForUpdates } from "./update.js";
|
||||
import { publicView } from "./views/public.js";
|
||||
import { acceptInviteView, shareView } from "./views/share.js";
|
||||
|
||||
@@ -276,13 +277,9 @@ async function start() {
|
||||
|
||||
await route();
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
try {
|
||||
await navigator.serviceWorker.register("/sw.js", { scope: "/" });
|
||||
} catch (err) {
|
||||
console.warn("Service Worker nicht registriert:", err.message);
|
||||
}
|
||||
}
|
||||
// Registriert den Service Worker und meldet sich, wenn eine neue
|
||||
// Fassung bereitliegt.
|
||||
watchForUpdates();
|
||||
}
|
||||
|
||||
start();
|
||||
|
||||
119
web/html/js/update.js
Normal file
119
web/html/js/update.js
Normal file
@@ -0,0 +1,119 @@
|
||||
// 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();
|
||||
}
|
||||
Reference in New Issue
Block a user