Dwukierunkowa komunikacja z skryptami service worker

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

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

Na przykład w progresywnej aplikacji internetowej do podcastów można utworzyć funkcję, która umożliwia użytkownikowi pobieranie odcinków do odtwarzania offline i pozwala pracownikowi usługi na regularne informowanie strony o postępach, dzięki czemu główny wątek może aktualizować interfejs.

W tym przewodniku omówimy różne sposoby implementacji dwukierunkowej komunikacji między kontekstem okna a kontekstem service workera. Poznasz różne interfejsy API, bibliotekę Workbox oraz niektóre zaawansowane przypadki.

Diagram przedstawiający wymianę wiadomości między skryptem service worker a stroną.

Korzystanie z Workbox

workbox-window to zestaw modułów biblioteki Workbox, które są przeznaczone do działania w kontekście okna. Klasa Workbox udostępnia metodę messageSW() do wysyłania wiadomości do zarejestrowanego w instancji service workera i oczekiwania na odpowiedź.

Ten kod strony tworzy nową instancję Workbox i wysyła do service workera wiadomość z prośbą o podanie jego wersji:

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

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

Po drugiej stronie skrypt service worker implementuje odbiornik wiadomości i odpowiada na zarejestrowany skrypt service worker:

const SW_VERSION = '1.0.0';

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

Biblioteka korzysta z interfejsu API przeglądarki, który omówimy w następnej sekcji: MessageChannel. Ukrywa jednak wiele szczegółów implementacji, co ułatwia korzystanie z niej, a jednocześnie wykorzystuje szeroką obsługę w przeglądarkach tego interfejsu API.

Diagram przedstawiający dwukierunkową komunikację między stroną a procesem service worker za pomocą Workbox Window.

Korzystanie z interfejsów API przeglądarki

Jeśli biblioteka Workbox nie spełnia Twoich potrzeb, możesz użyć kilku interfejsów API niższego poziomu, aby wdrożyć dwukierunkową komunikację między stronami a usługami. Są one podobne, ale też się od siebie różnią:

Podobieństwa:

  • W każdym przypadku komunikacja rozpoczyna się na jednym końcu za pomocą interfejsu postMessage(), a na drugim końcu jest odbierana przez implementację procedury obsługi message.
  • W praktyce wszystkie dostępne interfejsy API umożliwiają nam wdrażanie tych samych przypadków użycia, ale niektóre z nich mogą w pewnych sytuacjach uprościć proces tworzenia.

Różnice:

  • Różnią się one sposobem identyfikowania drugiej strony komunikacji: niektóre z nich używają jawnego odwołania do innego kontekstu, a inne mogą komunikować się w sposób niejawny za pomocą obiektu proxy utworzonego po każdej stronie.
  • Obsługa przeglądarek różni się w zależności od usługi.
Diagram przedstawiający dwukierunkową komunikację między stroną a procesem service worker oraz dostępne interfejsy API przeglądarki.

Broadcast Channel API

Browser Support

  • Chrome: 54.
  • Edge: 79.
  • Firefox: 38.
  • Safari: 15.4.

Source

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

Aby to zrobić, każdy kontekst musi najpierw utworzyć obiekt BroadcastChannel o tym samym identyfikatorze 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', });

Każdy 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 musisz najpierw uzyskiwać odniesienia do service workera ani żadnego konkretnego klienta.

Diagram przedstawiający dwukierunkową komunikację między stroną a procesem service worker za pomocą obiektu Broadcast Channel.

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

Client API

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

Interfejs API klienta umożliwia uzyskanie odwołania do wszystkich obiektów WindowClient reprezentujących aktywne karty, które kontroluje proces roboczy usługi.

Ponieważ stroną steruje jeden skrypt service worker, nasłuchuje on aktywnego skryptu service worker i wysyła do niego 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 usługa Service Worker nasłuchuje wiadomości, implementując odbiornik onmessage:

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

Aby komunikować się z klientami, service worker pobiera tablicę obiektów WindowClient, wykonując metody takie jak Clients.matchAll()Clients.get(). Wtedy może postMessage() dowolną 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 instancję service workera komunikującą się z tablicą klientów.

Client API to dobra opcja, aby w stosunkowo prosty sposób komunikować się ze wszystkimi aktywnymi kartami z poziomu komponentu Service Worker. Interfejs API jest obsługiwany przez wszystkie główne przeglądarki, ale nie wszystkie jego metody mogą być dostępne. Zanim zaimplementujesz go w swojej witrynie, sprawdź, czy jest obsługiwany przez przeglądarkę.

Kanał wiadomości

