// Enregistrement du service worker, application automatique au démarrage, bandeau en secours. // // Pourquoi ce fichier existe (et pourquoi il n'est pas qu'une ligne `register(...)`) : // // 1. Les empreintes des assets WASM sont DÉSACTIVÉES (voir CLAUDE.md). `blazor.webassembly.js` // et `dotnet.js` portent donc des noms stables, et c'est le service worker — lui seul — qui // empêche de servir éternellement une version périmée. Le mécanisme de Blazor fonctionne, // mais il est silencieux et différé : le nouveau worker attend que TOUS les onglets de // l'application soient fermés. Sur mobile, un onglet oublié fige la mise à jour sans que // personne comprenne pourquoi. // // 2. `navigator.serviceWorker` n'existe pas en contexte non sécurisé (http sur une IP locale). // L'appeler sans vérification lève une TypeError qui casse le script — et fait croire à un // défaut de la PWA alors que c'est le contexte qui n'est pas éligible. // // ⚠️ Le clic n'est plus obligatoire (décidé le 2026-08-20, après un blocage constaté sur // l'application installée sur téléphone). CLAUDE.md actait « jamais tout seul » pour ne pas // recharger sous une saisie en cours ; la raison reste valable, mais elle ne vaut que pour une // session déjà entamée. Voir FENETRE_DEMARRAGE ci-dessous. (function () { 'use strict'; if (!('serviceWorker' in navigator)) { // Cas normal en http sur une IP de réseau local : rien à signaler à l'utilisateur, // l'application fonctionne, elle n'est simplement pas installable ni hors-ligne. console.info('Service worker indisponible (contexte non sécurisé ou navigateur sans support).'); return; } // Fenêtre pendant laquelle une nouvelle version s'applique TOUTE SEULE, sans rien demander. // Au-delà, l'utilisateur est en train de faire quelque chose (une fiche à moitié saisie, un // prêt en cours d'enregistrement) : on repasse par le bandeau, pour ne pas lui arracher son // travail. Ouvrir l'application depuis l'écran d'accueil du téléphone tombe toujours dans // cette fenêtre — c'est exactement le cas qui ne se mettait plus à jour. var FENETRE_DEMARRAGE = 10000; // Si la reprise en main n'a pas lieu, on recharge quand même : le pire scénario est de // laisser l'utilisateur devant un bouton grisé qui ne fait rien. var DELAI_REPRISE = 5000; // Une seule reprise forcée par session : si la nouvelle version n'arrive pas à s'activer, // recharger en boucle transformerait une mise à jour ratée en application inutilisable. var CLE_FORCE = 'mb-maj-forcee'; var demandee = false; var recharge = false; function recharger() { if (recharge) return; recharge = true; window.location.reload(); } function appliquer(enAttente) { demandee = true; // Le worker en attente prend la main sans qu'on ait à fermer tous les onglets. enAttente.postMessage({ type: 'SKIP_WAITING' }); // Filet. `controllerchange` devrait suivre ; s'il ne vient pas (worker en attente issu // d'une version antérieure au gestionnaire SKIP_WAITING, reprise en main qui échoue), // rien ne se passerait du tout et le bouton resterait grisé pour toujours. window.setTimeout(function () { if (recharge) return; var dejaForcee = false; try { dejaForcee = window.sessionStorage.getItem(CLE_FORCE) === '1'; window.sessionStorage.setItem(CLE_FORCE, '1'); } catch (e) { // Navigation privée ou stockage refusé : on préfère ne pas forcer plutôt que // risquer une boucle de rechargement qu'on ne saurait plus arrêter. dejaForcee = true; } if (!dejaForcee) recharger(); }, DELAI_REPRISE); } function proposerLaMiseAJour(enAttente) { if (document.getElementById('mb-maj')) return; var barre = document.createElement('div'); barre.id = 'mb-maj'; barre.setAttribute('role', 'status'); barre.textContent = 'Une nouvelle version de MaBibli est disponible. '; var bouton = document.createElement('button'); bouton.type = 'button'; bouton.textContent = 'Mettre à jour'; bouton.addEventListener('click', function () { bouton.disabled = true; bouton.textContent = 'Mise à jour…'; appliquer(enAttente); }); barre.appendChild(bouton); document.body.appendChild(barre); } // Au démarrage : on applique sans demander. Plus tard : on propose. function traiter(enAttente) { if (performance.now() < FENETRE_DEMARRAGE) { appliquer(enAttente); } else { proposerLaMiseAJour(enAttente); } } // ⚠️ Chemin ABSOLU, et scope explicite. `register('service-worker.js')` se résout contre // l'URL du DOCUMENT et non contre : ouvrir l'application sur une route // profonde (/souhaits, /livres/3) visait /souhaits/service-worker.js, que le serveur ne sert // pas — l'enregistrement échouait alors silencieusement, et avec lui tout le hors-ligne. navigator.serviceWorker.register('/service-worker.js', { scope: '/', updateViaCache: 'none' }) .then(function (enregistrement) { // Un worker déjà installé attendait peut-être depuis la visite précédente. if (enregistrement.waiting && navigator.serviceWorker.controller) { traiter(enregistrement.waiting); } enregistrement.addEventListener('updatefound', function () { var nouveau = enregistrement.installing; if (!nouveau) return; nouveau.addEventListener('statechange', function () { // `controller` non nul = ce n'est pas la première installation, donc il y a // bien une version précédente à remplacer. if (nouveau.state === 'installed' && navigator.serviceWorker.controller) { traiter(nouveau); } }); }); // Vérification explicite à chaque chargement : ne pas dépendre du seul rythme // interne du navigateur pour découvrir une version plus récente. enregistrement.update().catch(function () { /* hors-ligne : sans objet */ }); }) .catch(function (erreur) { // Ne jamais laisser cet échec passer inaperçu : sans service worker, l'application // ne démarre pas hors-ligne, et le cache IndexedDB ne sert alors à rien. console.error("Échec de l'enregistrement du service worker :", erreur); }); // Le rechargement n'a lieu que si la mise à jour a été demandée — automatiquement ou par un // clic. Un changement de contrôleur survient aussi à la toute première installation, et // recharger la page à ce moment-là serait un clignotement inexplicable. navigator.serviceWorker.addEventListener('controllerchange', function () { if (demandee) recharger(); }); })();