Vamos analisar alguns padrões de implementação comuns para 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
.
Também há um evento notificationclose
que é chamado se o usuário dispensar uma das
notificações. Em vez de clicar na notificação, o usuário clica na cruz ou desliza a
notificação.
Esse evento normalmente é usado para que a análise de marketing rastreie 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, vamos conferir como verificar se a página para a qual 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, vale ressaltar que isso é apenas possível para páginas na sua origem. Isso acontece porque só podemos ver as páginas abertas que 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 saber 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 legal que aprendi com 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 pelos clientes de janela retornados e
comparamos os URLs deles com o URL que queremos abrir. Se encontrarmos uma correspondência, vamos focar esse
cliente, o que vai chamar a atenção dos usuários para essa janela. O foco é feito com a
chamada matchingClient.focus()
.
Se não encontrarmos um cliente correspondente, vamos abrir 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 com a mesma tag é substituída.
No entanto, você pode tornar o processo de redução de notificações mais sofisticado 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 vamos fazer é encontrar 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 exibida no momento, vamos incrementar a contagem de mensagens e definir o
título e a mensagem do corpo da notificação 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 agruparia as notificações assim:
A vantagem dessa abordagem é que, se o usuário testemunhar as notificações aparecendo uma sobre a outra, elas vão parecer e se sentir mais coesas 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 quando receber um push, e isso é verdade na maioria dos casos. O único cenário em que você não precisa mostrar uma notificação é quando o usuário tem 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 uma mensagem para uma página usando um evento push
Você pode 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 forma, a página da Web pode mostrar uma notificação ou uma atualização para o usuário, informando-o sobre o evento. Isso é útil em situações em que uma notificação sutil na página é melhor e mais amigável para o usuário.
Digamos que recebemos um push, verificamos se o app da Web está focado 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
- Assinar um usuário
- UX de permissão
- Como enviar mensagens com bibliotecas push da Web
- Protocolo push da Web
- Processamento de eventos push
- Como mostrar 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