Dwukierunkowa komunikacja z skryptami service worker

Andrew Guan
Andrew Guan

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

Na przykład w przypadku aplikacji internetowej do odtwarzania podcastów można utworzyć funkcję, która pozwoli użytkownikowi pobierać odcinki do odsłuchiwania offline, oraz umożliwić workerowi usługi regularne informowanie strony o postępach, aby główny wątek mógł aktualizować interfejs użytkownika.

W tym przewodniku omówimy różne sposoby implementowania dwukierunkowej komunikacji między kontekstem oknaobsługą usługi, korzystając z różnych interfejsów API, biblioteki Workbox oraz niektórych zaawansowanych przypadków.

Diagram pokazujący skrypt service worker i stronę wymieniające się wiadomościami.

Korzystanie z Workbox

workbox-window to zestaw modułów biblioteki Workbox, które mają działać w kontekście okna. Klasa Workbox udostępnia metodę messageSW(), która umożliwia wysłanie wiadomości do zarejestrowanego w instancji pracownika usługi i odczekanie na odpowiedź.

Poniższy kod strony tworzy nową instancję Workbox i wysyła wiadomość do usługi, aby pobrać jej wersję:

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

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

Usługa workera implementuje odsłuchiwanie wiadomości po drugiej stronie i reaguje na zarejestrowaną usługę workera:

const SW_VERSION = '1.0.0';

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

Pod maską biblioteka korzysta z interfejsu API przeglądarki, który omówimy w następnej sekcji: MessageChannel, ale abstrahuje wiele szczegółów implementacji, co ułatwia jej używanie, a zarazem wykorzystuje obsługę wielu przeglądarek, którą zapewnia ten interfejs API.

Diagram przedstawiający dwukierunkową komunikację między stroną a usługą workera za pomocą okna Workbox

Korzystanie z interfejsów API przeglądarki

Jeśli biblioteka Workbox nie spełnia Twoich potrzeb, możesz skorzystać z kilku interfejsów API niższego poziomu, aby zaimplementować „dwukierunkową” komunikację między stronami a workerami usługi. Wykazują pewne podobieństwa i różnice:

Podobieństwa:

  • We wszystkich przypadkach komunikacja rozpoczyna się po jednej stronie za pomocą interfejsu postMessage() i jest odbierana po drugiej stronie 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ć tworzenie w niektórych scenariuszach.

Różnice:

  • Mają różne sposoby identyfikowania drugiej strony komunikacji: niektóre z nich używają jawnego odwołania do innego kontekstu, a inne mogą komunikować się pośrednio za pomocą obiektu pośredniczącego utworzonego po obu stronach.
  • Obsługa przeglądarek różni się w zależności od urządzenia.
Diagram pokazujący dwukierunkową komunikację między stroną a usługą w tle i dostępne interfejsy API przeglądarki

Interfejs 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 to zaimplementować, każdy kontekst musi najpierw utworzyć instancję obiektu BroadcastChannel o tym samym identyfikatorze i wysyłać oraz 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łuchującego:

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

Każdy kontekst przeglądarki może odbierać 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 odwołania do konkretnego kontekstu, więc nie trzeba najpierw uzyskać odwołania do service workera ani żadnego konkretnego klienta.

Diagram przedstawiający dwukierunkową komunikację między stroną a usługą workera przy użyciu obiektu BroadcastChannel

W chwili pisania tego artykułu interfejs API jest obsługiwany przez przeglądarki Chrome, Firefox i Edge, ale inne przeglądarki, takie jak Safari, jeszcze go nie obsługują.

Interfejs API klienta

Browser Support

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

Source

Interfejs Client API umożliwia uzyskanie odwołania do wszystkich obiektów WindowClient reprezentujących aktywne karty, które kontroluje usługa działająca w tle.

Strona jest kontrolowana przez pojedynczy skrypt service worker, który nasłuchuje i wysyła wiadomości do aktywnego skryptu service worker bezpośrednio za pomocą interfejsu 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 nasłuchuje wiadomości, implementując listenera onmessage:

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

Aby komunikować się z dowolnym klientem, usługa robocza uzyskuje tablicę obiektów WindowClient, wykonując metody takie jak Clients.matchAll()Clients.get(). Następnie może postMessage() wykonać jedną z tych czynności:

