// Beszel Service Worker const CACHE_NAME = 'beszel-v1'; const STATIC_ASSETS = [ '/', '/index.html', '/manifest.json', '/favicon.ico', '/favicon.svg', ]; // Install event - cache static assets self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => { return cache.addAll(STATIC_ASSETS); }) .then(() => self.skipWaiting()) ); }); // Activate event - clean up old caches self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => name !== CACHE_NAME) .map((name) => caches.delete(name)) ); }) .then(() => self.clients.claim()) ); }); // Fetch event - serve from cache or network self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Skip non-GET requests if (request.method !== 'GET') { return; } // Skip API requests if (url.pathname.startsWith('/api/')) { return; } // Skip PocketBase API if (url.pathname.startsWith('/_/')) { return; } event.respondWith( caches.match(request).then((cached) => { if (cached) { // Return cached version and update in background fetch(request).then((response) => { if (response.ok) { caches.open(CACHE_NAME).then((cache) => { cache.put(request, response); }); } }); return cached; } // Fetch from network return fetch(request).then((response) => { if (!response || response.status !== 200 || response.type !== 'basic') { return response; } const responseToCache = response.clone(); caches.open(CACHE_NAME).then((cache) => { cache.put(request, responseToCache); }); return response; }); }).catch(() => { // Return offline page if available return caches.match('/offline.html'); }) ); }); // Push notification event self.addEventListener('push', (event) => { if (!event.data) { return; } const data = event.data.json(); const options = { body: data.body || 'New notification', icon: data.icon || '/favicon-192x192.png', badge: data.badge || '/favicon-72x72.png', tag: data.tag || 'default', requireInteraction: data.requireInteraction || false, data: data.data || {}, actions: data.actions || [ { action: 'open', title: 'Open' }, { action: 'close', title: 'Dismiss' } ] }; event.waitUntil( self.registration.showNotification( data.title || 'Beszel Alert', options ) ); }); // Notification click event self.addEventListener('notificationclick', (event) => { event.notification.close(); const { action, data } = event.notification; const urlToOpen = data?.url || '/'; event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }) .then((clientList) => { // Check if there's already a window open for (const client of clientList) { if (client.url === urlToOpen && 'focus' in client) { return client.focus(); } } // Open new window if not found if (clients.openWindow) { return clients.openWindow(urlToOpen); } }) ); }); // Background sync for offline support self.addEventListener('sync', (event) => { if (event.tag === 'background-sync') { event.waitUntil(doBackgroundSync()); } }); async function doBackgroundSync() { // Retry any pending API requests stored in IndexedDB // This is a placeholder - implement with actual pending request logic console.log('Background sync executed'); } // Periodic background sync (if supported) self.addEventListener('periodicsync', (event) => { if (event.tag === 'update-check') { event.waitUntil(checkForUpdates()); } }); async function checkForUpdates() { // Check for new data and show notifications if needed console.log('Periodic sync executed'); }