web-dev-qa-db-fra.com

Transmission des informations d'état à un technicien de service avant l'installation

Contexte

Je suis nouveau dans les services, mais je travaille sur un bibliothèque qui est destiné à devenir "hors ligne en premier" (vraiment, presque "hors ligne uniquement") (FWIW, l'intention est de permettre aux consommateurs de la pour fournir une configuration JSON représentant des textes tabulaires multilinéaires et obtenir en retour une application qui permet à leurs utilisateurs de parcourir ces textes de manière hautement personnalisable par plage de paragraphes/versets.)

D'autres projets consistent à installer la bibliothèque en tant que dépendance, puis à fournir des informations via notre API JavaScript, telles que le chemin d'un fichier de configuration JSON indiquant les fichiers que notre application consommera pour produire une application (hors ligne) pour eux.

Bien que je sache, nous pourrions faire l'une des choses suivantes:

  1. exiger que les utilisateurs fournissent un chemin codé en dur à partir duquel le script install de notre technicien de service pourrait utiliser waitUntil avec sa propre requête JSON pour récupérer les fichiers nécessaires de l'utilisateur
  2. ignorer l'étape install de l'agent de service pour le fichier JSON et s'appuyer sur les événements fetch pour mettre à jour le cache, fournissant un affichage de secours si l'utilisateur a terminé l'installation et s'est déconnecté avant le des récupérations pourraient se produire.
  3. Publiez des informations d'état de notre script principal sur un serveur que le technicien de service, une fois inscrit, interrogerait avant de terminer son événement install.

... mais tous les choix semblent moins qu'idéaux car, respectivement:

  1. Les consommateurs de notre bibliothèque peuvent préférer pouvoir désigner leur propre emplacement pour leur configuration JSON.
  2. Étant donné que la configuration JSON désigne les fichiers essentiels pour montrer à leurs utilisateurs quoi que ce soit d'utile, je préfère ne pas permettre à une installation de se terminer uniquement pour dire que l'utilisateur doit revenir en ligne pour obtenir le reste des fichiers s'il n'a pas pu rester. en ligne après l'événement install pour voir toutes les récupérations requises.
  3. En plus de vouloir éviter plus de déplacements sur le serveur et du code supplémentaire, je préférerais que notre code soit si déconnecté qu'il puisse fonctionner entièrement sur de simples serveurs de fichiers statiques.

Question:

Existe-t-il un moyen de transmettre un message ou des informations d'état à un technicien de service avant l'événement install se produit, que ce soit dans le cadre de la chaîne de requête de l'URL du technicien de service, ou via un événement de messagerie? L'événement de messagerie peut même arriver techniquement après le début de l'événement install tant qu'il peut se produire avant la fin d'un waitUntil dans le install.

Je sais que je pourrais tester cela moi-même, mais j'aimerais savoir quelles sont les meilleures pratiques de toute façon lorsque les fichiers d'application critiques doivent eux-mêmes être obtenus dynamiquement comme dans des bibliothèques comme la nôtre.

Je suppose que indexedDB pourrait être la seule alternative ici (c'est-à-dire, enregistrer les informations de configuration ou le chemin de la configuration JSON dans indexedDB, enregistrer un technicien de service et récupérer les données indexedDB à partir de install un événement)? Même cela ne serait pas idéal car je laisse les utilisateurs définir un espace de noms pour leur stockage, mais j'ai besoin d'un moyen pour qu'il soit également transmis au travailleur, ou sinon, plusieurs de ces applications sur l'Origin pourraient se heurter.

20
Brett Zamir

Utilisation d'un paramètre de requête

Si vous le trouvez utile, alors oui, vous pouvez fournir un état lors de l'installation du service worker en incluant un paramètre de requête à votre service service lorsque vous l'enregistrez, comme ceci:

// Inside your main page:
const pathToJson = '/path/to/file.json';
const swUrl = '/sw.js?pathToJson=' + encodeURIComponent(pathToJson);
navigator.serviceWorker.register(swUrl);

// Inside your sw.js:
self.addEventListener('install', event => {
  const pathToJson = new URL(location).searchParams.get('pathToJson');
  event.waitUntil(
    fetch(pathToJson)
      .then(response => response.json())
      .then(jsonData => /* Do something with jsonData */)
  );
});

Quelques points à noter sur cette approche:

  • Si vous fetch() le fichier JSON dans votre gestionnaire install (comme dans l'exemple de code), cela se produira effectivement une fois par version de votre script de service worker (sw.js). Si le contenu du fichier JSON change, mais que tout le reste reste le même, le technicien de service ne le détectera pas automatiquement et ne remplira pas vos caches.

  • À la suite du premier point, si vous contournez ce problème en incluant, par exemple, le versionnage basé sur le hachage dans l'URL de votre fichier JSON, chaque fois que vous modifiez cette URL, vous finirez par installer un nouveau service worker. Ce n'est pas une mauvaise chose en soi, mais vous devez garder cela à l'esprit si votre application Web a une logique qui écoute les événements du cycle de vie des agents de service.

Approches alternatives

Vous pouvez également trouver plus facile d'ajouter simplement des fichiers à vos caches depuis le contexte de votre page principale, car les navigateurs qui prennent en charge l'API Cache Storage l'exposent via window.caches. La mise en cache préalable des fichiers dans le gestionnaire install d'un technicien de service présente l'avantage de garantir que tous les fichiers ont bien été mis en cache avant l'installation du technicien de service.

Une autre approche consiste à écrire les informations d'état dans IndexedDB à partir du contexte window, puis à lire dans IndexedDB à l'intérieur du gestionnaire install de votre agent de service.

32
Jeff Posnick

Mise à jour 3:

Et comme il n'est pas censé être sûr de s'appuyer sur des globaux au sein du travailleur, ma solution de messagerie semble encore moins solide. Je pense que ce doit être la solution de Jeff Posnick (dans certains cas, importScripts peut fonctionner).

Mise à jour 2:

Bien que cela ne soit pas directement lié au sujet de ce fil concernant l'événement "install", selon une discussion commençant à https://github.com/w3c/ServiceWorker/issues/659#issuecomment-38491905 , il existe certains problèmes, en particulier avec l'utilisation de cette approche de transmission de messages pour l'événement activate. A savoir, l'événement activate peut ne jamais échouer, et donc ne jamais être réessayé, laissant son application dans un état instable. (Un échec de install n'appliquera pas au moins le nouveau technicien de service aux anciennes pages, tandis que activate gardera les récupérations en attente jusqu'à la fin de l'événement, ce qu'il ne pourra jamais faire s'il est laissé en attente pour un message qui n'a pas été reçu et que tout autre qu'un nouvel employé ne pourra pas corriger car les nouvelles pages ne pourront pas se charger pour envoyer à nouveau ce message.)

Mise à jour:

Bien que j'aie obtenu le client à partir du script install dans Chrome, je n'ai pas pu recevoir le message avec navigator.serviceWorker.onmessage pour certaines raisons.

Cependant, j'ai pu pleinement confirmer l'approche suivante à sa place:

Dans le technicien de service:

self.addEventListener('install', e => {
    e.waitUntil(
        new Promise((resolve, reject) => {
            self.addEventListener('message', ({data: {
                myData
            }}) => {
                // Do something with `myData` here
                //    then when ready, `resolve`
            });
        })
    );
 });

Dans le script appelant:

navigator.serviceWorker.register('sw.js').then((r) => {
    r.installing.postMessage({myData: 100});
});

@JeffPosnick est la meilleure réponse pour le cas simple que j'ai décrit dans l'OP, mais j'ai pensé présenter ma découverte que l'on peut obtenir des messages depuis et vers un script de service worker (testé sur Chrome) par les éléments suivants: :

Dans le technicien de service :

self.addEventListener('install', e => {
    e.waitUntil(self.clients.matchAll({
        includeUncontrolled: true,
        type: 'window'
    }).then((clients) => new Promise((resolve, reject) => {
        if (clients && clients.length) {
            const client = clients.pop();
            client.postMessage('send msg to main script');
            // One should presumably be able to poll to check for a
            //   variable set in the SW message listener below
            //   and then `resolve` when set
            // Despite the unreliability of setting globals in SW's
            //   I believe this could be safe here as the `install`
            //   event is to run while the main script is still open.
        }
    })));
});

self.addEventListener('message', e => {
    console.log('SW receiving main script msg', e.data);
    e.ports[0].postMessage('sw response');
});

Dans le script appelant:

navigator.serviceWorker.addEventListener('message', (e) => {
    console.log('msg recd in main script', e.data);
    e.source.postMessage('sending back to sw');
});
return navigator.serviceWorker.register(
    'sw.js'
).then((r) => {
    // navigator.serviceWorker.ready.then((r) => { // This had been necessary at some point in my testing (with r.active.postMessage), but not working for me atm...
        // Sending a subsequent message
        const messageChannel = new MessageChannel();
        messageChannel.port1.onmessage = (e) => {
            if (e.data.error) {
                console.log('err', e.data.error);
            } else {
                console.log('data', e.data);
            }
        };
        navigator.serviceWorker.controller.postMessage('sending to sw', [messageChannel.port2]);
    // });
});
2
Brett Zamir