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:
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'utilisateurinstall
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.install
.... mais tous les choix semblent moins qu'idéaux car, respectivement:
install
pour voir toutes les récupérations requises.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.
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.
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.
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]);
// });
});