service-worker.js modifié
This commit is contained in:
parent
dd0b96d84b
commit
b35b0f9017
|
|
@ -1,245 +1,452 @@
|
||||||
// Enregistrement du Service Worker amélioré
|
// sw-register.js - Version 2.0
|
||||||
|
// Enregistrement et gestion du Service Worker
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
// Vérifier si le Service Worker est supporté
|
'use strict';
|
||||||
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';
|
|
||||||
|
|
||||||
navigator.serviceWorker.register(swUrl)
|
// ============================================
|
||||||
.then(function(registration) {
|
// CONFIGURATION
|
||||||
console.log('[Service Worker] Enregistré avec succès:', registration.scope);
|
// ============================================
|
||||||
|
|
||||||
|
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() {
|
registration.addEventListener('updatefound', function() {
|
||||||
const installingWorker = registration.installing;
|
const newWorker = registration.installing;
|
||||||
console.log('[Service Worker] Mise à jour trouvée:', installingWorker.state);
|
log('Nouvelle version du Service Worker trouvée:', newWorker.state);
|
||||||
|
|
||||||
installingWorker.addEventListener('statechange', function() {
|
|
||||||
console.log('[Service Worker] Nouvel état:', this.state);
|
|
||||||
|
|
||||||
if (this.state === 'installed' && navigator.serviceWorker.controller) {
|
newWorker.addEventListener('statechange', function() {
|
||||||
// Nouvelle version disponible
|
log('État du nouveau Service Worker:', this.state);
|
||||||
showUpdateNotification();
|
|
||||||
}
|
if (this.state === 'installed' && navigator.serviceWorker.controller) {
|
||||||
|
showUpdateNotification();
|
||||||
if (this.state === 'activated') {
|
}
|
||||||
console.log('[Service Worker] Nouveau Service Worker activé');
|
|
||||||
// Notifier tous les clients
|
if (this.state === 'activated') {
|
||||||
sendMessageToSW({ type: 'NEW_VERSION_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(() => {
|
setInterval(() => {
|
||||||
registration.update().catch(err => {
|
registration.update().catch(err => {
|
||||||
console.debug('[SW] Pas de mise à jour disponible:', err);
|
log('Pas de mise à jour disponible:', err.message);
|
||||||
});
|
});
|
||||||
}, 60 * 60 * 1000); // Toutes les heures
|
}, CHECK_INTERVAL);
|
||||||
|
|
||||||
return registration;
|
// Vérifier aussi quand la page redevient visible
|
||||||
})
|
document.addEventListener('visibilitychange', function() {
|
||||||
.catch(function(error) {
|
if (!document.hidden) {
|
||||||
console.error('[Service Worker] Erreur d\'enregistrement:', error);
|
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
|
window.addEventListener('offline', function() {
|
||||||
if (error.name === 'SecurityError') {
|
log('Mode hors ligne');
|
||||||
console.warn('[SW] Erreur de sécurité - Vérifiez HTTPS');
|
document.documentElement.classList.add('offline');
|
||||||
} else if (error.name === 'TypeError') {
|
showOfflineNotification();
|
||||||
console.warn('[SW] Erreur de type - Vérifiez le chemin du SW');
|
});
|
||||||
|
|
||||||
|
// 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') {
|
// NOTIFICATIONS
|
||||||
Swal.fire({
|
// ============================================
|
||||||
title: isAnglophone ? 'Update Available' : 'Mise à jour disponible',
|
|
||||||
text: isAnglophone
|
function showUpdateNotification() {
|
||||||
? 'A new version is available. Reload to update?'
|
const isAnglophone = window.appConfig?.isAnglophone || false;
|
||||||
: 'Une nouvelle version est disponible. Recharger pour mettre à jour ?',
|
const message = isAnglophone
|
||||||
icon: 'info',
|
? 'A new version is available. Reload to update?'
|
||||||
showCancelButton: true,
|
: 'Une nouvelle version est disponible. Recharger pour mettre à jour ?';
|
||||||
confirmButtonText: isAnglophone ? 'Reload' : 'Recharger',
|
|
||||||
cancelButtonText: isAnglophone ? 'Later' : 'Plus tard',
|
// Utiliser SweetAlert2 si disponible
|
||||||
allowOutsideClick: false
|
if (typeof Swal !== 'undefined') {
|
||||||
}).then((result) => {
|
Swal.fire({
|
||||||
if (result.isConfirmed) {
|
title: isAnglophone ? 'Update Available' : 'Mise à jour disponible',
|
||||||
window.location.reload();
|
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;
|
function showOnlineNotification() {
|
||||||
|
const isAnglophone = window.appConfig?.isAnglophone || false;
|
||||||
toast.innerHTML = `
|
|
||||||
<div class="toast show" role="alert" aria-live="polite" aria-atomic="true">
|
showToast(
|
||||||
<div class="toast-header bg-success text-white">
|
isAnglophone ? 'Online' : 'En ligne',
|
||||||
<i class="bi bi-wifi me-2"></i>
|
isAnglophone ? 'Connection restored. Data will be synchronized.' : 'Connexion rétablie. Les données seront synchronisées.',
|
||||||
<strong class="me-auto">${isAnglophone ? 'Online' : 'En ligne'}</strong>
|
'success',
|
||||||
<button type="button" class="btn-close btn-close-white ms-2"
|
3000
|
||||||
onclick="this.closest('.toast').remove()"
|
);
|
||||||
aria-label="${isAnglophone ? 'Close' : 'Fermer'}"></button>
|
|
||||||
</div>
|
// Notifier le Service Worker
|
||||||
<div class="toast-body">
|
if (navigator.serviceWorker.controller) {
|
||||||
${isAnglophone
|
navigator.serviceWorker.controller.postMessage({
|
||||||
? 'Connection restored. Data will be synchronized.'
|
type: 'NETWORK_RESTORED',
|
||||||
: 'Connexion rétablie. Les données seront synchronisées.'}
|
timestamp: Date.now()
|
||||||
</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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
function showOfflineNotification() {
|
||||||
document.addEventListener('visibilitychange', function() {
|
const isAnglophone = window.appConfig?.isAnglophone || false;
|
||||||
if (!document.hidden && navigator.serviceWorker.controller) {
|
|
||||||
navigator.serviceWorker.controller.postMessage({ type: 'PAGE_VISIBLE' });
|
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);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
// Nettoyer les anciens Service Workers au chargement
|
||||||
// Exposer les fonctions publiques
|
window.addEventListener('load', function() {
|
||||||
window.serviceWorkerManager = {
|
if ('serviceWorker' in navigator) {
|
||||||
forceUpdate: forceUpdate,
|
navigator.serviceWorker.getRegistrations().then(function(registrations) {
|
||||||
getCacheStatus: getCacheStatus,
|
registrations.forEach(function(registration) {
|
||||||
isSupported: 'serviceWorker' in navigator,
|
// Supprimer les SW des anciens domaines
|
||||||
sendMessage: sendMessageToSW
|
if (!registration.scope.startsWith(window.location.origin)) {
|
||||||
};
|
log('Nettoyage ancien SW:', registration.scope);
|
||||||
|
registration.unregister();
|
||||||
console.log('[App] Service Worker Manager initialisé');
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 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é');
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
@ -35,17 +35,17 @@ foreach ($menus as $key0 => $menuParent) {
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<base href="<?= $racineWeb ?>">
|
<base href="<?= $racineWeb ?>">
|
||||||
|
|
||||||
<title><?= htmlspecialchars($_SESSION['vue'] ?? 'INTER SANTÉ') ?> | Portail Santé</title>
|
<title><?= htmlspecialchars($_SESSION['vue'] ?? 'INTER-SANTE') ?> | Portail Santé</title>
|
||||||
|
|
||||||
<!-- Meta pour UX améliorée -->
|
<!-- Meta pour UX améliorée -->
|
||||||
<meta name="description" content="Portail professionnel de gestion santé Inter Santé">
|
<meta name="description" content="Portail RH de gestion santé - INTER-SANTE">
|
||||||
<meta name="theme-color" content="#b7472a">
|
<meta name="theme-color" content="#b7472a">
|
||||||
|
|
||||||
<!-- PWA Meta Tags -->
|
<!-- PWA Meta Tags -->
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
<meta name="apple-mobile-web-app-title" content="INTER-SANTÉ">
|
<meta name="apple-mobile-web-app-title" content="INTER-SANTE">
|
||||||
|
|
||||||
<!-- Icône -->
|
<!-- Icône -->
|
||||||
<link rel="apple-touch-icon" href="<?= $racineWeb ?>Bootstrap_new/images/new/favicon.png">
|
<link rel="apple-touch-icon" href="<?= $racineWeb ?>Bootstrap_new/images/new/favicon.png">
|
||||||
|
|
@ -550,6 +550,6 @@ foreach ($menus as $key0 => $menuParent) {
|
||||||
<script src="/Bootstrap_new/js/ux-manager.js?ver=2025.12.21.01"></script>
|
<script src="/Bootstrap_new/js/ux-manager.js?ver=2025.12.21.01"></script>
|
||||||
|
|
||||||
<!-- Service Worker Registration -->
|
<!-- Service Worker Registration -->
|
||||||
<script src="/Js/sw-register.js?ver=2025.12.21.01"></script>
|
<script src="/Js/sw-register.js?ver=2025.12.22.00"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
163
offline.html
163
offline.html
|
|
@ -1,86 +1,85 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Mode hors ligne - INTER-SANTÉ</title>
|
<title>Mode hors ligne - INTER-SANTÉ</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', sans-serif;
|
font-family: 'Segoe UI', sans-serif;
|
||||||
background: linear-gradient(135deg, #f3f2f1 0%, #e6e6e6 100%);
|
background: linear-gradient(135deg, #f3f2f1 0%, #e6e6e6 100%);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
text-align: center;
|
||||||
.offline-container {
|
}
|
||||||
background: white;
|
.offline-container {
|
||||||
border-radius: 12px;
|
background: white;
|
||||||
padding: 40px;
|
border-radius: 12px;
|
||||||
text-align: center;
|
padding: 40px;
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
max-width: 500px;
|
||||||
max-width: 500px;
|
width: 100%;
|
||||||
width: 100%;
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
.offline-icon {
|
.offline-icon {
|
||||||
font-size: 64px;
|
font-size: 64px;
|
||||||
color: #b7472a;
|
color: #b7472a;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
color: #b7472a;
|
color: #b7472a;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
color: #666;
|
color: #666;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
background: #b7472a;
|
background: #b7472a;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: background 0.3s;
|
transition: background 0.3s;
|
||||||
}
|
}
|
||||||
button:hover {
|
button:hover {
|
||||||
background: #a53e24;
|
background: #a53e24;
|
||||||
}
|
}
|
||||||
button.secondary {
|
.try-again {
|
||||||
background: #2b579a;
|
background: #2b579a;
|
||||||
}
|
}
|
||||||
button.secondary:hover {
|
.try-again:hover {
|
||||||
background: #1e4a8b;
|
background: #1e4a8b;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
<body>
|
<div class="offline-container">
|
||||||
<div class="offline-container">
|
<div class="offline-icon">📶</div>
|
||||||
<div class="offline-icon">📶</div>
|
<h1>Mode hors ligne</h1>
|
||||||
<h1>Mode hors ligne</h1>
|
<p>Vous n'êtes pas connecté à Internet. Certaines fonctionnalités peuvent être limitées.</p>
|
||||||
<p>Vous n'êtes pas connecté à Internet. Certaines fonctionnalités peuvent être limitées.</p>
|
<p>Vous pouvez continuer à utiliser les fonctionnalités disponibles hors ligne.</p>
|
||||||
<p>Vous pouvez continuer à utiliser les fonctionnalités disponibles hors ligne.</p>
|
|
||||||
|
<div class="actions">
|
||||||
<div class="actions">
|
<button class="try-again" onclick="location.reload()">Réessayer la connexion</button>
|
||||||
<button onclick="location.reload()">Réessayer</button>
|
<button onclick="history.back()">Retour</button>
|
||||||
<button class="secondary" onclick="history.back()">Retour</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 30px; font-size: 14px; color: #999;">
|
|
||||||
<p>INTER-SANTÉ Portail RH • Version hors ligne</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
|
||||||
|
<div style="margin-top: 30px; font-size: 14px; color: #999;">
|
||||||
|
<p>INTER-SANTE Portail RH • Version hors ligne</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -1,220 +1,644 @@
|
||||||
// Service Worker pour Portail RH Inter Santé
|
/**
|
||||||
const CACHE_NAME = 'inter-sante-portal-v1.1';
|
* SERVICE WORKER - INTER SANTÉ PORTAL RH
|
||||||
|
* Version: 2.1 (2025.12.21)
|
||||||
|
*
|
||||||
|
* Fonctionnalités :
|
||||||
|
* - Cache stratégique des ressources statiques
|
||||||
|
* - Mode hors ligne avec fallback
|
||||||
|
* - Mise à jour automatique
|
||||||
|
* - Gestion intelligente des routes
|
||||||
|
* - Performance optimisée
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// CONFIGURATION
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Nom du cache (changer la version pour forcer une mise à jour)
|
||||||
|
const CACHE_NAME = 'inter-sante-portal-v2.1';
|
||||||
|
|
||||||
|
// URL de la page hors ligne
|
||||||
const OFFLINE_URL = '/offline.html';
|
const OFFLINE_URL = '/offline.html';
|
||||||
|
|
||||||
// Ressources ESSENTIELLES à mettre en cache (vérifier l'existence)
|
// Ressources critiques à pré-cacher (doivent être accessibles)
|
||||||
const PRECACHE_URLS = [
|
const PRECACHE_URLS = [
|
||||||
'/',
|
'/',
|
||||||
'/Bootstrap_new/css/style_office.css',
|
'/Bootstrap_new/css/style_office.css',
|
||||||
'/Bootstrap_new/css/ux_enhancements.css',
|
'/Bootstrap_new/css/ux_enhancements.css',
|
||||||
'/Bootstrap_new/js/ux-manager.js',
|
'/Bootstrap_new/css/override.css',
|
||||||
'/Js/fonctions.js',
|
'/Bootstrap_new/js/ux-manager.js',
|
||||||
'/manifest.json',
|
'/Js/fonctions.js',
|
||||||
'/Bootstrap_new/images/new/favicon.png'
|
'/manifest.json',
|
||||||
|
'/Bootstrap_new/images/new/favicon.png',
|
||||||
|
// Ajouter d'autres ressources critiques ici
|
||||||
];
|
];
|
||||||
|
|
||||||
// Installation - Pré-cache des ressources essentielles
|
// Chemins à IGNORER complètement (le SW ne les interceptera pas)
|
||||||
|
const IGNORE_PATHS = [
|
||||||
|
// Routes d'authentification
|
||||||
|
'/Connexion/deconnecter',
|
||||||
|
'/Connexion/quitter',
|
||||||
|
'/Connexion/connecter',
|
||||||
|
'/logout',
|
||||||
|
|
||||||
|
// API et endpoints dynamiques
|
||||||
|
'/api/',
|
||||||
|
'/ajax/',
|
||||||
|
'/webservice/',
|
||||||
|
|
||||||
|
// Admin et maintenance
|
||||||
|
'/admin/',
|
||||||
|
'/maintenance/',
|
||||||
|
|
||||||
|
// Uploads et fichiers dynamiques
|
||||||
|
'/upload/',
|
||||||
|
'/uploads/',
|
||||||
|
'/temp/',
|
||||||
|
|
||||||
|
// Fichiers de données
|
||||||
|
'.php', // Tous les fichiers PHP (sauf ceux explicitement cachés)
|
||||||
|
'.pdf',
|
||||||
|
'.xlsx',
|
||||||
|
'.docx'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Chemins qui DOIVENT être mis en cache (exceptions aux règles d'ignorance)
|
||||||
|
const CACHE_WHITELIST = [
|
||||||
|
'/offline.html',
|
||||||
|
'/Bootstrap_new/',
|
||||||
|
'/Js/',
|
||||||
|
'/css/',
|
||||||
|
'/images/'
|
||||||
|
];
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// ÉVÉNEMENT D'INSTALLATION
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Événement : Installation du Service Worker
|
||||||
|
* Se produit lors de la première installation ou mise à jour
|
||||||
|
*/
|
||||||
self.addEventListener('install', event => {
|
self.addEventListener('install', event => {
|
||||||
event.waitUntil(
|
console.log('[Service Worker] Début de l\'installation - Version:', CACHE_NAME);
|
||||||
caches.open(CACHE_NAME)
|
|
||||||
.then(cache => {
|
// Attendre que le pré-cache soit terminé avant de considérer l'installation comme complète
|
||||||
console.log('[Service Worker] Pré-cache des ressources');
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
// Cache only resources that exist
|
.then(cache => {
|
||||||
const cachePromises = PRECACHE_URLS.map(url => {
|
console.log('[Service Worker] Ouverture du cache:', CACHE_NAME);
|
||||||
return fetch(url, { mode: 'no-cors' })
|
|
||||||
.then(response => {
|
// Tenter de mettre en cache chaque ressource
|
||||||
if (response.ok || response.type === 'opaque') {
|
const cachePromises = PRECACHE_URLS.map(url => {
|
||||||
return cache.put(url, response);
|
return fetch(url, {
|
||||||
}
|
mode: 'no-cors', // Permet de récupérer même les ressources cross-origin
|
||||||
console.warn(`[SW] Resource not found: ${url}`);
|
credentials: 'same-origin'
|
||||||
return Promise.resolve();
|
})
|
||||||
|
.then(response => {
|
||||||
|
// Vérifier si la ressource est accessible
|
||||||
|
if (response && (response.ok || response.type === 'opaque')) {
|
||||||
|
console.log('[Service Worker] Pré-cache réussi:', url);
|
||||||
|
return cache.put(url, response);
|
||||||
|
} else {
|
||||||
|
console.warn('[Service Worker] Échec pré-cache:', url, response.status);
|
||||||
|
return Promise.resolve(); // Continuer même en cas d'échec
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.warn('[Service Worker] Erreur pré-cache', url, error);
|
||||||
|
return Promise.resolve(); // Ne pas bloquer l'installation
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(cachePromises);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('[Service Worker] Pré-cache terminé');
|
||||||
|
|
||||||
|
// Forcer l'activation immédiate du nouveau Service Worker
|
||||||
|
return self.skipWaiting();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.warn(`[SW] Failed to cache ${url}:`, error);
|
console.error('[Service Worker] Erreur lors de l\'installation:', error);
|
||||||
return Promise.resolve();
|
// Même en cas d'erreur, on continue pour ne pas bloquer l'app
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
// ============================================
|
||||||
|
// ÉVÉNEMENT D'ACTIVATION
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Événement : Activation du Service Worker
|
||||||
|
* Se produit après l'installation, nettoie les anciens caches
|
||||||
|
*/
|
||||||
|
self.addEventListener('activate', event => {
|
||||||
|
console.log('[Service Worker] Activation - Version:', CACHE_NAME);
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
// Nettoyer les anciennes versions du cache
|
||||||
|
caches.keys()
|
||||||
|
.then(cacheNames => {
|
||||||
|
return Promise.all(
|
||||||
|
cacheNames.map(cacheName => {
|
||||||
|
// Supprimer tous les caches qui ne correspondent pas à la version actuelle
|
||||||
|
if (cacheName !== CACHE_NAME) {
|
||||||
|
console.log('[Service Worker] Suppression ancien cache:', cacheName);
|
||||||
|
return caches.delete(cacheName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('[Service Worker] Nettoyage des anciens caches terminé');
|
||||||
|
|
||||||
|
// Prendre le contrôle immédiat de tous les clients
|
||||||
|
return self.clients.claim();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('[Service Worker] Activation terminée, contrôle pris');
|
||||||
|
|
||||||
|
// Envoyer un message à tous les clients pour les informer de l'activation
|
||||||
|
self.clients.matchAll().then(clients => {
|
||||||
|
clients.forEach(client => {
|
||||||
|
client.postMessage({
|
||||||
|
type: 'SW_ACTIVATED',
|
||||||
|
version: CACHE_NAME,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// ÉVÉNEMENT DE RÉCUPÉRATION (FETCH)
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Événement : Interception des requêtes réseau
|
||||||
|
* Cœur du Service Worker - gère le cache et les stratégies de récupération
|
||||||
|
*/
|
||||||
|
self.addEventListener('fetch', event => {
|
||||||
|
const request = event.request;
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// FILTRES : Routes à ignorer complètement
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// 1. Ignorer les requêtes non-GET (POST, PUT, DELETE, etc.)
|
||||||
|
if (request.method !== 'GET') {
|
||||||
|
console.debug('[SW] Ignorer - Méthode non-GET:', request.method, url.pathname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Ignorer les requêtes cross-origin (sauf CDN)
|
||||||
|
if (url.origin !== self.location.origin) {
|
||||||
|
// Pour les CDN, on laisse passer sans interception
|
||||||
|
const isCDN = url.hostname.includes('cdn.jsdelivr.net') ||
|
||||||
|
url.hostname.includes('cdnjs.cloudflare.com') ||
|
||||||
|
url.hostname.includes('code.jquery.com');
|
||||||
|
|
||||||
// Pas dans le cache, aller au réseau
|
if (!isCDN) {
|
||||||
return fetch(event.request)
|
console.debug('[SW] Ignorer - Cross-origin:', url.origin);
|
||||||
.then(response => {
|
return;
|
||||||
// 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é
|
|
||||||
|
// 3. Ignorer les chemins configurés dans IGNORE_PATHS
|
||||||
|
const shouldIgnore = IGNORE_PATHS.some(path => {
|
||||||
|
// Vérifier les chemins exacts
|
||||||
|
if (url.pathname === path) return true;
|
||||||
|
// Vérifier les chemins qui commencent par...
|
||||||
|
if (path.endsWith('/') && url.pathname.startsWith(path)) return true;
|
||||||
|
// Vérifier les extensions de fichiers
|
||||||
|
if (path.startsWith('.') && url.pathname.endsWith(path)) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldIgnore) {
|
||||||
|
console.debug('[SW] Ignorer - Route configurée:', url.pathname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Vérifier la whitelist (exceptions)
|
||||||
|
const isWhitelisted = CACHE_WHITELIST.some(path => url.pathname.includes(path));
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// STRATÉGIES DE RÉCUPÉRATION
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Pour les pages HTML : Network First, fallback Cache
|
||||||
|
if (request.headers.get('accept') && request.headers.get('accept').includes('text/html')) {
|
||||||
|
console.debug('[SW] Stratégie HTML pour:', url.pathname);
|
||||||
|
event.respondWith(handleHtmlRequest(event));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pour les ressources statiques (CSS, JS, images) : Cache First, fallback Network
|
||||||
|
if (isStaticResource(request)) {
|
||||||
|
console.debug('[SW] Stratégie Cache First pour:', url.pathname);
|
||||||
|
event.respondWith(handleStaticRequest(event));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Par défaut : Network First
|
||||||
|
console.debug('[SW] Stratégie par défaut pour:', url.pathname);
|
||||||
|
event.respondWith(handleDefaultRequest(event));
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// FONCTIONS AUXILIAIRES
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Détermine si une ressource est statique (CSS, JS, images, fonts)
|
||||||
|
* @param {Request} request - La requête à analyser
|
||||||
|
* @returns {boolean} - True si c'est une ressource statique
|
||||||
|
*/
|
||||||
|
function isStaticResource(request) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const staticExtensions = ['.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.woff', '.woff2', '.ttf', '.eot', '.ico'];
|
||||||
|
|
||||||
|
return staticExtensions.some(ext => url.pathname.endsWith(ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère les requêtes HTML (pages)
|
||||||
|
* Stratégie : Network First, fallback Cache
|
||||||
|
*/
|
||||||
|
function handleHtmlRequest(event) {
|
||||||
|
return 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mettre en cache la réponse pour la prochaine fois
|
// Cloner la réponse pour la mettre en cache
|
||||||
const responseToCache = response.clone();
|
const responseToCache = response.clone();
|
||||||
|
|
||||||
|
// Mettre en cache en arrière-plan
|
||||||
caches.open(CACHE_NAME)
|
caches.open(CACHE_NAME)
|
||||||
.then(cache => {
|
.then(cache => {
|
||||||
cache.put(event.request, responseToCache);
|
cache.put(event.request, responseToCache);
|
||||||
});
|
console.debug('[SW] HTML mis en cache:', event.request.url);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn('[SW] Erreur mise en cache HTML:', err);
|
||||||
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(() => {
|
||||||
console.error('[SW] Fetch failed:', error);
|
// Fallback : chercher dans le cache
|
||||||
|
console.debug('[SW] Mode hors ligne, fallback cache pour:', event.request.url);
|
||||||
|
|
||||||
// Pour les CSS/JS, retourner des réponses de secours
|
return caches.match(event.request)
|
||||||
const url = event.request.url;
|
.then(cachedResponse => {
|
||||||
if (url.includes('.css')) {
|
if (cachedResponse) {
|
||||||
return new Response('/* Ressource temporairement indisponible */', {
|
return cachedResponse;
|
||||||
headers: { 'Content-Type': 'text/css' }
|
}
|
||||||
});
|
|
||||||
}
|
// Si pas dans le cache, retourner la page hors ligne
|
||||||
if (url.includes('.js')) {
|
return caches.match(OFFLINE_URL)
|
||||||
return new Response('// Ressource temporairement indisponible', {
|
.then(offlineResponse => {
|
||||||
headers: { 'Content-Type': 'application/javascript' }
|
return offlineResponse || new Response(
|
||||||
});
|
'<h1>Mode hors ligne</h1><p>Cette ressource n\'est pas disponible hors ligne.</p>',
|
||||||
}
|
{ headers: { 'Content-Type': 'text/html' } }
|
||||||
|
);
|
||||||
// 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 });
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère les requêtes de ressources statiques
|
||||||
|
* Stratégie : Cache First, fallback Network
|
||||||
|
*/
|
||||||
|
function handleStaticRequest(event) {
|
||||||
|
return caches.match(event.request)
|
||||||
|
.then(cachedResponse => {
|
||||||
|
// Si trouvé dans le cache, le retourner immédiatement
|
||||||
|
if (cachedResponse) {
|
||||||
|
console.debug('[SW] Ressource statique depuis cache:', event.request.url);
|
||||||
|
|
||||||
|
// En arrière-plan, vérifier si une mise à jour est disponible
|
||||||
|
fetchAndUpdateCache(event.request);
|
||||||
|
|
||||||
|
return cachedResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response('Ressource non disponible hors ligne', {
|
// Sinon, aller au réseau
|
||||||
headers: { 'Content-Type': 'text/plain' }
|
console.debug('[SW] Ressource statique depuis réseau:', event.request.url);
|
||||||
});
|
return fetchAndCache(event.request);
|
||||||
});
|
})
|
||||||
})
|
.catch(error => {
|
||||||
);
|
console.error('[SW] Erreur gestion ressource statique:', error);
|
||||||
});
|
return createFallbackResponse(event.request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Gestion des messages entre l'app et le Service Worker
|
/**
|
||||||
self.addEventListener('message', event => {
|
* Gère les requêtes par défaut
|
||||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
* Stratégie : Stale-While-Revalidate
|
||||||
self.skipWaiting();
|
*/
|
||||||
}
|
function handleDefaultRequest(event) {
|
||||||
|
return caches.match(event.request)
|
||||||
if (event.data && event.data.type === 'GET_CACHE_STATUS') {
|
.then(cachedResponse => {
|
||||||
event.ports[0].postMessage({
|
// Toujours essayer de récupérer depuis le réseau
|
||||||
cacheName: CACHE_NAME,
|
const fetchPromise = fetch(event.request)
|
||||||
status: 'active'
|
.then(networkResponse => {
|
||||||
});
|
// Mettre à jour le cache avec la nouvelle version
|
||||||
}
|
if (networkResponse && networkResponse.ok) {
|
||||||
});
|
const responseToCache = networkResponse.clone();
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then(cache => {
|
||||||
|
cache.put(event.request, responseToCache);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return networkResponse;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// En cas d'erreur réseau, on ignore silencieusement
|
||||||
|
console.debug('[SW] Erreur réseau pour:', event.request.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Retourner la réponse en cache si disponible, sinon attendre le réseau
|
||||||
|
return cachedResponse || fetchPromise;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Gestion des notifications push (optionnel)
|
/**
|
||||||
self.addEventListener('push', event => {
|
* Récupère une ressource et la met en cache
|
||||||
if (!event.data) return;
|
*/
|
||||||
|
function fetchAndCache(request) {
|
||||||
const data = event.data.json();
|
return fetch(request)
|
||||||
const options = {
|
.then(response => {
|
||||||
body: data.body || 'Nouvelle notification',
|
// Vérifier si la réponse est valide
|
||||||
icon: '/Bootstrap_new/images/new/favicon.png',
|
if (!response || !response.ok) {
|
||||||
badge: '/Bootstrap_new/images/new/favicon.png',
|
return response; // Retourner la réponse même si elle a échoué
|
||||||
vibrate: [200, 100, 200],
|
}
|
||||||
data: {
|
|
||||||
url: data.url || '/'
|
// Mettre en cache
|
||||||
|
const responseToCache = response.clone();
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then(cache => {
|
||||||
|
cache.put(request, responseToCache);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('[SW] Erreur fetchAndCache:', error);
|
||||||
|
return createFallbackResponse(request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie et met à jour le cache en arrière-plan
|
||||||
|
*/
|
||||||
|
function fetchAndUpdateCache(request) {
|
||||||
|
fetch(request)
|
||||||
|
.then(response => {
|
||||||
|
if (response && response.ok) {
|
||||||
|
const responseToCache = response.clone();
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then(cache => {
|
||||||
|
cache.put(request, responseToCache);
|
||||||
|
console.debug('[SW] Cache mis à jour en arrière-plan:', request.url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Ignorer silencieusement les erreurs de mise à jour en arrière-plan
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une réponse de secours selon le type de ressource
|
||||||
|
*/
|
||||||
|
function createFallbackResponse(request) {
|
||||||
|
const url = request.url;
|
||||||
|
|
||||||
|
if (url.includes('.css')) {
|
||||||
|
return new Response(
|
||||||
|
'/* Ressource CSS temporairement indisponible */\nbody { background-color: #f3f2f1; }',
|
||||||
|
{ headers: { 'Content-Type': 'text/css' } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.includes('.js')) {
|
||||||
|
return new Response(
|
||||||
|
'// Ressource JavaScript temporairement indisponible\nconsole.log("Ressource en cache indisponible");',
|
||||||
|
{ headers: { 'Content-Type': 'application/javascript' } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.includes('.png') || url.includes('.jpg') || url.includes('.svg')) {
|
||||||
|
// Retourner une image placeholder
|
||||||
|
return fetch('/Bootstrap_new/images/new/favicon.png')
|
||||||
|
.catch(() => {
|
||||||
|
return new Response('', { status: 404 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
'Ressource non disponible hors ligne',
|
||||||
|
{
|
||||||
|
status: 503,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
'Cache-Control': 'no-store'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// ÉVÉNEMENTS DE MESSAGERIE
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère les messages entre l'application et le Service Worker
|
||||||
|
*/
|
||||||
|
self.addEventListener('message', event => {
|
||||||
|
console.log('[SW] Message reçu:', event.data);
|
||||||
|
|
||||||
|
switch (event.data.type) {
|
||||||
|
case 'SKIP_WAITING':
|
||||||
|
// Forcer l'activation immédiate (utilisé pour les mises à jour)
|
||||||
|
self.skipWaiting();
|
||||||
|
console.log('[SW] Skip waiting activé');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'GET_CACHE_STATUS':
|
||||||
|
// Retourner l'état du cache
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
cacheName: CACHE_NAME,
|
||||||
|
cacheSize: 'N/A',
|
||||||
|
status: 'active',
|
||||||
|
version: '2.1'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'CLEAR_CACHE':
|
||||||
|
// Nettoyer le cache spécifique
|
||||||
|
caches.delete(CACHE_NAME)
|
||||||
|
.then(() => {
|
||||||
|
event.ports[0].postMessage({ success: true });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
event.ports[0].postMessage({ success: false, error: error.message });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'UPDATE_RESOURCES':
|
||||||
|
// Mettre à jour des ressources spécifiques
|
||||||
|
updateSpecificResources(event.data.urls);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
event.waitUntil(
|
|
||||||
self.registration.showNotification(data.title || 'Inter Santé', options)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour des ressources spécifiques dans le cache
|
||||||
|
*/
|
||||||
|
function updateSpecificResources(urls) {
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then(cache => {
|
||||||
|
const updatePromises = urls.map(url => {
|
||||||
|
return fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return cache.put(url, response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.warn('[SW] Échec mise à jour:', url, error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(updatePromises);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('[SW] Mise à jour des ressources terminée');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// ÉVÉNEMENTS PUSH (NOTIFICATIONS)
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère les notifications push (optionnel)
|
||||||
|
*/
|
||||||
|
self.addEventListener('push', event => {
|
||||||
|
if (!event.data) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
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 || '/',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
},
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'open',
|
||||||
|
title: 'Ouvrir'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'close',
|
||||||
|
title: 'Fermer'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
self.registration.showNotification(
|
||||||
|
data.title || 'Inter Santé - Notification',
|
||||||
|
options
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[SW] Erreur notification push:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère les clics sur les notifications
|
||||||
|
*/
|
||||||
self.addEventListener('notificationclick', event => {
|
self.addEventListener('notificationclick', event => {
|
||||||
event.notification.close();
|
event.notification.close();
|
||||||
|
|
||||||
event.waitUntil(
|
const urlToOpen = event.notification.data.url || '/';
|
||||||
clients.openWindow(event.notification.data.url || '/')
|
|
||||||
);
|
event.waitUntil(
|
||||||
});
|
clients.matchAll({
|
||||||
|
type: 'window',
|
||||||
|
includeUncontrolled: true
|
||||||
|
})
|
||||||
|
.then(windowClients => {
|
||||||
|
// Vérifier si une fenêtre est déjà ouverte
|
||||||
|
for (let client of windowClients) {
|
||||||
|
if (client.url === urlToOpen && 'focus' in client) {
|
||||||
|
return client.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinon ouvrir une nouvelle fenêtre
|
||||||
|
if (clients.openWindow) {
|
||||||
|
return clients.openWindow(urlToOpen);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// ÉVÉNEMENT SYNC (SYNCHRONISATION)
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère la synchronisation en arrière-plan (optionnel)
|
||||||
|
*/
|
||||||
|
self.addEventListener('sync', event => {
|
||||||
|
console.log('[SW] Synchronisation:', event.tag);
|
||||||
|
|
||||||
|
if (event.tag === 'sync-data') {
|
||||||
|
event.waitUntil(syncPendingData());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronise les données en attente
|
||||||
|
*/
|
||||||
|
function syncPendingData() {
|
||||||
|
// À implémenter selon vos besoins
|
||||||
|
console.log('[SW] Synchronisation des données en cours...');
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// LOGGING ET DEBUG
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Niveau de log (0: none, 1: error, 2: warn, 3: info, 4: debug)
|
||||||
|
const LOG_LEVEL = 3;
|
||||||
|
|
||||||
|
function log(level, message, ...args) {
|
||||||
|
if (level <= LOG_LEVEL) {
|
||||||
|
const levels = ['', 'ERROR', 'WARN', 'INFO', 'DEBUG'];
|
||||||
|
console.log(`[SW ${levels[level]}] ${message}`, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialisation
|
||||||
|
console.log('[Service Worker] Initialisé - Version:', CACHE_NAME);
|
||||||
|
console.log('[Service Worker] Scope:', self.registration ? self.registration.scope : self.location.origin);
|
||||||
Loading…
Reference in New Issue
Block a user