fix: Improve update detection with ETag/Last-Modified headers

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 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-07-23 08:08:24 +02:00
co-authored by Claude Haiku 4.5
parent 68322b13d9
commit e9519ed7ae
+31 -10
View File
@@ -1196,24 +1196,45 @@ mountBuildBadge();
/* ---------- Update-Detection (Client-seitig, einfach & zuverlässig) ---------- */ /* ---------- Update-Detection (Client-seitig, einfach & zuverlässig) ---------- */
async function checkForUpdates(){ async function checkForUpdates(){
try { 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}`); if(!resp.ok) throw new Error(`HTTP ${resp.status}`);
const html = await resp.text(); 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 stored = localStorage.getItem('werkbaum-html-hash');
const hash = html.substring(0, 200) + html.substring(html.length - 200); /* First & last 200 chars */ const hash = html.substring(0, 300) + html.substring(html.length - 300);
const isNewVersion = stored && stored !== hash;
if(isNewVersion){ if(stored && stored !== hash){
localStorage.setItem('werkbaum-update-available', 'true'); localStorage.setItem('werkbaum-update-available', 'true');
logUpdate('✅ Neue Version erkannt!'); logUpdate('✅ NEUE VERSION ERKANNT!');
if(!document.hidden) checkAndShowUpdateNotification(); if(!document.hidden) checkAndShowUpdateNotification();
} else if(stored) { } else if(!stored) {
logUpdate('✓ Alles aktuell'); logUpdate('✓ Erste Prüfung Hash gespeichert');
} else {
logUpdate('✓ Headers änderten sich, aber Content gleich');
} }
localStorage.setItem('werkbaum-html-hash', hash); localStorage.setItem('werkbaum-html-hash', hash);
if(etag) localStorage.setItem('werkbaum-etag', etag);
if(lastModified) localStorage.setItem('werkbaum-last-modified', lastModified);
} catch(err) { } catch(err) {
logUpdate('⚠ Fehler: ' + (err.message || 'Unbekannt')); logUpdate('⚠ ' + (err.message || 'Unbekannter Fehler'));
} }
} }