// sw-register.js - Version 2.0 // Enregistrement et gestion du Service Worker (function() { 'use strict'; // ============================================ // CONFIGURATION // ============================================ const SW_URL = '/service-worker.js?v=2.1'; const CHECK_INTERVAL = 60 * 60 * 1000; // 1 heure const DEBUG = true; // ============================================ // VÉRIFICATIONS PRÉALABLES // ============================================ // 1. Vérifier si le Service Worker est supporté if (!('serviceWorker' in navigator)) { log('Service Worker non supporté par ce navigateur'); return; } // 2. Vérifier si on est en HTTPS ou localhost if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') { log('Service Worker nécessite HTTPS en production'); return; } // ============================================ // FONCTION PRINCIPALE D'ENREGISTREMENT // ============================================ function registerServiceWorker() { log('Tentative d\'enregistrement du Service Worker...'); navigator.serviceWorker.register(SW_URL) .then(handleRegistrationSuccess) .catch(handleRegistrationError); } // ============================================ // GESTION DE LA RÉUSSITE // ============================================ function handleRegistrationSuccess(registration) { log('Service Worker enregistré avec succès:', registration.scope); // Configurer les écouteurs d'événements setupEventListeners(registration); // Configurer la vérification périodique des mises à jour setupUpdateChecking(registration); // Initialiser la gestion hors ligne setupOfflineManagement(); // Exposer l'API publique exposePublicAPI(registration); } // ============================================ // GESTION DES ERREURS // ============================================ function handleRegistrationError(error) { console.error('Erreur d\'enregistrement du Service Worker:', error); // Suggestions de dépannage basées sur l'erreur if (error.name === 'SecurityError') { console.warn('⚠️ Vérifiez que vous êtes en HTTPS'); } else if (error.name === 'TypeError') { console.warn('⚠️ Vérifiez le chemin du Service Worker'); } else if (error.message.includes('MIME type')) { console.warn('⚠️ Vérifiez l\'en-tête Content-Type du Service Worker'); } } // ============================================ // CONFIGURATION DES ÉCOUTEURS // ============================================ function setupEventListeners(registration) { // Écouter les mises à jour du Service Worker registration.addEventListener('updatefound', function() { const newWorker = registration.installing; log('Nouvelle version du Service Worker trouvée:', newWorker.state); newWorker.addEventListener('statechange', function() { log('État du nouveau Service Worker:', this.state); if (this.state === 'installed' && navigator.serviceWorker.controller) { showUpdateNotification(); } if (this.state === 'activated') { log('Nouveau Service Worker activé'); notifyClientsOfUpdate(); } }); }); // Écouter les messages du Service Worker navigator.serviceWorker.addEventListener('message', handleServiceWorkerMessage); } // ============================================ // GESTION DES MESSAGES DU SERVICE WORKER // ============================================ function handleServiceWorkerMessage(event) { log('Message reçu du Service Worker:', event.data); switch (event.data.type) { case 'SW_ACTIVATED': log('Service Worker activé, version:', event.data.version); break; case 'CACHE_UPDATED': log('Cache mis à jour:', event.data.resources); break; case 'OFFLINE_MODE': showOfflineNotification(); break; } } // ============================================ // VÉRIFICATION PÉRIODIQUE DES MISES À JOUR // ============================================ function setupUpdateChecking(registration) { // Vérifier les mises à jour toutes les heures setInterval(() => { registration.update().catch(err => { log('Pas de mise à jour disponible:', err.message); }); }, CHECK_INTERVAL); // Vérifier aussi quand la page redevient visible document.addEventListener('visibilitychange', function() { if (!document.hidden) { registration.update().catch(() => {}); } }); } // ============================================ // GESTION HORS LIGNE // ============================================ function setupOfflineManagement() { window.addEventListener('online', function() { log('Connexion rétablie'); document.documentElement.classList.remove('offline'); showOnlineNotification(); syncData(); }); window.addEventListener('offline', function() { log('Mode hors ligne'); document.documentElement.classList.add('offline'); showOfflineNotification(); }); // Vérifier l'état initial if (!navigator.onLine) { document.documentElement.classList.add('offline'); } } // ============================================ // NOTIFICATIONS // ============================================ function showUpdateNotification() { const isAnglophone = window.appConfig?.isAnglophone || false; const message = isAnglophone ? 'A new version is available. Reload to update?' : 'Une nouvelle version est disponible. Recharger pour mettre à jour ?'; // Utiliser SweetAlert2 si disponible if (typeof Swal !== 'undefined') { Swal.fire({ title: isAnglophone ? 'Update Available' : 'Mise à jour disponible', text: message, icon: 'info', showCancelButton: true, confirmButtonText: isAnglophone ? 'Reload' : 'Recharger', cancelButtonText: isAnglophone ? 'Later' : 'Plus tard', allowOutsideClick: false }).then((result) => { if (result.isConfirmed) { window.location.reload(); } }); } else { // Fallback simple if (confirm(message)) { window.location.reload(); } } } function showOnlineNotification() { const isAnglophone = window.appConfig?.isAnglophone || false; showToast( isAnglophone ? 'Online' : 'En ligne', isAnglophone ? 'Connection restored. Data will be synchronized.' : 'Connexion rétablie. Les données seront synchronisées.', 'success', 3000 ); // Notifier le Service Worker if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: 'NETWORK_RESTORED', timestamp: Date.now() }); } } function showOfflineNotification() { const isAnglophone = window.appConfig?.isAnglophone || false; showToast( isAnglophone ? 'Offline' : 'Hors ligne', isAnglophone ? 'No internet connection. Working in offline mode.' : 'Pas de connexion Internet. Mode hors ligne actif.', 'warning', 5000 ); } function showToast(title, message, type, duration) { const toast = document.createElement('div'); toast.className = `sw-toast sw-toast-${type}`; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${type === 'success' ? '#27ae60' : '#e74c3c'}; color: white; padding: 15px 20px; border-radius: 8px; z-index: 9999; box-shadow: 0 4px 12px rgba(0,0,0,0.15); max-width: 300px; animation: slideIn 0.3s ease; `; toast.innerHTML = `
${title}
${message}
`; document.body.appendChild(toast); setTimeout(() => { if (toast.parentNode) { toast.style.animation = 'slideOut 0.3s ease'; setTimeout(() => toast.remove(), 300); } }, duration); // Ajouter les styles d'animation si nécessaire addToastStyles(); } function addToastStyles() { if (!document.getElementById('toast-styles')) { const styles = document.createElement('style'); styles.id = 'toast-styles'; styles.textContent = ` @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } `; document.head.appendChild(styles); } } // ============================================ // SYNCHRONISATION DES DONNÉES // ============================================ function syncData() { log('Synchronisation des données...'); // Synchroniser avec le Service Worker if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: 'SYNC_DATA', timestamp: Date.now() }); } // Synchroniser les données en attente syncPendingData(); } function syncPendingData() { // À implémenter selon vos besoins // Ex: synchroniser les formulaires en attente } // ============================================ // API PUBLIQUE // ============================================ function exposePublicAPI(registration) { window.serviceWorkerManager = { // Forcer une mise à jour update: function() { return registration.update(); }, // Nettoyer le cache clearCache: function() { return caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => caches.delete(cacheName)) ); }); }, // Obtenir l'état getStatus: function() { return { controller: !!navigator.serviceWorker.controller, scope: registration.scope, state: registration.installing ? registration.installing.state : 'active', supports: { push: 'pushManager' in registration, sync: 'sync' in registration } }; }, // Forcer l'activation skipWaiting: function() { if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' }); } }, // Recharger pour appliquer les mises à jour reload: function() { window.location.reload(); }, // Vérifier si supporté isSupported: 'serviceWorker' in navigator }; } // ============================================ // NOTIFICATION AUX CLIENTS // ============================================ function notifyClientsOfUpdate() { // Notifier toutes les pages ouvertes if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: 'NEW_VERSION_ACTIVATED', version: '2.1', timestamp: Date.now() }); } } // ============================================ // FONCTIONS UTILITAIRES // ============================================ function log(message, ...args) { if (DEBUG) { console.log('[SW Register]', message, ...args); } } // ============================================ // INITIALISATION // ============================================ // Attendre que la page soit complètement chargée window.addEventListener('load', function() { // Délai pour éviter la concurrence avec d'autres scripts setTimeout(registerServiceWorker, 100); }); // Nettoyer les anciens Service Workers au chargement window.addEventListener('load', function() { if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistrations().then(function(registrations) { registrations.forEach(function(registration) { // Supprimer les SW des anciens domaines if (!registration.scope.startsWith(window.location.origin)) { log('Nettoyage ancien SW:', registration.scope); registration.unregister(); } }); }); } }); // ============================================ // GESTION DES ERREURS GLOBALES // ============================================ window.addEventListener('error', function(event) { if (event.message && event.message.includes('ServiceWorker')) { console.error('Erreur Service Worker:', event.error); } }); // ============================================ // EXPORT POUR LES TESTS // ============================================ // Pour le débogage depuis la console window.debugSW = { unregisterAll: function() { return navigator.serviceWorker.getRegistrations() .then(registrations => Promise.all( registrations.map(r => r.unregister()) )); }, clearAllCaches: function() { return caches.keys() .then(cacheNames => Promise.all( cacheNames.map(name => caches.delete(name)) )); }, forceUpdate: function() { if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' }); setTimeout(() => window.location.reload(), 1000); } } }; log('Service Worker Manager initialisé'); })();