//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 pracownika usługi komunikującego się z wieloma klientami

Client API to dobra opcja umożliwiająca łatwe komunikowanie się ze wszystkimi aktywnymi kartami z poziomu usługi w sposób stosunkowo prosty. Interfejs API jest obsługiwany przez wszystkie główne przeglądarki, ale nie wszystkie metody mogą być dostępne. Przed wdrożeniem interfejsu w witrynie sprawdź, czy jest on 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 instancję obiektu MessageChannel i wykorzystuje go do wysłania portu do zarejestrowanego pracownika usługi. Strona implementuje też listenera onmessage, aby 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 pokazujący, jak strona przekazuje port do usługi workera, aby nawiązać dwukierunkową komunikację.

Usługa otrzymuje port, zapisuje odwołanie do niego i wykorzystuje go do wysł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 dwukierunkowej komunikacji w przypadku stosunkowo prostych przypadków, takich jak przekazywanie ciągu znaków opisującego operację do wykonania lub listy adresów URL do umieszczenia w pamięci podręcznej z jednego kontekstu do drugiego. W tej sekcji omówimy 2 interfejsy API do obsługi określonych 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 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ń do ponownego wykonania, gdy użytkownik ma stabilne połączenie. Dzięki temu użytkownik może mieć pewność, że wysłał to, co zamierzał.

Zamiast interfejsu postMessage() strona rejestruje sync:

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

Następnie usługa wątek oczekuje na zdarzenie sync, aby przetworzyć wiadomość:

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

Funkcja doSomeStuff() powinna zwracać obietnicę wskazującą, czy udało się wykonać daną czynność. Jeśli tak, synchronizacja została zakończona. Jeśli się nie powiedzie, zostanie zaplanowana kolejna synchronizacja. Ponowna synchronizacja również czeka na połączenie i wykorzystuje wzrastający czas do ponowienia.

Po wykonaniu operacji usługa może się komunikować z stroną, aby zaktualizować interfejs użytkownika, korzystając z dowolnego z wyjaśnionych wcześniej interfejsów API do komunikacji.

Wyszukiwarka Google używa synchronizacji w tle, aby przechowywać nieudane zapytania z powodu słabego połączenia i ponownie je próbować, gdy użytkownik jest online. Po wykonaniu operacji zespół powiadomi użytkownika o jej wyniku za pomocą powiadomienia push:

Diagram pokazujący, jak strona przekazuje port do usługi workera, aby nawiązać dwukierunkową komunikację.

Pobranie 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 lista adresów URL do umieszczenia w pamięci podręcznej, do tej pory zbadane opcje są dobrym wyborem. Jeśli zadanie zajmuje zbyt dużo czasu, przeglądarka zatrzyma proces. W przeciwnym razie może to stanowić zagrożenie dla prywatności i baterii użytkownika.

Interfejs Background Fetch API umożliwia przeniesienie długiego zadania do workera usługi, np. pobieranie filmów, podcastów lub poziomów gry.

Aby komunikować się ze skryptem service worker ze strony, zamiast postMessage() użyj backgroundFetch.fetch:

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 odbieranie 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 pokazujący, jak strona przekazuje port do usługi workera, aby nawiązać dwukierunkową komunikację.
Interfejs jest aktualizowany, aby wskazywać postęp pobierania (po lewej). Dzięki usługom działającym w tle operacja może być kontynuowana po zamknięciu wszystkich kart (po prawej stronie).

Dalsze kroki

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

Często do komunikacji z innym użytkownikiem wystarczy tylko jeden kontekst, bez konieczności otrzymywania odpowiedzi. Aby dowiedzieć się, jak wdrażać techniki jednokierunkowe na stronach, z których korzystają lub do których prowadzą workery usług, zapoznaj się z tymi przewodnikami, a także z przypadkami użycia i przykładami z produkcji:

  • Przewodnik po pamięci podręcznej imperatywnej: wywołanie skryptu service worker ze strony w celu wcześniejszego zapisania zasobów do pamięci podręcznej (np. w scenariuszach wstępnego pobierania).
  • Wyświetlanie powiadomień: wywołanie strony z użyciem workera usługowego w celu poinformowania o ważnych aktualizacjach (np. o dostępnej nowej wersji aplikacji internetowej).