From: PhiTux Date: Sun, 5 Oct 2025 15:48:06 +0000 (+0200) Subject: fixed pwa-update-notification X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=3d8f82744808c56b99ed35b01c4e321aa480edc4;p=DailyTxT.git fixed pwa-update-notification --- diff --git a/frontend/src/routes/(authed)/+layout.svelte b/frontend/src/routes/(authed)/+layout.svelte index e1b0c98..b9c0e54 100644 --- a/frontend/src/routes/(authed)/+layout.svelte +++ b/frontend/src/routes/(authed)/+layout.svelte @@ -1164,7 +1164,6 @@ DailyTxT - 5
@@ -1374,9 +1373,6 @@ {/each}
- {#if showInstallationHelp} -
{@html $t('settings.installation_help')}
- {/if}

🎨 {$t('settings.appearance')}

@@ -2316,6 +2312,10 @@

💡 {$t('settings.about')}

+ {#if showInstallationHelp} +
{@html $t('settings.installation_help')}
+ {/if} + {@html $t('settings.about.made_by', { creator: diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index d08344d..2b7043f 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -2,7 +2,7 @@ import { blur } from 'svelte/transition'; import axios from 'axios'; import { goto } from '$app/navigation'; - import { onMount } from 'svelte'; + import { onMount, onDestroy } from 'svelte'; import '../scss/styles.scss'; import { page } from '$app/state'; import { API_URL } from '$lib/APIurl.js'; @@ -35,6 +35,9 @@ let inDuration = 150; let outDuration = 150; + // Keep a reference to the SW registration to allow immediate skipWaiting + let swRegistration = null; + // PWA install prompt state let deferredInstallPrompt = $state(null); let showInstallToast = $state(false); @@ -94,6 +97,8 @@ calculateResize(); }); + let isStandalone = false; + onMount(() => { calculateResize(); @@ -102,42 +107,116 @@ generateNeonMesh($darkMode); } + // helper to present the PWA update toast and wire the reload button once + function showPwaUpdateToast() { + setTimeout(() => { + const toast = new bootstrap.Toast(document.getElementById('toastPwaUpdate'), { + autohide: false + }); + toast.show(); + const btn = document.getElementById('btnPwaReload'); + let swReloadScheduled = false; + if (btn) { + btn.removeAttribute('disabled'); + btn.onclick = () => { + if (swReloadScheduled) return; + swReloadScheduled = true; + btn.setAttribute('disabled', 'true'); + // Ask the waiting SW (if present) to activate immediately + try { + swRegistration?.waiting?.postMessage({ type: 'SKIP_WAITING' }); + } catch (_) {} + // Request update with built-in reload once activated; don't await to keep UI responsive + try { + updateSW(true); + } catch (_) {} + // Fallback: force a reload after a brief delay in case the event doesn't fire on this platform + setTimeout(() => { + try { + window.location.reload(); + } catch (_) {} + }, 1200); + }; + } + }, 500); + } + // PWA auto-update with user prompt const updateSW = registerSW({ onNeedRefresh() { - // toast - setTimeout(() => { - const toast = new bootstrap.Toast(document.getElementById('toastPwaUpdate'), { - autohide: false - }); - toast.show(); - }, 500); - const btn = document.getElementById('btnPwaReload'); - let swReloadScheduled = false; - btn?.addEventListener('click', async () => { - if (swReloadScheduled) return; - swReloadScheduled = true; - btn.setAttribute('disabled', 'true'); - // Request update without auto-reload; we'll reload once on controller change - await updateSW(); - // Reload exactly once when the new SW takes control - navigator.serviceWorker.addEventListener( - 'controllerchange', - () => { - // Use a micro delay to ensure new assets are ready - setTimeout(() => window.location.reload(), 50); - }, - { once: true } - ); - }); + showPwaUpdateToast(); }, onOfflineReady() { // not needed, we don't aim offline, skip toast + }, + onRegisteredSW(_swUrl, registration) { + // When the app stays open, explicitly ask the SW to check for updates + // on focus, visibility regain, going online, and periodically. + if (!registration) return; + swRegistration = registration; + const checkForUpdate = () => { + try { + registration.update(); + } catch (_) {} + }; + // If a waiting SW already exists (e.g. app was open when update installed), show the toast now. + if (registration.waiting) { + showPwaUpdateToast(); + } + // Initial delayed check after mount + const initialTimer = setTimeout(checkForUpdate, 5000); + // Check on app focus / tab visible / network back + const onFocus = () => checkForUpdate(); + const onVisibility = () => { + if (document.visibilityState === 'visible') checkForUpdate(); + }; + const onOnline = () => checkForUpdate(); + window.addEventListener('focus', onFocus); + document.addEventListener('visibilitychange', onVisibility); + window.addEventListener('online', onOnline); + // Periodic check (e.g., every 30 minutes) + const intervalId = setInterval(checkForUpdate, 30 * 60 * 1000); + // Cleanup on destroy (main layout rarely unmounts, but keep things tidy) + onDestroy(() => { + clearTimeout(initialTimer); + clearInterval(intervalId); + window.removeEventListener('focus', onFocus); + document.removeEventListener('visibilitychange', onVisibility); + window.removeEventListener('online', onOnline); + }); + }, + onRegistered(registration) { + // Fallback for environments exposing onRegistered instead of onRegisteredSW + if (!registration) return; + swRegistration = registration; + const checkForUpdate = () => { + try { + registration.update(); + } catch (_) {} + }; + if (registration.waiting) showPwaUpdateToast(); + const initialTimer = setTimeout(checkForUpdate, 5000); + const onFocus = () => checkForUpdate(); + const onVisibility = () => { + if (document.visibilityState === 'visible') checkForUpdate(); + }; + const onOnline = () => checkForUpdate(); + window.addEventListener('focus', onFocus); + document.addEventListener('visibilitychange', onVisibility); + window.addEventListener('online', onOnline); + const intervalId = setInterval(checkForUpdate, 30 * 60 * 1000); + onDestroy(() => { + clearTimeout(initialTimer); + clearInterval(intervalId); + window.removeEventListener('focus', onFocus); + document.removeEventListener('visibilitychange', onVisibility); + window.removeEventListener('online', onOnline); + }); } }); // Detect standalone (already installed) and platforms where auto prompt won't show - const isStandalone = + isStandalone = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone; // Capture the install prompt event (Android/Chrome etc.) diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 96448a4..37f7736 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,14 +1,11 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; import { SvelteKitPWA } from '@vite-pwa/sveltekit'; -//import mkcert from 'vite-plugin-mkcert'; export default defineConfig({ plugins: [ sveltekit(), - //mkcert(), SvelteKitPWA({ - registerType: 'autoUpdate', scope: '/', manifest: { name: 'DailyTxT', @@ -22,20 +19,6 @@ export default defineConfig({ { src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any maskable' } ] }, - workbox: { - // Don’t cache API calls; only precache built assets - navigateFallbackDenylist: [/^\/users\//, /^\/logs\//, /^\/api\//], - runtimeCaching: [ - { - urlPattern: ({ url }) => url.origin === self.origin && url.pathname.startsWith('/build/'), - handler: 'CacheFirst', - options: { - cacheName: 'app-assets', - expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 * 30 }, - } - } - ] - }, devOptions: { enabled: true } }) ],