- Fix: ErrorBoundary movido dentro de BrowserRouter para resolver error de contexto React Router - Fix: Service Worker actualizado con Promise.allSettled para manejar errores de caché - Feat: Iconos PWA optimizados (192x192, 512x512, maskable) - Feat: Scripts de diagnóstico y limpieza de desarrollo - Feat: Documentación de diagnóstico de errores - Update: React Router future flags configurados - Update: Manifest.json con iconos y screenshots configurados - Clean: Eliminados archivos obsoletos y documentación antigua - Docs: Actualizado RESUMEN_MANUAL_TES.md y CHECKLIST_PWA_COMPLETA.md
179 lines
5 KiB
JavaScript
179 lines
5 KiB
JavaScript
// Service Worker para PWA
|
|
// Cache First Strategy para funcionamiento offline
|
|
|
|
// Versión del cache - Incrementar cuando hay cambios importantes
|
|
const CACHE_VERSION = 'v1.0.1';
|
|
const CACHE_NAME = `emerges-tes-${CACHE_VERSION}`;
|
|
const RUNTIME_CACHE = `emerges-tes-runtime-${CACHE_VERSION}`;
|
|
|
|
// 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}`;
|
|
};
|
|
|
|
// Archivos estáticos a cachear en la instalación
|
|
const STATIC_ASSETS = [
|
|
normalizePath('/'),
|
|
normalizePath('/index.html'),
|
|
normalizePath('/manifest.json'),
|
|
normalizePath('/favicon.ico'),
|
|
// Nota: Las imágenes en /assets/infografias/ se cachearán automáticamente
|
|
// cuando se soliciten (cache-first strategy para imágenes)
|
|
];
|
|
|
|
// 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');
|
|
// Cachear recursos uno por uno para manejar errores individualmente
|
|
return Promise.allSettled(
|
|
STATIC_ASSETS.map(url =>
|
|
cache.add(url).catch(err => {
|
|
console.warn(`[SW] Failed to cache ${url}:`, err);
|
|
return null; // Continuar aunque falle uno
|
|
})
|
|
)
|
|
);
|
|
})
|
|
.then(() => {
|
|
console.log('[SW] Static assets cached');
|
|
self.skipWaiting(); // Activar inmediatamente
|
|
})
|
|
.catch((error) => {
|
|
console.error('[SW] Installation failed:', error);
|
|
// Continuar aunque falle la instalación
|
|
self.skipWaiting();
|
|
})
|
|
);
|
|
});
|
|
|
|
// 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') ||
|
|
url.pathname.includes('/assets/infografias/') ||
|
|
url.pathname.includes('/assets/')
|
|
) {
|
|
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);
|
|
const indexHtml = await indexCache.match(normalizePath('/index.html'));
|
|
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);
|
|
})
|
|
);
|
|
}
|
|
});
|