Browser Support

  • Chrome: 2.
  • Edge: 12.
  • Firefox: 41.
  • Safari: 5.

Source

Message Channel wymaga zdefiniowania i przekazania portu z jednego kontekstu do drugiego w celu utworzenia dwukierunkowego kanału komunikacji.

Aby zainicjować kanał, strona tworzy obiekt MessageChannel i używa go do wysyłania portu do zarejestrowanego procesu service worker. Na stronie zaimplementowano też detektor onmessage, który odbiera 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
};
Diagram przedstawiający stronę przekazującą port do service workera w celu nawiązania dwukierunkowej komunikacji.

Service worker otrzymuje port, zapisuje do niego odwołanie i używa go do wysyłania wiadomości do drugiej strony:

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ługiwana przez wszystkie główne przeglądarki.

Zaawansowane interfejsy API: synchronizacja w tle i pobieranie w tle

W tym przewodniku omówiliśmy sposoby wdrażania technik komunikacji dwukierunkowej w stosunkowo prostych przypadkach, takich jak przekazywanie ciągu znaków opisującego operację do wykonania lub listy adresów URL do buforowania z jednego kontekstu do drugiego. W tej sekcji omówimy 2 interfejsy API, które obsługują konkretne scenariusze: brak połączenia i długie pobieranie.

Synchronizacja w tle

Browser Support

  • Chrome: 49.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

Aplikacja do czatowania może chcieć mieć pewność, że wiadomości nigdy nie zostaną utracone z powodu słabego połączenia. Interfejs Background Sync API umożliwia odroczenie działań, które mają być ponawiane, gdy użytkownik ma stabilne połączenie. Jest to przydatne, aby mieć pewność, że wszystko, co użytkownik chce wysłać, zostanie wysłane.

Zamiast interfejsu postMessage() strona rejestruje element sync:

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

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

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

Funkcja doSomeStuff() powinna zwracać obietnicę wskazującą, czy działanie, które próbuje wykonać, zakończyło się sukcesem czy nie. Jeśli obietnica zostanie spełniona, synchronizacja jest zakończona. Jeśli się nie uda, zaplanujemy kolejną synchronizację, aby ponowić próbę. Ponowne próby synchronizacji również czekają na połączenie i wykorzystują wzrastający czas do ponowienia.

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

Wyszukiwarka Google używa synchronizacji w tle, aby zachowywać zapytania, które nie powiodły się z powodu słabego połączenia, i ponawiać je później, gdy użytkownik jest online. Po wykonaniu operacji przekazują wynik użytkownikowi za pomocą powiadomienia push w przeglądarce:

Diagram przedstawiający stronę przekazującą port do service workera w celu nawiązania dwukierunkowej komunikacji.

Pobieranie w tle

Browser Support

  • Chrome: 74.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

W przypadku stosunkowo krótkich zadań, takich jak wysyłanie wiadomości lub listy adresów URL do zapisania w pamięci podręcznej, dotychczasowe opcje są dobrym wyborem. Jeśli zadanie trwa zbyt długo, przeglądarka zakończy działanie service workera, ponieważ w przeciwnym razie może to stanowić zagrożenie dla prywatności użytkownika i baterii.

Interfejs Background Fetch API umożliwia przeniesienie długotrwałego zadania do service workera, np. pobierania filmów, podcastów lub poziomów gry.

Aby komunikować się ze skryptem service worker ze 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}%`);
});
Diagram przedstawiający stronę przekazującą port do service workera w celu nawiązania dwukierunkowej komunikacji.
Interfejs został zaktualizowany, aby wskazywać postęp pobierania (po lewej). Dzięki service workerowi operacja może być kontynuowana po zamknięciu wszystkich kart (po prawej).

Dalsze kroki

W tym przewodniku omówiliśmy najbardziej ogólny przypadek komunikacji między stroną a usługą (komunikacja dwukierunkowa).

Często wystarczy tylko jeden kontekst, aby komunikować się z drugą osobą bez otrzymywania odpowiedzi. Zapoznaj się z tymi przewodnikami, aby dowiedzieć się, jak wdrożyć na stronach techniki jednokierunkowe w odniesieniu do komponentu Service Worker, a także poznać przypadki użycia i przykłady wdrożeń:

  • Przewodnik po buforowaniu imperatywnym: wywoływanie skryptu service worker ze strony w celu wcześniejszego buforowania zasobów (np. w scenariuszach wstępnego pobierania).
  • Rozsyłanie aktualizacji: wywoływanie strony z poziomu service workera w celu informowania o ważnych aktualizacjach (np. dostępna jest nowa wersja aplikacji internetowej).