// Service Worker para PWA // Cache First Strategy para funcionamiento offline const isDevelopment = self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1' || self.location.hostname === '[::1]'; if (isDevelopment) { self.addEventListener('install', (event) => { console.log('[SW] Development mode detected, skipping installation'); self.skipWaiting(); }); self.addEventListener('activate', (event) => { console.log('[SW] Development mode detected, unregistering...'); event.waitUntil( self.clients.matchAll().then((clients) => { clients.forEach((client) => { client.postMessage({ type: 'SW_UNREGISTER' }); }); }).then(() => { return self.registration.unregister(); }) ); }); self.addEventListener('fetch', (event) => { return; }); } const CACHE_VERSION = 'v1.0.0'; const CACHE_NAME = `codigo0-${CACHE_VERSION}`; const RUNTIME_CACHE = `codigo0-runtime-${CACHE_VERSION}`; const BASE_PATH = self.location.pathname.split('/').slice(0, -1).join('/') || '/'; const normalizePath = (path) => { if (path.startsWith('/')) { return BASE_PATH === '/' ? path : `${BASE_PATH}${path}`; } return `${BASE_PATH}/${path}`; }; const STATIC_ASSETS = [ normalizePath('/'), normalizePath('/index.html'), normalizePath('/manifest.json'), normalizePath('/favicon.ico'), ]; self.addEventListener('install', (event) => { console.log('[SW] Installing service worker...'); event.waitUntil( caches.open(CACHE_NAME) .then((cache) => { console.log('[SW] Caching static assets'); return Promise.allSettled( STATIC_ASSETS.map(url => cache.add(url).catch(err => { console.warn(`[SW] Failed to cache ${url}:`, err); return null; }) ) ); }) .then(() => { console.log('[SW] Static assets cached'); self.skipWaiting(); }) .catch((error) => { console.error('[SW] Installation failed:', error); self.skipWaiting(); }) ); }); self.addEventListener('activate', (event) => { console.log('[SW] Activating service worker...'); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((cacheName) => { return cacheName !== CACHE_NAME && cacheName !== RUNTIME_CACHE; }) .map((cacheName) => { console.log('[SW] Deleting old cache:', cacheName); return caches.delete(cacheName); }) ); }) .then(() => self.clients.claim()) ); }); self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); if (request.method !== 'GET') { return; } if (url.origin !== location.origin) { return; } if ( url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]' ) { if ( url.pathname.startsWith('/src/') || url.pathname.startsWith('/@') || url.pathname.startsWith('/node_modules/') || url.pathname.includes('?t=') || url.pathname.endsWith('.tsx') || url.pathname.endsWith('.ts') || url.pathname.includes('node_modules') || url.searchParams.has('import') || url.protocol === 'ws:' || url.protocol === 'wss:' ) { return; } } if ( request.destination === 'script' || request.destination === 'style' || request.destination === 'image' || request.destination === 'font' || url.pathname.endsWith('.md') || url.pathname.includes('/assets/') ) { event.respondWith(cacheFirst(request)); } else { event.respondWith(networkFirst(request)); } }); async function cacheFirst(request) { const cache = await caches.open(CACHE_NAME); 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.error('[SW] Fetch failed:', error); if (request.destination === 'image') { return new Response('', { status: 404 }); } throw error; } } async function networkFirst(request) { const cache = await caches.open(RUNTIME_CACHE); 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:', error); const cached = await cache.match(request); if (cached) { return cached; } if (request.mode === 'navigate') { const indexCache = await caches.open(CACHE_NAME); const indexHtml = await indexCache.match(normalizePath('/index.html')); if (indexHtml) { return indexHtml; } } throw error; } } self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } if (event.data && event.data.type === 'CACHE_URLS') { event.waitUntil( caches.open(CACHE_NAME).then((cache) => { return cache.addAll(event.data.urls); }) ); } });