frontend/public/sw.js
Rama master · réplica de lectura del repositorio original.
/**
* 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 || '/')
)
})