/** * KundenKarte PWA Service Worker * Offline-First für Schaltschrank-Dokumentation */ const CACHE_NAME = 'kundenkarte-pwa-v12.4'; const OFFLINE_CACHE = 'kundenkarte-offline-v12.4'; // Statische Assets die immer gecached werden (ohne Query-String) const STATIC_ASSETS = [ 'pwa.php', 'img/pwa-icon-192.png', 'img/pwa-icon-512.png', '../../../includes/jquery/js/jquery.min.js' ]; // Assets mit Versions-Query-String - NICHT cachen, immer vom Netzwerk laden const VERSIONED_ASSETS = ['pwa.css', 'pwa.js']; // Install - Cache statische Assets self.addEventListener('install', event => { console.log('[SW] Installing...'); event.waitUntil( caches.open(CACHE_NAME).then(cache => { console.log('[SW] Caching static assets'); return cache.addAll(STATIC_ASSETS); }) ); self.skipWaiting(); }); // Activate - Alte Caches löschen self.addEventListener('activate', event => { console.log('[SW] Activating...'); event.waitUntil( caches.keys().then(keys => { return Promise.all( keys.filter(key => key !== CACHE_NAME && key !== OFFLINE_CACHE) .map(key => { console.log('[SW] Deleting old cache:', key); return caches.delete(key); }) ); }) ); self.clients.claim(); }); // Fetch Strategy self.addEventListener('fetch', event => { const url = new URL(event.request.url); // PWA Auth Endpoints - immer Netzwerk if (url.pathname.includes('pwa_auth.php')) { event.respondWith(fetch(event.request)); return; } // Versionierte Assets (CSS/JS mit ?v=X) - IMMER Netzwerk, kein Cache // Damit neue Versionen sofort geladen werden if (url.search && VERSIONED_ASSETS.some(a => url.pathname.includes(a))) { event.respondWith( fetch(event.request).catch(() => caches.match(event.request)) ); return; } // AJAX Requests - Netzwerk mit Offline-Fallback if (url.pathname.includes('/ajax/')) { event.respondWith( fetch(event.request) .then(response => { // Cache erfolgreiche GET-Requests für Offline if (event.request.method === 'GET' && response.ok) { const clone = response.clone(); caches.open(OFFLINE_CACHE).then(cache => { cache.put(event.request, clone); }); } return response; }) .catch(() => { // Offline - versuche aus Cache return caches.match(event.request).then(cached => { if (cached) { return cached; } // Kein Cache - Offline-Fehler return new Response(JSON.stringify({ success: false, offline: true, error: 'Offline - Keine Verbindung' }), { headers: { 'Content-Type': 'application/json' } }); }); }) ); return; } // PHP Seiten - Network First if (url.pathname.endsWith('.php')) { event.respondWith( fetch(event.request) .then(response => { // Cache für Offline const clone = response.clone(); caches.open(CACHE_NAME).then(cache => { cache.put(event.request, clone); }); return response; }) .catch(() => caches.match(event.request)) ); return; } // Statische Assets (JS, CSS, Bilder) - Stale-While-Revalidate // Liefert sofort aus Cache, holt parallel frische Version für nächsten Aufruf event.respondWith( caches.open(CACHE_NAME).then(cache => { return cache.match(event.request).then(cached => { const fetchPromise = fetch(event.request).then(response => { if (response.ok) { cache.put(event.request, response.clone()); } return response; }).catch(() => cached); return cached || fetchPromise; }); }) ); }); // Background Sync für Offline-Änderungen self.addEventListener('sync', event => { if (event.tag === 'sync-changes') { console.log('[SW] Syncing offline changes...'); event.waitUntil(syncOfflineChanges()); } }); // Offline-Änderungen synchronisieren async function syncOfflineChanges() { // Wird von pwa.js gesteuert - sendet Message wenn sync fertig const clients = await self.clients.matchAll(); clients.forEach(client => { client.postMessage({ type: 'SYNC_REQUESTED' }); }); } // Messages von der App self.addEventListener('message', event => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } });