Dwukierunkowa komunikacja z skryptami service worker

Andrew Guan
Andrew Guan

W niektórych przypadkach aplikacja internetowa może wymagać ustanowienia dwukierunkowego kanału komunikacji między stroną a skryptem service worker.

Na przykład: w PWA podcastu można utworzyć funkcję, która umożliwia użytkownikowi pobieranie odcinków do wykorzystania w trybie offline i pozwala skryptowi service worker regularnie informować stronę o postępach, dzięki czemu wątek główny może aktualizować UI.

W tym przewodniku omówimy różne sposoby implementacji dwukierunkowej komunikacji między środowiskiem okno a kontekstem skryptu service worker – poznasz różne interfejsy API, bibliotekę Workbox oraz kilka zaawansowanych przypadków.

Diagram przedstawiający mechanizm Service Worker i stronę z wymienianiem wiadomości.

Korzystanie z Workbox

workbox-window to zestaw modułów biblioteki Workbox, które są uruchamiane w kontekście okna. Klasa Workbox udostępnia metodę messageSW(), która umożliwia wysłanie wiadomości do zarejestrowanego skryptu service worker instancji i oczekiwanie na odpowiedź.

Ten kod strony tworzy nową instancję Workbox i wysyła wiadomość do skryptu service worker, by uzyskać jej wersję:

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

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

Skrypt service worker implementuje po drugiej stronie odbiornik wiadomości, który odpowiada zarejestrowanemu skryptowi service worker:

const SW_VERSION = '1.0.0';

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

Zasadniczo biblioteka korzysta z interfejsu API przeglądarki, który omówimy w następnej sekcji: Message Channel, ale wymieniono w nim wiele szczegółów implementacji, dzięki czemu interfejs API jest łatwiejszy w użyciu, a jednocześnie wykorzystuje jego obsługę w całej przeglądarce.

Diagram przedstawiający dwukierunkową komunikację między stroną a skryptem service worker z użyciem okna skrzynki roboczej.

Korzystanie z interfejsów API przeglądarek

Jeśli biblioteka Workbox nie spełnia Twoich wymagań, możesz skorzystać z kilku interfejsów API niższego poziomu, które umożliwiają wdrożenie „dwukierunkowej” komunikacji między stronami a skryptami service worker. Są między nimi pewne podobieństwa i różnice:

Podobieństwa:

  • We wszystkich przypadkach komunikacja zaczyna się po jednej stronie przez interfejs postMessage(), a odbierana po drugiej stronie przez wdrożenie modułu obsługi message.
  • W praktyce wszystkie dostępne interfejsy API umożliwiają nam wdrożenie tych samych przypadków użycia, ale niektóre z nich w niektórych sytuacjach mogą uprościć programowanie.

Różnice:

  • Różnią się przy tym na różne sposoby, identyfikując drugą stronę komunikacji: niektóre z nich korzystają z jawnego odniesienia do drugiego kontekstu, a inne – pośrednio przez obiekt proxy utworzony po każdej stronie.
  • Obsługa przeglądarek różni się w zależności od rodzaju usługi.
Diagram przedstawiający dwukierunkową komunikację między stroną a mechanizmem Service Worker oraz dostępnymi interfejsami API przeglądarek.

Interfejs Broadcast Channel API

Obsługa przeglądarek

  • 54
  • 79
  • 38
  • 15,4

Źródło

Interfejs Broadcast Channel API umożliwia podstawową komunikację między kontekstami przeglądania za pomocą obiektów BroadcastChannel.

Aby można było go zaimplementować, każdy kontekst musi utworzyć instancję obiektu BroadcastChannel z tym samym identyfikatorem oraz wysyłać i odbierać z niego wiadomości:

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

Obiekt BroadcastChannel udostępnia interfejs postMessage() do wysyłania wiadomości do dowolnego kontekstu nasłuchiwania:

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

Dowolny kontekst przeglądarki może nasłuchiwać wiadomości za pomocą metody onmessage obiektu BroadcastChannel:

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

Jak widać, nie ma wyraźnego odniesienia do konkretnego kontekstu, więc nie ma potrzeby wcześniejszego uzyskania odniesienia do mechanizmu Service Worker ani konkretnego klienta.

Diagram przedstawiający dwukierunkową komunikację między stroną a skryptem service worker przy użyciu obiektu kanału naziemnego.

Wadą jest to, że obecnie interfejs API jest obsługiwany przez Chrome, Firefox i Edge, ale inne przeglądarki, takie jak Safari, jeszcze go nie obsługują.

Interfejs API klienta

Obsługa przeglądarek

  • 40
  • 17
  • 44
  • 11.1

Źródło

Interfejs Client API umożliwia uzyskanie odniesienia do wszystkich obiektów WindowClient reprezentujących aktywne karty, którymi steruje skrypt service worker.

Stroną steruje pojedynczy skrypt service worker, dlatego nasłuchuje i wysyła wiadomości bezpośrednio przez interfejs serviceWorker:

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

Podobnie skrypt service worker nasłuchuje wiadomości, implementując detektor onmessage:

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

Aby komunikować się z klientami, skrypt service worker otrzymuje tablicę obiektów WindowClient za pomocą takich metod jak Clients.matchAll() i Clients.get(). Następnie może wykonać postMessage() dowolne z nich:

//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'});
  }
});
Diagram przedstawiający mechanizm Service Worker komunikujący się z tablicą klientów.

