diff --git a/public/sw.js b/public/sw.js index 82e336b..1952b88 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,30 +1,182 @@ -const CACHE_NAME = 'construprogress-cache-v1'; -const urlsToCache = [ +const CACHE_NAME = 'avante-cache-v2'; +const DATA_CACHE_NAME = 'avante-data-cache-v1'; + +// Files to cache for offline functionality +const FILES_TO_CACHE = [ '/', '/dashboard', - '/projects', - '/projects-list', - '/projects/templates', + '/reports/dashboard', + '/client', + '/client/projects', + '/manifest.json', '/css/app.css', '/js/app.js', - // Add other assets as needed + 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', + 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', + 'https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css', + 'https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js', + 'https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap', + '/icons/icon-72x72.png', + '/icons/icon-96x96.png', + '/icons/icon-128x128.png', + '/icons/icon-144x144.png', + '/icons/icon-152x152.png', + '/icons/icon-192x192.png', + '/icons/icon-384x384.png', + '/icons/icon-512x512.png' ]; +// Install the service worker and cache the app shell self.addEventListener('install', (event) => { + console.log('[ServiceWorker] Install'); event.waitUntil( caches.open(CACHE_NAME) - .then((cache) => cache.addAll(urlsToCache)) + .then((cache) => { + console.log('[ServiceWorker] Caching app shell'); + return cache.addAll(FILES_TO_CACHE); + }) ); + self.skipWaiting(); }); +// Activate the service worker and clean up old caches +self.addEventListener('activate', (event) => { + console.log('[ServiceWorker] Activate'); + event.waitUntil( + caches.keys().then((keyList) => { + return Promise.all( + keyList.map((key) => { + if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) { + console.log('[ServiceWorker] Removing old cache', key); + return caches.delete(key); + } + }) + ); + }) + ); + self.clients.claim(); +}); + +// Fetch strategy: Network first, falling back to cache self.addEventListener('fetch', (event) => { + // Handle API requests differently - cache first, then network + if (event.request.url.includes('/api/') || event.request.url.includes('/offline/')) { + event.respondWith( + caches.open(DATA_CACHE_NAME) + .then((cache) => { + return fetch(event.request) + .then((response) => { + // Clone the response to put in cache and return original + if (response.status === 200) { + cache.put(event.request.url, response.clone()); + } + return response; + }) + .catch(() => { + // If network fails, try to get from cache + return caches.match(event.request); + }) + }) + ); + return; + } + + // For everything else, use cache first with network fallback event.respondWith( caches.match(event.request) - .then((response) => { - if (response) { - return response; + .then((cachedResponse) => { + if (cachedResponse) { + return cachedResponse; + } + + // If not in cache, fetch from network and cache it + return caches.open(CACHE_NAME) + .then((cache) => { + return fetch(event.request) + .then((networkResponse) => { + // Don't cache non-successful responses + if (networkResponse && networkResponse.status === 200 && networkResponse.type === 'basic') { + cache.put(event.request, networkResponse.clone()); + } + return networkResponse; + }); + }); + }) + .catch(() => { + // If both cache and network fail, show offline fallback + if (event.request.mode === 'navigate') { + return caches.match('/'); } - return fetch(event.request); }) ); }); + +// Handle background sync for offline actions +self.addEventListener('sync', (event) => { + console.log('[ServiceWorker] Background syncing', event.tag); + if (event.tag === 'offline-sync') { + event.waitUntil(syncOfflineActions()); + } +}); + +// Handle push notifications +self.addEventListener('push', (event) => { + console.log('[ServiceWorker] Push received'); + const options = { + body: event.data ? event.data.text() : 'Tienes una nueva notificación', + icon: '/icons/icon-192x192.png', + badge: '/icons/icon-72x72.png', + vibrate: [100, 50, 100], + data: { + dateOfArrival: Date.now(), + primaryKey: 1 + } + }; + + event.waitUntil( + self.registration.showNotification('Avante', options) + ); +}); + +self.addEventListener('notificationclick', (event) => { + console.log('[ServiceWorker] Notification click: ', event.notification); + event.notification.close(); + + // Determine what to open based on notification data + const urlToOpen = '/client'; // Default to client portal + + event.waitUntil( + clients.matchAll({ + type: 'window', + includeUncontrolled: true + }).then((clientList) => { + for (const client of clientList) { + if (client.url === urlToOpen && 'focus' in client) { + return client.focus(); + } + } + if (clients.openWindow) { + return clients.openWindow(urlToOpen); + } + }) + ); +}); + +// Helper function to sync offline actions +async function syncOfflineActions() { + console.log('[ServiceWorker] Syncing offline actions'); + // This would typically make a request to your backend to sync pending actions + // For now, we'll just log it + try { + const response = await fetch('/offline/sync', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }); + return response.json(); + } catch (error) { + console.error('[ServiceWorker] Sync failed:', error); + throw error; + } +}