const SHELL_CACHE = 'tenant-admin-shell-v1'; const ASSET_CACHE = 'tenant-admin-assets-v1'; self.addEventListener('install', (event) => { event.waitUntil( caches.open(SHELL_CACHE).then((cache) => cache.addAll(['/event-admin'])) ); self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil( caches .keys() .then((keys) => Promise.all( keys .filter((key) => ![SHELL_CACHE, ASSET_CACHE].includes(key)) .map((key) => caches.delete(key)) ) ) ); event.waitUntil(self.clients.claim()); }); self.addEventListener('fetch', (event) => { const { request } = event; if (request.method !== 'GET') return; const url = new URL(request.url); if (url.origin !== self.location.origin) return; // Allow API traffic to bypass the SW if (url.pathname.startsWith('/api/')) return; // Navigation requests for the admin shell if (request.mode === 'navigate' && url.pathname.startsWith('/event-admin')) { event.respondWith( (async () => { try { const networkResponse = await fetch(request); const cache = await caches.open(SHELL_CACHE); cache.put('/event-admin', networkResponse.clone()); return networkResponse; } catch { const cached = await caches.match('/event-admin'); if (cached) return cached; return Response.error(); } })() ); return; } // Static asset caching (CSS/JS/fonts) if ( request.destination === 'style' || request.destination === 'script' || request.destination === 'font' ) { event.respondWith( (async () => { const cache = await caches.open(ASSET_CACHE); const cached = await cache.match(request); const fetchPromise = fetch(request) .then((response) => { if (response.ok) { cache.put(request, response.clone()); } return response; }) .catch(() => null); return cached || (await fetchPromise) || Response.error(); })() ); return; } // Images and icons (cache-first) if ( request.destination === 'image' || /\.(png|jpg|jpeg|webp|avif|gif|svg)(\?.*)?$/i.test(url.pathname) ) { event.respondWith( (async () => { const cache = await caches.open(ASSET_CACHE); 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 { return cached || Response.error(); } })() ); } }); self.addEventListener('push', (event) => { const payload = event.data?.json?.() ?? {}; event.waitUntil( (async () => { const title = payload.title ?? 'Neue Admin-Benachrichtigung'; const options = { body: payload.body ?? '', icon: '/admin-icon-192.png', badge: '/admin-badge.png', data: payload.data ?? {}, }; await self.registration.showNotification(title, options); const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); clients.forEach((client) => client.postMessage({ type: 'admin-notification-refresh' })); })() ); }); self.addEventListener('notificationclick', (event) => { event.notification.close(); const targetUrl = event.notification.data?.url || '/event-admin/mobile/notifications'; event.waitUntil( self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { for (const client of clientList) { if ('focus' in client) { client.navigate(targetUrl); return client.focus(); } } if (self.clients.openWindow) { return self.clients.openWindow(targetUrl); } }) ); }); self.addEventListener('pushsubscriptionchange', (event) => { event.waitUntil( self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { clientList.forEach((client) => client.postMessage({ type: 'push-subscription-change' })); }) ); });