Diffuser des mises à jour sur les pages avec des service workers

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

Dans certains cas, le service worker peut avoir besoin de communiquer de manière proactive avec l'un des onglets actifs qu'il contrôle pour vous informer d'un événement donné. Voici quelques exemples :

  • Informer la page lorsqu'une nouvelle version du service worker a été installée, afin que la page puisse afficher un bouton Update to refresh (Mettre à jour pour actualiser) afin que l'utilisateur puisse accéder immédiatement à la nouvelle fonctionnalité.
  • Informez l'utilisateur d'une modification apportée aux données mises en cache du côté du service worker, en affichant une indication comme: "L'application est maintenant prête à fonctionner hors connexion" ou "Nouvelle version du contenu disponible".
Schéma illustrant un service worker communiquant avec la page pour envoyer une mise à jour

Nous appellerons ces types de cas d'utilisation où le service worker n'a pas besoin de recevoir un message de la page pour lancer une communication des mises à jour de diffusion. Dans ce guide, nous allons passer en revue différentes manières de mettre en œuvre ce type de communication entre les pages et les service workers, à l'aide des API de navigateur standards et de la bibliothèque Workbox.

Scénarios de production

Tinder

La PWA Tinder utilise workbox-window pour écouter les moments importants du cycle de vie d'un service worker depuis la page ("installée", "contrôlée" et "activée"). Ainsi, lorsqu'un nouveau service worker entre en jeu, une bannière "Update Available" (Mise à jour disponible) s'affiche, ce qui lui permet d'actualiser la PWA et d'accéder aux dernières fonctionnalités:

Capture d'écran de la fonctionnalité "Mise à jour disponible" de l'application Web de Tinder.
Dans la PWA Tinder, le service worker indique à la page qu'une nouvelle version est prête, et une bannière "Mise à jour disponible" s'affiche sur cette page.

Squoosh

Dans la PWA de Squoosh, lorsque le service worker a mis en cache tous les éléments nécessaires pour fonctionner hors connexion, il envoie un message à la page pour afficher un toast "Prêt à fonctionner hors connexion", afin d'informer l'utilisateur de la fonctionnalité:

Capture d'écran de la fonctionnalité "Prêt à fonctionner hors connexion" de l'application Web Squoosh.
Dans la PWA Squoosh, le service worker diffuse une mise à jour sur la page lorsque le cache est prêt, et la page affiche un toast "Prêt à fonctionner hors connexion".

Utiliser Workbox

Écouter les événements de cycle de vie d'un service worker

workbox-window fournit une interface simple pour écouter les événements importants du cycle de vie d'un service worker. En arrière-plan, la bibliothèque utilise des API côté client telles que updatefound et statechange, et fournit des écouteurs d'événements de niveau supérieur dans l'objet workbox-window, ce qui permet à l'utilisateur de consommer ces événements plus facilement.

Le code de page suivant vous permet de détecter chaque fois qu'une nouvelle version du service worker est installée, afin que vous puissiez la communiquer à l'utilisateur:

const wb = new Workbox('/sw.js');

wb.addEventListener('installed', (event) => {
  if (event.isUpdate) {
    // Show "Update App" banner
  }
});

wb.register();

Informer la page des modifications apportées aux données du cache

Le package Workbox workbox-broadcast-update fournit un moyen standard d'informer les clients de la fenêtre qu'une réponse mise en cache a été mise à jour. Cette méthode est le plus souvent utilisée avec la stratégie StalewhileRevalidate.

Pour diffuser des mises à jour, ajoutez un broadcastUpdate.BroadcastUpdatePlugin à vos options de stratégie côté service worker:

import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';

registerRoute(
  ({url}) => url.pathname.startsWith('/api/'),
  new StaleWhileRevalidate({
    plugins: [
      new BroadcastUpdatePlugin(),
    ],
  })
);

Dans votre application Web, vous pouvez écouter les événements suivants:

