Bidirektionale Kommunikation mit Service Workern

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

In einigen Fällen muss eine Webanwendung möglicherweise einen Zwei-Wege-Kommunikationskanal zwischen der Seite und dem Service Worker einrichten.

Beispiel: In einer Podcast-PWA könnte eine Funktion erstellt werden, mit der Nutzer Folgen zur Offlinewiedergabe herunterladen und der Service Worker die Seite regelmäßig über den Fortschritt informieren kann, damit die Benutzeroberfläche im Hauptthread aktualisiert werden kann.

In diesem Leitfaden werden die verschiedenen Möglichkeiten zur Implementierung einer bidirektionalen Kommunikation zwischen dem Window- und dem Service-Worker-Kontext anhand verschiedener APIs, der Workbox-Bibliothek und einiger komplexerer Anwendungsfälle erläutert.

Diagramm, das einen Service Worker und die Seite zeigt, über die Nachrichten ausgetauscht werden.

Workbox verwenden

workbox-window besteht aus einer Reihe von Modulen der Workbox-Bibliothek, die im Fensterkontext ausgeführt werden sollen. Die Klasse Workbox bietet eine messageSW()-Methode, um eine Nachricht an den registrierten Service Worker der Instanz zu senden und auf eine Antwort zu warten.

Mit dem folgenden Seitencode wird eine neue Workbox-Instanz erstellt und eine Nachricht an den Service Worker gesendet, um dessen 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 auf der anderen Seite einen Message-Listener und antwortet dem 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);
  }
});

Intern verwendet die Bibliothek eine Browser-API, die wir im nächsten Abschnitt behandeln werden: Nachrichtenkanal. Dabei werden jedoch viele Implementierungsdetails abstrahiert, was die Verwendung erleichtert und gleichzeitig die breite Browserunterstützung dieser API nutzt.

Diagramm, das die bidirektionale Kommunikation zwischen Page und Service Worker unter Verwendung von Workbox Window zeigt

Browser-APIs verwenden

Wenn die Workbox-Bibliothek für Ihre Anforderungen nicht ausreicht, gibt es mehrere untergeordnete APIs, mit denen Sie die "Zwei-Wege"-Kommunikation zwischen Seiten und Service Workern implementieren können. Sie haben 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 Implementierung eines message-Handlers empfangen.
  • In der Praxis können wir mit allen verfügbaren APIs die gleichen Anwendungsfälle implementieren. Einige davon können jedoch in einigen Szenarien die Entwicklung vereinfachen.

Unterschiede:

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

Broadcast-Channel-API

Unterstützte Browser

  • 54
  • 79
  • 38
  • 15,4

Quelle

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

Um sie zu implementieren, muss jeder Kontext zuerst ein BroadcastChannel-Objekt mit derselben ID instanziieren und Nachrichten von diesem senden und empfangen:

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

Das BroadcastChannel-Objekt stellt eine postMessage()-Schnittstelle zur Verfügung, über die eine Nachricht an jeden Zuhörerkontext gesendet wird:

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

Jeder Browserkontext kann Nachrichten über die Methode onmessage des BroadcastChannel-Objekts abhören:

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

Wie gesehen, 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 Page und Service Worker unter Verwendung eines Broadcast Channel-Objekts zeigt

Der Nachteil besteht darin, dass die API zum jetzigen Zeitpunkt von Chrome, Firefox und Edge unterstützt wird. Andere Browser wie Safari unterstützen sie aber noch nicht.

Client-API

Unterstützte Browser

  • 40
  • 17
  • 44
  • 11.1

Quelle

Mit der Client API können Sie eine Referenz auf alle WindowClient-Objekte abrufen, die die aktiven Tabs darstellen, die der Service Worker steuert.

Da die Seite von einem einzelnen Service Worker gesteuert wird, hört sie Nachrichten ab und sendet sie direkt über die Schnittstelle serviceWorker an den aktiven Service Worker:

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

In ähnlicher Weise ü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
  }
});

Für die Rückkommunikation mit einem seiner Clients ruft der Service Worker ein Array von WindowClient-Objekten ab, indem er Methoden wie Clients.matchAll() und Clients.get() ausführt. Dann kann sie jede von ihnen postMessage():

//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 gängigen Browsern unterstützt, unter Umständen stehen jedoch nicht alle zugehörigen Methoden zur Verfügung. Prüfen Sie daher die Browserunterstützung, bevor Sie die API auf Ihrer Website implementieren.

Kanal für Nachrichten

Unterstützte Browser

  • 2
  • 12
  • 41
  • 5

Quelle

Ein Nachrichtenkanal erfordert die Definition und Übergabe eines Ports von einem Kontext zum anderen, um einen Zwei-Wege-Kommunikationskanal einzurichten.

