Ctrl K
ComunidadGuías…

frontend/public/sw.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * 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 || '/')
  )
})