navigator.serviceWorker.addEventListener('message', async (event) => {
  // Optional: ensure the message came from workbox-broadcast-update
  if (event.data.meta === 'workbox-broadcast-update') {
    const {cacheName, updatedUrl} = event.data.payload;

    // Do something with cacheName and updatedUrl.
    // For example, get the cached content and update
    // the content on the page.
    const cache = await caches.open(cacheName);
    const updatedResponse = await cache.match(updatedUrl);
    const updatedText = await updatedResponse.text();
  }
});

Utiliser les API du navigateur

Si les fonctionnalités fournies par Workbox ne répondent pas à vos besoins, utilisez les API de navigateur suivantes pour implémenter les mises à jour de diffusion:

API Broadcast Channel

Le service worker crée un objet BroadcastChannel et commence à lui envoyer des messages. Tout contexte (par exemple, une page) souhaitant recevoir ces messages peut instancier un objet BroadcastChannel et implémenter un gestionnaire de messages pour recevoir des messages.

Pour informer la page lorsqu'un nouveau service worker est installé, utilisez le code suivant:

// Create Broadcast Channel to send messages to the page
const broadcast = new BroadcastChannel('sw-update-channel');

self.addEventListener('install', function (event) {
  // Inform the page every time a new service worker is installed
  broadcast.postMessage({type: 'CRITICAL_SW_UPDATE'});
});

La page écoute ces événements en s'abonnant à sw-update-channel:

// Create Broadcast Channel and listen to messages sent to it
const broadcast = new BroadcastChannel('sw-update-channel');

broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'CRITICAL_SW_UPDATE') {
    // Show "update to refresh" banner to the user.
  }
};

Il s'agit d'une technique simple, mais celle-ci est limitée par la compatibilité des navigateurs: au moment de la rédaction de ce document, Safari n'est pas compatible avec cette API.

API cliente

L'API cliente offre un moyen simple de communiquer avec plusieurs clients à partir du service worker en effectuant des itérations sur un tableau d'objets Client.

Utilisez le code de service worker suivant pour envoyer un message au dernier onglet sélectionné:

// 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'});
  }
});

La page implémente un gestionnaire de messages pour intercepter ces messages:

// Listen to messages
navigator.serviceWorker.onmessage = (event) => {
     if (event.data && event.data.type === 'MSG_ID') {
         // Process response
   }
};

L'API cliente est une excellente option pour la diffusion d'informations sur plusieurs onglets actifs, par exemple. L'API est compatible avec tous les principaux navigateurs, mais toutes ses méthodes ne le sont pas. Vérifiez la compatibilité du navigateur avant de l'utiliser.

Canal de messagerie

Le canal de message nécessite une étape de configuration initiale, en transmettant un port de la page au service worker, pour établir un canal de communication entre eux. La page instancie un objet MessageChannel et transmet un port au service worker via l'interface postMessage():

const messageChannel = new MessageChannel();

// Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
  messageChannel.port2,
]);

La page écoute les messages en implémentant un gestionnaire "onmessage" sur ce port:

// Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};

Le service worker reçoit le port et enregistre une référence à celui-ci:

// Initialize
let communicationPort;

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});

À partir de là, il peut envoyer des messages à la page, en appelant postMessage() dans la référence du port:

// Communicate
communicationPort.postMessage({type: 'MSG_ID' });

MessageChannel peut être plus complexe à implémenter, en raison de la nécessité d'initialiser des ports, mais il est compatible avec tous les principaux navigateurs.

Étapes suivantes

Dans ce guide, nous avons exploré un cas particulier de communication entre des services d'une fenêtre à l'autre : les mises à jour de diffusion. Les exemples explorés incluent l'écoute des événements importants du cycle de vie d'un service worker et la communication à la page des modifications de contenu ou des données mises en cache. Vous pouvez envisager des cas d'utilisation plus intéressants dans lesquels le service worker communique de manière proactive avec la page, sans avoir reçu de message auparavant.

Pour découvrir d'autres modèles de communication entre les fenêtres et les service workers, consultez:

  • 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)
  • Communication bidirectionnelle: déléguer une tâche à un service worker (un téléchargement volumineux, par exemple) et tenir la page informée de sa progression

Ressources supplémentaires