streamflow/frontend/public/service-worker.js

179 lines
5 KiB
JavaScript
Raw Permalink Normal View History

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...');
}