Schémas de notification courants

Nous allons examiner quelques modèles d'implémentation courants pour les notifications push Web.

Pour ce faire, vous devrez utiliser différentes API disponibles dans le service worker.

Événement de fermeture de notification

Dans la section précédente, nous avons vu comment écouter des événements notificationclick.

Un événement notificationclose est également appelé si l'utilisateur ignore l'une de vos notifications (c'est-à-dire qu'au lieu de cliquer sur la notification, l'utilisateur clique sur la croix ou balaie la notification).

Cet événement est généralement utilisé pour les analyses afin de suivre l'engagement des utilisateurs avec les notifications.

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

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

Ajouter des données à une notification

Lorsqu'un message push est reçu, il est courant que les données ne soient utiles que si l'utilisateur a cliqué sur la notification. Par exemple, l'URL qui doit s'ouvrir lorsqu'un utilisateur clique sur une notification.

Le moyen le plus simple de récupérer les données d'un événement push et de les associer à une notification consiste à ajouter un paramètre data à l'objet d'options transmis à showNotification(), comme suit :

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

Dans un gestionnaire de clics, les données sont accessibles avec 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('');

Ouvrir une fenêtre

L'une des réponses les plus courantes à une notification consiste à ouvrir une fenêtre/un onglet vers une URL spécifique. Pour ce faire, nous pouvons utiliser l'API clients.openWindow().

Dans notre événement notificationclick, nous exécutons du code comme suit :

const examplePage = '/demos/notification-examples/example-page.html';
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);

Dans la section suivante, nous verrons comment vérifier si la page vers laquelle nous souhaitons rediriger l'utilisateur est déjà ouverte ou non. De cette façon, nous pouvons mettre en avant l'onglet ouvert plutôt que d'ouvrir de nouveaux onglets.

Activer une fenêtre existante

Dans la mesure du possible, nous devons mettre en surbrillance une fenêtre plutôt que d'en ouvrir une nouvelle chaque fois que l'utilisateur clique sur une notification.

Avant de voir comment procéder, il est important de souligner que cela n'est possible que pour les pages de votre origine. En effet, nous ne pouvons voir que les pages ouvertes qui appartiennent à notre site. Cela empêche les développeurs de voir tous les sites que leurs utilisateurs consultent.

Prenons l'exemple précédent. Nous allons modifier le code pour voir si /demos/notification-examples/example-page.html est déjà ouvert.

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

Passons en revue le code.

