Em alguns casos, 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 for instalada para que ela possa mostrar um botão "Atualizar para atualizar" ao usuário e permitir o acesso imediato à nova funcionalidade.
- Informar ao usuário sobre uma mudança nos dados armazenados em cache que ocorreu no lado do service worker, mostrando uma indicação, como: "O app agora está pronto para funcionar off-line" ou "Nova versão do conteúdo disponível".
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 padrão do navegador e a biblioteca Workbox.
Casos de produção
Tinder
O PWA do Tinder usa workbox-window para detectar
momentos importantes do ciclo de vida do service worker na página ("instalado", "controlado" e
"ativado"). Assim, quando um novo service worker entra em ação, ele mostra um banner "Atualização disponível"
para que o usuário possa atualizar o PWA e acessar os recursos mais recentes:
Squoosh
No PWA Squoosh, quando o service worker armazena em cache todos os recursos necessários para funcionar off-line, ele envia uma mensagem à página para mostrar um toast "Pronto para trabalhar off-line" , informando o usuário sobre o recurso:
Como usar o Workbox
Detectar eventos de ciclo de vida do service worker
O workbox-window oferece 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 mais alto no objeto workbox-window, facilitando o consumo 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 comunicar isso 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 em cache
O pacote Workbox
workbox-broadcast-update
oferece uma maneira padrão de notificar os clientes da janela de que uma resposta em cache foi atualizada. Essa estratégia é
mais usada com a StaleWhileRevalidate.
Para transmitir atualizações, adicione um 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 web app, você pode detectar esses eventos da seguinte maneira:
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 "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 manipulador de mensagens para receber mensagens.
Para informar a página quando um novo service worker é 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 é o suporte do navegador: no momento da redação deste artigo, o Safari não oferece suporte a essa API.
API do cliente
A API Client oferece uma maneira simples de se comunicar com vários clientes do service worker ao iterar em uma matriz de objetos Client.
Use o seguinte código do 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 é compatível com todos os principais navegadores, mas nem todos os métodos dela são. Verifique a compatibilidade do navegador antes de usar.
Canal de mensagens
O canal de mensagens exige
uma etapa de configuração inicial, transmitindo uma porta 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 usando a interface postMessage():
const messageChannel = new MessageChannel();
// Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
messageChannel.port2,
]);
A página detecta mensagens implementando um manipulador "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 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 é compatível com todos os principais navegadores.
Próximas etapas
Neste guia, exploramos um caso específico de comunicação da janela com o service worker: "atualizações de transmissão". Os exemplos abordados 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 antes.
Para mais padrões de comunicação entre janela e 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é-busca).
- Comunicação bidirecional: delegar uma tarefa a um service worker (por exemplo, um download pesado) e manter a página informada sobre o progresso.