From 050924458f29bfc769befbfafb2e9c0fb2db8f5d Mon Sep 17 00:00:00 2001 From: Dicken Date: Wed, 17 Jun 2026 00:23:00 +0200 Subject: [PATCH] fix: SW-Version dynamisch per Build-Time, sofort Reload bei neuem Deploy --- Dockerfile | 2 ++ frontend/public/sw.js | 18 ++++++++++++------ frontend/src/main.jsx | 17 +++++++++++++---- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 531fb7a..2d51b76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,8 @@ COPY frontend/ ./ # Dieser Wert wird von vite.config.js als __BUILD_TIME__ ins Bundle gebacken # UND vom Backend als /api/build-time ausgeliefert → exakt derselbe Wert RUN date -u +"%s" > ./version.txt +# Build-Version in sw.js einsetzen damit der Browser bei jedem Deploy einen neuen SW erkennt +RUN BUILD_V=$(cat ./version.txt) && sed -i "s/__BUILD_VERSION__/${BUILD_V}/g" ./public/sw.js RUN npm run build # ── Stage 2: Express liefert API + React in einem Container ────────────────── diff --git a/frontend/public/sw.js b/frontend/public/sw.js index f654ce0..d7580d8 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1,5 +1,6 @@ -// Service Worker v4 – garantiert frische index.html, Auto-Reload bei neuem Build -const VERSION = 'v5'; +// Service Worker – Version wird zur Build-Zeit injiziert via Dockerfile +// Dadurch erkennt der Browser bei jedem Deploy einen neuen SW → Cache-Purge +const VERSION = '__BUILD_VERSION__'; // wird im Dockerfile ersetzt const CACHE_NAME = 'dickendock-' + VERSION; self.addEventListener('install', () => { @@ -8,9 +9,9 @@ self.addEventListener('install', () => { self.addEventListener('activate', e => { e.waitUntil((async () => { - // alte Caches löschen + // alle alten Caches löschen const keys = await caches.keys(); - await Promise.all(keys.map(k => caches.delete(k))); + await Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))); await self.clients.claim(); // allen offenen Clients sagen: neue Version aktiv → reload const clients = await self.clients.matchAll({ type: 'window' }); @@ -21,8 +22,7 @@ self.addEventListener('activate', e => { self.addEventListener('fetch', e => { const url = new URL(e.request.url); - // Cross-Origin-Requests (externe Bilder, Fonts usw.) NICHT abfangen - // → Browser verarbeitet sie normal, kein CORS-Problem in der PWA + // Cross-Origin-Requests nicht abfangen if (url.origin !== self.location.origin) return; // API immer direkt ans Netz @@ -31,6 +31,12 @@ self.addEventListener('fetch', e => { return; } + // sw.js selbst nie cachen + if (url.pathname === '/sw.js') { + e.respondWith(fetch(e.request, { cache: 'no-store' })); + return; + } + // HTML-Navigation: IMMER frisch vom Netzwerk if (e.request.mode === 'navigate') { e.respondWith( diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 3f0c3be..1a61403 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -56,13 +56,18 @@ window.__safeReloadIfNewBuild = function () { return true; }; -// Beim Start prüfen (kein sofortiger Reload – Seite lädt gerade erst) -checkBuild(); +// Beim Start prüfen – nach 3s sofort reloaden wenn neue Version (App hat Zeit zu rendern) +checkBuild().then(() => { + if (window.__newBuildAvailable && !isUserTyping() && !hasUnsavedInput()) { + setTimeout(() => { + if (!isUserTyping() && !hasUnsavedInput()) window.location.reload(); + }, 3000); + } +}); // Im Hintergrund alle 60s setInterval(() => checkBuild(), 60_000); // App kommt in den Vordergrund → prüfen und bei Bedarf sofort laden -// (PWA vom Homescreen öffnen / aus dem Hintergrund holen) document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { checkBuild({ reloadNow: true }); @@ -72,7 +77,11 @@ document.addEventListener('visibilitychange', () => { // ── Service Worker ──────────────────────────────────────────────────────────── if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', (e) => { - if (e.data?.type === 'SW_ACTIVATED') window.__newBuildAvailable = true; + // SW hat neue Version aktiviert → sofort reloaden + if (e.data?.type === 'SW_ACTIVATED') { + window.__newBuildAvailable = true; + if (!isUserTyping() && !hasUnsavedInput()) window.location.reload(); + } }); navigator.serviceWorker.register('/sw.js').then(reg => {