Bidirektionale Kommunikation mit Service Workern

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

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

Beispiel: In einer Podcast-PWA könnte man eine Funktion einbauen, mit der der Nutzer Folgen für die Offline-Nutzung herunterladen kann. Der Service Worker könnte die Seite dann 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 Window- und dem Service Worker-Kontext erläutert. Dazu werden verschiedene APIs, die Workbox-Bibliothek sowie einige erweiterte Fälle untersucht.

Diagramm, das einen Service Worker und die Seite zeigt, die 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, 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 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 am anderen Ende einen Nachrichten-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);
  }
});

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

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

Browser-APIs verwenden

Wenn die Workbox-Bibliothek für Ihre Anforderungen nicht ausreicht, stehen mehrere APIs auf niedrigerer Ebene zur Verfügung, um eine bidirektionale Kommunikation zwischen Seiten und Service Workern zu implementieren. Sie haben einige Gemeinsamkeiten und Unterschiede:

Gemeinsamkeiten:

  • In allen Fällen beginnt die Kommunikation auf der einen Seite über die postMessage()-Schnittstelle und wird auf der anderen Seite durch die Implementierung eines message-Handlers empfangen.
  • In der Praxis können wir mit allen verfügbaren APIs dieselben Anwendungsfälle implementieren, aber einige davon können die Entwicklung in bestimmten Szenarien 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 Proxy-Objekt kommunizieren können, das auf jeder Seite instanziiert wird.
  • Die Browserunterstützung variiert je nach Browser.
Diagramm, das die wechselseitige 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 jeder Kontext zuerst ein BroadcastChannel-Objekt mit derselben ID instanziieren und Nachrichten von diesem Objekt senden und empfangen:

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

Das BroadcastChannel-Objekt stellt eine postMessage()-Schnittstelle bereit, über die eine Nachricht an jeden beliebigen Kontext gesendet werden kann:

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

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

//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. Es ist also nicht erforderlich, zuerst einen Verweis auf den Service Worker oder einen bestimmten Client zu erhalten.

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

Der Nachteil ist, dass die API zum Zeitpunkt der Erstellung dieses Beitrags von Chrome, Firefox und Edge unterstützt wird, aber von anderen Browsern wie Safari noch nicht.

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 der Service Worker steuert.

Da die Seite von einem einzelnen Service Worker gesteuert wird, überwacht sie Nachrichten und sendet sie direkt über die serviceWorker-Schnittstelle 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
  }
};

Der Service Worker überwacht 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 Kommunikation mit seinen Clients ruft der Service Worker ein Array von WindowClient-Objekten ab, indem er Methoden wie Clients.matchAll() und Clients.get() ausführt. Anschließend kann sie jede davon 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, aber möglicherweise sind 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 definiert und von einem Kontext an einen anderen übergeben werden, um einen bidirektionalen Kommunikationskanal einzurichten.

Um den Kanal zu initialisieren, instanziiert die Seite ein MessageChannel-Objekt und verwendet es, um einen Port an den registrierten Service Worker zu senden. Auf der Seite wird außerdem 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 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 bidirektionalen Kommunikationstechniken für relativ einfache Fälle untersucht, z. B. die Übergabe einer Stringnachricht, 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 soll beispielsweise dafür sorgen, dass Nachrichten aufgrund einer schlechten Verbindung nie verloren gehen. Mit der Background Sync API können Sie Aktionen verschieben, die wiederholt werden sollen, wenn der Nutzer eine stabile Verbindung hat. So wird sichergestellt, dass das, was der Nutzer senden möchte, auch tatsächlich gesendet wird.

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

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 sie erfüllt wird, ist die Synchronisierung abgeschlossen. Wenn die Synchronisierung fehlschlägt, wird eine weitere Synchronisierung geplant, um es noch einmal zu versuchen. Bei wiederholten Synchronisierungen wird ebenfalls auf eine Verbindung gewartet und ein exponentieller Backoff verwendet.

Nachdem 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 Suchanfragen aufgrund einer schlechten Verbindung zu speichern und sie später noch einmal zu versuchen, wenn der Nutzer online ist. Nachdem der Vorgang ausgeführt wurde, wird das Ergebnis dem Nutzer über eine Web-Push-Benachrichtigung mitgeteilt:

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 einer Liste von URLs, die im Cache gespeichert werden sollen, sind die bisher vorgestellten Optionen eine gute Wahl. Wenn die Aufgabe zu lange dauert, beendet der Browser den Service Worker, da sonst die Privatsphäre und der Akku des Nutzers gefährdet sind.

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

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

Nächste Schritte

In diesem Leitfaden haben wir den allgemeinsten Fall der Kommunikation zwischen Seite und Service Worker (bidirektionale Kommunikation) behandelt.

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

  • Leitfaden zum imperativen Caching: Aufruf eines Service Workers von der Seite, um Ressourcen im Voraus im Cache zu speichern (z.B. in Prefetching-Szenarien).
  • Updates übertragen: Die Seite wird vom Service Worker aufgerufen, um über wichtige Updates zu informieren (z.B. dass eine neue Version der Web-App verfügbar ist).