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, vimos como detectar eventos notificationclick
.
Há também um evento notificationclose
que é chamado quando 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 na análise para rastrear 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 ter dados que só serão úteis se o usuário tiver clicado na notificação. Por exemplo, o URL que precisa ser aberto quando uma notificação é clicada.
A maneira mais fácil de extrair 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()
, desta forma:
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. Podemos fazer isso com a
API
clients.openWindow()
.
No evento notificationclick
, executaríamos 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 aprender a 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 em uma janela existente
Sempre que possível, é necessário focar em uma janela, em vez de abrir uma nova sempre que o usuário clica em uma notificação.
Antes de mostrar como fazer isso, vale destacar que isso só é possível para páginas na sua origem. Isso acontece porque só podemos ver quais páginas estão abertas e pertencem ao nosso site. Isso impede que os desenvolvedores vejam todos os sites acessados pelos usuários.
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. Este é um truque legal que escolhai 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 compará-lo ao URL 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. Lembre-se de que essas guias são apenas para sua origem.
const promiseChain = clients.matchAll({
type: 'window',
includeUncontrolled: true,
});
As opções transmitidas para matchAll
informam ao navegador que queremos
pesquisar apenas clientes do tipo "janela" (ou seja, apenas procurar 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. Geralmente, você
sempre quer que includeUncontrolled
seja verdadeiro ao chamar matchAll()
.
Capturamos a promessa retornada como promiseChain
para que possamos passá-la para
event.waitUntil()
mais tarde, mantendo nosso service worker ativo.
Quando a promessa matchAll()
é resolvida, iteramos os clientes da janela retornada e
comparamos os URLs deles com o URL que queremos abrir. Se encontrarmos uma correspondência, focaremos esse
cliente, o que chamará a atenção dos usuários para essa janela. O foco é feito com a
chamada matchingClient.focus()
.
Se não for possível encontrar um cliente correspondente, abrimos 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);
}
});
Mesclagem de notificações
Vimos 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 pode querer uma nova notificação para mostrar uma mensagem semelhante a "Você tem duas mensagens de Matt", em vez de apenas mostrar a mensagem mais recente.
É possível fazer isso ou manipular as notificações atuais de outras maneiras usando a API registration.getNotifications(), que fornece acesso a todas as notificações visíveis do app da Web.
Vamos conferir como podemos usar essa API para implementar o exemplo de chat.
Em nosso aplicativo de bate-papo, vamos supor que cada notificação tenha alguns dados, que incluem um nome de usuário.
A primeira coisa que queremos fazer é encontrar todas as notificações abertas para um usuário com um nome de usuário específico. Vamos usar registration.getNotifications()
, fazer uma repetição e verificar se
notification.data
há 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.
Nesse app de mensagens falsas, vamos rastrear o número de novas mensagens adicionando uma contagem aos dados da nova notificação e o incrementaremos 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 uma notificação estiver sendo exibida, vamos aumentar a contagem de mensagens e definir o
título da notificação e o corpo da mensagem de acordo. Se não
houver notificações, vamos criar uma nova com um newMessageCount
de 1.
O resultado é que a primeira mensagem seria assim:
Uma segunda notificação recolheria as notificações desta forma:
O bom dessa abordagem é que, se o usuário testemunhar as notificações aparecendo uma sobre a outra, ela parecerá mais coesa do que apenas substituir a notificação pela mensagem mais recente.
A exceção à regra
Eu tenho declarado que você precisa mostrar uma notificação quando recebe um push, e isso acontece na maioria do tempo. Você não precisa mostrar uma notificação quando o usuário está com seu site aberto e focado.
Dentro do evento de push, você pode verificar se precisa exibir uma notificação examinando os clientes da janela e procurando uma janela em foco.
O código para extrair todas as janelas e procurar uma janela em foco fica assim:
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 reunir todos os clientes de janela e, em seguida, fazemos a repetição deles verificando o parâmetro focused
.
Dentro do nosso evento push, usaríamos 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 mensagem para uma página de um evento push
Observamos que é possível pular a exibição de uma notificação se o usuário estiver atualmente em 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. Assim, a página da Web pode mostrar uma notificação ou atualização ao usuário, informando sobre o evento. Isso é útil nas situações em que uma notificação sutil na página é melhor e mais simples para o usuário.
Digamos que recebemos um push, verificamos se nosso aplicativo da Web está em foco no momento e podemos "postar uma mensagem" em cada página aberta, da seguinte forma:
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, ouvimos mensagens adicionando um listener de eventos de mensagem:
navigator.serviceWorker.addEventListener('message', function (event) {
console.log('Received a message from service worker: ', event.data);
});
Nesse listener de mensagens, você pode fazer o que quiser, mostrar uma IU personalizada na sua página ou ignorar completamente a mensagem.
Vale ressaltar que, se você não definir um listener de mensagens na página da Web, as mensagens do service worker não terão efeito.
Armazenar uma página em cache e abrir uma janela
Um cenário que está fora do escopo deste guia, mas 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.
Isso exige que o service worker esteja configurado para processar eventos fetch
.
No entanto, se você implementar um listener de eventos fetch
, aproveite
esse recurso 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 esta postagem de introdução aos service workers.
A seguir
- Visão geral das notificações push da Web
- Como funciona o envio por push
- Como inscrever um usuário
- UX de permissão
- Como enviar mensagens com bibliotecas push da Web
- Protocolo push da Web
- Como processar eventos push
- Exibir uma notificação
- Comportamento das notificações
- Padrões de notificação comuns
- Perguntas frequentes sobre notificações push
- Problemas comuns e como informar bugs