From 94cce19d74753a610186b937df41669c4eb40ef7 Mon Sep 17 00:00:00 2001 From: Hermes AI Bot Date: Sat, 27 Jun 2026 13:03:24 +0000 Subject: [PATCH] =?UTF-8?q?fix(cache):=20SW=20network-first=20f=C3=BCr=20e?= =?UTF-8?q?igene=20Files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Eigene HTML/JS/CSS werden IMMER frisch vom Netz geladen (kein Cache-First mehr) - Map-Tiles (OSM/cartocdn) bleiben für Offline im Cache - Cache-Version v3 → v4 (alte Bug-Caches werden in activate() gelöscht) - Update-Hook: Browser fragt nach Reload wenn neuer SW aktiviert wird - Fix: Updates an GitHub Pages sind sofort für alle sichtbar --- countdown.html | 20 +++++- index.html | 20 +++++- memory.html | 27 +++++++- packliste.html | 20 +++++- strafen.html | 27 +++++++- sw.js | 144 +++++++++++++++++++++++++-------------- tourenplan-mit-tram.html | 20 +++++- tourenplan.html | 20 +++++- trinkspiel.html | 27 +++++++- wetter.html | 19 +++++- 10 files changed, 271 insertions(+), 73 deletions(-) diff --git a/countdown.html b/countdown.html index caaf132..6b9112f 100644 --- a/countdown.html +++ b/countdown.html @@ -398,12 +398,26 @@ } }); - // PWA SW + // PWA Service Worker — Network-First Strategie (Updates kommen sofort durch) if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('./sw.js') - .then(function(r){ console.log('[PWA] SW registered:', r.scope); }) - .catch(function(e){ console.warn('[PWA] SW failed:', e); }); + .then(function(reg) { + console.log('[PWA] SW registered:', reg.scope); + reg.addEventListener('updatefound', function() { + var newSw = reg.installing; + if (!newSw) return; + newSw.addEventListener('statechange', function() { + if (newSw.state === 'activated' && navigator.serviceWorker.controller) { + console.log('[PWA] Neue Version aktiviert'); + if (window.confirm('🔄 Neue Version der JGA-Seite verfügbar — jetzt neu laden?')) { + window.location.reload(); + } + } + }); + }); + }) + .catch(function(err) { console.warn('[PWA] SW failed:', err); }); }); } diff --git a/index.html b/index.html index 376c753..d62cf68 100644 --- a/index.html +++ b/index.html @@ -886,10 +886,26 @@ }); }); - /* ===== PWA Service Worker ===== */ + // PWA Service Worker — Network-First Strategie (Updates kommen sofort durch) if ('serviceWorker' in navigator) { window.addEventListener('load', () => { - navigator.serviceWorker.register('./sw.js').catch(() => {}); + navigator.serviceWorker.register('./sw.js') + .then(reg => { + console.log('[PWA] SW registered:', reg.scope); + reg.addEventListener('updatefound', () => { + const newSw = reg.installing; + if (!newSw) return; + newSw.addEventListener('statechange', () => { + if (newSw.state === 'activated' && navigator.serviceWorker.controller) { + console.log('[PWA] Neue Version aktiviert'); + if (window.confirm('🔄 Neue Version der JGA-Seite verfügbar — jetzt neu laden?')) { + window.location.reload(); + } + } + }); + }); + }) + .catch(err => console.warn('[PWA] SW registration failed:', err)); }); } diff --git a/memory.html b/memory.html index 0d261b7..296e8f4 100644 --- a/memory.html +++ b/memory.html @@ -1219,9 +1219,30 @@ if (!state.setupDone) { } checkStorage(); -if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('sw.js').catch(() => {}); -} + // PWA Service Worker — Network-First Strategie (Updates kommen sofort durch) + if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('./sw.js') + .then(reg => { + console.log('[PWA] SW registered:', reg.scope); + // Update-Hook: wenn ein neuer SW installiert wurde, kurz reloaden + reg.addEventListener('updatefound', () => { + const newSw = reg.installing; + if (!newSw) return; + newSw.addEventListener('statechange', () => { + if (newSw.state === 'activated' && navigator.serviceWorker.controller) { + console.log('[PWA] Neue Version aktiviert — lade neu'); + // Sanfter Reload ohne Datenverlust, Cache-Bust mit Timestamp + if (window.confirm('🔄 Neue Version der JGA-Seite verfügbar — jetzt neu laden?')) { + window.location.reload(); + } + } + }); + }); + }) + .catch(err => console.warn('[PWA] SW registration failed:', err)); + }); + } diff --git a/packliste.html b/packliste.html index 71af452..9cce70d 100644 --- a/packliste.html +++ b/packliste.html @@ -381,11 +381,27 @@ updateProgress(); diff --git a/sw.js b/sw.js index 8568587..b05b22a 100644 --- a/sw.js +++ b/sw.js @@ -1,74 +1,116 @@ // JGA Prag 2026 — Service Worker -// Cache-Strategie: cache-first für HTML/Assets, network-first für externe APIs (OSRM) -const CACHE_VERSION = 'jga-2026-v3'; -const CORE_ASSETS = [ - './', - './index.html', - './tourenplan.html', - './tourenplan-mit-tram.html', - './wetter.html', - './packliste.html', - './countdown.html', - './trinkspiel.html', - './strafen.html', - './memory.html', +// Cache-Strategie: +// • Eigene HTML/JS/CSS → network-first (immer frisch laden, Fallback Cache offline) +// • Map-Tiles (OSM) → cache-first (für Offline-Karten) +// • Externe APIs (OSRM) → network-only (kein Cache) +const CACHE_VERSION = 'jga-2026-v4'; +const TILE_CACHE = 'jga-2026-tiles-v4'; +const PAGE_CACHE = 'jga-2026-pages-v4'; + +// Nur Map-Tiles werden offline gecached — KEINE eigenen HTML-Files +const PRECACHE_URLS = [ './manifest.json' ]; -// Install: alle Core-Files cachen +// Install: nur manifest precachen, KEINE HTML-Files self.addEventListener('install', (event) => { event.waitUntil( - caches.open(CACHE_VERSION) - .then(cache => cache.addAll(CORE_ASSETS)) + caches.open(PAGE_CACHE) + .then(cache => cache.addAll(PRECACHE_URLS)) .then(() => self.skipWaiting()) ); }); -// Activate: alte Caches aufräumen +// Activate: alle alten Caches (inkl. v3, v2, v1) löschen self.addEventListener('activate', (event) => { event.waitUntil( caches.keys() .then(keys => Promise.all( - keys.filter(k => k !== CACHE_VERSION).map(k => caches.delete(k)) + keys + .filter(k => k !== CACHE_VERSION && k !== TILE_CACHE && k !== PAGE_CACHE) + .map(k => caches.delete(k)) )) - .then(() => self.clients.claim()) + .then(async () => { + // Auch alte Tile- und Page-Caches aufräumen + const old = await caches.keys(); + await Promise.all( + old + .filter(k => k.startsWith('jga-2026-') && k !== CACHE_VERSION && k !== TILE_CACHE && k !== PAGE_CACHE) + .map(k => caches.delete(k)) + ); + await self.clients.claim(); + }) ); }); -// Fetch: cache-first für eigene Files, network-first für externe APIs +// Fetch-Handler self.addEventListener('fetch', (event) => { - const url = new URL(event.request.url); + const req = event.request; + if (req.method !== 'GET') return; - // Externe APIs (OSRM, OpenStreetMap, Wetter) → network-first + const url = new URL(req.url); + + // ─── Externe APIs (OSRM, Open-Meteo) → network-only, kein Cache ─── if (url.origin !== self.location.origin) { - event.respondWith( - fetch(event.request) - .then(response => { - // Erfolgreich: clone & cache für offline - if (response.ok && (url.hostname.includes('openstreetmap') || - url.hostname.includes('cartocdn') || - url.hostname.includes('unpkg'))) { - const clone = response.clone(); - caches.open(CACHE_VERSION).then(c => c.put(event.request, clone)); - } - return response; - }) - .catch(() => caches.match(event.request)) - ); - return; + // Map-Tiles (OSM / Carto / unpkg Leaflet) → cache-first + if ( + url.hostname.includes('openstreetmap') || + url.hostname.includes('cartocdn') || + url.hostname.includes('tile.openstreetmap') + ) { + event.respondWith(cacheFirst(req, TILE_CACHE)); + return; + } + // Alles andere extern → einfach durch, kein Caching + return; // default browser behavior } - // Eigene Files → cache-first - event.respondWith( - caches.match(event.request) - .then(cached => cached || fetch(event.request) - .then(response => { - if (response.ok) { - const clone = response.clone(); - caches.open(CACHE_VERSION).then(c => c.put(event.request, clone)); - } - return response; - }) - ) - ); -}); \ No newline at end of file + // ─── Eigene Files (HTML/JS/CSS/JSON) → network-first ─── + // Immer versuchen frisch zu laden, nur bei Offline-Fallback aus Cache + event.respondWith(networkFirst(req, PAGE_CACHE)); +}); + +// Network-first: frisch vom Netz, Fallback auf Cache (offline) +async function networkFirst(request, cacheName) { + const cache = await caches.open(cacheName); + try { + const response = await fetch(request, { cache: 'no-store' }); + if (response.ok) { + cache.put(request, response.clone()); + } + return response; + } catch (err) { + const cached = await cache.match(request); + if (cached) return cached; + // Letzter Fallback: index.html (für SPA-ähnliches Verhalten) + if (request.mode === 'navigate') { + const fallback = await cache.match('./index.html'); + if (fallback) return fallback; + } + throw err; + } +} + +// Cache-first: aus Cache, Fallback aufs Netz (für Tiles) +async function cacheFirst(request, cacheName) { + const cache = await caches.open(cacheName); + const cached = await cache.match(request); + if (cached) return cached; + try { + const response = await fetch(request); + if (response.ok) { + cache.put(request, response.clone()); + } + return response; + } catch (err) { + // Tile offline + nicht im Cache → leere 503 + return new Response('', { status: 503, statusText: 'Tile offline' }); + } +} + +// Clients über neues Update benachrichtigen +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); diff --git a/tourenplan-mit-tram.html b/tourenplan-mit-tram.html index d62cff1..6ab64b5 100644 --- a/tourenplan-mit-tram.html +++ b/tourenplan-mit-tram.html @@ -1148,11 +1148,27 @@ document.addEventListener('keydown', (e) => { }); diff --git a/wetter.html b/wetter.html index 9255faa..3e7eea6 100644 --- a/wetter.html +++ b/wetter.html @@ -187,11 +187,26 @@ h1 { font-size:42px; margin:0 0 8px 0; font-weight:700; letter-spacing:-1px; col })();