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".
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:
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:
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 todas as vezes que uma nova versão do service worker é instalada e 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 uma broadcastUpdate.BroadcastUpdatePlugin
às 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 do cliente oferece uma maneira
direta de se comunicar com vários clientes do service worker por meio da iteração 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 essas mensagens:
// Listen to messages
navigator.serviceWorker.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
// Process response
}
};
A API Client é uma ótima opção para casos como a transmissão de 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 por meio da 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:
- 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é-carregamento).
- Comunicação bidirecional: delegar uma tarefa a um worker de serviço (por exemplo, um download pesado) e manter a página informada sobre o progresso.