service-worker.js modifié

This commit is contained in:
2025-12-22 07:57:04 +00:00
parent dd0b96d84b
commit b35b0f9017
4 changed files with 1135 additions and 505 deletions

View File

@@ -1,245 +1,452 @@
// Enregistrement du Service Worker amélioré
// sw-register.js - Version 2.0
// Enregistrement et gestion du Service Worker
(function() {
// Vérifier si le Service Worker est supporté
if (!('serviceWorker' in navigator)) {
console.warn('[App] Service Worker non supporté');
return;
}
// Vérifier si nous sommes en HTTPS ou localhost
if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') {
console.warn('[App] Service Worker nécessite HTTPS en production');
return;
}
// Fonction principale d'enregistrement
function registerServiceWorker() {
const swUrl = '/service-worker.js?v=1.1';
'use strict';
navigator.serviceWorker.register(swUrl)
.then(function(registration) {
console.log('[Service Worker] Enregistré avec succès:', registration.scope);
// ============================================
// 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...');
// Vérifier si une mise à jour est disponible
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 installingWorker = registration.installing;
console.log('[Service Worker] Mise à jour trouvée:', installingWorker.state);
installingWorker.addEventListener('statechange', function() {
console.log('[Service Worker] Nouvel état:', this.state);
const newWorker = registration.installing;
log('Nouvelle version du Service Worker trouvée:', newWorker.state);
if (this.state === 'installed' && navigator.serviceWorker.controller) {
// Nouvelle version disponible
showUpdateNotification();
}
if (this.state === 'activated') {
console.log('[Service Worker] Nouveau Service Worker activé');
// Notifier tous les clients
sendMessageToSW({ type: 'NEW_VERSION_ACTIVATED' });
}
});
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();
}
});
});
// Vérifier l'état du Service Worker périodiquement
// É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 => {
console.debug('[SW] Pas de mise à jour disponible:', err);
});
}, 60 * 60 * 1000); // Toutes les heures
registration.update().catch(err => {
log('Pas de mise à jour disponible:', err.message);
});
}, CHECK_INTERVAL);
return registration;
})
.catch(function(error) {
console.error('[Service Worker] Erreur d\'enregistrement:', error);
// 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();
});
// Tentative de récupération
if (error.name === 'SecurityError') {
console.warn('[SW] Erreur de sécurité - Vérifiez HTTPS');
} else if (error.name === 'TypeError') {
console.warn('[SW] Erreur de type - Vérifiez le chemin du SW');
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');
}
});
}
// Gestion de l'état de connexion
function setupConnectionHandlers() {
window.addEventListener('online', function() {
console.log('[App] Connexion rétablie');
document.documentElement.classList.remove('offline');
showOnlineNotification();
// Synchroniser les données
if (navigator.serviceWorker.controller) {
sendMessageToSW({ type: 'SYNC_DATA' });
}
});
window.addEventListener('offline', function() {
console.log('[App] Mode hors ligne');
document.documentElement.classList.add('offline');
showOfflineNotification();
});
// Vérifier l'état initial
if (!navigator.onLine) {
document.documentElement.classList.add('offline');
}
}
// Notification de mise à jour
function showUpdateNotification() {
const isAnglophone = window.appConfig?.isAnglophone || false;
// Utiliser SweetAlert2 si disponible
if (typeof Swal !== 'undefined') {
Swal.fire({
title: isAnglophone ? 'Update Available' : 'Mise à jour disponible',
text: isAnglophone
? 'A new version is available. Reload to update?'
: 'Une nouvelle version est disponible. Recharger pour mettre à jour ?',
icon: 'info',
showCancelButton: true,
confirmButtonText: isAnglophone ? 'Reload' : 'Recharger',
cancelButtonText: isAnglophone ? 'Later' : 'Plus tard',
allowOutsideClick: false
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
// ============================================
// 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();
}
}
});
} else {
// Fallback simple
if (confirm(isAnglophone
? 'New version available. Reload?'
: 'Nouvelle version disponible. Recharger ?')) {
window.location.reload();
}
}
}
// Notification en ligne
function showOnlineNotification() {
// Créer un toast temporaire
const toast = document.createElement('div');
toast.className = 'position-fixed top-0 end-0 p-3';
toast.style.zIndex = '9999';
const isAnglophone = window.appConfig?.isAnglophone || false;
toast.innerHTML = `
<div class="toast show" role="alert" aria-live="polite" aria-atomic="true">
<div class="toast-header bg-success text-white">
<i class="bi bi-wifi me-2"></i>
<strong class="me-auto">${isAnglophone ? 'Online' : 'En ligne'}</strong>
<button type="button" class="btn-close btn-close-white ms-2"
onclick="this.closest('.toast').remove()"
aria-label="${isAnglophone ? 'Close' : 'Fermer'}"></button>
</div>
<div class="toast-body">
${isAnglophone
? 'Connection restored. Data will be synchronized.'
: 'Connexion rétablie. Les données seront synchronisées.'}
</div>
</div>
`;
document.body.appendChild(toast);
// Auto-remove après 3 secondes
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 3000);
}
// Notification hors ligne
function showOfflineNotification() {
// Créer un toast temporaire
const toast = document.createElement('div');
toast.className = 'position-fixed top-0 end-0 p-3';
toast.style.zIndex = '9999';
const isAnglophone = window.appConfig?.isAnglophone || false;
toast.innerHTML = `
<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header bg-warning text-dark">
<i class="bi bi-wifi-off me-2"></i>
<strong class="me-auto">${isAnglophone ? 'Offline' : 'Hors ligne'}</strong>
<button type="button" class="btn-close ms-2"
onclick="this.closest('.toast').remove()"
aria-label="${isAnglophone ? 'Close' : 'Fermer'}"></button>
</div>
<div class="toast-body">
${isAnglophone
? 'No internet connection. Working in offline mode.'
: 'Pas de connexion Internet. Mode hors ligne actif.'}
</div>
</div>
`;
document.body.appendChild(toast);
// Auto-remove après 5 secondes
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 5000);
}
// Envoyer un message au Service Worker
function sendMessageToSW(message) {
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage(message);
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()
});
}
}
}
// Obtenir l'état du cache
function getCacheStatus() {
if (navigator.serviceWorker.controller) {
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
console.log('[SW] Cache status:', event.data);
};
navigator.serviceWorker.controller.postMessage(
{ type: 'GET_CACHE_STATUS' },
[messageChannel.port2]
);
}
}
// Fonction publique pour forcer la mise à jour
function forceUpdate() {
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' });
}
}
// Initialiser quand la page est prête
window.addEventListener('load', function() {
registerServiceWorker();
setupConnectionHandlers();
// Ajouter les écouteurs de visibilité pour les rafraîchissements
document.addEventListener('visibilitychange', function() {
if (!document.hidden && navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({ type: 'PAGE_VISIBLE' });
}
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 = `
<div style="font-weight: bold; margin-bottom: 5px;">${title}</div>
<div style="font-size: 14px;">${message}</div>
`;
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);
});
});
// Exposer les fonctions publiques
window.serviceWorkerManager = {
forceUpdate: forceUpdate,
getCacheStatus: getCacheStatus,
isSupported: 'serviceWorker' in navigator,
sendMessage: sendMessageToSW
};
console.log('[App] Service Worker Manager initialisé');
// 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é');
})();