/** * Service Worker for Sublarr PWA * * Provides offline caching and background sync capabilities. */ const CACHE_NAME = 'sublarr-v3' const STATIC_CACHE = 'sublarr-static-v3' const API_CACHE = 'sublarr-api-v3' // Assets to cache on install const STATIC_ASSETS = [ '/', '/index.html', '/manifest.json', ] // Install event - cache static assets self.addEventListener('install', (event) => { event.waitUntil( caches.open(STATIC_CACHE).then((cache) => { return cache.addAll(STATIC_ASSETS) }) ) self.skipWaiting() }) // Activate event - clean up old caches self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => { return name !== STATIC_CACHE && name !== API_CACHE }) .map((name) => caches.delete(name)) ) }) ) return self.clients.claim() }) // Fetch event - serve from cache, fallback to network self.addEventListener('fetch', (event) => { const { request } = event const url = new URL(request.url) // Skip non-GET requests if (request.method !== 'GET') { return } // Skip external URLs if (url.origin !== location.origin) { return } // Cache strategy: Cache First for static assets, Network First for API if (url.pathname.startsWith('/api/')) { // API: Network first, fallback to cache event.respondWith( fetch(request) .then((response) => { if (response.ok) { const clone = response.clone() caches.open(API_CACHE).then((cache) => { cache.put(request, clone) }) } return response }) .catch(() => { return caches.match(request) }) ) } else { // JS/CSS chunks (Vite uses content hashes): Network first — never stale // Images/fonts/manifest: Cache first (truly immutable) const isImmutable = /\.(png|jpg|jpeg|ico|webp|woff2?|svg)(\?.*)?$/.test(url.pathname) if (isImmutable) { event.respondWith( caches.match(request).then((response) => { return ( response || fetch(request).then((response) => { if (response.ok) { const clone = response.clone() caches.open(STATIC_CACHE).then((cache) => { cache.put(request, clone) }) } return response }) ) }) ) } else { // HTML, JS, CSS: Network first so deploys and HMR updates are always fresh event.respondWith( fetch(request) .then((response) => { if (response.ok) { const clone = response.clone() caches.open(STATIC_CACHE).then((cache) => { cache.put(request, clone) }) } return response }) .catch(() => caches.match(request)) ) } } }) // Background sync for downloads (if needed) self.addEventListener('sync', (event) => { if (event.tag === 'background-sync') { event.waitUntil( // Perform background sync operations Promise.resolve() ) } }) // Push notifications (if needed) self.addEventListener('push', (event) => { const data = event.data?.json() || {} const title = data.title || 'Sublarr' const options = { body: data.body || 'New notification', icon: '/icon-192.png', badge: '/icon-192.png', tag: data.tag || 'default', } event.waitUntil(self.registration.showNotification(title, options)) }) // Notification click handler self.addEventListener('notificationclick', (event) => { event.notification.close() event.waitUntil( clients.openWindow(event.notification.data?.url || '/') ) })