// WebMoney Service Worker - PWA Support const CACHE_VERSION = 'webmoney-v1.40.0'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_DYNAMIC = `${CACHE_VERSION}-dynamic`; const CACHE_IMMUTABLE = `${CACHE_VERSION}-immutable`; // Recursos para cache imediato (install) const STATIC_ASSETS = [ '/', '/index.html', '/manifest.json', '/logo-192.png', '/logo-512.png', '/apple-touch-icon.png', ]; // Estratégia: Cache First com fallback para Network const CACHE_FIRST_PATTERNS = [ /\.(png|jpg|jpeg|svg|gif|webp|ico)$/, /\.(woff|woff2|ttf|eot)$/, /bootstrap-icons/, ]; // Estratégia: Network First com fallback para Cache const NETWORK_FIRST_PATTERNS = [ /\/api\//, ]; // Install - cacheia recursos estáticos self.addEventListener('install', (event) => { console.log('[SW] Installing Service Worker...', CACHE_VERSION); event.waitUntil( caches.open(CACHE_STATIC).then((cache) => { console.log('[SW] Caching static assets'); return cache.addAll(STATIC_ASSETS); }) .then(() => self.skipWaiting()) // Ativa imediatamente ); }); // Activate - limpa caches antigos self.addEventListener('activate', (event) => { console.log('[SW] Activating Service Worker...', CACHE_VERSION); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => name.startsWith('webmoney-') && name !== CACHE_STATIC && name !== CACHE_DYNAMIC && name !== CACHE_IMMUTABLE) .map((name) => { console.log('[SW] Deleting old cache:', name); return caches.delete(name); }) ); }) .then(() => self.clients.claim()) // Assume controle imediatamente ); }); // Fetch - estratégias de cache self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Ignora requisições não-GET if (request.method !== 'GET') { return; } // Ignora chrome-extension e outras URLs especiais if (!url.protocol.startsWith('http')) { return; } // Cache First para assets estáticos if (CACHE_FIRST_PATTERNS.some(pattern => pattern.test(request.url))) { event.respondWith(cacheFirst(request)); return; } // Network First para API calls if (NETWORK_FIRST_PATTERNS.some(pattern => pattern.test(request.url))) { event.respondWith(networkFirst(request)); return; } // Stale-While-Revalidate para HTML/CSS/JS event.respondWith(staleWhileRevalidate(request)); }); // Estratégia: Cache First (imagens, fontes) async function cacheFirst(request) { const cache = await caches.open(CACHE_IMMUTABLE); const cached = await cache.match(request); if (cached) { return cached; } try { const response = await fetch(request); if (response.ok) { cache.put(request, response.clone()); } return response; } catch (error) { console.log('[SW] Fetch failed for:', request.url); // Retorna cache mesmo se expirado return cached || new Response('Network error', { status: 408 }); } } // Estratégia: Network First (API) async function networkFirst(request) { const cache = await caches.open(CACHE_DYNAMIC); try { const response = await fetch(request); if (response.ok) { cache.put(request, response.clone()); } return response; } catch (error) { console.log('[SW] Network failed, trying cache:', request.url); const cached = await cache.match(request); return cached || new Response(JSON.stringify({ error: 'Offline' }), { status: 503, headers: { 'Content-Type': 'application/json' } }); } } // Estratégia: Stale-While-Revalidate (HTML, CSS, JS) async function staleWhileRevalidate(request) { const cache = await caches.open(CACHE_DYNAMIC); const cached = await cache.match(request); const fetchPromise = fetch(request).then((response) => { if (response.ok) { cache.put(request, response.clone()); } return response; }); return cached || fetchPromise; } // Mensagens do cliente self.addEventListener('message', (event) => { if (event.data === 'SKIP_WAITING') { self.skipWaiting(); } if (event.data === 'CLEAR_CACHE') { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((name) => caches.delete(name)) ); }) ); } }); console.log('[SW] Service Worker loaded:', CACHE_VERSION);