Nous commençons par analyser notre page exemple à l'aide de l'API URL. C'est une astuce intéressante que j'ai apprise auprès de Jeff Posnick. L'appel de new URL() avec l'objet location renvoie une URL absolue si la chaîne transmise est relative (c'est-à-dire que / devient https://example.com/).

Nous rendons l'URL absolue afin de pouvoir la faire correspondre aux URL de fenêtre ultérieurement.

const urlToOpen = new URL(examplePage, self.location.origin).href;

Nous obtenons ensuite la liste des objets WindowClient, qui correspond à la liste des onglets et des fenêtres actuellement ouverts. (N'oubliez pas qu'il s'agit d'onglets pour votre origine uniquement.)

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

Les options transmises dans matchAll indiquent au navigateur que nous ne voulons rechercher que les clients de type "fenêtre" (c'est-à-dire qu'il suffit de rechercher les onglets et les fenêtres, et d'exclure les nœuds de calcul Web). includeUncontrolled nous permet de rechercher tous les onglets de votre origine qui ne sont pas contrôlés par le service worker actuel, c'est-à-dire le service worker qui exécute ce code. En règle générale, vous devez toujours définir includeUncontrolled sur "true" lorsque vous appelez matchAll().

Nous capturons la promesse renvoyée en tant que promiseChain afin de pouvoir la transmettre à event.waitUntil() plus tard, ce qui permet de maintenir notre service worker en vie.

Lorsque la promesse matchAll() est résolue, nous itérons les clients de fenêtre renvoyés et comparons leurs URL à l'URL que nous souhaitons ouvrir. Si nous trouvons une correspondance, nous mettons en avant ce client, ce qui attirera l'attention des utilisateurs sur cette fenêtre. Le focus est effectué avec l'appel matchingClient.focus().

Si nous ne trouvons pas de client correspondant, nous ouvrons une nouvelle fenêtre, comme dans la section précédente.

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

Fusionner des notifications

Nous avons constaté que l'ajout d'une balise à une notification active un comportement où toute notification existante contenant la même balise est remplacée.

Vous pouvez toutefois affiner le repli des notifications à l'aide de l'API Notifications. Prenons l'exemple d'une application de chat. Le développeur peut souhaiter qu'une nouvelle notification affiche un message semblable à "Vous avez deux messages de Matt" plutôt que de n'afficher que le dernier message.

Vous pouvez le faire ou manipuler les notifications actuelles d'une autre manière à l'aide de l'API registration.getNotifications(), qui vous permet d'accéder à toutes les notifications actuellement visibles pour votre application Web.

Voyons comment utiliser cette API pour implémenter l'exemple de chat.

Dans notre application de chat, nous supposons que chaque notification contient des données, dont un nom d'utilisateur.

La première chose que nous allons faire est de rechercher les notifications ouvertes d'un utilisateur disposant d'un nom d'utilisateur spécifique. Nous récupérons registration.getNotifications(), nous la mettons en boucle et vérifions le notification.data pour un nom d'utilisateur spécifique:

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

L'étape suivante consiste à remplacer cette notification par une nouvelle.

Dans cette application de faux messages, nous allons suivre le nombre de nouveaux messages en ajoutant un nombre aux données de nos nouvelles notifications et en l'incrémentant à chaque nouvelle notification.

.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 une notification est actuellement affichée, nous incrémentons le nombre de messages et définissons le titre et le corps du message de la notification en conséquence. Si aucune notification n'est envoyée, nous en créons une avec une valeur newMessageCount de 1.

Le premier message se présente donc comme suit :

Première notification sans fusion.

Une deuxième notification regrouperait les notifications comme suit :

Deuxième notification avec fusion.

L'avantage de cette approche est que si votre utilisateur voit les notifications s'afficher les unes sur les autres, l'interface sera plus cohérente que si vous ne remplaciez que la notification par le dernier message.

L'exception à la règle

J'ai indiqué que vous devez afficher une notification lorsque vous recevez une notification push. C'est vrai la plupart du temps. Le seul scénario dans lequel vous n'avez pas besoin d'afficher de notification est lorsque l'utilisateur a votre site ouvert et en plein écran.

Dans votre événement push, vous pouvez vérifier si vous devez afficher une notification ou non en examinant les clients de fenêtre et en recherchant une fenêtre sélectionnée.

Le code permettant d'obtenir toutes les fenêtres et de rechercher une fenêtre sélectionnée se présente comme suit:

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

Nous utilisons clients.matchAll() pour obtenir tous nos clients de fenêtre, puis nous les consultons en boucle en vérifiant le paramètre focused.

Dans notre événement push, nous utiliserons cette fonction pour décider si nous devons afficher une notification :

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

Envoyer un message à une page à partir d'un événement push

Nous avons vu que vous pouvez ne pas afficher de notification si l'utilisateur se trouve actuellement sur votre site. Mais que faire si vous souhaitez tout de même informer l'utilisateur qu'un événement s'est produit, mais qu'une notification est trop lourde ?

Une approche consiste à envoyer un message à la page à partir du service worker. Ainsi, la page Web peut afficher une notification ou une mise à jour à l'utilisateur pour l'informer de l'événement. Cela est utile dans les cas où une notification subtile sur la page est plus efficace et plus conviviale pour l'utilisateur.

Imaginons que nous ayons reçu un push, vérifié que notre application Web est actuellement au premier plan, puis que nous puissions "publier un message" sur chaque page ouverte, comme suit :

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

Dans chacune des pages, nous écoutons les messages en ajoutant un écouteur d'événements de message:

navigator.serviceWorker.addEventListener('message', function (event) {
  console.log('Received a message from service worker: ', event.data);
});

Dans cet écouteur de messages, vous pouvez faire tout ce que vous voulez, afficher une UI personnalisée sur votre page ou ignorer complètement le message.

Notez également que si vous ne définissez pas d'écouteur de messages dans votre page Web, les messages du service worker ne serviront à rien.

Mettre en cache une page et ouvrir une fenêtre

Un scénario qui n'entre pas dans le champ d'application de ce guide, mais qui mérite d'être évoqué, est que vous pouvez améliorer l'expérience utilisateur globale de votre application Web en mettant en cache les pages Web que vous prévoyez que les utilisateurs consulteront après avoir cliqué sur votre notification.

Pour ce faire, vous devez configurer votre service worker pour qu'il gère les événements fetch. Toutefois, si vous implémentez un écouteur d'événement fetch, assurez-vous de l'utiliser dans votre événement push en mettant en cache la page et les éléments dont vous avez besoin avant d'afficher votre notification.

Compatibilité du navigateur

L'événement notificationclose

Navigateurs pris en charge

  • Chrome : 50.
  • Edge : 17.
  • Firefox : 44.
  • Safari : 16.

Source

Clients.openWindow()

Navigateurs pris en charge

  • Chrome: 40
  • Edge : 17.
  • Firefox : 44.
  • Safari : 11.1.

Source

ServiceWorkerRegistration.getNotifications()

Navigateurs pris en charge

  • Chrome : 40.
  • Edge : 17.
  • Firefox : 44.
  • Safari : 16.

Source

clients.matchAll()

Navigateurs pris en charge

  • Chrome : 42.
  • Edge: 17
  • Firefox : 54.
  • Safari : 11.1.

Source

Pour en savoir plus, consultez cet article de présentation des service workers.

Étapes suivantes

Ateliers de programmation