2025-12-17 14:19:57 +00:00
|
|
|
// Service Worker para PWA
|
|
|
|
|
// Cache First Strategy para funcionamiento offline
|
|
|
|
|
|
|
|
|
|
const CACHE_NAME = 'emerges-tes-v1';
|
|
|
|
|
const RUNTIME_CACHE = 'emerges-tes-runtime-v1';
|
|
|
|
|
|
2025-12-17 15:19:37 +00:00
|
|
|
// Detectar base path dinámicamente (para GitHub Pages)
|
|
|
|
|
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}`;
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-17 14:19:57 +00:00
|
|
|
// Archivos estáticos a cachear en la instalación
|
|
|
|
|
const STATIC_ASSETS = [
|
2025-12-17 15:19:37 +00:00
|
|
|
normalizePath('/'),
|
|
|
|
|
normalizePath('/index.html'),
|
|
|
|
|
normalizePath('/manifest.json'),
|
|
|
|
|
normalizePath('/favicon.ico'),
|
2025-12-17 14:19:57 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Instalación del Service Worker
|
|
|
|
|
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 cache.addAll(STATIC_ASSETS);
|
|
|
|
|
})
|
|
|
|
|
.then(() => self.skipWaiting()) // Activar inmediatamente
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Activación del Service Worker
|
|
|
|
|
self.addEventListener('activate', (event) => {
|
|
|
|
|
console.log('[SW] Activating service worker...');
|
|
|
|
|
|
|
|
|
|
event.waitUntil(
|
|
|
|
|
caches.keys().then((cacheNames) => {
|
|
|
|
|
return Promise.all(
|
|
|
|
|
cacheNames
|
|
|
|
|
.filter((cacheName) => {
|
|
|
|
|
// Eliminar caches antiguos
|
|
|
|
|
return cacheName !== CACHE_NAME && cacheName !== RUNTIME_CACHE;
|
|
|
|
|
})
|
|
|
|
|
.map((cacheName) => {
|
|
|
|
|
console.log('[SW] Deleting old cache:', cacheName);
|
|
|
|
|
return caches.delete(cacheName);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
.then(() => self.clients.claim()) // Tomar control de todas las páginas
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Interceptar peticiones
|
|
|
|
|
self.addEventListener('fetch', (event) => {
|
|
|
|
|
const { request } = event;
|
|
|
|
|
const url = new URL(request.url);
|
|
|
|
|
|
|
|
|
|
// Ignorar peticiones no GET
|
|
|
|
|
if (request.method !== 'GET') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ignorar peticiones a APIs externas (si las hay)
|
|
|
|
|
if (url.origin !== location.origin) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Estrategia: Cache First para assets estáticos
|
|
|
|
|
if (
|
|
|
|
|
request.destination === 'script' ||
|
|
|
|
|
request.destination === 'style' ||
|
|
|
|
|
request.destination === 'image' ||
|
|
|
|
|
request.destination === 'font' ||
|
|
|
|
|
url.pathname.endsWith('.md')
|
|
|
|
|
) {
|
|
|
|
|
event.respondWith(cacheFirst(request));
|
|
|
|
|
} else {
|
|
|
|
|
// Network First para HTML y otros
|
|
|
|
|
event.respondWith(networkFirst(request));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Cache First Strategy
|
|
|
|
|
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);
|
|
|
|
|
// Si es una imagen, retornar una imagen placeholder
|
|
|
|
|
if (request.destination === 'image') {
|
|
|
|
|
return new Response('', { status: 404 });
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Network First Strategy
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
// Si no hay cache y estamos offline, retornar index.html para SPA
|
|
|
|
|
if (request.mode === 'navigate') {
|
|
|
|
|
const indexCache = await caches.open(CACHE_NAME);
|
2025-12-17 15:19:37 +00:00
|
|
|
const indexHtml = await indexCache.match(normalizePath('/index.html'));
|
2025-12-17 14:19:57 +00:00
|
|
|
if (indexHtml) {
|
|
|
|
|
return indexHtml;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Manejar mensajes del cliente
|
|
|
|
|
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);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|