Transmitir atualizações para páginas com os service workers

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

Em alguns cenários, o service worker pode precisar se comunicar proativamente com qualquer uma das guias ativas que ele controla para informar sobre um determinado evento. Por exemplo:

  • Informar a página quando uma nova versão do service worker tiver sido instalada, para que a página possa mostrar um botão "Atualizar para atualizar" ao usuário e acessar a nova funcionalidade imediatamente.
  • Informar o usuário sobre uma mudança nos dados em cache que ocorreu no lado do service worker, mostrando uma indicação, como: "O app agora está pronto para trabalhar off-line" ou "Nova versão do conteúdo disponível".
Diagrama mostrando um service worker se comunicando com a página para enviar uma atualização.

Vamos chamar esses tipos de casos de uso em que o service worker não precisa receber uma mensagem da página para iniciar uma comunicação de "atualizações de transmissão". Neste guia, vamos analisar diferentes maneiras de implementar esse tipo de comunicação entre páginas e service workers usando APIs de navegador padrão e a biblioteca Workbox.

Casos de produção

Tinder

O Tinder PWA usa workbox-window para detectar momentos importantes do ciclo de vida do service worker da página ("instalado", "controlado" e "ativado"). Dessa forma, quando um novo service worker entrar em ação, ele mostrará um banner "Atualização disponível" para que ele possa atualizar o PWA e acessar os recursos mais recentes:

Uma captura de tela da funcionalidade "Atualização disponível" do app da Web do Tinder.
No Tinder PWA, o service worker informa à página que uma nova versão está pronta, e a página mostra aos usuários um banner "Atualização disponível".

Squoosh

No Squoosh PWA, quando o service worker armazena em cache todos os recursos necessários para fazê-lo funcionar off-line, ele envia uma mensagem para a página mostrando um aviso "Pronto para trabalhar off-line", informando ao usuário sobre o recurso:

Uma captura de tela da funcionalidade "Pronto para trabalhar off-line" do Squoosh webapp.
No Squoosh PWA, o service worker transmite uma atualização na página quando o cache está pronto, e a página exibe o aviso "Pronto para trabalhar off-line".

Usar o Workbox

Detectar eventos de ciclo de vida do service worker

workbox-window fornece uma interface simples para detectar eventos importantes do ciclo de vida do service worker. Internamente, a biblioteca usa APIs do lado do cliente, como updatefound e statechange e fornece listeners de eventos de nível superior no objeto workbox-window, facilitando o consumo desses eventos pelo usuário.

O código da página a seguir permite detectar todas as vezes que uma nova versão do service worker é instalada, para que você possa comunicá-la ao usuário:

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

wb.addEventListener('installed', (event) => {
  if (event.isUpdate) {
    // Show "Update App" banner
  }
});

wb.register();

Informar a página sobre alterações nos dados em cache

O pacote Workbox workbox-broadcast-update oferece uma maneira padrão de notificar os clientes da janela que uma resposta armazenada em cache foi atualizada. Ela é mais usada com a estratégia StalewhileRevalidate.

Para transmitir atualizações, adicione um broadcastUpdate.BroadcastUpdatePlugin às suas opções de estratégia no service worker:

import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';

registerRoute(
  ({url}) => url.pathname.startsWith('/api/'),
  new StaleWhileRevalidate({
    plugins: [
      new BroadcastUpdatePlugin(),
    ],
  })
);

No seu app da Web, é possível detectar esses eventos, como:

navigator.serviceWorker.addEventListener('message', async (event) => {
  // Optional: ensure the message came from workbox-broadcast-update
  if (event.data.meta === 'workbox-broadcast-update') {
    const {cacheName, updatedUrl} = event.data.payload;

    // Do something with cacheName and updatedUrl.
    // For example, get the cached content and update
    // the content on the page.
    const cache = await caches.open(cacheName);
    const updatedResponse = await cache.match(updatedUrl);
    const updatedText = await updatedResponse.text();
  }
});

Como usar APIs do navegador

Se a funcionalidade oferecida pelo Workbox não for suficiente para suas necessidades, use as seguintes APIs de navegador para implementar "atualizações de transmissão":

API Broadcast Channel

O service worker cria um objeto BroadcastChannel e começa a enviar mensagens para ele. Qualquer contexto (por exemplo, página) interessado em receber essas mensagens pode instanciar um objeto BroadcastChannel e implementar um gerenciador para receber mensagens.

Para informar a página quando um novo service worker for instalado, use o seguinte código:

// Create Broadcast Channel to send messages to the page
const broadcast = new BroadcastChannel('sw-update-channel');

self.addEventListener('install', function (event) {
  // Inform the page every time a new service worker is installed
  broadcast.postMessage({type: 'CRITICAL_SW_UPDATE'});
});

A página detecta esses eventos ao se inscrever em sw-update-channel:

// Create Broadcast Channel and listen to messages sent to it
const broadcast = new BroadcastChannel('sw-update-channel');

broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'CRITICAL_SW_UPDATE') {
    // Show "update to refresh" banner to the user.
  }
};

Esta é uma técnica simples, mas a limitação é a compatibilidade com o navegador. No momento, o Safari não é compatível com essa API.

API do cliente

A API Client oferece uma maneira direta de se comunicar com vários clientes do service worker iterando uma matriz de objetos Client.

Use o código do service worker a seguir para enviar uma mensagem para a última guia em foco:

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

A página implementa um gerenciador para interceptar estas mensagens:

// Listen to messages
navigator.serviceWorker.onmessage = (event) => {
     if (event.data && event.data.type === 'MSG_ID') {
         // Process response
   }
};

A API do cliente é uma ótima opção para casos como transmissão de informações para várias guias ativas. A API é compatível com todos os principais navegadores, mas nem todos os métodos dela são. Verifique a compatibilidade com navegadores antes de usá-lo.

Canal de mensagens

O Message Channel requer uma etapa inicial de configuração, passando uma porta da página da página para o service worker, para estabelecer um canal de comunicação entre eles. A página instancia um objeto MessageChannel e transmite uma porta para o service worker pela interface postMessage():

const messageChannel = new MessageChannel();

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

A página detecta mensagens implementando um gerenciador "onmessage" nessa porta:

// Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};

O service worker recebe a porta e salva uma referência a ela:

// Initialize
let communicationPort;

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});

A partir desse ponto, ele poderá enviar mensagens para a página chamando postMessage() na referência à porta:

// Communicate
communicationPort.postMessage({type: 'MSG_ID' });

O MessageChannel pode ser mais complexo de implementar, devido à necessidade de inicializar portas, mas tem suporte de todos os principais navegadores.

Próximas etapas

Neste guia, exploramos um caso específico de comunicação entre janelas e service worker: "atualizações de transmissão". Os exemplos explorados incluem a detecção de eventos importantes do ciclo de vida do service worker e a comunicação com a página sobre alterações no conteúdo ou nos dados armazenados em cache. Pense em casos de uso mais interessantes, em que o service worker se comunica proativamente com a página, sem receber nenhuma mensagem anteriormente.

Para mais padrões de comunicação do Window e do service worker, confira:

Outros recursos