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 que o usuário baixe episódios para consumo off-line e permitir que o service worker mantenha a página informada regularmente sobre o progresso, para que a linha de execução principal possa atualizar a interface.
Neste guia, vamos analisar as diferentes maneiras de implementar uma comunicação bidirecional entre o contexto de janela e o service worker, analisando diferentes APIs, a biblioteca do Workbox e alguns casos avançados.
Como usar o Workbox
workbox-window é um conjunto de
módulos da biblioteca do Workbox (link em inglês) que são destinados
à execução no contexto da janela. A classe Workbox
fornece um método messageSW() para enviar uma mensagem ao service worker registrado da instância e
aguardar uma resposta.
O código de 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);
}
});
Nos bastidores, a biblioteca usa uma API de navegador que vamos analisar na próxima seção: Message Channel, mas abstrai muitos detalhes de implementação, facilitando o uso e aproveitando o amplo suporte do navegador que essa API tem.
Como usar APIs do navegador
Se a biblioteca do 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. Eles 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 pela implementação de um gerenciadormessage. - 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 do navegador 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 isso, 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 você pode ver, não há referência explícita a um contexto específico, então não é necessário obter uma referência primeiro ao service worker ou a qualquer cliente específico.
A desvantagem é que, no momento, a API é compatível com o Chrome, o Firefox e o Edge, mas outros navegadores, como o Safari, ainda não são compatíveis.
API do cliente
A API Client permite que você receba uma
referência a todos os objetos WindowClient que representam as guias ativas que o service worker está controlando.
Como a página é controlada por um único service worker, ela detecta e envia mensagens para o
service worker ativo diretamente 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 um deles:
//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 dela estão disponíveis. Por isso, verifique a compatibilidade do navegador antes
de implementá-la no seu site.
Canal de mensagens
O Message Channel 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 para o 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, mostramos como implementar técnicas de comunicação bidirecional para casos relativamente simples, como transmitir uma mensagem de string que descreve a operação a ser realizada ou uma lista de URLs para armazenar em cache de um contexto para o outro. Nesta seção, vamos analisar 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 à conectividade ruim. A API Background Sync permite 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 ou a falha do que ela está
tentando fazer. Se ela for cumprida, a sincronização será concluída. Se houver falha, outra sincronização será programada para
tentar novamente. As sincronizações de nova tentativa também aguardam a conectividade e usam uma espera exponencial.
Depois que a operação é realizada, o service worker pode se comunicar de volta com a página para atualizar a interface, usando qualquer uma das APIs de comunicação mostradas anteriormente.
A Pesquisa Google usa a sincronização em segundo plano para manter as consultas que falharam devido à conectividade ruim e tentar novamente 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 armazenamento em cache, as opções analisadas até agora são uma boa escolha. Se a tarefa demorar muito, o navegador vai encerrar o service worker. Caso contrário, isso será um risco para a privacidade e a bateria do usuário.
A API Background Fetch permite que você transfira 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, um contexto só precisa se comunicar com o outro, sem receber uma resposta. Confira os guias a seguir para saber como implementar técnicas unidirecionais nas suas páginas de e para o service worker, além de casos de uso e exemplos de produção:
- 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).
- 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 app da Web está disponível).