Dans certains cas, une application Web peut avoir besoin d'établir un canal de communication bidirectionnelle entre la page et le service worker.
Par exemple, dans une PWA de podcast, vous pouvez créer une fonctionnalité permettant à l'utilisateur de télécharger des épisodes pour les utiliser hors connexion et permettre au service worker de tenir régulièrement la page informée de la progression afin que le thread principal puisse mettre à jour l'UI.
Dans ce guide, nous allons explorer les différentes manières de mettre en œuvre une communication bidirectionnelle entre les contextes de Window et de service worker, en explorant différentes API, la bibliothèque Workbox, ainsi que des cas avancés.
![Schéma illustrant un service worker et la page échangeant des messages](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-service-8bb189d1e9e40.png?authuser=9&hl=fr)
Utiliser Workbox
workbox-window
est un ensemble de modules de la bibliothèque Workbox destinés à s'exécuter dans le contexte de la fenêtre. La classe Workbox
fournit une méthode messageSW()
pour envoyer un message au service worker enregistré de l'instance et attendre une réponse.
Le code de page suivant crée une instance Workbox
et envoie un message au service worker pour obtenir sa version:
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
Le service worker met en œuvre un écouteur de messages à l'autre extrémité et répond au service worker enregistré:
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
En arrière-plan, la bibliothèque utilise une API de navigateur que nous étudierons dans la section suivante: Message Channel. Toutefois, elle extrait de nombreux détails d'implémentation, ce qui la rend plus facile à utiliser, tout en exploitant la compatibilité avec les navigateurs étendus avec cette API.
![Schéma illustrant la communication bidirectionnelle entre le service worker et la page, à l'aide de la fenêtre de la boîte de travail](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-way-comm-3573ad75f3cd1.png?authuser=9&hl=fr)
Utiliser les API du navigateur
Si la bibliothèque Workbox ne répond pas à vos besoins, plusieurs API de niveau inférieur sont disponibles pour mettre en œuvre la communication bidirectionnelle entre les pages et les service workers. Ils présentent des similitudes et des différences:
Similitudes :
- Dans tous les cas, la communication commence à une extrémité via l'interface
postMessage()
et est reçue à l'autre extrémité en implémentant un gestionnairemessage
. - En pratique, toutes les API disponibles nous permettent de mettre en œuvre les mêmes cas d'utilisation, mais certains d'entre eux peuvent simplifier le développement dans certains scénarios.
Différences :
- Ils ont différentes manières d'identifier l'autre côté de la communication: certains utilisent une référence explicite à l'autre contexte, tandis que d'autres peuvent communiquer implicitement via un objet proxy instancié de chaque côté.
- Les navigateurs compatibles varient d'un navigateur à l'autre.
![Schéma illustrant la communication bidirectionnelle entre le service worker et la page, ainsi que les API de navigateur disponibles.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-way-comm-7b2a46c7cc95.png?authuser=9&hl=fr)
API Broadcast Channel
L'API Broadcast Channel permet une communication de base entre les contextes de navigation via des objets BroadcastChannel.
Pour l'implémenter, chaque contexte doit d'abord instancier un objet BroadcastChannel
avec le même ID, puis envoyer et recevoir des messages à partir de celui-ci:
const broadcast = new BroadcastChannel('channel-123');
L'objet BroadcastChannel expose une interface postMessage()
pour envoyer un message à tout contexte d'écoute:
//send message
broadcast.postMessage({ type: 'MSG_ID', });
N'importe quel contexte de navigateur peut écouter les messages via la méthode onmessage
de l'objet BroadcastChannel
:
//listen to messages
broadcast.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process message...
}
};
Comme nous l'avons vu, il n'existe aucune référence explicite à un contexte particulier. Il n'est donc pas nécessaire d'obtenir d'abord une référence au service worker ou à un client particulier.
![Schéma illustrant la communication bidirectionnelle entre le service worker et la page, à l'aide d'un objet Broadcast Channel](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-way-comm-d328101990fe9.png?authuser=9&hl=fr)
L'inconvénient est qu'au moment de la rédaction de cet article, l'API est compatible avec Chrome, Firefox et Edge, mais que d'autres navigateurs, tels que Safari, ne sont pas encore compatibles.
API cliente
L'API cliente vous permet d'obtenir une référence à tous les objets WindowClient
représentant les onglets actifs contrôlés par le service worker.
Comme la page est contrôlée par un seul service worker, elle écoute et envoie des messages au service worker actif directement via l'interface serviceWorker
:
//send message
navigator.serviceWorker.controller.postMessage({
type: 'MSG_ID',
});
//listen to messages
navigator.serviceWorker.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process response
}
};
De même, le service worker écoute les messages en implémentant un écouteur onmessage
:
//listen to messages
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//Process message
}
});
Pour communiquer avec l'un de ses clients, le service worker obtient un tableau d'objets WindowClient
en exécutant des méthodes telles que Clients.matchAll()
et Clients.get()
. Il peut ensuite postMessage()
n'importe lequel d'entre eux:
//Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
if (clients && clients.length) {
//Respond to last focused tab
clients[0].postMessage({type: 'MSG_ID'});
}
});
![Schéma illustrant un service worker communiquant avec un tableau de clients](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-service-6eb1e0aa007c2.png?authuser=9&hl=fr)
Client API
est une bonne option pour communiquer facilement avec tous les onglets actifs d'un service worker de manière relativement simple. L'API est compatible avec tous les principaux navigateurs, mais il est possible que toutes ses méthodes ne soient pas disponibles. Assurez-vous donc de vérifier la compatibilité des navigateurs avant de l'implémenter sur votre site.
Canal de messagerie
Le canal de message nécessite de définir et de transmettre un port d'un contexte à un autre pour établir un canal de communication bidirectionnel.
Pour initialiser le canal, la page instancie un objet MessageChannel
et l'utilise pour envoyer un port au service worker enregistré. La page implémente également un écouteur onmessage
pour recevoir des messages de l'autre contexte:
const messageChannel = new MessageChannel();
//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
messageChannel.port2,
]);
//Listen to messages
messageChannel.port1.onmessage = (event) => {
// Process message
};
![Schéma illustrant une page transmettant un port à un service worker pour établir une communication bidirectionnelle.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-page-pas-57eb808e284e1.png?authuser=9&hl=fr)
Le service worker reçoit le port, enregistre une référence à celui-ci et l'utilise pour envoyer un message à l'autre côté:
let communicationPort;
//Save reference to port
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'PORT_INITIALIZATION') {
communicationPort = event.ports[0];
}
});
//Send messages
communicationPort.postMessage({type: 'MSG_ID'});
MessageChannel
est actuellement compatible avec tous les principaux navigateurs.
API avancées: synchronisation et récupération en arrière-plan
Dans ce guide, nous avons exploré des moyens de mettre en œuvre des techniques de communication bidirectionnelle, pour des cas relativement simples tels que la transmission d'un message de chaîne décrivant l'opération à effectuer ou d'une liste d'URL à mettre en cache d'un contexte à l'autre. Dans cette section, nous allons étudier deux API permettant de gérer des scénarios spécifiques: le manque de connectivité et les téléchargements longs.
Synchronisation en arrière-plan
Une application de chat peut souhaiter s'assurer que les messages ne sont jamais perdus en raison d'une mauvaise connectivité. L'API Background Sync vous permet de différer les actions à relancer lorsque l'utilisateur dispose d'une connectivité stable. Cela permet de s'assurer que tout ce que l'utilisateur souhaite envoyer est effectivement envoyé.
Au lieu de l'interface postMessage()
, la page enregistre un sync
:
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
Le service worker écoute ensuite l'événement sync
pour traiter le message:
self.addEventListener('sync', function (event) {
if (event.tag == 'myFirstSync') {
event.waitUntil(doSomeStuff());
}
});
La fonction doSomeStuff()
doit renvoyer une promesse indiquant la réussite ou l'échec de l'opération. Si elle s'exécute, la synchronisation est terminée. En cas d'échec, une autre tentative de synchronisation est planifiée. Les nouvelles tentatives de synchronisation attendent également la connectivité et utilisent un intervalle exponentiel entre les tentatives.
Une fois l'opération terminée, le service worker peut communiquer avec la page pour mettre à jour l'UI à l'aide de l'une des API de communication explorées précédemment.
La recherche Google utilise la synchronisation en arrière-plan pour conserver les requêtes ayant échoué en raison d'une mauvaise connectivité et les relancer plus tard lorsque l'utilisateur sera en ligne. Une fois l'opération terminée, ils communiquent le résultat à l'utilisateur via une notification push Web:
![Schéma illustrant une page transmettant un port à un service worker pour établir une communication bidirectionnelle.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-page-pas-4ed0ed40b8bc1.png?authuser=9&hl=fr)
Récupération en arrière-plan
Pour les tâches relativement courtes telles que l'envoi d'un message ou une liste d'URL à mettre en cache, les options explorées jusqu'à présent constituent un bon choix. Si la tâche prend trop de temps, le navigateur ferme le service worker. Dans le cas contraire, cela présente un risque pour la confidentialité et la batterie de l'utilisateur.
L'API Background Fetch vous permet de déléguer une longue tâche à un service worker, comme le téléchargement de films, de podcasts ou de niveaux de jeu.
Pour communiquer avec le service worker à partir de la page, utilisez backgroundFetch.fetch
au lieu de postMessage()
:
navigator.serviceWorker.ready.then(async (swReg) => {
const bgFetch = await swReg.backgroundFetch.fetch(
'my-fetch',
['/ep-5.mp3', 'ep-5-artwork.jpg'],
{
title: 'Episode 5: Interesting things.',
icons: [
{
sizes: '300x300',
src: '/ep-5-icon.png',
type: 'image/png',
},
],
downloadTotal: 60 * 1024 * 1024,
},
);
});
L'objet BackgroundFetchRegistration
permet à la page d'écouter l'événement progress
de suivre la progression du téléchargement:
bgFetch.addEventListener('progress', () => {
// If we didn't provide a total, we can't provide a %.
if (!bgFetch.downloadTotal) return;
const percent = Math.round(
(bgFetch.downloaded / bgFetch.downloadTotal) * 100,
);
console.log(`Download progress: ${percent}%`);
});
![Schéma illustrant une page transmettant un port à un service worker pour établir une communication bidirectionnelle.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-page-pas-e7b1a5ce84bde.png?authuser=9&hl=fr)
Étapes suivantes
Dans ce guide, nous avons exploré le cas de communication le plus général entre les service workers de page et les service workers (communication bidirectionnelle).
Souvent, l'un peut n'avoir besoin que d'un seul contexte pour communiquer avec l'autre, sans recevoir de réponse. Consultez les guides suivants pour découvrir comment mettre en œuvre des techniques unidirectionnelles dans vos pages depuis et vers le service worker. Consultez également des cas d'utilisation et des exemples de production:
- Guide de mise en cache impérative: appeler un service worker à partir de la page pour mettre en cache les ressources à l'avance (par exemple, dans des scénarios de préchargement)
- Diffuser les mises à jour: appel de la page depuis le service worker pour être informé des mises à jour importantes (par exemple, une nouvelle version de l'application Web est disponible).