Feature: Welten Info-Cards — User-Avatar in JETZT, Hunde-Avatar+Cycle+Overlap in HUND, SW by-v639

This commit is contained in:
rene 2026-05-03 10:18:11 +02:00
parent dfd68f2a07
commit fc2002847c
4 changed files with 1074 additions and 6 deletions

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v607';
const CACHE_VERSION = 'by-v639';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
@ -125,11 +125,34 @@ const _CACHEABLE_GET = [
/^\/api\/training\/progress/,
/^\/api\/wiki\/rassen/,
/^\/api\/dogs\/\d+\/diary\/stats/,
// Drei Welten — offline-fähig
/^\/api\/streak\/\d+/,
/^\/api\/forum\/threads/,
/^\/api\/weather$/,
/^\/api\/passport\/\d+$/,
];
function _isCacheableGet(pathname) {
return _CACHEABLE_GET.some(re => re.test(pathname));
}
// Cache-TTL: stabile Daten länger, dynamische kürzer
const _STABLE_GET = [/^\/api\/training\/exercises/, /^\/api\/wiki\/rassen/];
const _TTL_STABLE = 60 * 60 * 1000; // 1 Stunde
const _TTL_DEFAULT = 5 * 60 * 1000; // 5 Minuten
const _cacheTs = new Map(); // pathname → timestamp (in-memory, ok bei SW-Neustart)
function _cacheTTL(pathname) {
return _STABLE_GET.some(re => re.test(pathname)) ? _TTL_STABLE : _TTL_DEFAULT;
}
function _cacheStale(pathname) {
const ts = _cacheTs.get(pathname);
return !ts || (Date.now() - ts) > _cacheTTL(pathname);
}
function _cacheMark(pathname) {
_cacheTs.set(pathname, Date.now());
}
// ----------------------------------------------------------
// INSTALL — App Shell cachen
// ----------------------------------------------------------
@ -173,19 +196,27 @@ self.addEventListener('fetch', event => {
if (method === 'GET' && _isCacheableGet(url.pathname)) {
event.respondWith((async () => {
const cached = await caches.match(event.request);
const stale = _cacheStale(url.pathname);
const networkPromise = _fetchTimeout(event.request.clone(), 8000)
.then(resp => {
if (resp.ok) caches.open(CACHE_API).then(c => c.put(event.request, resp.clone()));
if (resp.ok) {
_cacheMark(url.pathname);
caches.open(CACHE_API).then(c => c.put(event.request, resp.clone()));
}
return resp;
})
.catch(() => null);
// Stale-While-Revalidate: sofort aus Cache, im Hintergrund holen
if (cached) {
networkPromise.catch(() => {}); // fire and forget
// Cache noch frisch → sofort zurückgeben, Netz im Hintergrund
if (cached && !stale) {
networkPromise.catch(() => {});
return cached;
}
// Cache vorhanden aber abgelaufen → Netz zuerst, Cache als Fallback
const fresh = await networkPromise;
if (fresh) return fresh;
if (cached) return cached; // lieber veraltet als nichts
return new Response(JSON.stringify({ detail: 'Offline — keine Daten im Cache.' }),
{ status: 503, headers: { 'Content-Type': 'application/json' } });
})());