/** * The Nexus Service Worker * Provides offline capability and home screen install support * Strategy: Cache-first for local assets, stale-while-revalidate for CDN */ const CACHE_VERSION = 'nexus-v1'; const STATIC_CACHE = `${CACHE_VERSION}-static`; const CDN_CACHE = `${CACHE_VERSION}-cdn`; const OFFLINE_PAGE = '/offline.html'; // Core local assets that must be cached const CORE_ASSETS = [ '/', '/index.html', '/style.css', '/app.js', '/manifest.json', '/icons/nexus-icon.svg', '/icons/nexus-maskable.svg', OFFLINE_PAGE ]; // CDN resources that benefit from caching but can be stale const CDN_PATTERNS = [ /^https:\/\/unpkg\.com/, /^https:\/\/cdn\.jsdelivr\.net/, /^https:\/\/fonts\.googleapis\.com/, /^https:\/\/fonts\.gstatic\.com/, /^https:\/\/cdn\.threejs\.org/ ]; /** * Check if a URL matches any CDN pattern */ function isCdnResource(url) { return CDN_PATTERNS.some(pattern => pattern.test(url)); } /** * Install event - cache core assets */ self.addEventListener('install', (event) => { console.log('[Nexus SW] Installing...'); event.waitUntil( caches.open(STATIC_CACHE) .then(cache => { console.log('[Nexus SW] Caching core assets'); return cache.addAll(CORE_ASSETS); }) .then(() => { console.log('[Nexus SW] Core assets cached'); return self.skipWaiting(); }) .catch(err => { console.error('[Nexus SW] Cache failed:', err); }) ); }); /** * Activate event - clean up old caches */ self.addEventListener('activate', (event) => { console.log('[Nexus SW] Activating...'); event.waitUntil( caches.keys() .then(cacheNames => { return Promise.all( cacheNames .filter(name => name.startsWith('nexus-') && name !== STATIC_CACHE && name !== CDN_CACHE) .map(name => { console.log('[Nexus SW] Deleting old cache:', name); return caches.delete(name); }) ); }) .then(() => { console.log('[Nexus SW] Activated'); return self.clients.claim(); }) ); }); /** * Fetch event - handle requests with appropriate strategy */ self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Skip non-GET requests if (request.method !== 'GET') { return; } // Skip cross-origin requests that aren't CDN resources if (url.origin !== self.location.origin && !isCdnResource(url.href)) { return; } // Handle CDN resources with stale-while-revalidate if (isCdnResource(url.href)) { event.respondWith(handleCdnRequest(request)); return; } // Handle local assets with cache-first strategy event.respondWith(handleLocalRequest(request)); }); /** * Cache-first strategy for local assets * Fastest response, updates cache in background */ async function handleLocalRequest(request) { try { const cache = await caches.open(STATIC_CACHE); const cached = await cache.match(request); // Return cached version immediately if available if (cached) { // Revalidate in background for next time fetch(request) .then(response => { if (response.ok) { cache.put(request, response.clone()); } }) .catch(() => { // Network failed, cached version is already being used }); return cached; } // Not in cache - fetch from network const response = await fetch(request); if (response.ok) { cache.put(request, response.clone()); } return response; } catch (error) { console.error('[Nexus SW] Local request failed:', error); // Return offline page for navigation requests if (request.mode === 'navigate') { const cache = await caches.open(STATIC_CACHE); const offlinePage = await cache.match(OFFLINE_PAGE); if (offlinePage) { return offlinePage; } } throw error; } } /** * Stale-while-revalidate strategy for CDN resources * Serves stale content while updating in background */ async function handleCdnRequest(request) { const cache = await caches.open(CDN_CACHE); const cached = await cache.match(request); // Always try to fetch fresh version const fetchPromise = fetch(request) .then(response => { if (response.ok) { cache.put(request, response.clone()); } return response; }) .catch(error => { console.error('[Nexus SW] CDN fetch failed:', error); throw error; }); // Return cached version immediately if available, otherwise wait for network if (cached) { // Return stale but revalidate for next time fetchPromise.catch(() => {}); // Swallow errors, we have cached version return cached; } // No cache - must wait for network return fetchPromise; } /** * Message handler for runtime cache updates */ self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } if (event.data && event.data.type === 'GET_VERSION') { event.ports[0].postMessage({ version: CACHE_VERSION }); } }); /** * Background sync for deferred actions (future enhancement) */ self.addEventListener('sync', (event) => { if (event.tag === 'nexus-sync') { console.log('[Nexus SW] Background sync triggered'); // Future: sync chat messages, state updates, etc. } }); console.log('[Nexus SW] Service worker loaded');