// Service Worker pour Portail RH Inter Santé const CACHE_NAME = 'inter-sante-portal-v1.1'; const OFFLINE_URL = '/offline.html'; // Ressources ESSENTIELLES à mettre en cache (vérifier l'existence) const PRECACHE_URLS = [ '/', '/Bootstrap_new/css/style_office.css', '/Bootstrap_new/css/ux_enhancements.css', '/Bootstrap_new/js/ux-manager.js', '/Js/fonctions.js', '/manifest.json', '/Bootstrap_new/images/new/favicon.png' ]; // Installation - Pré-cache des ressources essentielles self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => { console.log('[Service Worker] Pré-cache des ressources'); // Cache only resources that exist const cachePromises = PRECACHE_URLS.map(url => { return fetch(url, { mode: 'no-cors' }) .then(response => { if (response.ok || response.type === 'opaque') { return cache.put(url, response); } console.warn(`[SW] Resource not found: ${url}`); return Promise.resolve(); }) .catch(error => { console.warn(`[SW] Failed to cache ${url}:`, error); return Promise.resolve(); }); }); return Promise.all(cachePromises); }) .then(() => { console.log('[Service Worker] Installation terminée'); return self.skipWaiting(); }) .catch(error => { console.error('[Service Worker] Erreur installation:', error); }) ); }); // Activation - Nettoyage des anciens caches self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { if (cacheName !== CACHE_NAME) { console.log('[Service Worker] Suppression ancien cache:', cacheName); return caches.delete(cacheName); } }) ); }).then(() => { console.log('[Service Worker] Activation terminée'); return self.clients.claim(); }) ); }); // Stratégie de cache: Stale-While-Revalidate self.addEventListener('fetch', event => { // Ignorer les requêtes non-GET if (event.request.method !== 'GET') { return; } // Ignorer les requêtes chrome-extension if (event.request.url.includes('chrome-extension')) { return; } // Pour les pages HTML: Network First avec fallback cache if (event.request.headers.get('accept').includes('text/html')) { event.respondWith( fetch(event.request) .then(response => { // Vérifier si la réponse est valide if (!response || response.status !== 200 || response.type === 'error') { throw new Error('Network response was not ok'); } // Cloner la réponse pour le cache const responseToCache = response.clone(); caches.open(CACHE_NAME) .then(cache => { cache.put(event.request, responseToCache); }); return response; }) .catch(() => { // Fallback au cache return caches.match(event.request) .then(cachedResponse => { return cachedResponse || caches.match('/'); }); }) ); return; } // Pour les autres ressources: Cache First, fallback Network event.respondWith( caches.match(event.request) .then(cachedResponse => { if (cachedResponse) { // Toujours rafraîchir le cache en arrière-plan fetch(event.request) .then(response => { if (response && response.ok) { caches.open(CACHE_NAME) .then(cache => cache.put(event.request, response)); } }) .catch(() => {}); // Ignorer les erreurs de mise à jour return cachedResponse; } // Pas dans le cache, aller au réseau return fetch(event.request) .then(response => { // Vérifier si nous avons reçu une réponse valide if (!response || !response.ok) { return response; // Retourner la réponse même si elle a échoué } // Mettre en cache la réponse pour la prochaine fois const responseToCache = response.clone(); caches.open(CACHE_NAME) .then(cache => { cache.put(event.request, responseToCache); }); return response; }) .catch(error => { console.error('[SW] Fetch failed:', error); // Pour les CSS/JS, retourner des réponses de secours const url = event.request.url; if (url.includes('.css')) { return new Response('/* Ressource temporairement indisponible */', { headers: { 'Content-Type': 'text/css' } }); } if (url.includes('.js')) { return new Response('// Ressource temporairement indisponible', { headers: { 'Content-Type': 'application/javascript' } }); } // Pour les images, retourner une image de secours if (url.includes('.png') || url.includes('.jpg') || url.includes('.svg')) { return fetch('/Bootstrap_new/images/new/favicon.png') .catch(() => { // Si même l'icône de secours échoue, retourner une réponse vide return new Response('', { status: 404 }); }); } return new Response('Ressource non disponible hors ligne', { headers: { 'Content-Type': 'text/plain' } }); }); }) ); }); // Gestion des messages entre l'app et le Service Worker self.addEventListener('message', event => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } if (event.data && event.data.type === 'GET_CACHE_STATUS') { event.ports[0].postMessage({ cacheName: CACHE_NAME, status: 'active' }); } }); // Gestion des notifications push (optionnel) self.addEventListener('push', event => { if (!event.data) return; const data = event.data.json(); const options = { body: data.body || 'Nouvelle notification', icon: '/Bootstrap_new/images/new/favicon.png', badge: '/Bootstrap_new/images/new/favicon.png', vibrate: [200, 100, 200], data: { url: data.url || '/' } }; event.waitUntil( self.registration.showNotification(data.title || 'Inter Santé', options) ); }); self.addEventListener('notificationclick', event => { event.notification.close(); event.waitUntil( clients.openWindow(event.notification.data.url || '/') ); });