Bidirektionale Kommunikation mit Service Workern

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

In einigen Fällen muss eine Web-App einen bidirektionalen Kommunikationskanal zwischen der Seite und dem Service Worker einrichten.

Beispiel: In einer Podcast-PWA könnte eine Funktion erstellt werden, mit der Nutzer Folgen für die Offline-Wiedergabe herunterladen können. Der Service Worker kann die Seite regelmäßig über den Fortschritt informieren, damit der Hauptthread die Benutzeroberfläche aktualisieren kann.

In diesem Leitfaden werden die verschiedenen Möglichkeiten zur Implementierung einer bidirektionalen Kommunikation zwischen dem Fenster und dem Service-Worker-Kontext erläutert. Dabei werden verschiedene APIs, die Workbox-Bibliothek sowie einige erweiterte Anwendungsfälle behandelt.

Diagramm, das zeigt, wie ein Service Worker und die Seite Nachrichten austauschen.

Workbox verwenden

workbox-window ist eine Reihe von Modulen der Workbox-Bibliothek, die im Fensterkontext ausgeführt werden sollen. Die Klasse Workbox bietet eine messageSW()-Methode zum Senden einer Nachricht an den registrierten Service Worker der Instanz und zum Warten auf eine Antwort.

Mit dem folgenden Seitencode wird eine neue Workbox-Instanz erstellt und eine Nachricht an den Service Worker gesendet, um seine Version abzurufen:

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

const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);

Der Service Worker implementiert einen Nachrichten-Listener am anderen Ende und antwortet auf den registrierten Service Worker:

const SW_VERSION = '1.0.0';

self.addEventListener('message', (event) => {
  if (event.data.type === 'GET_VERSION') {
    event.ports[0].postMessage(SW_VERSION);
  }
});

Im Hintergrund verwendet die Bibliothek eine Browser-API, die wir uns im nächsten Abschnitt ansehen: MessageChannel. Sie abstrahiert jedoch viele Implementierungsdetails, was die Verwendung erleichtert, und nutzt die breite Browserunterstützung dieser API.

Diagramm, das die bidirektionale Kommunikation zwischen Seite und Service Worker mit Workbox Window zeigt.

Browser-APIs verwenden

Wenn die Workbox-Bibliothek nicht ausreicht, stehen mehrere APIs auf niedrigerer Ebene zur Verfügung, um die bidirektionale Kommunikation zwischen Seiten und Service Workern zu implementieren. Es gibt einige Gemeinsamkeiten und Unterschiede:

Gemeinsamkeiten:

  • In allen Fällen beginnt die Kommunikation an einem Ende über die postMessage()-Schnittstelle und wird am anderen Ende durch die Implementierung eines message-Handlers empfangen.
  • In der Praxis können mit allen verfügbaren APIs dieselben Anwendungsfälle implementiert werden. Einige von ihnen können die Entwicklung in bestimmten Szenarien jedoch vereinfachen.

Unterschiede:

  • Sie identifizieren die andere Seite der Kommunikation auf unterschiedliche Weise: Einige verwenden einen expliziten Verweis auf den anderen Kontext, während andere implizit über ein Proxy-Objekt kommunizieren können, das auf jeder Seite instanziiert wird.
  • Die Browserunterstützung variiert.
Diagramm, das die bidirektionale Kommunikation zwischen Seite und Service Worker sowie die verfügbaren Browser-APIs zeigt.

Broadcast Channel API

Browser Support

  • Chrome: 54.
  • Edge: 79.
  • Firefox: 38.
  • Safari: 15.4.

Source

Die Broadcast Channel API ermöglicht die grundlegende Kommunikation zwischen Browserkontexten über BroadcastChannel-Objekte.

Dazu muss zuerst in jedem Kontext ein BroadcastChannel-Objekt mit derselben ID instanziiert werden, über das Nachrichten gesendet und empfangen werden:

const broadcast = new BroadcastChannel('channel-123');

Das BroadcastChannel-Objekt stellt eine postMessage()-Schnittstelle zum Senden einer Nachricht an einen beliebigen Kontext bereit, der darauf wartet:

