feat: add styled PWA update modal with auto cache purge
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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 =
|
||||
'<div class="pwa-update-modal">' +
|
||||
'<img class="pwa-update-logo" src="img/logo.png" alt="" aria-hidden="true">' +
|
||||
'<h2 class="pwa-update-title" id="pwa-update-title">Nouvelle version disponible</h2>' +
|
||||
'<p class="pwa-update-text">' +
|
||||
'Une mise à jour du site est prête à être installée. ' +
|
||||
'Elle est rapide et ne supprime aucune de vos données.' +
|
||||
'</p>' +
|
||||
'<div class="pwa-update-actions">' +
|
||||
'<button type="button" class="pwa-update-btn pwa-update-btn-primary">' +
|
||||
'Mettre à jour' +
|
||||
'</button>' +
|
||||
'<button type="button" class="pwa-update-btn pwa-update-btn-secondary">' +
|
||||
'Plus tard' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user