fix: Vereinfache Update-Detection – Client-seitig statt Service Worker

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 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-07-23 07:22:12 +02:00
co-authored by Claude Haiku 4.5
parent 475b4d6fef
commit a9232ecbe7
+35 -95
View File
@@ -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 */