/** * 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); }); }); })();