/**
* Enregistrement du Service Worker et gestion des mises à jour.
*
* Quand une nouvelle version du site est déployée (sw.js modifié, suffixe de
* version des caches bumpé), le nouveau Service Worker s'installe en arrière-
* plan et reste en attente. Un modal propose alors la mise à jour :
* - « Mettre à jour » : le SW en attente prend le contrôle (SKIP_WAITING),
* les anciens caches sont purgés à l'activation, puis la page se recharge
* sur la nouvelle version (controllerchange).
* - « Plus tard » : le modal se ferme ; la mise à jour sera reproposée au
* prochain chargement de page.
*
* Aucune purge manuelle du cache navigateur n'est nécessaire.
*/
(function () {
'use strict';
if (!('serviceWorker' in navigator)) {
return;
}
let modalShown = false;
let refreshing = false;
// Recharge la page quand le nouveau Service Worker prend le contrôle
navigator.serviceWorker.addEventListener('controllerchange', function () {
if (refreshing) {
return;
}
refreshing = true;
window.location.reload();
});
/**
* Construit et affiche le modal de mise à jour (une seule fois).
* @param {ServiceWorker} waitingWorker Le SW en attente d'activation
*/
function showUpdateModal(waitingWorker) {
if (modalShown || !waitingWorker) {
return;
}
modalShown = true;
const overlay = document.createElement('div');
overlay.className = 'pwa-update-overlay';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
overlay.setAttribute('aria-labelledby', 'pwa-update-title');
overlay.innerHTML =
'
' +
'' +
'
Nouvelle version disponible
' +
'
' +
'Une mise à jour du site est prête à être installée. ' +
'Elle est rapide et ne supprime aucune de vos données.' +
'
' +
'
' +
'' +
'' +
'
' +
'
';
document.body.appendChild(overlay);
// Force le reflow pour que la transition CSS d'entrée joue
void overlay.offsetWidth;
overlay.classList.add('pwa-update-visible');
const primaryBtn = overlay.querySelector('.pwa-update-btn-primary');
const secondaryBtn = overlay.querySelector('.pwa-update-btn-secondary');
function closeModal() {
overlay.classList.remove('pwa-update-visible');
overlay.addEventListener('transitionend', function () {
overlay.remove();
}, { once: true });
}
primaryBtn.addEventListener('click', function () {
primaryBtn.disabled = true;
secondaryBtn.disabled = true;
primaryBtn.textContent = 'Mise à jour…';
// Le rechargement est déclenché par l'event controllerchange
waitingWorker.postMessage({ type: 'SKIP_WAITING' });
});
secondaryBtn.addEventListener('click', closeModal);
document.addEventListener('keydown', function onEscape(event) {
if (event.key === 'Escape' && document.body.contains(overlay)) {
closeModal();
document.removeEventListener('keydown', onEscape);
}
});
// Accessibilité : focus sur l'action principale
primaryBtn.focus();
}
/**
* Surveille l'installation d'un nouveau Service Worker.
* @param {ServiceWorkerRegistration} registration
*/
function trackInstalling(registration) {
registration.addEventListener('updatefound', function () {
const newWorker = registration.installing;
if (!newWorker) {
return;
}
newWorker.addEventListener('statechange', function () {
// installed + un contrôleur actif = mise à jour en attente
// (sans contrôleur, c'est la toute première installation)
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
showUpdateModal(registration.waiting);
}
});
});
}
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js')
.then(function (registration) {
// Cas où une mise à jour est déjà en attente (installée lors
// d'une visite précédente, jamais activée)
if (registration.waiting && navigator.serviceWorker.controller) {
showUpdateModal(registration.waiting);
}
trackInstalling(registration);
})
.catch(function (err) {
console.log('Échec de l\'enregistrement du Service Worker:', err);
});
});
})();