PostMessage zwischen Service Worker und Client(s)

n

nEin Service Worker ist nur ein Worker und entsprechend sollte der Nachrichtenaustausch via postMessage() und MessageEvent eine Kleinigkeit sein – möchte man meinen! Tatsächlich ist die ganze Angelegenheit beim Service Worker etwas weniger trivial als bei anderen Workern, was an ein paar besonderen Umständen liegt:n

n

    n

  1. Eine Seite/App wird immer von einem Service Worker kontrolliert …
  2. n

  3. … der zu einem gegebenen Zeitpunkt ggf. noch inaktiv sein könnte …
  4. n

  5. … und nicht der einzige vorhandene Worker sein muss …
  6. n

  7. … aber seinerseits mehrere Seiten/Apps (Clients) kontrollieren könnte.
  8. n

n

nDiese Gemengelage führt zu einer etwas asymmetrischen API, die zwar logisch, aber nicht direkt offensichtlich ist.n

n

nEine Seite/App kann immer nur einen Service Worker haben. Allerhöchstens könnte es verschiedene Versionen geben, wenn sich z.B. nach der Installation eines Updates ein neuer Worker inaktiv in Warteposition hinter dem aktuellen, aktiven Worker steht. Daher kann es für eine Seite/App nur eine Service-Worker-Message-Quelle geben, die unter navigator.serviceWorker anzuzapfen ist:n

n

navigator.serviceWorker.addEventListener("message", (evt) => {n  window.alert(`Nachricht vom SW: ${ evt.data }`);n});

n

nEs kann natürlich sein, dass gar kein Service Worker aktiv ist oder es überhaupt niemals einen Service Worker für die Seite/App geben wird, aber das ist für das Registrieren eines Event Listeners nicht relevant. Wenn es keine Message-Events gibt (entweder mangels Nachrichten oder mangels Senders), wird der Handler einfach nie aufgerufen. Es ist, als würde man an einen Briefkasten aufstellen – das kann man machen, auch wenn man niemals einen Brief erhalten wird.n

n

nAnders sieht es beim Senden von Nachrichten aus. Hier wird ein Ziel benötigt und dieses Ziel muss in der Lage sein, Nachrichten anzunehmen. Das heißt, dass wir darauf warten müssen dass ein Service Worker aktiv wird und dann auch explizit diesen als Adressaten auswählen müssen. In Code bedeutete das:n

n

// Nachrichten zum SW sendenn// 1. warten bis der SW aktiv istnnavigator.serviceWorker.readyn  .then( (registration) => {n    // 2. Zugriff auf aktiven SW erhaltenn    if (registration.active) {n      // 3. Nachricht sendenn      registration.active.postMessage(23);n    }n  });

n

nDas Promise unter navigator.serviceWorker.ready liefert eine ServiceWorkerRegistration, sobald es einen aktiven Service Worker gibt. Das ServiceWorkerRegistration-Objekt enthält Properties für Service Worker in verschiedenen Stadien: installing, waiting und active. Eine Seite/App wird immer von exakt einem Worker kontrolliert, aber wenn sich neben dem aktivem Worker beispielsweise grade ein Update installiert, sind trotzdem zwei Worker vorhanden. Eine Message muss immer an einen Worker gehen, also rufen wir dort postMessage() auf.n

n

nNatürlich spricht auch nichts dagegen, einem nicht-kontrollierenden Worker eine Nachricht zu senden oder mehrere Nachrichten an mehrere Worker zu verteilen:n

n

// Nachrichten zum SW sendenn// 1. warten bis der SW aktiv istnnavigator.serviceWorker.readyn  .then( (registration) => {n    // 2. Zugriff auf aktiven SW erhaltenn    if (registration.active) {n      // 3. Nachricht sendenn      registration.active.postMessage(23);n    }n    // 4. Zugriff auf SW für bereits installiertes Update erhaltenn    if (registration.waiting) {n      // 5. Nachricht sendenn      registration.waiting.postMessage(42);n    }n  });

n

nAufseiten des Workers ist das Empfangen von Nachrichten recht simpel: das message-Event auf dem globalen Objekt fängt alle Meldungen aller verbundener Seiten/Apps ein. In der source-Eigenschaft des Event-Objekts befindet sich das Client-Objekt, das die sendende Seiten/App repräsentiert. Ein solches Client-Objekt implementiert seinerseits postMessage(), so dass sich Nachrichten leicht zurücksenden lassen:n

n

// Nachrichten aus der Webseite empfangennself.addEventListener("message", (evt) => {n  const client = evt.source;n  client.postMessage(`Pong: ${ evt.data }`);n});

n

nZugriff auf alle übrigen Clients gibt es über die asynchrone Methode self.clients.matchAll(), so dass sich eine Nachricht von einem Client in den Worker an alle anderen Clients weiterverteilen lässt:n

n

self.addEventListener("message", async (evt) => {n  const messageSource = evt.source;n  const clients = await self.clients.matchAll();n  for (const client of clients) {n    if (client !== messageSource) {n      client.postMessage(`Message from ${ messageSource.id }: ${ evt.data }`);n    }n  }n});

n

nUnd schon funktioniert der Nachrichtenaustausch zwischen Client(s) und Service Worker problemlos! Eigentlich ist das ganze Wirrwarr nur eine Konsequenz aus der grundsätzlichen Funktionsweise von Service Worker und somit hoffentlich nur auf den ersten Blick leicht irritierend.n


Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert