/// /// /// /// import { build, files, version } from '$service-worker'; declare const self: ServiceWorkerGlobalScope; const CACHE = `tonemark-${version}`; const ASSETS = [...build, ...files]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE).then((cache) => cache.addAll(ASSETS)) ); self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))) ) ); self.clients.claim(); }); self.addEventListener('fetch', (event) => { if (event.request.method !== 'GET') return; event.respondWith( (async () => { const url = new URL(event.request.url); const cache = await caches.open(CACHE); // Serve cached assets instantly if (ASSETS.includes(url.pathname)) { const cached = await cache.match(url.pathname); if (cached) return cached; } // Network-first for everything else try { const response = await fetch(event.request); if (response instanceof Response && response.status === 200) { cache.put(event.request, response.clone()); } return response; } catch { const cached = await cache.match(event.request); if (cached) return cached; throw new Error('Offline and no cached response'); } })() ); }); // ── Push notifications ────────────────────────────────────────────────────── self.addEventListener('push', (event) => { const data = (event.data?.json() as { jobId?: string; title?: string; body?: string } | null) ?? {}; const title = data.title ?? 'Tonemark'; const body = data.body ?? 'Your transcript is ready.'; const jobId = data.jobId; event.waitUntil( self.registration.showNotification(title, { body, icon: '/icons/android-icon-192.png', badge: '/icons/android-icon-192.png', tag: jobId ? `job-${jobId}` : 'tonemark', data: { jobId } }) ); }); self.addEventListener('notificationclick', (event) => { event.notification.close(); const jobId = (event.notification.data as { jobId?: string } | null)?.jobId; const url = jobId ? `/jobs/${jobId}` : '/'; event.waitUntil( self.clients .matchAll({ type: 'window', includeUncontrolled: true }) .then((clients) => { const target = clients.find((c) => jobId ? c.url.includes(`/jobs/${jobId}`) : c.url === self.location.origin + '/' ); if (target) return target.focus(); return self.clients.openWindow(url); }) ); });