// Veza Platform Service Worker // Version 1.0.0 const CACHE_VERSION = '__BUILD_VERSION__'; const CACHE_NAME = `veza-platform-${CACHE_VERSION}`; const STATIC_CACHE_NAME = `veza-static-${CACHE_VERSION}`; const DYNAMIC_CACHE_NAME = `veza-dynamic-${CACHE_VERSION}`; // Files to cache on install const STATIC_ASSETS = [ '/', '/dashboard', '/chat', '/library', '/profile', '/settings', '/manifest.json', '/icons/icon-192x192.png', '/icons/icon-512x512.png' ]; // API endpoints to cache with network-first strategy const API_CACHE_PATTERNS = [ /^https?:\/\/.*\/api\/v1\/user\/profile$/, /^https?:\/\/.*\/api\/v1\/library\/files$/, /^https?:\/\/.*\/api\/v1\/dashboard\/stats$/ ]; // Install event - cache static assets // CRITICAL FIX: Service Worker disabled - this code is kept for reference but should not execute // The Service Worker registration is disabled in pwa.ts to prevent React.Children cache issues self.addEventListener('install', (event) => { console.log('[SW] Service Worker install event - DISABLED to prevent cache conflicts'); // CRITICAL FIX: Skip waiting and immediately activate to avoid caching old chunks event.waitUntil( Promise.all([ // Delete all existing caches to prevent serving old JS chunks caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { console.log('[SW] Deleting old cache:', cacheName); return caches.delete(cacheName); }) ); }), // Skip waiting immediately self.skipWaiting() ]).then(() => { console.log('[SW] All caches cleared, skipping waiting'); }) ); }); // Activate event - clean old caches // CRITICAL FIX: Aggressively clear all caches to prevent serving old JS chunks self.addEventListener('activate', (event) => { console.log('[SW] Activating service worker - clearing all caches'); event.waitUntil( Promise.all([ // Delete ALL caches to prevent serving old JS chunks caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { console.log('[SW] Deleting cache:', cacheName); return caches.delete(cacheName); }) ); }), // Take control immediately self.clients.claim() ]).then(() => { console.log('[SW] All caches cleared, service worker activated'); }) ); }); // Fetch event - handle requests with appropriate caching strategy self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Skip non-GET requests if (request.method !== 'GET') { return; } // Skip WebSocket connections if (request.headers.get('upgrade') === 'websocket') { return; } // CRITICAL FIX: Skip service worker registration and JS chunks to avoid blocking // These must always come from network to ensure latest code if (url.pathname.includes('/sw.js') || url.pathname.includes('/js/') || url.pathname.includes('/assets/')) { return; // Let browser fetch directly from network } // Skip external requests (except API) if (!url.origin.includes(self.location.origin) && !isApiRequest(request.url)) { return; } event.respondWith( handleRequest(request) ); }); // Handle different types of requests with appropriate strategies async function handleRequest(request) { try { // Strategy 1: Cache First for static assets if (isStaticAsset(request.url)) { return await cacheFirst(request, STATIC_CACHE_NAME); } // Strategy 2: Network First for API requests if (isApiRequest(request.url)) { return await networkFirst(request, DYNAMIC_CACHE_NAME); } // Strategy 3: Stale While Revalidate for pages if (isPageRequest(request.url)) { return await staleWhileRevalidate(request, DYNAMIC_CACHE_NAME); } // Default: Network only return await fetch(request); } catch (error) { console.error('[SW] Request failed:', error); // Return offline page for navigation requests if (isPageRequest(request.url)) { return await getOfflinePage(); } // Return cached version if available const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; } // Return generic offline response return new Response('Offline', { status: 503, statusText: 'Service Unavailable' }); } } // Cache First strategy async function cacheFirst(request, cacheName) { const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; } const networkResponse = await fetch(request); if (networkResponse.ok) { const cache = await caches.open(cacheName); cache.put(request, networkResponse.clone()); } return networkResponse; } // Network First strategy async function networkFirst(request, cacheName) { try { const networkResponse = await fetch(request); if (networkResponse.ok) { const cache = await caches.open(cacheName); cache.put(request, networkResponse.clone()); } return networkResponse; } catch (error) { const cachedResponse = await caches.match(request); if (cachedResponse) { console.log('[SW] Serving cached API response'); return cachedResponse; } throw error; } } // Stale While Revalidate strategy // CORRECTION DURABLE: Clone la réponse IMMÉDIATEMENT pour éviter "Response body is already used" async function staleWhileRevalidate(request, cacheName) { const cachedResponse = await caches.match(request); const networkResponsePromise = fetch(request) .then((networkResponse) => { if (networkResponse.ok) { // ✅ Cloner IMMÉDIATEMENT la réponse avant toute autre opération const responseToCache = networkResponse.clone(); // Mettre en cache de manière asynchrone (sans bloquer) caches.open(cacheName).then((cache) => { cache.put(request, responseToCache).catch((err) => { console.warn('[SW] Failed to cache response:', err); }); }); } return networkResponse; }) .catch(() => null); return cachedResponse || await networkResponsePromise; } // Get offline page async function getOfflinePage() { const cache = await caches.open(STATIC_CACHE_NAME); const offlineResponse = await cache.match('/'); if (offlineResponse) { return offlineResponse; } return new Response(`
Vous êtes actuellement hors ligne. Certaines fonctionnalités peuvent être limitées.