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ć ustanowienia dwukierunkowego kanału komunikacji między stroną a skryptem service worker.

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

W tym przewodniku omówimy różne sposoby wdrażania dwukierunkowej komunikacji między kontekstem okna a skryptu service worker, analizując różne interfejsy API, bibliotekę Workbox oraz kilka zaawansowanych przypadków.

Diagram przedstawiający skrypt service worker i stronę wymieniające wiadomości.

Korzystanie z Workbox

workbox-window to zestaw modułów biblioteki Workbox, które mają być 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ź.

Poniższy kod strony tworzy nową instancję Workbox i wysyła wiadomość do skryptu service worker, aby uzyskać jego 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 detektor wiadomości po drugiej stronie i 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);
  }
});

Biblioteka ta korzysta z interfejsu API przeglądarki, który omówimy w następnej sekcji: Message Channel. Ukrywa ona jednak wiele szczegółów implementacji, dzięki czemu jest łatwiejsza w użyciu, a jednocześnie wykorzystuje szeroką obsługę przeglądarek, którą zapewnia ten interfejs API.

Diagram przedstawiający dwukierunkową komunikację między stroną a skryptem service worker przy użyciu Workbox Window.

Korzystanie z interfejsów API przeglądarki

Jeśli biblioteka Workbox nie wystarcza do Twoich potrzeb, dostępnych jest kilka interfejsów API niższego poziomu, które umożliwiają wdrożenie komunikacji „dwukierunkowej” między stronami a skryptami service worker. Są między nimi pewne podobieństwa i różnice:

Podobieństwa:

  • We wszystkich przypadkach komunikacja rozpoczyna się na jednym końcu przez interfejs postMessage() i jest odbierana na drugim końcu przez implementację modułu obsługi message.
  • W praktyce wszystkie dostępne interfejsy API umożliwiają implementację tych samych przypadków użycia, ale niektóre z nich mogą uprościć programowanie w określonych scenariuszach.

Różnice:

  • Istnieją różne sposoby identyfikowania drugiej strony komunikacji: niektóre z nich używają wyraźnego odwołania do drugiego kontekstu, podczas gdy inne mogą komunikować się niejawnie za pomocą obiektu proxy utworzonego po każdej stronie.
  • Obsługa przeglądarek różni się w zależności od platformy.
Diagram przedstawiający dwukierunkową komunikację między stroną a skryptem service worker oraz dostępne interfejsy API przeglądarki.

Broadcast Channel API

Browser Support

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

Source

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

Aby go wdrożyć, każdy kontekst musi najpierw 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(), który umożliwia wysyłanie 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 tu wyraźnego odniesienia do konkretnego kontekstu, więc nie trzeba najpierw uzyskiwać odniesienia do skryptu service worker ani konkretnego klienta.

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

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

Client API

Browser Support

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

Source

Client API umożliwia uzyskanie odwołania do wszystkich obiektów WindowClient reprezentujących aktywne karty, które są kontrolowane przez skrypt service worker.

Ponieważ strona jest kontrolowana przez jeden skrypt service worker, nasłuchuje ona i wysyła wiadomości do aktywnego skryptu service worker 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 uzyskuje tablicę obiektów WindowClient, wykonując metody takie jak Clients.matchAll() i Clients.get(). Następnie może 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'});
  }
});
Schemat przedstawiający skrypt service worker komunikujący się z tablicą klientów.

Client API to dobra opcja, która pozwala łatwo komunikować się ze wszystkimi aktywnymi kartami z poziomu skryptu service worker w stosunkowo prosty sposób. Interfejs API jest obsługiwany przez wszystkie główne przeglądarki, ale nie wszystkie jego metody mogą być dostępne, dlatego przed wdrożeniem go w 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 ustanowienia dwukierunkowego kanału komunikacyjnego.

Aby zainicjować kanał, strona tworzy instancję obiektu MessageChannel i używa jej do wysłania portu do zarejestrowanego skryptu service worker. Strona implementuje 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 skryptu service worker w celu nawiązania komunikacji dwukierunkowej.

Skrypt service worker odbiera port, zapisuje odwołanie do niego i używa go do wysył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 omówiliśmy sposoby wdrażania technik komunikacji dwukierunkowej w stosunkowo prostych przypadkach, takich jak przekazywanie wiadomości tekstowej opisującej operację do wykonania lub listy adresów URL do zapisania w pamięci podręcznej z jednego kontekstu do drugiego. W tej sekcji omówimy 2 interfejsy API do obsługi konkretnych scenariuszy: braku połączenia i długiego pobierania.

Synchronizacja w tle

Browser Support

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

Source

Aplikacja do obsługi czatu 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ą zostać ponowione, gdy użytkownik będzie miał stabilne połączenie. Jest to przydatne, aby mieć pewność, że to, co użytkownik chce wysłać, zostanie faktycznie wysłane.

Zamiast interfejsu postMessage() strona rejestruje sync:

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

Następnie skrypt 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ą powodzenie lub niepowodzenie operacji, którą próbuje wykonać. Jeśli obietnica zostanie spełniona, synchronizacja zostanie zakończona. Jeśli się nie powiedzie, zostanie zaplanowana kolejna próba synchronizacji. Ponowne próby synchronizacji również czekają na połączenie i wykorzystują wzrastający czas do ponowienia.

Po wykonaniu operacji skrypt service worker może skomunikować 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 zachować zapytania, które nie powiodły się z powodu słabego połączenia, i ponowić je później, gdy użytkownik będzie online. Po wykonaniu operacji informują użytkownika o jej wyniku za pomocą internetowego powiadomienia push:

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

Pobieranie w tle

Browser Support

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

Source

W przypadku stosunkowo krótkich zadań, takich jak wysł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 wyłączy skrypt service worker, aby nie narażać prywatności użytkownika i nie rozładowywać baterii.

Interfejs API pobierania w tle umożliwia przeniesienie długiego zadania, takiego jak pobieranie filmów, podcastów czy poziomów gry, do skryptu service worker.

Aby komunikować się z 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 skryptu service worker w celu nawiązania komunikacji dwukierunkowej.
Interfejs został zaktualizowany, aby pokazywać postęp pobierania (po lewej). Dzięki skryptom service worker 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 skryptami service worker (komunikacja dwukierunkowa).

Często wystarczy tylko jeden kontekst, aby komunikować się z drugim, bez otrzymywania odpowiedzi. W tych przewodnikach znajdziesz wskazówki dotyczące wdrażania technik jednokierunkowych na stronach z skryptu service worker i do niego, a także przypadki użycia i przykłady produkcyjne:

  • Przewodnik po imperatywnym buforowaniu: wywoływanie skryptu service worker ze strony w celu wcześniejszego buforowania zasobów (np. w scenariuszach pobierania z wyprzedzeniem).
  • Rozpowszechnianie aktualizacji: wywoływanie strony z poziomu skryptu service worker w celu poinformowania o ważnych aktualizacjach (np. o dostępności nowej wersji aplikacji internetowej).