Em alguns casos, um app da Web pode precisar estabelecer um canal de comunicação dupla 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 faça o download de 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 conhecer as diferentes maneiras de implementar uma comunicação dupla entre o contexto do Window e do service worker analisando diferentes APIs, a biblioteca do Workbox e alguns casos avançados.
Usar o Workbox
workbox-window
é um conjunto de
módulos da biblioteca Workbox que se destinam a ser executados
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 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);
}
});
Internamente, a biblioteca usa uma API de navegador que analisaremos na próxima seção: Canal de mensagens, mas abstrai muitos detalhes de implementação, facilitando o uso e aproveitando o suporte amplo a navegadores que essa API tem.
Como usar as 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 na interface
postMessage()
e é recebida do outro lado, implementando um gerenciadormessage
. - Na prática, todas as APIs disponíveis nos 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 de proxy instanciado em cada lado.
- A compatibilidade com navegadores varia entre eles.
API Broadcast Channel
A API Broadcast Channel permite a comunicação básica entre contextos de navegação por objetos BroadcastChannel.
Para implementá-lo, 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 detecção:
//send message
broadcast.postMessage({ type: 'MSG_ID', });
Qualquer contexto de navegador pode detectar mensagens com o método onmessage
do objeto
BroadcastChannel
:
//listen to messages
broadcast.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process message...
}
};
Como mostrado, não há referência explícita a um contexto específico. Portanto, não é necessário obter primeiro uma referência 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 oferecem suporte.
API do cliente
A API Client permite acessar 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 de volta 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 estão disponíveis. Por isso, verifique o suporte do navegador antes de implementá-la no seu site.
Canal de mensagens
O canal da mensagem requer a definição e a transmissão de uma porta de um contexto a 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 em segundo plano 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 executada ou uma lista de URLs a serem armazenados em cache de um contexto para o outro. Nesta seção, vamos conhecer 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 conexão ruim. A API Background Sync permite adiar ações para que sejam repetidas quando o usuário tiver uma conectividade estável. Isso é útil para garantir que o conteúdo que o usuário quiser 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 de qualquer coisa que esteja
tentando fazer. Se ela for atendida, a sincronização estará concluída. Se falhar, outra sincronização será agendada para
tentar de novo. As novas sincronizações também aguardam a conectividade e empregam uma espera exponencial.
Depois que a operação for executada, o service worker poderá se comunicar de volta com a página para atualizar a IU, usando qualquer uma das APIs de comunicação exploradas anteriormente.
A Pesquisa Google usa a sincronização em segundo plano para manter consultas com falha devido à conectividade ruim e as tenta novamente mais tarde, quando o usuário estiver on-line. Quando a operação é realizada, eles comunicam o resultado ao usuário por meio de uma notificação push da Web:
Busca em segundo plano
Para partes 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 eliminar o service worker. Caso contrário, há um risco à privacidade e à 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 a partir da 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 pode precisar de apenas um contexto para se comunicar com o outro, sem receber uma resposta. Consulte os guias a seguir para saber como implementar técnicas unidirecionais nas páginas de e para o service worker, além de casos de uso e exemplos de produção:
- Guia imperativo de armazenamento em cache: 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: chamada da página do service worker para informar sobre atualizações importantes (por exemplo, uma nova versão do app da Web está disponível).