Em alguns casos, um app da Web pode precisar estabelecer um canal de comunicação bidirecional entre a página e o service worker.
Por exemplo, em um PWA de podcast, é possível criar um recurso para permitir que o usuário baixe episódios para consumo off-line e permitir que o service worker mantenha a página regularmente informada sobre o progresso, para que a thread principal possa atualizar a interface.
Neste guia, vamos explorar as diferentes maneiras de implementar uma comunicação bidirecional entre o contexto Window e service worker, explorando diferentes APIs, a biblioteca Workbox e alguns casos avançados.

Como usar o Workbox
workbox-window
é um conjunto de módulos da biblioteca Workbox (link em inglês) que são destinados a serem executados no contexto da janela. A classe Workbox
oferece um método messageSW()
para enviar uma mensagem ao service worker registrado da instância e
aguardar uma resposta.
O código da página a seguir cria uma nova instância Workbox
e envia uma mensagem ao service worker
para receber a versão dele:
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 registrado:
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
Por baixo dos panos, a biblioteca usa uma API do navegador que vamos analisar na próxima seção: MessageChannel. No entanto, ela abstrai muitos detalhes de implementação, facilitando o uso e aproveitando o amplo suporte do navegador que essa API oferece.

Como usar 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. Elas 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 na outra extremidade implementando um manipuladormessage
. - Na prática, todas as APIs disponíveis permitem implementar os mesmos casos de uso, mas algumas delas podem simplificar o desenvolvimento em alguns cenários.
Diferenças:
- Eles têm maneiras diferentes de identificar o outro lado da comunicação: alguns usam uma referência explícita ao outro contexto, enquanto outros podem se comunicar implicitamente por um objeto proxy instanciado em cada lado.
- O suporte a navegadores varia entre eles.

API Broadcast Channel
A API Broadcast Channel permite a comunicação básica entre contextos de navegação usando objetos BroadcastChannel.
Para implementar, 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 contexto
de escuta:
//send message
broadcast.postMessage({ type: 'MSG_ID', });
Qualquer contexto de navegador pode detectar mensagens usando o método onmessage
do objeto BroadcastChannel
:
//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 uma referência primeiro ao service worker ou a um cliente específico.

A desvantagem é que, no momento da redação deste artigo, a API tem suporte do Chrome, Firefox e Edge, mas outros navegadores, como o Safari, ainda não são compatíveis com ela.
API do cliente
A API Client permite obter uma
referência a todos os objetos WindowClient
que representam as guias ativas controladas pelo service worker.
Como a página é controlada por um único service worker, ela ouve e envia mensagens diretamente para o service worker ativo pela 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 com qualquer um dos clientes, o service worker recebe uma matriz de objetos
WindowClient
executando
métodos como
Clients.matchAll()
e
Clients.get()
. Em seguida, ele pode postMessage()
qualquer uma delas:
//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'});
}
});

Client API
é uma boa opção para se comunicar facilmente com todas as guias ativas de um service worker
de maneira relativamente simples. A API é compatível com todos os principais navegadores, mas nem todos os métodos podem estar disponíveis. Portanto, verifique a compatibilidade do navegador antes de implementar no seu site.
Canal de mensagens
O MessageChannel exige a definição e a transmissão de uma porta de um contexto para outro para estabelecer um canal de comunicação bidirecional.
Para inicializar o canal, a página instancia um objeto MessageChannel
e o usa
para enviar uma porta ao service worker registrado. A página também implementa um listener onmessage
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
};

O service worker recebe a porta, salva uma referência a ela e a usa para enviar uma mensagem ao 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'});
No momento, o MessageChannel
é compatível com todos os principais navegadores.
APIs avançadas: sincronização e busca em segundo plano
Neste guia, exploramos maneiras de implementar técnicas de comunicação bidirecional para casos relativamente simples, como transmitir uma mensagem de string descrevendo a operação a ser realizada ou uma lista de URLs a serem armazenados em cache de um contexto para outro. Nesta seção, vamos explorar duas APIs para lidar com cenários específicos: falta de conectividade e downloads longos.
Sincronização em segundo plano
Um app de chat pode querer garantir que as mensagens nunca sejam perdidas devido a uma conectividade ruim. Com a API Background Sync, é possível adiar ações para serem repetidas quando o usuário tiver uma conexão estável. Isso é útil para garantir que o que o usuário quer enviar seja realmente 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/falha do que ela
está tentando fazer. Se ela for cumprida, a sincronização será concluída. Se isso falhar, outra sincronização será programada para
tentar novamente. As novas tentativas de sincronização também aguardam a conectividade e usam uma espera exponencial.
Depois que a operação for realizada, o service worker poderá se comunicar novamente com a página para atualizar a interface usando qualquer uma das APIs de comunicação abordadas anteriormente.
A Pesquisa Google usa a sincronização em segundo plano para manter as consultas com falha devido à conectividade ruim e tentar de novo mais tarde, quando o usuário estiver on-line. Depois que a operação é realizada, eles comunicam o resultado ao usuário por uma notificação push da Web:

Busca em segundo plano
Para tarefas relativamente curtas, 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 vai encerrar o service worker. Caso contrário, isso representa um risco para a privacidade e a bateria do usuário.
A API Background Fetch permite transferir 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 na 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 acompanhar
o progresso 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}%`);
});

Próximas etapas
Neste guia, exploramos o caso mais geral de comunicação entre a página e os service workers (comunicação bidirecional).
Muitas vezes, é 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 nas páginas do e para o service worker, além de casos de uso e exemplos de produção:
- Guia de 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).
- Atualizações de transmissão: chamar a página do service worker para informar sobre atualizações importantes (por exemplo, uma nova versão do web app está disponível).