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

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

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

  • Informar à página quando uma nova versão do service worker foi instalada, para que ela mostre um botão "Update to refresh" para o usuário acessar a nova funcionalidade imediatamente.
  • Informar o usuário sobre uma mudança nos dados em cache que ocorreu no lado do worker do serviço, mostrando uma indicação, como: "O app está pronto para funcionar off-line" ou "Nova versão do conteúdo disponível".
Diagrama mostrando um worker de serviço se comunicando com a página para enviar uma atualização.

Chamaremos 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 "transmissão de atualizações". Neste guia, vamos analisar diferentes maneiras de implementar esse tipo de comunicação entre páginas e service workers usando APIs padrão do navegador e a biblioteca Workbox.

Casos de produção

Tinder

A PWA do Tinder usa workbox-window para detectar momentos importantes do ciclo de vida do service worker na página ("installed", "controlled" e "activated"). Dessa forma, quando um novo service worker entra em ação, ele mostra um banner "Update Available" para que o usuário possa atualizar a PWA e acessar os recursos mais recentes:

Captura de tela da funcionalidade "Update Available" da WebApp do Tinder.
No PWA do Tinder, o service worker informa à página que uma nova versão está pronta e a página mostra aos usuários um banner "Update Available".

Squoosh

No Squoosh PWA, quando o service worker armazena em cache todos os recursos necessários para funcionar off-line, ele envia uma mensagem para a página para mostrar uma mensagem pop-up "Pronto para funcionar off-line", informando o usuário sobre o recurso:

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

Como usar o Workbox

Ouvir eventos do ciclo de vida do service worker

O workbox-window oferece uma interface simples para detectar eventos importantes do ciclo de vida do service worker. Por trás dos bastidores, a biblioteca usa APIs do lado do cliente, como updatefound e statechange, e fornece listeners de eventos de nível mais alto no objeto workbox-window, facilitando o uso desses eventos pelo usuário.

O código da página a seguir permite detectar sempre 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 mudanças nos dados do cache

O pacote Workbox workbox-broadcast-update oferece uma maneira padrão de notificar clientes de janela de que uma resposta em cache foi atualizada. Esse método é usado com mais frequência com a estratégia StaleWhileRevalidate.

Para transmitir atualizações, adicione um broadcastUpdate.BroadcastUpdatePlugin às suas opções de estratégia no lado do 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 assim:

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 fornecida pelo Workbox não for suficiente para suas necessidades, use as seguintes APIs do navegador para implementar "broadcast updates":

API Broadcast Channel

O worker do serviço 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 de mensagens para receber mensagens.

Para informar à 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 no 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.
  }
};

Essa é uma técnica simples, mas a limitação dela é o suporte do navegador: no momento em que este artigo foi escrito, o Safari não oferecia suporte a essa API.

API do cliente

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

Use o seguinte código de service worker para enviar uma mensagem à ú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 de mensagens 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 transmitir informações para várias guias ativas. A API tem suporte de todos os principais navegadores, mas não de todos os métodos. Verifique o suporte do navegador antes de usá-lo.

Canal de mensagens

O canal de mensagens exige uma etapa de configuração inicial, transmitindo uma porta da página para o worker do serviço, 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 ouve as mensagens implementando um gerenciador "onmessage" nessa porta:

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

O worker do serviço 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 pode enviar mensagens para a página chamando postMessage() na referência à porta:

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

MessageChannel pode ser mais complexo de implementar devido à necessidade de inicializar portas, mas ele é compatível com todos os principais navegadores.

Próximas etapas

Neste guia, abordamos um caso específico de comunicação entre a janela e o worker do serviço: "broadcast updates". Os exemplos analisados incluem a detecção de eventos importantes do ciclo de vida do service worker e a comunicação com a página sobre mudanças no conteúdo ou nos dados armazenados em cache. Você pode pensar 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 entre o Window e o service worker, confira:

Outros recursos