diff --git a/src/lib/client/pwa.svelte.ts b/src/lib/client/pwa.svelte.ts new file mode 100644 index 0000000..bc125ac --- /dev/null +++ b/src/lib/client/pwa.svelte.ts @@ -0,0 +1,52 @@ +class PwaStore { + updateAvailable = $state(false); + private registration: ServiceWorkerRegistration | null = null; + private pollTimer: ReturnType | null = null; + + async init(): Promise { + 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(); diff --git a/src/lib/components/UpdateToast.svelte b/src/lib/components/UpdateToast.svelte new file mode 100644 index 0000000..bcf383b --- /dev/null +++ b/src/lib/components/UpdateToast.svelte @@ -0,0 +1,110 @@ + + +{#if pwaStore.updateAvailable} +
+ Neue Kochwas-Version verfügbar + + +
+{/if} + + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 0866220..2d247b4 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,9 +5,11 @@ import { Settings, CookingPot, Globe, Utensils } from 'lucide-svelte'; import { profileStore } from '$lib/client/profile.svelte'; import { wishlistStore } from '$lib/client/wishlist.svelte'; + import { pwaStore } from '$lib/client/pwa.svelte'; import ProfileSwitcher from '$lib/components/ProfileSwitcher.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import SearchLoader from '$lib/components/SearchLoader.svelte'; + import UpdateToast from '$lib/components/UpdateToast.svelte'; import type { SearchHit } from '$lib/server/recipes/search-local'; import type { WebHit } from '$lib/server/search/searxng'; @@ -112,6 +114,7 @@ onMount(() => { profileStore.load(); void wishlistStore.refresh(); + void pwaStore.init(); document.addEventListener('click', handleClickOutside); document.addEventListener('keydown', handleKey); return () => { @@ -122,6 +125,7 @@ +