Zum Initialisieren des Kanals instanziiert die Seite ein MessageChannel-Objekt und verwendet es, um einen Port an den registrierten Service Worker zu senden. 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 mit einer Seite, die einen Port an einen Service Worker weiterleitet, um eine bidirektionale Kommunikation herzustellen.

Der Service Worker empfängt den Port, speichert einen Verweis 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 gängigen Browsern unterstützt.

Erweiterte APIs: Hintergrundsynchronisierung und Hintergrundabruf

In diesem Leitfaden haben wir Möglichkeiten zur Implementierung von Zwei-Wege-Kommunikationstechniken für relativ einfache Fälle untersucht, z. B. das Übergeben einer Stringnachricht, die den auszuführenden Vorgang beschreibt, oder einer Liste von URLs, die von einem Kontext zum anderen im Cache gespeichert werden sollen. In diesem Abschnitt geht es um zwei APIs für bestimmte Szenarien: mangelnde Verbindung und lange Downloads.

Hintergrundsynchronisierung

Unterstützte Browser

  • 49
  • 79
  • x
  • x

Quelle

Eine Chat-App kann sicherstellen, dass Nachrichten niemals aufgrund einer schlechten Verbindung verloren gehen. Mit der Background Sync API können Sie Aktionen aufschieben, die wiederholt werden sollen, wenn der Nutzer eine stabile Verbindung hat. Dies ist nützlich, um sicherzustellen, dass alles gesendet wird, was der Nutzer senden möchte.

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

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

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

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

Die Funktion doSomeStuff() sollte ein Versprechen zurückgeben, das den Erfolg oder Misserfolg von dem, was sie zu tun versucht, angibt. Wird dies erfüllt, ist die Synchronisierung abgeschlossen. Wenn er fehlschlägt, wird ein neuer Versuch für die Synchronisierung geplant. Wiederholungssynchronisierungen warten auch auf eine Verbindung und verwenden ein exponentielles Backoff.

Sobald der Vorgang ausgeführt wurde, kann der Service Worker über eine der zuvor behandelten Kommunikations-APIs wieder mit der Seite kommunizieren, um die UI zu aktualisieren.

Die Google Suche verwendet die Hintergrundsynchronisierung, um fehlgeschlagene Abfragen aufgrund schlechter Verbindung beizubehalten und sie später noch einmal zu senden, wenn der Nutzer online ist. Sobald der Vorgang ausgeführt wurde, teilen sie dem Nutzer das Ergebnis über eine Web-Push-Benachrichtigung mit:

Diagramm mit einer Seite, die einen Port an einen Service Worker weiterleitet, um eine bidirektionale Kommunikation herzustellen.

Hintergrundabruf

Unterstützte Browser

  • 74
  • 79
  • x
  • x

Quelle

Bei relativ kurzen Aufgaben wie dem Senden einer Nachricht oder einer Liste von im Cache gespeicherten URLs sind die bisher untersuchten Optionen eine gute Wahl. Wenn die Aufgabe zu lange dauert, beendet der Browser den Service-Worker. Andernfalls besteht ein Risiko für den Datenschutz und den Akku des Nutzers.

Mit der Background Fetch API können Sie lange Aufgaben an einen Service Worker auslagern, z. B. Filme, Podcasts oder Level eines Spiels herunterladen.

Verwenden Sie backgroundFetch.fetch anstelle von postMessage(), um von der Seite aus mit dem Service Worker zu kommunizieren:

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 mit einer Seite, die einen Port an einen Service Worker weiterleitet, um eine bidirektionale Kommunikation herzustellen.
Die Benutzeroberfläche wird aktualisiert, um den Fortschritt eines Downloads anzuzeigen (links). Dank der Service Worker kann der Vorgang fortgesetzt werden, wenn alle Tabs geschlossen wurden (rechts).

Nächste Schritte

In diesem Leitfaden haben wir uns mit dem allgemeinsten Fall der Kommunikation zwischen Page und Service Workern (bidirektionale Kommunikation) beschäftigt.

Oftmals wird nur ein Kontext benötigt, um mit dem anderen zu kommunizieren, ohne eine Antwort zu erhalten. Die folgenden Leitfäden und Anwendungsfälle und Produktionsbeispiele enthalten Anleitungen zum Implementieren unidirektionaler Techniken auf Ihren Seiten vom und zum Service Worker:

  • Leitfaden für Imperatives Caching: Aufrufen eines Service Workers von der Seite aus, um Ressourcen im Voraus im Cache zu speichern (z.B. in Prefetch-Szenarien).
  • Broadcast-Updates: Durch Aufrufen der Seite durch den Service Worker werden Sie über wichtige Updates informiert (z.B. wenn eine neue Version der Web-App verfügbar ist).