From e9519ed7ae68336a6e33b0a85b5a15d33ba9cf0b Mon Sep 17 00:00:00 2001 From: mhoennig Date: Thu, 23 Jul 2026 08:08:24 +0200 Subject: [PATCH] fix: Improve update detection with ETag/Last-Modified headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Robustere Update-Detection für unzuverlässige Netzwerk-Bedingungen: Neuer Ansatz (statt nur Content-Hash): 1. HEAD-Request prüft ETag + Last-Modified Headers - Effizient: Kein großer HTML-Download nötig - Verlässlich: Standard HTTP-Cache-Header 2. Wenn Headers gleich → "✓ Alles aktuell" (schnell raus) 3. Nur bei Header-Änderung → volles HTML fetchen + Content-Hash-Vergleich 4. Speichert ETag + Last-Modified für nächste Prüfung Besseres Logging: - "✅ NEUE VERSION ERKANNT!" (highlight) - "⚠ " + Fehlerdetails (was genau fehlgeschlagen ist) - "✓ Erste Prüfung – Hash gespeichert" - "✓ Headers änderten sich, aber Content gleich" Das verhindert: - Falsche "alles aktuell" wenn Fetch fehlschlägt - Mehrfaches Fetchen der großen HTML - "Failed to Fetch" auf unstabilen Verbindungen Co-Authored-By: Claude Haiku 4.5 --- frontend/src/app.js | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/frontend/src/app.js b/frontend/src/app.js index 21ca376..9443491 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -1196,24 +1196,45 @@ mountBuildBadge(); /* ---------- Update-Detection (Client-seitig, einfach & zuverlässig) ---------- */ async function checkForUpdates(){ try { - const resp = await fetch('?' + new Date().getTime(), { cache: 'no-store' }); + /* Cache-Busting: HEAD-Request zuerst, um Last-Modified/ETag zu prüfen */ + const headResp = await fetch(location.href, { method: 'HEAD', cache: 'no-store' }); + if(!headResp.ok) throw new Error(`HTTP ${headResp.status}`); + + const lastModified = headResp.headers.get('last-modified'); + const etag = headResp.headers.get('etag'); + const storedETag = localStorage.getItem('werkbaum-etag'); + const storedModified = localStorage.getItem('werkbaum-last-modified'); + + /* Wenn ETag/Last-Modified gleich, ist nichts geändert */ + if((etag && etag === storedETag) || (lastModified && lastModified === storedModified)){ + logUpdate('✓ Alles aktuell'); + return; + } + + /* Neue Version: Vollständiges HTML fetchen */ + const resp = await fetch(location.href, { cache: 'no-store' }); if(!resp.ok) throw new Error(`HTTP ${resp.status}`); 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. */ + + /* Prüfe auf echte Änderung via Content-Hash */ const stored = localStorage.getItem('werkbaum-html-hash'); - const hash = html.substring(0, 200) + html.substring(html.length - 200); /* First & last 200 chars */ - const isNewVersion = stored && stored !== hash; - if(isNewVersion){ + const hash = html.substring(0, 300) + html.substring(html.length - 300); + + if(stored && stored !== hash){ localStorage.setItem('werkbaum-update-available', 'true'); - logUpdate('✅ Neue Version erkannt!'); + logUpdate('✅ NEUE VERSION ERKANNT!'); if(!document.hidden) checkAndShowUpdateNotification(); - } else if(stored) { - logUpdate('✓ Alles aktuell'); + } else if(!stored) { + logUpdate('✓ Erste Prüfung – Hash gespeichert'); + } else { + logUpdate('✓ Headers änderten sich, aber Content gleich'); } + localStorage.setItem('werkbaum-html-hash', hash); + if(etag) localStorage.setItem('werkbaum-etag', etag); + if(lastModified) localStorage.setItem('werkbaum-last-modified', lastModified); } catch(err) { - logUpdate('⚠ Fehler: ' + (err.message || 'Unbekannt')); + logUpdate('⚠ ' + (err.message || 'Unbekannter Fehler')); } }