fix(cache): SW network-first für eigene Files

- 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
This commit is contained in:
Hermes AI Bot
2026-06-27 13:03:24 +00:00
parent 6793ba5ae5
commit 94cce19d74
10 changed files with 271 additions and 73 deletions
+93 -51
View File
@@ -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;
})
)
);
});
// ─── 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();
}
});