Patrones de notificaciones comunes

Veremos algunos patrones de implementación comunes para notificaciones push web.

Esto implicará usar algunas APIs diferentes que estén disponibles en el service worker.

Evento de cierre de notificación

En la última sección, vimos cómo escuchar eventos de notificationclick.

También se llama a un evento notificationclose si el usuario descarta uno de tus notificaciones (es decir, en lugar de hacer clic en la notificación, el usuario hace clic en la cruz o desliza el dedo sobre notificación).

Por lo general, este evento se usa para que las estadísticas hagan un seguimiento de la participación de los usuarios con notificaciones.

self.addEventListener('notificationclose', function (event) {
  const dismissedNotification = event.notification;

  const promiseChain = notificationCloseAnalytics();
  event.waitUntil(promiseChain);
});

Agrega datos a una notificación

Cuando se recibe un mensaje push, es común tener datos que solo es útil 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 sencilla de tomar datos de un evento de envío y adjuntarlos a un la notificación es agregar un parámetro data al objeto de opciones que se pasa en 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('');

Abrir una ventana

Una de las respuestas más comunes a una notificación es abrir una ventana o pestaña a una URL específica. Podemos hacerlo con el clients.openWindow() en la API de Cloud.

En nuestro evento notificationclick, ejecutaríamos un código como el siguiente:

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 está ya están abiertas o no. De esta manera, podemos enfocar la pestaña abierta en lugar de abrir una nueva. pestañas.

Enfocar una ventana existente

Cuando sea posible, debemos enfocar una ventana en lugar de abrir una nueva ventana cada vez que el usuario hace clic en una notificación.

Antes de que veamos cómo lograr esto, vale la pena destacar que solo es posible para las páginas de tu origen. Esto se debe a que podemos solo ver las páginas abiertas que pertenecen a nuestro sitio. Esto evita los desarrolladores no puedan ver todos los sitios que visitan sus usuarios.

Tomando 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);

Revisemos el código paso a paso.

Primero, analizamos nuestra página de ejemplo con la API de URL. Este es un buen truco que aprendí de Jeff Posnick Llamar a new URL() con el objeto location hará lo siguiente: Devuelve una URL absoluta si la cadena pasada es relativa (es decir, / se convertirá en https://example.com/).

Hacemos que la URL sea absoluta para poder hacerla coincidir con las URL de 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 los pestañas y ventanas abiertas actualmente. (Recuerda que estas son solo las pestañas de origen).

const promiseChain = clients.matchAll({
  type: 'window',
  includeUncontrolled: true,
});

Las opciones que se pasan a matchAll informan al navegador que solo queremos para buscar "ventana" tipo de clientes (es decir, buscar pestañas y ventanas) y excluir a los trabajadores web). includeUncontrolled nos permite buscar todas las pestañas de tu origen que no están controladas por el servicio actual worker, es decir, el service worker que ejecuta este código. Por lo general, siempre se desea que includeUncontrolled sea verdadero cuando se llame a matchAll().

Capturamos la promesa que se muestra como promiseChain para poder pasarla al event.waitUntil() más adelante, lo que mantiene activo nuestro service worker.

Cuando se resuelve la promesa matchAll(), iteramos a través de los clientes que se muestran durante la ventana y comparar sus URLs con la que queremos abrir. Si encontramos una coincidencia, nos enfocamos en cliente, lo que atraerá la atención de los usuarios a esa ventana. El enfoque se hace con el matchingClient.focus() llamada.

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);
  }
});

Combinación de notificaciones

Vimos que agregar una etiqueta a una notificación habilita un comportamiento en el que la notificación existente con la misma etiqueta se reemplaza.

Sin embargo, puedes ser más sofisticado con el cierre de las notificaciones mediante la API de notificaciones. Piensa en una app de chat, en la que el desarrollador podría querer una nueva notificación para mostrar un mensaje similar a "Tienes dos mensajes de Mateo" en lugar de solo mostrar la última mensaje.

Puedes hacerlo o manipular las notificaciones actuales de otras formas, con el registration.getNotifications() API que te otorga acceso a todas las notificaciones visibles actualmente de tu app web.

Veamos cómo podemos 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 debemos hacer es encontrar las notificaciones abiertas para un usuario con un nombre de usuario. Obtendremos registration.getNotifications(), haremos un bucle 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 los mensajes nuevos agregando un recuento a la nueva de la notificación y aumentarlos 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 aparece una notificación, aumentamos el recuento de mensajes y configuramos el título y el cuerpo de la notificación según corresponda. Si no hay notificaciones, creamos una nueva con un newMessageCount de 1.

El resultado es que el primer mensaje se vería así:

Primera notificación sin combinar.

Una segunda notificación las contraería de la siguiente manera:

Segunda notificación con combinación.

Lo bueno de este enfoque es que si el usuario es testigo notificaciones aparecen una encima de la otra, se verá y se sentirá más coherente no basta con reemplazar la notificación con el último mensaje.

La excepción a la regla

He indicado que debes mostrar una notificación cuando recibas un mensaje push, y esto es verdadero la mayor parte del tiempo. Una situación en la que no tienes que mostrar una notificación es cuando el usuario tiene tu sitio abierto y enfocado.

En tu evento push, puedes verificar si necesitas mostrar una notificación o no examinar los clientes de la ventana y buscar una ventana enfocada.

El código para obtener todas las ventanas y buscar una ventana enfocada se ve de la siguiente manera:

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 repasamos verificando el parámetro focused.

En nuestro evento push, usaríamos 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);

Enviar un mensaje a una página desde un evento push

Notamos que puedes omitir mostrar una notificación si el usuario está actualmente en tu sitio. Sin embargo, ¿qué sucede si de todas formas deseas que el usuario sepa que ocurrió un evento, pero se con las manos demasiado pesadas?

Un enfoque es enviar un mensaje desde el service worker a la página, de esta manera Puede mostrar una notificación o una actualización al usuario para informarle del evento. Esto es útil para situaciones en las que una notificación sutil en la página es mejor y más amigable para el usuario.

Digamos que recibimos un mensaje push, comprobamos que la app web está enfocada entonces podemos "publicar un mensaje" a cada página abierta, así:

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 evento de mensaje para escuchar los mensajes. objeto de escucha:

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 completamente el mensaje.

Si no defines un objeto de escucha de mensajes en tu página web, mensajes del service worker no hará 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 discutir es que puedes mejorar la UX general de tu aplicación web almacenando en caché las páginas web que esperas que visiten los usuarios haciendo clic en tu notificación.

Para ello, debes configurar tu service worker para controlar los eventos fetch. pero, si implementas un objeto de escucha de eventos fetch, asegúrate de tomar aprovéchalo en tu evento push almacenando la página y los recursos en caché necesitarás antes de mostrar la notificación.

Compatibilidad del navegador

El evento notificationclose

Navegadores compatibles

  • Chrome: 50
  • Límite: 17.
  • Firefox: 44.
  • Safari: 16.

Origen

Clients.openWindow()

Navegadores compatibles

  • Chrome: 40.
  • Límite: 17.
  • Firefox: 44.
  • Safari: 11.1.

Origen

ServiceWorkerRegistration.getNotifications()

Navegadores compatibles

  • Chrome: 40.
  • Límite: 17.
  • Firefox: 44.
  • Safari: 16.

Origen

clients.matchAll()

Navegadores compatibles

  • Chrome: 42.
  • Límite: 17.
  • Firefox: 54.
  • Safari: 11.1.

Origen

Para obtener más información, consulta esta introducción a los service workers publicación.

Próximos pasos

Code labs