//send message
broadcast.postMessage({ type: 'MSG_ID', });

Jeder Browserkontext kann Nachrichten über die Methode onmessage des Objekts BroadcastChannel empfangen:

//listen to messages
broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process message...
  }
};

Wie Sie sehen, gibt es keinen expliziten Verweis auf einen bestimmten Kontext. Daher ist es nicht erforderlich, zuerst einen Verweis auf den Service Worker oder einen bestimmten Client abzurufen.

Diagramm, das die bidirektionale Kommunikation zwischen Seite und Service Worker mithilfe eines Broadcast Channel-Objekts zeigt.

Der Nachteil ist, dass die API zum Zeitpunkt der Erstellung dieses Dokuments von Chrome, Firefox und Edge unterstützt wird, andere Browser wie Safari sie jedoch noch nicht unterstützen.

Client API

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

Mit der Client API können Sie einen Verweis auf alle WindowClient-Objekte abrufen, die die aktiven Tabs darstellen, die vom Service Worker gesteuert werden.

Da die Seite von einem einzelnen Service Worker gesteuert wird, werden Nachrichten direkt über die serviceWorker-Schnittstelle an den aktiven Service Worker gesendet und von diesem empfangen:

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

Ebenso überwacht der Service Worker Nachrichten, indem er einen onmessage-Listener implementiert:

//listen to messages
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //Process message
  }
});

Um mit einem seiner Clients zu kommunizieren, ruft der Service Worker ein Array von WindowClient-Objekten ab, indem er Methoden wie Clients.matchAll() und Clients.get() ausführt. Dann kann postMessage() eine der folgenden Aktionen ausführen:

//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'});
  }
});
Diagramm, das zeigt, wie ein Service Worker mit einer Reihe von Clients kommuniziert.

Client API ist eine gute Option, um auf relativ einfache Weise mit allen aktiven Tabs eines Service Workers zu kommunizieren. Die API wird von allen wichtigen Browsern unterstützt. Allerdings sind möglicherweise nicht alle Methoden verfügbar. Prüfen Sie daher die Browserunterstützung, bevor Sie die API auf Ihrer Website implementieren.

Kanal für Nachrichten

Browser Support

  • Chrome: 2.
  • Edge: 12.
  • Firefox: 41.
  • Safari: 5.

Source

Für Message Channel muss ein Port von einem Kontext an einen anderen übergeben werden, um einen bidirektionalen> Kommunikationskanal einzurichten.

Zum Initialisieren des Kanals wird auf der Seite ein MessageChannel-Objekt instanziiert und damit ein Port an den registrierten Service Worker gesendet. Auf der Seite wird auch ein onmessage-Listener implementiert, um Nachrichten aus dem anderen Kontext zu empfangen:

const messageChannel = new MessageChannel();

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

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
Diagramm, das zeigt, wie eine Seite einen Port an einen Service Worker übergibt, um eine bidirektionale Kommunikation herzustellen.

Der Service Worker empfängt den Port, speichert eine Referenz darauf und verwendet ihn, um eine Nachricht an die andere Seite zu senden:

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 wird derzeit von allen wichtigen Browsern unterstützt.

Erweiterte APIs: Hintergrundsynchronisierung und Hintergrundabruf

In dieser Anleitung haben wir Möglichkeiten zur Implementierung von bidirektionalen Kommunikationstechniken für relativ einfache Fälle untersucht, z. B. das Übergeben einer String-Nachricht, die den auszuführenden Vorgang beschreibt, oder einer Liste von URLs, die von einem Kontext in den anderen übertragen werden sollen. In diesem Abschnitt werden zwei APIs für bestimmte Szenarien behandelt: fehlende Verbindung und lange Downloads.

Hintergrundsynchronisierung

Browser Support

  • Chrome: 49.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

Eine Chat-App möchte möglicherweise sicherstellen, dass Nachrichten aufgrund einer schlechten Verbindung nie verloren gehen. Mit der Background Sync API können Sie Aktionen aufschieben, die wiederholt werden sollen, wenn der Nutzer eine stabile Verbindung hat. Das ist nützlich, um sicherzustellen, dass das, was der Nutzer senden möchte, auch tatsächlich gesendet wird.