Client API to dobry sposób na łatwą komunikację ze wszystkimi aktywnymi kartami z skryptu service worker w stosunkowo prosty sposób. Interfejs API jest obsługiwany przez wszystkie główne przeglądarki, ale niektóre jego metody mogą nie być dostępne. Dlatego zanim zaimplementujesz go w swojej witrynie, sprawdź obsługę przeglądarek.

Kanał wiadomości

Obsługa przeglądarek

  • 2
  • 12
  • 41
  • 5

Źródło

Kanał wiadomości wymaga zdefiniowania i przekazywania portów z jednego kontekstu do drugiego w celu utworzenia dwukierunkowego kanału komunikacji.

Aby zainicjować kanał, strona tworzy instancję obiektu MessageChannel i używa go do wysłania portu do zarejestrowanego skryptu service worker. Strona ma też implementowany na niej odbiornik onmessage, który umożliwia odbieranie wiadomości z innego kontekstu:

const messageChannel = new MessageChannel();

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

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
Schemat przedstawiający stronę przekazującą port do skryptu service worker w celu nawiązania dwukierunkowej komunikacji.

Skrypt service worker odbiera port, zapisuje do niego odniesienie i używa go do wysłania wiadomości na drugą stronę:

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 jest obecnie obsługiwany przez wszystkie główne przeglądarki.

Zaawansowane interfejsy API: synchronizacja w tle i pobieranie w tle

W tym przewodniku omawiamy sposoby wdrażania dwukierunkowych technik komunikacji w stosunkowo prostych przypadkach, takich jak przekazywanie komunikatu tekstowego opisującego operację lub listę adresów URL do buforowania z jednego kontekstu do drugiego. W tej sekcji omówimy 2 interfejsy API w konkretnych sytuacjach: brak połączenia i długi czas pobierania.

Synchronizacja w tle

Obsługa przeglądarek

  • 49
  • 79
  • x
  • x

Źródło

Aplikacja do obsługi czatu może zadbać o to, by wiadomości nie zostały utracone z powodu słabej łączności. Interfejs Background Sync API pozwala opóźnić wykonywanie działań, gdy użytkownik ma stabilne połączenie. Dzięki temu możesz mieć pewność, że wszystko, co użytkownik chce przesłać, zostanie rzeczywiście wysłane.

Zamiast interfejsu postMessage() strona rejestruje sync:

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

Skrypt service worker nasłuchuje następnie zdarzenia sync, aby przetworzyć wiadomość:

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

Funkcja doSomeStuff() powinna zwrócić obietnicę wskazującą na udany/niepowodzenie operacji. Jeśli zostanie wykonana, synchronizacja została ukończona. Jeśli się nie uda, zaplanowana jest kolejna próba synchronizacji. Ponowne synchronizacje także oczekują na połączenie i przeprowadzają wykładnicze ponawianie.

Po wykonaniu operacji skrypt service worker może ponownie komunikować się ze stroną, aby zaktualizować interfejs, korzystając z dowolnego z omówionych wcześniej interfejsów API komunikacji.

Wyszukiwarka Google korzysta z synchronizacji w tle, aby zachowywać nieudane zapytania z powodu problemów z połączeniem i ponawiać je później, gdy użytkownik będzie online. Po wykonaniu operacji użytkownik przekazuje wynik użytkownikowi za pomocą internetowego powiadomienia push:

Schemat przedstawiający stronę przekazującą port do skryptu service worker w celu nawiązania dwukierunkowej komunikacji.

Pobranie w tle

Obsługa przeglądarek

  • 74
  • 79
  • x
  • x

Źródło

W przypadku stosunkowo krótkich zadań, takich jak wysyłanie wiadomości lub tworzenie listy adresów URL do pamięci podręcznej, dostępne do tej pory opcje są dobrym rozwiązaniem. Jeśli zadanie będzie trwać zbyt długo, przeglądarka wyłączy skrypt service worker, w przeciwnym razie będzie to stanowiło zagrożenie dla prywatności i baterii użytkownika.

Interfejs Background Fetch API pozwala przekierować do skryptu service worker długie zadania, takie jak pobieranie filmów, podcastów czy poziomów gry.

Aby komunikować się z Skryptem service worker z poziomu strony, użyj backgroundFetch.fetch zamiast 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,
    },
  );
});

Obiekt BackgroundFetchRegistration umożliwia stronie nasłuchiwanie zdarzenia progress, aby śledzić postęp pobierania:

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}%`);
});
Schemat przedstawiający stronę przekazującą port do skryptu service worker w celu nawiązania dwukierunkowej komunikacji.
Interfejs zostaje zaktualizowany, aby wskazać postęp pobierania (po lewej stronie). Dzięki mechanizmom Service Worker operacja może być kontynuowana po zamknięciu wszystkich kart (po prawej).

Dalsze kroki

W tym przewodniku zajęliśmy się najbardziej ogólnym przypadkiem komunikacji między skryptami stron i skryptami service worker (komunikacja dwukierunkowa).

Czasami jedno z nich może potrzebować tylko jednego kontekstu do komunikowania się z drugim, bez otrzymania odpowiedzi. Zapoznaj się z poniższymi przewodnikami, które zawierają wskazówki dotyczące wdrażania technik jednokierunkowych na stronach z skryptu service worker i do niego, a także przykładowe zastosowania i przykłady produkcji:

  • Przewodnik po buforowaniu: wywoływanie skryptu service worker ze strony w celu wcześniejszego buforowania zasobów (np. w sytuacjach pobierania z wyprzedzeniem).
  • Transmitowanie aktualizacji: wywołanie strony z skryptu service worker w celu poinformowania o ważnych aktualizacjach (np. że jest dostępna nowa wersja aplikacji internetowej).