feat(pwa): Update-Toast zeigt neue Version an
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m15s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m15s
pwaStore ($lib/client/pwa.svelte.ts): - Hängt sich an navigator.serviceWorker.ready, hört auf updatefound und setzt updateAvailable = true, sobald ein neuer SW im Status 'installed' ist UND es einen aktiven controller gibt (= Update eines bestehenden Tabs, nicht die erste Installation). - Polling alle 30 Minuten via registration.update(), damit der User den Toast auch sieht, wenn er die Seite lange offen hat ohne zu navigieren. - reload() ruft location.reload(); dismiss() schließt den Toast nur. UpdateToast.svelte: - Schwarzer Pill-Toast unten zentriert, mit Text, grünem "Neu laden"- Button (RefreshCw-Icon) und X zum Wegklicken. - Slide-Up-Animation beim Erscheinen. - Responsive: auf Mobile (<420px) wird's zum vollbreiten Banner statt Pill. Root-Layout mountet <UpdateToast /> direkt neben <ConfirmDialog />. onMount ruft pwaStore.init(). Status-Check der Live-Instanz https://kochwas.siegeln.net: - manifest.webmanifest wird korrekt als JSON ausgeliefert - service-worker.js (3.4 KB) ist verfügbar - iOS Apple-Meta-Tags + Android theme-color sind im HTML <head> PWA selbst funktioniert also bereits; der Toast war das fehlende Teil für transparente User-seitige Updates.
This commit is contained in:
52
src/lib/client/pwa.svelte.ts
Normal file
52
src/lib/client/pwa.svelte.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
class PwaStore {
|
||||
updateAvailable = $state(false);
|
||||
private registration: ServiceWorkerRegistration | null = null;
|
||||
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
async init(): Promise<void> {
|
||||
if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) return;
|
||||
try {
|
||||
this.registration = await navigator.serviceWorker.ready;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!this.registration) return;
|
||||
|
||||
// Wenn beim Mount schon ein neuer SW installiert und aktiv wartet,
|
||||
// zeigen wir den Toast direkt an.
|
||||
if (this.registration.waiting) {
|
||||
this.updateAvailable = true;
|
||||
}
|
||||
|
||||
this.registration.addEventListener('updatefound', () => this.onUpdateFound());
|
||||
|
||||
// Alle 30 Minuten aktiv nach Updates fragen, damit der User sie auch
|
||||
// mitbekommt, wenn er die Seite lange offen lässt ohne zu navigieren.
|
||||
this.pollTimer = setInterval(() => {
|
||||
void this.registration?.update().catch(() => {});
|
||||
}, 30 * 60_000);
|
||||
}
|
||||
|
||||
private onUpdateFound(): void {
|
||||
const installing = this.registration?.installing;
|
||||
if (!installing) return;
|
||||
installing.addEventListener('statechange', () => {
|
||||
// 'installed' UND ein laufender controller = Update für bestehenden Tab.
|
||||
// (Ohne controller wäre das die erste Installation, kein Update.)
|
||||
if (installing.state === 'installed' && navigator.serviceWorker.controller) {
|
||||
this.updateAvailable = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reload(): void {
|
||||
this.updateAvailable = false;
|
||||
location.reload();
|
||||
}
|
||||
|
||||
dismiss(): void {
|
||||
this.updateAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const pwaStore = new PwaStore();
|
||||
Reference in New Issue
Block a user