Anstelle der postMessage()-Schnittstelle registriert die Seite einen sync:

navigator.serviceWorker.ready.then(function (swRegistration) {
  return swRegistration.sync.register('myFirstSync');
});

Der Service Worker wartet dann auf das sync-Ereignis, um die Nachricht zu verarbeiten:

self.addEventListener('sync', function (event) {
  if (event.tag == 'myFirstSync') {
    event.waitUntil(doSomeStuff());
  }
});

Die Funktion doSomeStuff() sollte ein Promise zurückgeben, das den Erfolg oder Misserfolg des Vorgangs angibt. Wenn das Promise erfüllt wird, ist die Synchronisierung abgeschlossen. Wenn die Synchronisierung fehlschlägt, wird ein weiterer Synchronisierungsversuch geplant. Bei der Wiederholung der Synchronisierung wird ebenfalls auf eine Verbindung gewartet und ein exponentieller Backoff verwendet.

Sobald der Vorgang ausgeführt wurde, kann der Service Worker über eine der zuvor beschriebenen Kommunikations-APIs mit der Seite kommunizieren, um die Benutzeroberfläche zu aktualisieren.

Die Google Suche verwendet die Hintergrundsynchronisierung, um fehlgeschlagene Anfragen aufgrund einer schlechten Verbindung beizubehalten und sie später noch einmal zu versuchen, wenn der Nutzer online ist. Sobald der Vorgang ausgeführt wurde, wird das Ergebnis über eine Web-Push-Benachrichtigung an den Nutzer gesendet:

Diagramm, das zeigt, wie eine Seite einen Port an einen Service Worker übergibt, um eine bidirektionale Kommunikation herzustellen.

Hintergrundabruf

Browser Support

  • Chrome: 74.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

Für relativ kurze Aufgaben wie das Senden einer Nachricht oder eine Liste von URLs, die im Cache gespeichert werden sollen, sind die bisher untersuchten Optionen eine gute Wahl. Wenn die Aufgabe zu lange dauert, beendet der Browser den Service Worker, da dies sonst ein Risiko für den Datenschutz und den Akku des Nutzers darstellt.

Mit der Background Fetch API können Sie eine lange Aufgabe an einen Service Worker auslagern, z. B. das Herunterladen von Filmen, Podcasts oder Levels eines Spiels.

Wenn Sie von der Seite aus mit dem Service Worker kommunizieren möchten, verwenden Sie backgroundFetch.fetch anstelle von 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,
    },
  );
});

Mit dem BackgroundFetchRegistration-Objekt kann die Seite auf das progress-Ereignis warten, um den Fortschritt des Downloads zu verfolgen:

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}%`);
});
Diagramm, das zeigt, wie eine Seite einen Port an einen Service Worker übergibt, um eine bidirektionale Kommunikation herzustellen.
Die Benutzeroberfläche wurde aktualisiert, um den Fortschritt eines Downloads anzuzeigen (links). Dank Service Workern kann der Vorgang weiter ausgeführt werden, wenn alle Tabs geschlossen wurden (rechts).

Nächste Schritte

In diesem Leitfaden haben wir uns den allgemeinsten Fall der Kommunikation zwischen Seiten- und Service-Workern angesehen (bidirektionale Kommunikation).

Oftmals ist nur ein Kontext erforderlich, um mit dem anderen zu kommunizieren, ohne eine Antwort zu erhalten. In den folgenden Leitfäden finden Sie Informationen zur Implementierung unidirektionaler Techniken auf Ihren Seiten vom und zum Service Worker sowie Anwendungsfälle und Produktionsbeispiele:

  • Imperative Caching Guide: Aufrufen eines Service Workers von der Seite, um Ressourcen im Voraus zu cachen (z.B. in Prefetching-Szenarien).
  • Broadcast-Updates: Die Seite wird über den Service Worker aufgerufen, um über wichtige Updates zu informieren, z.B. wenn eine neue Version der Web-App verfügbar ist.