fixed pwa-update-notification
authorPhiTux <redacted>
Sun, 5 Oct 2025 15:48:06 +0000 (17:48 +0200)
committerPhiTux <redacted>
Sun, 5 Oct 2025 15:48:06 +0000 (17:48 +0200)
frontend/src/routes/(authed)/+layout.svelte
frontend/src/routes/+layout.svelte
frontend/vite.config.js

index e1b0c9810fd39b9d41c78496367cc91a145bc136..b9c0e546c9370d9446e053942801465ea5ac1cc1 100644 (file)
                                        <img src={dailytxt} alt="" height="38px" />
                                        <span class="dailytxt ms-2 user-select-none">DailyTxT</span>
                                </div>
-                               5
                        </div>
 
                        <div class="col-lg-4 col-sm-5 col pe-0 d-flex flex-row justify-content-end">
                                                                                {/each}
                                                                        </select>
                                                                </div>
-                                                               {#if showInstallationHelp}
-                                                                       <div class="alert alert-info">{@html $t('settings.installation_help')}</div>
-                                                               {/if}
                                                                <div id="appearance">
                                                                        <h3 class="text-primary">🎨 {$t('settings.appearance')}</h3>
                                                                        <div id="lightdark">
                                                                <div id="about">
                                                                        <h3 class="text-primary">💡 {$t('settings.about')}</h3>
 
+                                                                       {#if showInstallationHelp}
+                                                                               <div class="alert alert-info">{@html $t('settings.installation_help')}</div>
+                                                                       {/if}
+
                                                                        <span class="d-table mx-auto"
                                                                                >{@html $t('settings.about.made_by', {
                                                                                        creator:
index d08344df9cf1b486c8bbaa46daded4b4fd90a7b8..2b7043fad164014f6a20b2622517352b89a6c8eb 100644 (file)
@@ -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();
 
                        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.)
index 96448a4609a66b038d089ef61deefdb4f346e968..37f7736c974fcef9caecd3f5afd2b1d6c2261789 100644 (file)
@@ -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 }
                })
        ],
git clone https://git.99rst.org/PROJECT