const CACHE_NAME = 'streamflow-v1'; const urlsToCache = [ '/', '/index.html', '/manifest.json' ]; // Global error handler for service worker (CWE-391 protection) self.addEventListener('error', (event) => { // Log error but don't expose to user console.error('Service Worker error:', event.error); event.preventDefault(); }); // Handle unhandled promise rejections in service worker self.addEventListener('unhandledrejection', (event) => { // Log error but don't expose to user console.error('Service Worker unhandled rejection:', event.reason); event.preventDefault(); }); // Install event - cache static assets self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => cache.addAll(urlsToCache)) .then(() => self.skipWaiting()) .catch((error) => { // Handle cache errors gracefully console.error('Cache installation error:', error); // Still skip waiting even if caching fails return self.skipWaiting(); }) ); }); // Activate event - 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 event - serve from cache, fallback to network self.addEventListener('fetch', (event) => { // Skip non-GET requests - let them pass through to network if (event.request.method !== 'GET') { event.respondWith(fetch(event.request)); return; } // Skip API requests and streaming URLs if (event.request.url.includes('/api/') || event.request.url.includes('m3u8') || event.request.url.includes('ts')) { event.respondWith(fetch(event.request)); return; } event.respondWith( caches.match(event.request) .then((response) => { // Cache hit - return response if (response) { return response; } return fetch(event.request).then((response) => { // Check if valid response if (!response || response.status !== 200 || response.type !== 'basic') { return response; } // Clone the response const responseToCache = response.clone(); caches.open(CACHE_NAME) .then((cache) => { cache.put(event.request, responseToCache); }) .catch((error) => { // Silently handle cache write errors console.error('Cache write error:', error); }); return response; }).catch((error) => { // Network error - return generic offline response console.error('Fetch error:', error); // Return a basic error response instead of throwing return new Response('Offline', { status: 503, statusText: 'Service Unavailable' }); }); }) .catch((error) => { // Handle any cache read errors console.error('Cache read error:', error); // Try to fetch from network as fallback return fetch(event.request).catch(() => { return new Response('Offline', { status: 503, statusText: 'Service Unavailable' }); }); }) ); }); // Handle background sync self.addEventListener('sync', (event) => { if (event.tag === 'sync-playlists') { event.waitUntil(syncPlaylists()); } }); // Handle push notifications self.addEventListener('push', (event) => { const data = event.data ? event.data.json() : {}; const options = { body: data.body || 'New notification from StreamFlow', icon: '/icons/icon-192x192.svg', badge: '/icons/icon-72x72.svg', vibrate: [200, 100, 200], tag: data.tag || 'streamflow-notification', renotify: true, data: data.data || {}, actions: data.actions || [ { action: 'open', title: 'Open' } ] }; event.waitUntil( self.registration.showNotification(data.title || 'StreamFlow', options) ); }); // Handle notification clicks self.addEventListener('notificationclick', (event) => { event.notification.close(); const urlToOpen = event.notification.data.url || '/'; event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }) .then((clientList) => { // Check if there's already a window open for (let i = 0; i < clientList.length; i++) { const client = clientList[i]; if (client.url.includes(urlToOpen) && 'focus' in client) { return client.focus(); } } // If no window is open, open a new one if (clients.openWindow) { return clients.openWindow(urlToOpen); } }) ); }); // Sync function async function syncPlaylists() { // Implementation for syncing playlists console.log('Syncing playlists...'); }