Comunicação bidirecional com service workers

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

Em alguns casos, um app da Web pode precisar estabelecer um canal de comunicação bidirecional entre as e o service worker.

Por exemplo: em um PWA de podcast, é possível criar um recurso para permitir que o usuário faça o download de episódios do consumo off-line e permitir que service worker para manter a página regularmente informada sobre o progresso, de modo que os recursos linha de execução podem atualizar a interface.

Neste guia, exploraremos as diferentes maneiras de implementar uma comunicação bidirecional entre as opções Window e service profissional, analisando diferentes APIs, a biblioteca Workbox, bem como alguns casos avançados.

Diagrama mostrando um service worker e a página trocando mensagens.

Como usar o Workbox

workbox-window é um conjunto de da biblioteca Workbox que se destinam seja executado no contexto da janela. O Workbox A classe fornece um método messageSW() para enviar uma mensagem ao service worker registrado da instância e aguardam uma resposta.

O código da página a seguir cria uma nova instância Workbox e envia uma mensagem para o service worker para obter sua versão:

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

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

O service worker implementa um listener de mensagens na outra extremidade e responde ao service worker:

const SW_VERSION = '1.0.0';

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

Internamente, a biblioteca usa uma API de navegador que analisaremos na próxima seção: Mensagem canal, mas abstrai muitos detalhes de implementação, facilitando o uso e aproveitando a amplitude do navegador suporte dessa API.

Diagrama mostrando a comunicação bidirecional entre a página e o service worker, usando a janela da caixa de trabalho.

Como usar as APIs do navegador

Se a biblioteca Workbox não for suficiente para suas necessidades, há várias APIs de nível inferior disponíveis para implementar a comunicação bidirecional entre páginas e service workers. Eles têm algumas semelhanças e diferenças:

Semelhanças:

  • Em todos os casos, a comunicação começa em uma extremidade pela interface postMessage() e é recebida por outro lado, implementando um gerenciador message.
  • Na prática, todas as APIs disponíveis nos permitem implementar os mesmos casos de uso, mas algumas delas pode simplificar o desenvolvimento em alguns cenários.

Diferenças:

  • Eles têm maneiras diferentes de identificar o outro lado da comunicação: alguns usam um referência explícita ao outro contexto, enquanto outros podem se comunicar implicitamente por meio de um proxy objeto instanciado em cada lado.
  • O suporte ao navegador varia entre eles.
Diagrama mostrando a comunicação bidirecional entre a página e o service worker, além das APIs de navegador disponíveis.

API Broadcast Channel

Compatibilidade com navegadores

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

Origem

A API Broadcast Channel permite a comunicação básica entre contextos de navegação via BroadcastChannel objetos.

Para implementá-lo, primeiro, cada contexto precisa instanciar um objeto BroadcastChannel com o mesmo ID e enviar e receber mensagens dele:

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

O objeto BroadcastChannel expõe uma interface postMessage() para enviar uma mensagem a qualquer pessoa que ouve contexto:

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

Qualquer contexto de navegador pode ouvir mensagens pelo método onmessage da BroadcastChannel objeto:

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

Como visto, não há referência explícita a um contexto específico, portanto, não é necessário obter um referência primeiro ao service worker ou a qualquer cliente em particular.

Diagrama mostrando comunicação bidirecional entre a página e o service worker, usando um objeto de canal de transmissão.

A desvantagem é que, no momento em que este artigo foi escrito, a API é compatível com Chrome, Firefox e o Edge, mas outros navegadores, como o Safari, não são compatíveis com esse recurso. ainda.

API do cliente

Compatibilidade com navegadores

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

Origem

Com a API do cliente, é possível referência a todos os objetos WindowClient que representam as guias ativas que o service worker está controlando.

Como a página é controlada por um único service worker, ela detecta e envia mensagens para o service worker ativo diretamente por meio da interface 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
  }
};

Da mesma forma, o service worker detecta mensagens implementando um listener onmessage:

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

Para se comunicar de volta com qualquer um de seus clientes, o service worker obtém uma matriz de Objetos WindowClient executando métodos como Clients.matchAll() e Clients.get(). Assim, é possível postMessage() qualquer um deles:

//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'});
  }
});
Diagrama mostrando um service worker se comunicando com uma matriz de clientes.

O Client API é uma boa opção para se comunicar facilmente com todas as guias ativas de um service worker de forma relativamente simples. A API é compatível com todas as principais navegadores da Web, mas talvez nem todos os métodos estejam disponíveis. Portanto, verifique o suporte do navegador antes implementá-las em seu site.

