Analizaremos algunos patrones de implementación comunes para los servicios web push.
Esto implicará usar algunas APIs diferentes que están disponibles en el trabajador de servicio.
Evento de cierre de notificación
En la última sección, vimos cómo podemos detectar eventos notificationclick
.
También se llama al evento notificationclose
si el usuario descarta una de tus notificaciones (es decir, en lugar de hacer clic en la notificación, el usuario hace clic en la cruz o desliza la notificación para descartarla).
Por lo general, este evento se usa para que las estadísticas realicen un seguimiento de la participación de los usuarios con las notificaciones.
self.addEventListener('notificationclose', function (event) {
const dismissedNotification = event.notification;
const promiseChain = notificationCloseAnalytics();
event.waitUntil(promiseChain);
});
Cómo agregar datos a una notificación
Cuando se recibe un mensaje push, es común tener datos que solo son útiles si el usuario hizo clic en la notificación. Por ejemplo, la URL que se debe abrir cuando se hace clic en una notificación.
La forma más fácil de tomar datos de un evento push y adjuntarlos a una notificación es agregar un parámetro data
al objeto de opciones que se pasa a showNotification()
, de la siguiente manera:
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);
Dentro de un controlador de clics, se puede acceder a los datos con 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('');
Cómo abrir una ventana
Una de las respuestas más comunes a una notificación es abrir una ventana o pestaña en una URL específica. Podemos hacerlo con la API de clients.openWindow()
.
En nuestro evento notificationclick
, ejecutaríamos un código como este:
const examplePage = '/demos/notification-examples/example-page.html';
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);
En la siguiente sección, veremos cómo verificar si la página a la que queremos dirigir al usuario ya está abierta o no. De esta manera, podemos enfocar la pestaña abierta en lugar de abrir pestañas nuevas.
Enfocar una ventana existente
Cuando sea posible, debemos enfocar una ventana en lugar de abrir una nueva cada vez que el usuario hace clic en una notificación.
Antes de ver cómo lograrlo, vale la pena destacar que esto solo es posible para las páginas de tu origen. Esto se debe a que solo podemos ver las páginas abiertas que pertenecen a nuestro sitio. Esto evita que los desarrolladores puedan ver todos los sitios que ven sus usuarios.
En el ejemplo anterior, modificaremos el código para ver si /demos/notification-examples/example-page.html
ya está abierto.
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);
Analicemos el código.
Primero, analizamos nuestra página de ejemplo con la API de URL. Este es un truco ingenioso que aprendí de Jeff Posnick. Si llamas a new URL()
con el objeto location
, se mostrará una URL absoluta si la cadena que se pasa es relativa (es decir, /
se convertirá en https://example.com/
).
Hacemos que la URL sea absoluta para que podamos hacer coincidirla con las URLs de la ventana más adelante.
const urlToOpen = new URL(examplePage, self.location.origin).href;
Luego, obtenemos una lista de los objetos WindowClient
, que es la lista de las pestañas y ventanas abiertas en ese momento. (Recuerda que estas son pestañas solo para tu origen).
const promiseChain = clients.matchAll({
type: 'window',
includeUncontrolled: true,
});
Las opciones que se pasan a matchAll
le informan al navegador que solo queremos buscar clientes de tipo "ventana" (es decir, solo buscar pestañas y ventanas, y excluir los trabajadores web). includeUncontrolled
nos permite buscar todas las pestañas de tu origen que no están controladas por el service worker actual, es decir, el service worker que ejecuta este código. Por lo general, siempre querrás que includeUncontrolled
sea verdadero cuando llames a matchAll()
.
Capturamos la promesa que se muestra como promiseChain
para poder pasarla a event.waitUntil()
más adelante y mantener activo nuestro service worker.
Cuando se resuelve la promesa matchAll()
, iteramos por los clientes de ventana que se devuelven y comparamos sus URLs con la URL que queremos abrir. Si encontramos una coincidencia, enfocamos ese cliente, lo que llevará esa ventana a la atención de los usuarios. El enfoque se realiza con la llamada matchingClient.focus()
.
Si no podemos encontrar un cliente que coincida, abriremos una nueva ventana, como en la sección 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);
}
});
Cómo combinar notificaciones
Observamos que agregar una etiqueta a una notificación habilita un comportamiento en el que se reemplaza cualquier notificación existente con la misma etiqueta.
Sin embargo, puedes hacer que el colapso de notificaciones sea más sofisticado con la API de notificaciones. Considera una app de chat en la que el desarrollador podría querer que una notificación nueva muestre un mensaje similar a "Tienes dos mensajes de Matt" en lugar de solo mostrar el mensaje más reciente.
Puedes hacer esto o manipular las notificaciones actuales de otras maneras con la API de registration.getNotifications(), que te brinda acceso a todas las notificaciones visibles actualmente de tu app web.
Veamos cómo podríamos usar esta API para implementar el ejemplo de chat.
En nuestra app de chat, supongamos que cada notificación tiene algunos datos que incluyen un nombre de usuario.
Lo primero que haremos será encontrar las notificaciones abiertas de un usuario con un nombre de usuario
específico. Obtendremos registration.getNotifications()
, repetiremos y verificaremos el notification.data
para un nombre de usuario 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;
});
El siguiente paso es reemplazar esta notificación por una nueva.
En esta app de mensajes falsos, haremos un seguimiento de la cantidad de mensajes nuevos agregando un recuento a los datos de nuestra notificación nueva y aumentándolo con cada notificación nueva.
.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
);
});
Si se muestra una notificación, aumentamos la cantidad de mensajes y configuramos el título y el cuerpo de la notificación según corresponda. Si no hay notificaciones, crearemos una nueva con un newMessageCount
de 1.
El resultado es que el primer mensaje se verá de la siguiente manera:
Una segunda notificación las contraería de la siguiente manera:
Lo bueno de este enfoque es que si tu usuario observa que las notificaciones aparecen una sobre la otra, se verá y se sentirá más coherente que solo reemplazar la notificación con el mensaje más reciente.
La excepción a la regla
Escribo que debes mostrar una notificación cuando recibes un mensaje push, y esto es cierto la mayor parte del tiempo. La única situación en la que no tienes que mostrar una notificación es cuando el usuario tiene tu sitio abierto y enfocado.
Dentro del evento push, puedes verificar si necesitas mostrar una notificación o no. Para ello, examina los clientes de la ventana y busca una ventana enfocada.
El código para obtener todas las ventanas y buscar una ventana enfocada se ve así:
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 obtener todos nuestros clientes de ventana y, luego, los iteramos para verificar el parámetro focused
.
Dentro de nuestro evento push, usaremos esta función para decidir si necesitamos mostrar una notificación:
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);
Cómo enviar un mensaje a una página desde un evento push
Vimos que puedes omitir mostrar una notificación si el usuario está en tu sitio. Pero ¿qué sucede si aún quieres informarle al usuario que ocurrió un evento, pero una notificación es demasiado invasiva?
Un enfoque es enviar un mensaje del trabajador de servicio a la página. De esta manera, la página web puede mostrarle al usuario una notificación o una actualización para informarle sobre el evento. Esto es útil en situaciones en las que una notificación sutil en la página es mejor y más amigable para el usuario.
Supongamos que recibimos un mensaje push, verificamos que nuestra app web esté enfocada en este momento y, luego, podemos "publicar un mensaje" en cada página abierta, de la siguiente manera:
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);
En cada una de las páginas, agregamos un objeto de escucha de eventos de mensaje para detectarlos:
navigator.serviceWorker.addEventListener('message', function (event) {
console.log('Received a message from service worker: ', event.data);
});
En este objeto de escucha de mensajes, puedes hacer lo que quieras, mostrar una IU personalizada en tu página o ignorar por completo el mensaje.
También vale la pena señalar que, si no defines un objeto de escucha de mensajes en tu página web, los mensajes del servicio trabajador no harán nada.
Cómo almacenar en caché una página y abrir una ventana
Una situación que está fuera del alcance de esta guía, pero que vale la pena analizar, es que puedes mejorar la UX general de tu aplicación web almacenando en caché las páginas web que esperas que los usuarios visiten después de hacer clic en tu notificación.
Esto requiere que tu trabajador del servicio esté configurado para controlar eventos fetch
, pero si implementas un objeto de escucha de eventos fetch
, asegúrate de aprovecharlo en tu evento push
almacenando en caché la página y los recursos que necesitarás antes de mostrar tu notificación.
Compatibilidad del navegador
El evento notificationclose
Clients.openWindow()
ServiceWorkerRegistration.getNotifications()
clients.matchAll()
Para obtener más información, consulta esta entrada de introducción a los trabajadores de servicio.
Próximos pasos
- Descripción general de las notificaciones push en la web
- Cómo funcionan las notificaciones push
- Cómo suscribir a un usuario
- UX de permisos
- Envía mensajes con bibliotecas push web
- Protocolo de notificaciones push web
- Cómo controlar los eventos push
- Cómo mostrar una notificación
- Comportamiento de las notificaciones
- Patrones de notificación comunes
- Preguntas frecuentes sobre las notificaciones push
- Problemas comunes e informar errores