Veremos alguns padrões comuns de implementação de push na Web.
Isso envolve o uso de algumas APIs diferentes disponíveis no service worker.
Evento de fechamento de notificação
Na última seção, mostramos como detectar eventos notificationclick
.
Há também um evento notificationclose
que é chamado se o usuário dispensa uma das suas
notificações, ou seja, em vez de clicar na notificação, o usuário clica na cruz ou desliza a
notificação para fora.
Esse evento normalmente é usado para que a análise de marketing acompanhe o engajamento do usuário com as notificações.
self.addEventListener('notificationclose', function (event) {
const dismissedNotification = event.notification;
const promiseChain = notificationCloseAnalytics();
event.waitUntil(promiseChain);
});
Como adicionar dados a uma notificação
Quando uma mensagem push é recebida, é comum que os dados sejam úteis apenas se o usuário clicar na notificação. Por exemplo, o URL que será aberto quando uma notificação for clicada.
A maneira mais fácil de coletar dados de um evento push e anexá-los a uma
notificação é adicionar um parâmetro data
ao objeto de opções transmitido
para showNotification()
, assim:
const options = {
body:
'This notification has data attached to it that is printed ' +
"to the console when it's clicked.",
tag: 'data-notification',
data: {
time: new Date(Date.now()).toString(),
message: 'Hello, World!',
},
};
registration.showNotification('Notification with Data', options);
Em um gerenciador de cliques, os dados podem ser acessados com event.notification.data
.
const notificationData = event.notification.data;
console.log('');
console.log('The notification data has the following parameters:');
Object.keys(notificationData).forEach((key) => {
console.log(` ${key}: ${notificationData[key]}`);
});
console.log('');
Abrir uma janela
Uma das respostas mais comuns a uma notificação é abrir uma
janela / guia para um URL específico. Isso pode ser feito com a API
clients.openWindow()
.
No evento notificationclick
, executamos um código como este:
const examplePage = '/demos/notification-examples/example-page.html';
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);
Na próxima seção, veremos como verificar se a página para onde queremos direcionar o usuário já está aberta ou não. Dessa forma, podemos focar a guia aberta em vez de abrir novas guias.
Focar uma janela
Sempre que possível, foque uma janela em vez de abrir uma nova janela sempre que o usuário clicar em uma notificação.
Antes de saber como fazer isso, é importante destacar que isso é possível apenas para páginas na sua origem. Isso ocorre porque só podemos ver quais páginas estão abertas pertencem ao nosso site. Isso impede que os desenvolvedores acessem todos os sites que os usuários estão visualizando.
Usando o exemplo anterior, vamos alterar o código para ver se
/demos/notification-examples/example-page.html
já está aberto.
const urlToOpen = new URL(examplePage, self.location.origin).href;
const promiseChain = clients
.matchAll({
type: 'window',
includeUncontrolled: true,
})
.then((windowClients) => {
let matchingClient = null;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.url === urlToOpen) {
matchingClient = windowClient;
break;
}
}
if (matchingClient) {
return matchingClient.focus();
} else {
return clients.openWindow(urlToOpen);
}
});
event.waitUntil(promiseChain);
Vamos analisar o código.
Primeiro, analisamos nossa página de exemplo usando a API URL. Esse é um truque interessante que aprendi de Jeff
Posnick. Chamar new URL()
com o objeto location
vai
retornar um URL absoluto se a string transmitida for relativa (ou seja, /
vai se tornar
https://example.com/
).
Tornamos o URL absoluto para que possamos fazer a correspondência com os URLs da janela mais tarde.
const urlToOpen = new URL(examplePage, self.location.origin).href;
Em seguida, recebemos uma lista dos objetos WindowClient
, que é a lista das
guias e janelas abertas no momento. Essas são as guias apenas para sua origem.
const promiseChain = clients.matchAll({
type: 'window',
includeUncontrolled: true,
});
As opções transmitidas para matchAll
informam ao navegador que só queremos
procurar clientes do tipo "janela" (ou seja, procurar apenas guias e janelas
e excluir workers da Web). includeUncontrolled
permite pesquisar
todas as guias da sua origem que não são controladas pelo service worker
atual, ou seja, o service worker que executa esse código. Em geral, é
sempre melhor que includeUncontrolled
seja verdadeiro ao chamar matchAll()
.
Capturamos a promessa retornada como promiseChain
para que possamos transmiti-la para
event.waitUntil()
mais tarde, mantendo nosso service worker ativo.
Quando a promessa matchAll()
é resolvida, iteramos os clientes de janela retornados e
comparamos os URLs deles com o URL que queremos abrir. Quando encontramos uma correspondência, focamos o cliente, e isso chama a atenção dos usuários. O foco é feito com a
chamada matchingClient.focus()
.
Se não conseguirmos encontrar um cliente correspondente, abriremos uma nova janela, como na seção anterior.
.then((windowClients) => {
let matchingClient = null;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.url === urlToOpen) {
matchingClient = windowClient;
break;
}
}
if (matchingClient) {
return matchingClient.focus();
} else {
return clients.openWindow(urlToOpen);
}
});
Mesclar notificações
Percebemos que adicionar uma tag a uma notificação ativa um comportamento em que qualquer notificação existente com a mesma tag é substituída.
No entanto, você pode ficar mais sofisticado com o recolhimento de notificações usando a API Notifications. Considere um app de chat em que o desenvolvedor queira que uma nova notificação mostre uma mensagem semelhante a "Você tem duas mensagens de Matt" em vez de mostrar apenas a mensagem mais recente.
Você pode fazer isso ou manipular as notificações atuais de outras maneiras usando a API registration.getNotifications(), que dá acesso a todas as notificações visíveis do seu app da Web.
Vamos conferir como usar essa API para implementar o exemplo de chat.
No nosso app de chat, vamos supor que cada notificação tenha alguns dados, incluindo um nome de usuário.
A primeira coisa que queremos fazer é encontrar quaisquer notificações abertas para um usuário com um nome de usuário específico. Vamos receber registration.getNotifications()
, fazer um loop sobre eles e verificar o
notification.data
para um nome de usuário específico:
const promiseChain = registration.getNotifications().then((notifications) => {
let currentNotification;
for (let i = 0; i < notifications.length; i++) {
if (notifications[i].data && notifications[i].data.userName === userName) {
currentNotification = notifications[i];
}
}
return currentNotification;
});
A próxima etapa é substituir essa notificação por uma nova.
Neste app de mensagens falsas, vamos acompanhar o número de novas mensagens adicionando uma contagem aos dados da nova notificação e incrementá-la a cada nova notificação.
.then((currentNotification) => {
let notificationTitle;
const options = {
icon: userIcon,
}
if (currentNotification) {
// We have an open notification, let's do something with it.
const messageCount = currentNotification.data.newMessageCount + 1;
options.body = `You have ${messageCount} new messages from ${userName}.`;
options.data = {
userName: userName,
newMessageCount: messageCount
};
notificationTitle = `New Messages from ${userName}`;
// Remember to close the old notification.
currentNotification.close();
} else {
options.body = `"${userMessage}"`;
options.data = {
userName: userName,
newMessageCount: 1
};
notificationTitle = `New Message from ${userName}`;
}
return registration.showNotification(
notificationTitle,
options
);
});
Se houver uma notificação em exibição no momento, incrementamos a contagem de mensagens e definimos o título e o corpo da mensagem de acordo. Se não
houver notificações, criaremos uma nova notificação com um newMessageCount
de 1.
O resultado é que a primeira mensagem fica assim:
Uma segunda notificação reduziria as notificações da seguinte forma:
O bom dessa abordagem é que, se o usuário testemunhar as notificações aparecendo uma sobre a outra, elas terão uma aparência mais coesa do que apenas substituir a notificação pela mensagem mais recente.
A exceção à regra
Eu tenho dito que você precisa mostrar uma notificação ao receber um push, e isso é verdade na maioria das vezes. O único cenário em que você não precisa mostrar uma notificação é quando o usuário está com seu site aberto e focado.
No seu evento push, é possível verificar se você precisa mostrar uma notificação ou não analisando os clientes de janela e procurando uma janela em foco.
O código para buscar todas as janelas e procurar uma janela em foco tem esta aparência:
function isClientFocused() {
return clients
.matchAll({
type: 'window',
includeUncontrolled: true,
})
.then((windowClients) => {
let clientIsFocused = false;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.focused) {
clientIsFocused = true;
break;
}
}
return clientIsFocused;
});
}
Usamos clients.matchAll()
para receber todos os clientes de janela e, em seguida, os percorremos verificando o parâmetro focused
.
No evento push, usamos essa função para decidir se precisamos mostrar uma notificação:
const promiseChain = isClientFocused().then((clientIsFocused) => {
if (clientIsFocused) {
console.log("Don't need to show a notification.");
return;
}
// Client isn't focused, we need to show a notification.
return self.registration.showNotification('Had to show a notification.');
});
event.waitUntil(promiseChain);
Enviar mensagens para uma página usando um evento push
Notamos que é possível pular a exibição de uma notificação se o usuário estiver no seu site. Mas e se você ainda quiser informar ao usuário que um evento ocorreu, mas uma notificação for muito pesada?
Uma abordagem é enviar uma mensagem do service worker para a página, dessa maneira, a página da Web pode mostrar uma notificação ou atualização para o usuário, informando sobre o evento. Isso é útil para situações em que uma notificação sutil na página é melhor e mais amigável para o usuário.
Digamos que recebemos um envio, verificamos se nosso app da Web está em foco no momento e, em seguida, podemos "postar uma mensagem" em cada página aberta, assim:
const promiseChain = isClientFocused().then((clientIsFocused) => {
if (clientIsFocused) {
windowClients.forEach((windowClient) => {
windowClient.postMessage({
message: 'Received a push message.',
time: new Date().toString(),
});
});
} else {
return self.registration.showNotification('No focused windows', {
body: 'Had to show a notification instead of messaging each page.',
});
}
});
event.waitUntil(promiseChain);
Em cada uma das páginas, detectamos mensagens adicionando um listener de evento de mensagem:
navigator.serviceWorker.addEventListener('message', function (event) {
console.log('Received a message from service worker: ', event.data);
});
Nesse listener de mensagem, você pode fazer o que quiser, mostrar uma IU personalizada na página ou ignorar completamente a mensagem.
Também é importante observar que, se você não definir um listener de mensagens na sua página da Web, as mensagens do service worker não vão fazer nada.
Armazenar uma página em cache e abrir uma janela
Um cenário que está fora do escopo deste guia, mas que vale a pena discutir, é que você pode melhorar a UX geral do seu app da Web armazenando em cache as páginas da Web que você espera que os usuários acessem depois de clicar na notificação.
Para isso, é necessário configurar o worker do serviço para processar eventos fetch
.
No entanto, se você implementar um listener de evento fetch
, aproveite
essa configuração no evento push
armazenando em cache a página e os recursos
necessários antes de mostrar a notificação.
Compatibilidade com navegadores
O evento notificationclose
Clients.openWindow()
ServiceWorkerRegistration.getNotifications()
clients.matchAll()
Para mais informações, confira este post de introdução aos service workers.
A seguir
- Visão geral das notificações push na Web
- Como o push funciona
- Como inscrever um usuário
- UX de permissão
- Como enviar mensagens com bibliotecas push da Web
- Protocolo push da Web
- Processamento de eventos push
- Como exibir uma notificação
- Comportamento da notificação
- Padrões de notificação comuns
- Perguntas frequentes sobre notificações push
- Problemas comuns e como informar bugs