Canal de mensagens

Compatibilidade com navegadores

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

Origem

O canal de mensagem exige definir e transmitir uma porta de um contexto a outro para estabelecer uma comunicação bidirecional. canal.

Para inicializar o canal, a página instancia e usa um objeto MessageChannel para enviar uma porta ao service worker registrado. A página também implementa um listener onmessage em para receber mensagens do outro contexto:

const messageChannel = new MessageChannel();

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

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
Diagrama mostrando uma página passando uma porta para um service worker para estabelecer uma comunicação bidirecional.

O service worker recebe a porta, salva uma referência a ela e a usa para enviar uma mensagem para o outro lado:

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'});

Atualmente, o MessageChannel é suportado por todas as principais navegadores da Web.

APIs avançadas: sincronização e busca em segundo plano

Neste guia, exploramos maneiras de implementar técnicas de comunicação bidirecional, para relativamente casos simples, como passar uma mensagem de string descrevendo a operação a ser executada ou uma lista de URLs armazenar em cache de um contexto para outro. Nesta seção, vamos conhecer duas APIs para lidar com para estes cenários: falta de conectividade e downloads longos.

Sincronização em segundo plano

Compatibilidade com navegadores

  • Chrome: 49.
  • Borda: 79.
  • Firefox: incompatível.
  • Safari: incompatível.

Origem

Um app de chat pode querer garantir que as mensagens nunca sejam perdidas devido a uma conectividade ruim. O A API Background Sync permite adiar ações para que sejam repetidas quando o usuário tiver conectividade estável. Isso é útil para garantir que tudo o que o usuário quer enviar é de fato enviado.

Em vez da interface postMessage(), a página registra um sync:

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

Em seguida, o service worker detecta o evento sync para processar a mensagem:

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

A função doSomeStuff() precisa retornar uma promessa indicando o sucesso/fracasso de tudo o que tentando fazer. Em caso afirmativo, a sincronização estará concluída. Se ela falhar, outra sincronização será agendada para tentar de novo. Novas sincronizações também aguardam a conectividade e empregam uma espera exponencial.

Depois que a operação for executada, o service worker poderá se comunicar de volta com a página para atualizar a IU usando qualquer uma das APIs de comunicação exploradas anteriormente.

A Pesquisa Google usa a sincronização em segundo plano para manter consultas com falha devido a conectividade ruim e tente novamente. mais tarde, quando o usuário estiver on-line. Depois que a operação é realizada, eles comunicam o resultado para o usuário por meio de uma notificação push da Web:

Diagrama mostrando uma página passando uma porta para um service worker para estabelecer uma comunicação bidirecional.

Busca em segundo plano

Compatibilidade com navegadores

  • Chrome: 74.
  • Borda: 79.
  • Firefox: incompatível.
  • Safari: incompatível.

Origem

Para trabalhos relativamente curtos, como enviar uma mensagem ou uma lista de URLs para armazenar em cache, as opções exploradas até agora são uma boa escolha. Se a tarefa demorar muito, o navegador encerrará o serviço do dispositivo, porque isso seria um risco para a privacidade e a bateria do usuário.

A API Background Fetch permite que você descarregue uma tarefa longa para um service worker, como o download de filmes, podcasts ou níveis de um jogo.

Para se comunicar com o service worker pela página, use backgroundFetch.fetch, em vez de 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,
    },
  );
});

O objeto BackgroundFetchRegistration permite que a página detecte o evento progress para seguir o andamento do download:

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}%`);
});
Diagrama mostrando uma página passando uma porta para um service worker para estabelecer uma comunicação bidirecional.
A interface é atualizada para indicar o progresso de um download (à esquerda). Graças aos service workers, a operação pode continuar em execução quando todas as guias forem fechadas (à direita).
.

Próximas etapas

Neste guia, exploramos o caso mais geral de comunicação entre os page workers e os service workers. (comunicação bidirecional).

Muitas vezes, pode ser necessário apenas um contexto para se comunicar com o outro, sem receber uma resposta. Confira os guias a seguir para saber como implementar técnicas unidirecionais em suas páginas de e para o service worker, junto com casos de uso e exemplos de produção:

  • Guia de armazenamento em cache imperativo: chamar um service worker da página para armazenar recursos em cache com antecedência (por exemplo, em cenários de pré-busca);
  • Transmitir atualizações: chamar a página do service worker para informar sobre atualizações importantes (por exemplo, uma nova versão do webapp está disponível).