// ============================================================ // ELIVIAT SERVICE WORKER // Handles caching, offline fallback, and push notifications // ============================================================ const CACHE_NAME = 'eliviat-v1'; const OFFLINE_URL = '/app'; const PRECACHE_URLS = [ '/app', '/manifest.json', 'https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;500;600&family=DM+Sans:wght@300;400;500;600&display=swap' ]; // ── INSTALL ─────────────────────────────────────────────────── self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME).then(cache => { return cache.addAll(PRECACHE_URLS).catch(() => {}); }).then(() => self.skipWaiting()) ); }); // ── ACTIVATE ────────────────────────────────────────────────── self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(keys => Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))) ).then(() => self.clients.claim()) ); }); // ── FETCH ───────────────────────────────────────────────────── self.addEventListener('fetch', event => { // Skip non-GET and Supabase API requests if (event.request.method !== 'GET') return; if (event.request.url.includes('supabase.co')) return; if (event.request.url.includes('googleapis.com/mcp')) return; event.respondWith( fetch(event.request) .then(response => { // Cache successful HTML/CSS/JS responses if (response && response.status === 200) { const clone = response.clone(); caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone)); } return response; }) .catch(() => { // Offline fallback return caches.match(event.request).then(cached => { return cached || caches.match(OFFLINE_URL); }); }) ); }); // ── PUSH NOTIFICATIONS (OneSignal ready) ────────────────────── self.addEventListener('push', event => { if (!event.data) return; let data; try { data = event.data.json(); } catch { data = { title: 'Eliviat', body: event.data.text() }; } event.waitUntil( self.registration.showNotification(data.title || 'Eliviat', { body: data.body || '', icon: '/icons/icon-192.png', badge: '/icons/icon-96.png', tag: data.tag || 'eliviat', data: { url: data.url || '/app' }, actions: data.actions || [], requireInteraction: data.requireInteraction || false }) ); }); self.addEventListener('notificationclick', event => { event.notification.close(); const url = event.notification.data?.url || '/app'; event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then(list => { const existing = list.find(c => c.url.includes(url)); if (existing) return existing.focus(); return clients.openWindow(url); }) ); });