Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts, scan de code-barres, consultation hors-ligne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
149 lines
7.1 KiB
JavaScript
149 lines
7.1 KiB
JavaScript
// 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 <base href="/"> : 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();
|
|
});
|
|
})();
|