From a9232ecbe78708295de5ff648fb0aee7d3d6acb4 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Thu, 23 Jul 2026 07:22:12 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Vereinfache=20Update-Detection=20?= =?UTF-8?q?=E2=80=93=20Client-seitig=20statt=20Service=20Worker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Service Worker mit Blob-URL funktionierte nicht zuverlässig (blob: Protocol nicht registrierbar in einigen Browsern). Neue Lösung: - Client-seitige Prüfung statt Service Worker - Alle 60 Sekunden fetch() der index.html (mit Cache-Busting via Timestamp) - Einfacher Hash-Check (erste + letzte 200 Zeichen des Content) - localStorage speichert Hash zum Vergleichen - Update-Flag wird gesetzt wenn sich Hash ändert - Benachrichtigung nur wenn User aktiv ist (Page Visibility API) - Erste Prüfung nach 3 Sekunden, dann alle 60 Sekunden Viel zuverlässiger als der frühere SW-Ansatz. Funktioniert überall ohne Browser-Limit. Co-Authored-By: Claude Haiku 4.5 --- frontend/src/app.js | 130 ++++++++++++-------------------------------- 1 file changed, 35 insertions(+), 95 deletions(-) diff --git a/frontend/src/app.js b/frontend/src/app.js index ab810f1..1861b9b 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -1193,106 +1193,46 @@ mountBuildBadge(); } })(); -/* ---------- Service Worker für Update-Detection ---------- */ -if('serviceWorker' in navigator){ - const swCode = ` - self.addEventListener('install', (e) => self.skipWaiting()); - self.addEventListener('activate', (e) => e.waitUntil(clients.claim())); - - self.addEventListener('message', async (e) => { - if(e.data?.type === 'CHECK_UPDATE') { - try { - const response = await fetch(self.location.href, { cache: 'no-store' }); - const newContent = await response.text(); - const stored = await getStored(); - if(stored && stored !== newContent) { - notifyClients({ type: 'UPDATE_AVAILABLE' }); - } - await storeContent(newContent); - } catch(err) { console.error('Update-Check:', err); } - } - }); - - function notifyClients(msg) { - self.clients.matchAll().then(clients => clients.forEach(c => c.postMessage(msg))); - } - - async function getStored() { - try { - const db = await new Promise((r, x) => { - const req = indexedDB.open('werkbaum-updates', 1); - req.onerror = () => x(req.error); - req.onsuccess = () => r(req.result); - req.onupgradeneeded = (e) => { - if(!e.target.result.objectStoreNames.contains('content')) { - e.target.result.createObjectStore('content', { keyPath: 'id' }); - } - }; - }); - return new Promise((r, x) => { - const t = db.transaction('content', 'readonly'); - const req = t.objectStore('content').get('index'); - req.onerror = () => x(req.error); - req.onsuccess = () => r(req.result?.data); - }); - } catch(e) { return null; } - } - - async function storeContent(content) { - try { - const db = await new Promise((r, x) => { - const req = indexedDB.open('werkbaum-updates', 1); - req.onerror = () => x(req.error); - req.onsuccess = () => r(req.result); - }); - return new Promise((r, x) => { - const t = db.transaction('content', 'readwrite'); - const req = t.objectStore('content').put({ id: 'index', data: content }); - req.onerror = () => x(req.error); - req.onsuccess = () => r(); - }); - } catch(e) {} - } - `; - - const blob = new Blob([swCode], { type: 'application/javascript' }); - const swUrl = URL.createObjectURL(blob); - - navigator.serviceWorker.register(swUrl).then((registration) => { - /* Starte erste Prüfung nach 2 Sekunden */ - setTimeout(() => { - if(registration.active) registration.active.postMessage({ type: 'CHECK_UPDATE' }); - }, 2000); - - /* Periodisch auf Updates prüfen (jede 5 Minuten) */ - setInterval(() => { - if(registration.active) registration.active.postMessage({ type: 'CHECK_UPDATE' }); - }, 300000); - }).catch((error) => { - console.error('Service Worker Registration fehlgeschlagen:', error); - }); - - /* Höre auf Update-Benachrichtigungen vom Service Worker */ - navigator.serviceWorker.addEventListener('message', (event) => { - if(event.data?.type === 'UPDATE_AVAILABLE'){ +/* ---------- Update-Detection (Client-seitig, einfach & zuverlässig) ---------- */ +async function checkForUpdates(){ + try { + const resp = await fetch('?' + new Date().getTime(), { cache: 'no-store' }); + const html = await resp.text(); + /* Einfacher Check: Suche nach der Build-Badge (existiert nur in Nicht-Prod). + Wenn sich der Badge ändert oder der Content sich ändert, ist eine neue Version da. */ + const stored = localStorage.getItem('werkbaum-html-hash'); + const hash = html.substring(0, 200) + html.substring(html.length - 200); /* First & last 200 chars */ + if(stored && stored !== hash){ localStorage.setItem('werkbaum-update-available', 'true'); - checkAndShowUpdateNotification(); + if(!document.hidden) checkAndShowUpdateNotification(); } - }); - - /* Prüfe auf Update wenn User zur App zurückkommt */ - document.addEventListener('visibilitychange', () => { - if(!document.hidden && localStorage.getItem('werkbaum-update-available')){ - checkAndShowUpdateNotification(); - } - }); - - /* Prüfe beim Laden */ - if(!document.hidden && localStorage.getItem('werkbaum-update-available')){ - checkAndShowUpdateNotification(); + localStorage.setItem('werkbaum-html-hash', hash); + } catch(err) { + /* Offline oder Fehler — ignorieren */ } } +/* Erste Prüfung nach 3 Sekunden */ +setTimeout(checkForUpdates, 3000); + +/* Periodisch prüfen (jede 60 Sekunden statt 5 Minuten = schneller Feedback) */ +setInterval(checkForUpdates, 60000); + +/* Prüfe wenn User zur App zurückkommt */ +document.addEventListener('visibilitychange', () => { + if(!document.hidden){ + checkForUpdates(); + if(localStorage.getItem('werkbaum-update-available')){ + checkAndShowUpdateNotification(); + } + } +}); + +/* Prüfe beim Laden, falls Update bereits verfügbar */ +if(!document.hidden && localStorage.getItem('werkbaum-update-available')){ + checkAndShowUpdateNotification(); +} + function checkAndShowUpdateNotification(){ const existingNotif = document.getElementById('updateNotification'); if(existingNotif) return; /* Schon angezeigt */