/** * Service Worker for PWA * Handles offline functionality and caching */ const CACHE_NAME = 'mipesca-v2'; const STATIC_CACHE = [ './', 'index.html', 'css/styles.css', 'js/app.js', 'js/db.js', 'js/geolocation.js', 'js/api.js', 'js/sync.js', 'js/components/home.js', 'js/components/species-selector.js', 'js/components/capture-form.js', 'js/components/confirmation.js', 'js/components/history.js', 'js/components/info.js', 'manifest.json', 'https://unpkg.com/dexie@3.2.4/dist/dexie.min.js' ]; // Install event - cache static assets self.addEventListener('install', (event) => { console.log('Service Worker installing...'); event.waitUntil( caches.open(CACHE_NAME) .then((cache) => { console.log('Caching static assets'); return cache.addAll(STATIC_CACHE); }) .then(() => self.skipWaiting()) ); }); // Activate event - clean up old caches self.addEventListener('activate', (event) => { console.log('Service Worker activating...'); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (cacheName !== CACHE_NAME) { console.log('Deleting old cache:', cacheName); return caches.delete(cacheName); } }) ); }).then(() => self.clients.claim()) ); }); // Fetch event - serve from cache, fallback to network self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // API requests - network first, then cache if (url.pathname.startsWith('/api/')) { event.respondWith( fetch(request) .then((response) => { // Clone response to cache it const responseClone = response.clone(); caches.open(CACHE_NAME).then((cache) => { cache.put(request, responseClone); }); return response; }) .catch(() => { // If network fails, try cache return caches.match(request); }) ); return; } // Static assets - cache first, then network event.respondWith( caches.match(request) .then((cachedResponse) => { if (cachedResponse) { return cachedResponse; } return fetch(request) .then((response) => { // Don't cache non-successful responses if (!response || response.status !== 200 || response.type === 'error') { return response; } // Clone response to cache it const responseClone = response.clone(); caches.open(CACHE_NAME).then((cache) => { cache.put(request, responseClone); }); return response; }); }) .catch(() => { // Return offline page if available if (request.mode === 'navigate') { return caches.match('/index.html'); } }) ); }); // Background sync for captures self.addEventListener('sync', (event) => { if (event.tag === 'sync-captures') { console.log('Background sync triggered'); event.waitUntil( // Notify clients to sync self.clients.matchAll().then((clients) => { clients.forEach((client) => { client.postMessage({ type: 'BACKGROUND_SYNC', action: 'sync-captures' }); }); }) ); } });