frontend: Update-Detection überarbeitet - nur anzeigen wenn User aktiv ist

- Service Worker prüft im Hintergrund auf neue Versionen (jede 5 Minuten)
- Update-Status wird in localStorage gespeichert
- Benachrichtigung erscheint nur wenn:
  * User zur App zurückkommt (Page Visibility API)
  * Oder beim initialen Laden wenn Update verfügbar
- "Später" Button zum Dismissieren, "Jetzt laden" für Reload
- Keine Browser-Permission nötig
- localStorage-Daten bleiben beim Reload erhalten
- Pragmatisch: User sieht Updates nur wenn er arbeitet

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-07-23 06:48:16 +02:00
co-authored by Claude Haiku 4.5
parent a9d2cf3054
commit cad7a8bb6d
+51 -38
View File
@@ -1195,9 +1195,7 @@ mountBuildBadge();
/* ---------- Service Worker für Update-Detection ---------- */ /* ---------- Service Worker für Update-Detection ---------- */
if('serviceWorker' in navigator){ if('serviceWorker' in navigator){
/* Service Worker als Blob registrieren (Single-File-App) */
const swCode = ` const swCode = `
const CACHE_NAME = 'werkbaum-v1';
self.addEventListener('install', (e) => self.skipWaiting()); self.addEventListener('install', (e) => self.skipWaiting());
self.addEventListener('activate', (e) => e.waitUntil(clients.claim())); self.addEventListener('activate', (e) => e.waitUntil(clients.claim()));
@@ -1216,9 +1214,7 @@ if('serviceWorker' in navigator){
}); });
function notifyClients(msg) { function notifyClients(msg) {
self.clients.matchAll().then(clients => self.clients.matchAll().then(clients => clients.forEach(c => c.postMessage(msg)));
clients.forEach(c => c.postMessage(msg))
);
} }
async function getStored() { async function getStored() {
@@ -1263,37 +1259,44 @@ if('serviceWorker' in navigator){
const swUrl = URL.createObjectURL(blob); const swUrl = URL.createObjectURL(blob);
navigator.serviceWorker.register(swUrl).then((registration) => { navigator.serviceWorker.register(swUrl).then((registration) => {
/* Periodisch auf Updates prüfen */ /* Starte erste Prüfung nach 2 Sekunden */
setInterval(() => {
if(registration.active){
registration.active.postMessage({ type: 'CHECK_UPDATE' });
}
}, 60000); /* Jede Minute */
/* Starte erste Prüfung nach 5 Sekunden */
setTimeout(() => { setTimeout(() => {
if(registration.active){ if(registration.active) registration.active.postMessage({ type: 'CHECK_UPDATE' });
registration.active.postMessage({ type: 'CHECK_UPDATE' }); }, 2000);
}
}, 5000); /* Periodisch auf Updates prüfen (jede 5 Minuten) */
setInterval(() => {
if(registration.active) registration.active.postMessage({ type: 'CHECK_UPDATE' });
}, 300000);
}).catch((error) => { }).catch((error) => {
console.error('Service Worker Registration fehlgeschlagen:', error); console.error('Service Worker Registration fehlgeschlagen:', error);
}); });
/* Höre auf Update-Benachrichtigungen vom Service Worker */ /* Höre auf Update-Benachrichtigungen vom Service Worker */
navigator.serviceWorker.addEventListener('message', (event) => { navigator.serviceWorker.addEventListener('message', (event) => {
if(event.data && event.data.type === 'UPDATE_AVAILABLE'){ if(event.data?.type === 'UPDATE_AVAILABLE'){
showUpdateNotification(); localStorage.setItem('werkbaum-update-available', 'true');
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();
}
} }
function showUpdateNotification(){ function checkAndShowUpdateNotification(){
/* Entferne alte Benachrichtigung, falls vorhanden */
const existingNotif = document.getElementById('updateNotification'); const existingNotif = document.getElementById('updateNotification');
if(existingNotif) existingNotif.remove(); if(existingNotif) return; /* Schon angezeigt */
/* Erstelle Benachrichtigungselement */
const notif = document.createElement('div'); const notif = document.createElement('div');
notif.id = 'updateNotification'; notif.id = 'updateNotification';
notif.style.cssText = ` notif.style.cssText = `
@@ -1315,26 +1318,36 @@ function showUpdateNotification(){
notif.innerHTML = ` notif.innerHTML = `
<span>📦 Neue Version verfügbar</span> <span>📦 Neue Version verfügbar</span>
<button id="updateBtn" style=" <div style="display: flex; gap: 8px;">
background: white; <button class="dismissBtn" style="
color: var(--or, #0F766E); background: transparent;
border: none; color: white;
padding: 6px 12px; border: 1px solid white;
border-radius: 4px; padding: 4px 8px;
cursor: pointer; border-radius: 4px;
font-weight: 500; cursor: pointer;
font-size: 13px; font-size: 12px;
">Jetzt laden</button> ">Später</button>
<button class="updateBtn" style="
background: white;
color: var(--or, #0F766E);
border: none;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
font-size: 12px;
">Jetzt laden</button>
</div>
`; `;
document.body.insertBefore(notif, document.body.firstChild); document.body.insertBefore(notif, document.body.firstChild);
document.getElementById('updateBtn').addEventListener('click', () => { notif.querySelector('.updateBtn').addEventListener('click', () => {
window.location.reload(); window.location.reload();
}); });
/* Auto-dismiss nach 30 Sekunden wenn nicht geklickt */ notif.querySelector('.dismissBtn').addEventListener('click', () => {
setTimeout(() => { notif.remove();
if(notif.parentNode) notif.remove(); });
}, 30000);
} }