In einigen Fällen muss eine Webanwendung möglicherweise einen zweiseitigen Kommunikationskanal zwischen der Seite und dem Dienst-Worker herstellen.
Beispiel: In einer Podcast-PWA könnte eine Funktion implementiert werden, mit der Nutzer Folgen zum Offline-Abspielen herunterladen und dem Dienst-Worker ermöglichen, die Seite regelmäßig über den Fortschritt zu informieren, damit die Hauptschleife die Benutzeroberfläche aktualisieren kann.
In diesem Leitfaden werden die verschiedenen Möglichkeiten zur Implementierung einer zweiseitigen Kommunikation zwischen dem Window- und dem Service Worker-Kontext erläutert. Dazu werden verschiedene APIs, die Workbox-Bibliothek sowie einige erweiterte Anwendungsfälle vorgestellt.
Workbox verwenden
workbox-window
ist eine Reihe von Modulen der Workbox-Bibliothek, die im Fensterkontext ausgeführt werden sollen. Die Klasse Workbox
bietet die Methode messageSW()
, mit der eine Nachricht an den registrierten Dienst-Worker der Instanz gesendet und auf eine Antwort gewartet werden kann.
Im folgenden Seitencode wird eine neue Workbox
-Instanz erstellt und eine Nachricht an den Service Worker gesendet, um die 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 Dienst-Worker implementiert einen Nachrichtenempfänger am anderen Ende und antwortet auf den registrierten Dienst-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 Message Channel näher betrachten. Viele Implementierungsdetails werden jedoch abstrahiert, was die Verwendung erleichtert und gleichzeitig die breite Browserunterstützung dieser API nutzt.
Browser-APIs verwenden
Wenn die Workbox-Bibliothek nicht Ihren Anforderungen entspricht, gibt es mehrere APIs auf niedrigerem Niveau, mit denen Sie eine „zweiseitige“ 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 Implementieren einesmessage
-Handlers empfangen. - In der Praxis können wir mit allen verfügbaren APIs dieselben Anwendungsfälle implementieren. Einige von ihnen können die Entwicklung in einigen Szenarien jedoch 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 Proxyobjekt kommunizieren können, das auf jeder Seite instanziiert wird.
- Die Browserunterstützung variiert.
Broadcast Channel API
Die Broadcast Channel API ermöglicht die grundlegende Kommunikation zwischen Browserkontexten über BroadcastChannel-Objekte.
Dazu muss in jedem Kontext zuerst ein BroadcastChannel
-Objekt mit derselben ID instanziiert und Nachrichten von ihm gesendet und empfangen werden:
const broadcast = new BroadcastChannel('channel-123');
Das BroadcastChannel-Objekt stellt eine postMessage()
-Schnittstelle bereit, um eine Nachricht an jeden wartenden Kontext zu senden:
//send message
broadcast.postMessage({ type: 'MSG_ID', });
Jeder Browserkontext kann über die Methode onmessage
des BroadcastChannel
-Objekts auf Nachrichten lauschen:
//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 eine Referenz auf den Service Worker oder einen bestimmten Client abzurufen.
Der Nachteil ist, dass die API derzeit von Chrome, Firefox und Edge unterstützt wird, aber von anderen Browsern wie Safari noch nicht.
Client API
Über die Client API können Sie eine Referenz auf alle WindowClient
-Objekte abrufen, die die aktiven Tabs darstellen, die vom Dienstarbeiter gesteuert werden.
Da die Seite von einem einzelnen Service Worker gesteuert wird, überwacht und sendet sie Nachrichten 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
}
};
Ähnlich überwacht der Dienst-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 seinen Clients zu kommunizieren, ruft der Dienst-Worker Methoden wie Clients.matchAll()
und Clients.get()
auf, um ein Array von WindowClient
-Objekten zu erhalten. Dann kann es 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'});
}
});
Client API
ist eine gute Option, um relativ einfach mit allen aktiven Tabs von einem Service Worker zu kommunizieren. Die API wird von allen gängigen Browsern unterstützt. Möglicherweise sind jedoch nicht alle Methoden verfügbar. Prüfen Sie daher vor der Implementierung auf Ihrer Website, ob der Browser die API unterstützt.
Kanal für Nachrichten
Für Message Channel muss ein Port definiert und von einem Kontext an einen anderen übergeben werden, um einen zweiseitigen Kommunikationskanal herzustellen.
Um den Kanal zu initialisieren, instanziiert die Seite ein MessageChannel
-Objekt und sendet damit einen Port an den registrierten Dienst-Worker. 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
};
Der Service Worker empfängt den Port, speichert eine Referenz darauf und verwendet sie, 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 zwischen zwei Kontexten im Cache gespeichert werden sollen. In diesem Abschnitt werden zwei APIs vorgestellt, die für bestimmte Szenarien geeignet sind: fehlende Verbindung und lange Downloads.
Hintergrundsynchronisierung
Eine Chat-App sollte dafür sorgen, dass Nachrichten aufgrund einer schlechten Verbindung nicht verloren gehen. Mit der Background Sync API können Sie Aktionen verschieben, damit sie wiederholt werden, wenn der Nutzer eine stabile Verbindung hat. So wird sichergestellt, dass alles, was der Nutzer senden möchte, auch tatsächlich gesendet wird.
Anstelle der postMessage()
-Schnittstelle registriert die Seite eine sync
:
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
Der Service Worker überwacht dann 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 des jeweiligen Vorgangs angibt. Wenn das Versprechen erfüllt ist, ist die Synchronisierung abgeschlossen. Falls die Synchronisierung fehlschlägt, wird ein neuer Synchronisierungsversuch geplant. Auch bei wiederholten Synchronisierungen wird auf eine Verbindung gewartet und ein exponentielles Backoff verwendet.
Sobald der Vorgang ausgeführt wurde, kann der Dienst-Worker mithilfe einer der zuvor beschriebenen Kommunikations-APIs mit der Seite kommunizieren, um die Benutzeroberfläche zu aktualisieren.
Die Google Suche verwendet die Hintergrundsynchronisierung, um fehlgeschlagene Abfragen aufgrund einer schlechten Verbindung zu speichern und später noch einmal zu versuchen, wenn der Nutzer online ist. Nach Abschluss des Vorgangs wird der Nutzer über eine Web-Push-Benachrichtigung über das Ergebnis informiert:
Hintergrundabruf
Für relativ kurze Aufgaben wie das Senden einer Nachricht oder einer Liste von URLs, die im Cache gespeichert werden sollen, sind die bisher beschriebenen Optionen eine gute Wahl. Wenn die Aufgabe zu lange dauert, beendet der Browser den Dienstarbeiter. Andernfalls besteht ein Risiko für die Privatsphäre und den Akku des Nutzers.
Mit der Background Fetch API können Sie eine langwierige Aufgabe an einen Service Worker auslagern, z. B. den Download von Filmen, Podcasts oder Levels eines Spiels.
Verwenden Sie backgroundFetch.fetch
anstelle von postMessage()
, um über die Seite mit dem Dienst-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 Ereignis progress
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}%`);
});
Nächste Schritte
In diesem Leitfaden haben wir den allgemeinsten Fall der Kommunikation zwischen Seiten- und Dienst-Workern (d. h. die bidirektionale Kommunikation) untersucht.
Oft ist nur ein Kontext erforderlich, um mit dem anderen zu kommunizieren, ohne eine Antwort zu erhalten. In den folgenden Anleitungen finden Sie Informationen dazu, wie Sie unidirektionale Techniken auf Ihren Seiten vom und zum Service Worker implementieren. Außerdem finden Sie Anwendungsfälle und Produktionsbeispiele:
- Imperatives Caching: Ein Service Worker wird von der Seite aufgerufen, 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 Webanwendung verfügbar ist.