Harmonize navigation and modals across cookbook & shopping list, fix WebSocket proxying, and add PWA offline support

This commit is contained in:
2026-10-15 21:03:15 +02:00
parent 55b2823113
commit d59629cc6e
6 changed files with 1371 additions and 1427 deletions
+100
View File
@@ -0,0 +1,100 @@
// What To Cook? - PWA Service Worker (v1.0.0)
const CACHE_NAME = 'wtc-cache-v1';
const PRECACHE_URLS = [
'/',
'/cookbook',
'/shopping-list',
'/manifest.json',
'/static/cookbook.css',
'/static/icon-192.png',
'/static/icon-512.png'
];
// Install: Pre-cache shell assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(PRECACHE_URLS).catch((err) => {
console.warn('[SW] Pre-caching partial failure, continuing:', err);
});
}).then(() => self.skipWaiting())
);
});
// Activate: Clean up old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
// Fetch: Strategy
self.addEventListener('fetch', (event) => {
const req = event.request;
const url = new URL(req.url);
// Skip non-GET and WebSocket/API mutation requests
if (req.method !== 'GET') return;
if (url.pathname.startsWith('/ws/') || url.pathname.startsWith('/api/')) return;
// Navigation requests (HTML pages): Network first, fallback to cache
if (req.mode === 'navigate') {
event.respondWith(
fetch(req).then((response) => {
if (response && response.status === 200) {
const copy = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(req, copy));
}
return response;
}).catch(async () => {
const cached = await caches.match(req);
if (cached) return cached;
// If exact page not cached, return home or shopping list if available
if (url.pathname === '/shopping-list') {
const shopCached = await caches.match('/shopping-list');
if (shopCached) return shopCached;
}
const homeCached = await caches.match('/');
if (homeCached) return homeCached;
return new Response('Offline – keine Verbindung vorhanden.', {
status: 503,
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
});
})
);
return;
}
// Static assets: Cache-first, then network update
if (url.pathname.startsWith('/static/') || url.hostname.includes('fonts.googleapis.com') || url.hostname.includes('fonts.gstatic.com')) {
event.respondWith(
caches.match(req).then((cached) => {
if (cached) {
// In background, refresh cache
fetch(req).then((networkRes) => {
if (networkRes && networkRes.status === 200) {
caches.open(CACHE_NAME).then((cache) => cache.put(req, networkRes));
}
}).catch(() => {});
return cached;
}
return fetch(req).then((networkRes) => {
if (networkRes && networkRes.status === 200) {
const copy = networkRes.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(req, copy));
}
return networkRes;
});
})
);
return;
}
});