Schémas de notification courants

Nous allons examiner quelques schémas d'implémentation courants pour le Web push.

Cela implique l'utilisation de quelques API différentes disponibles dans le service worker.

Événement de fermeture de notification

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

Un événement notificationclose est également appelé si l'utilisateur ignore l'une de vos notifications (par exemple, au lieu de cliquer sur la notification, il clique sur la croix ou la fait disparaître d'elle).

Cet événement est normalement utilisé à des fins d'analyse 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 de disposer de données qui ne sont utiles que si l'utilisateur a cliqué sur la notification. Par exemple, l'URL à 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 joindre à 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, vous pouvez accéder aux données 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 ou un onglet pour accéder à une URL spécifique. Pour ce faire, vous pouvez utiliser l'API clients.openWindow().

Dans notre événement notificationclick, nous exécutions du code comme celui-ci:

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 voulons rediriger l'utilisateur est déjà ouverte ou non. De cette façon, nous pouvons sélectionner l'onglet ouvert plutôt que d'en ouvrir de nouveaux.

Sélectionner une fenêtre existante

Lorsque cela est possible, nous devons sélectionner une fenêtre plutôt que d'en ouvrir une nouvelle chaque fois que l'utilisateur clique sur une notification.

Avant de voir comment y parvenir, il convient de souligner que cela n'est possible qu'avec 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 consultés par leurs utilisateurs.

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

Examinons le code.

Nous commençons par analyser notre page d'exemple à l'aide de l'API URL. C'est une super astuce que j'ai prise 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 (par exemple, / devient https://example.com/).

Nous rendons l'URL absolue afin de pouvoir la faire correspondre par la suite à l'URL de la fenêtre.

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

Nous obtenons ensuite la liste des objets WindowClient, qui correspond à la liste des onglets et fenêtres actuellement ouverts. (N'oubliez pas que ces onglets ne concernent que votre origine.)

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

Les options transmises dans matchAll indiquent au navigateur que nous voulons uniquement rechercher des clients de type "fenêtre" (par exemple, rechercher simplement des onglets et des fenêtres, et 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 exécutant ce code. En règle générale, vous souhaitez toujours que includeUncontrolled soit défini sur "true" lorsque vous appelez matchAll().

Nous enregistrons la promesse renvoyée en tant que promiseChain afin de pouvoir la transmettre ultérieurement à event.waitUntil(), ce qui permet à notre service worker de rester actif.

Lorsque la promesse matchAll() est résolue, nous effectuons une itération des clients de fenêtre renvoyés et comparons leurs URL à l'URL que nous souhaitons ouvrir. Si nous trouvons une correspondance, nous concentrons ce client, ce qui attirera l'attention de l'utilisateur sur cette fenêtre. La mise au point est effectuée 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);
  }
});

Fusion des notifications

Nous avons constaté que l'ajout d'une balise à une notification entraîne le remplacement de toute notification existante contenant la même balise.

Toutefois, vous pouvez aller de l'avant avec la réduction des notifications à l'aide de l'API Notifications. Prenons l'exemple d'une application de chat dans laquelle le développeur peut souhaiter qu'une nouvelle notification affiche un message semblable à "Vous avez deux messages de Matt" au lieu d'afficher uniquement le dernier message.

Pour ce faire, ou pour manipuler les notifications actuelles d'autres manières, vous pouvez utiliser l'API registration.getNotifications(), qui vous donne accès à 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, supposons que chaque notification contienne des données incluant un nom d'utilisateur.

La première chose à faire est de rechercher toutes les notifications ouvertes pour un utilisateur avec un nom d'utilisateur spécifique. Nous obtenons registration.getNotifications() et les effectuons en boucle, puis vérifions 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 messagerie fictive, nous suivrons le nombre de nouveaux messages en ajoutant un nombre aux données de notre nouvelle notification et nous incrémentons ce nombre à 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 de la notification et le corps du message en conséquence. En l'absence de notification, nous en créons une nouvelle avec une newMessageCount de 1.

Le résultat est que le premier message ressemblerait à ceci:

Première notification sans fusion.

Une deuxième notification les réduit comme suit:

Deuxième notification avec fusion.

L'avantage de cette approche est que si l'utilisateur constate que les notifications s'affichent l'une sur l'autre, la cohérence sera plus efficace au lieu de simplement remplacer 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, et c'est vrai pour la plupart du temps. Le seul scénario où vous n'avez pas besoin d'afficher de notification est lorsque votre site est ouvert et sélectionné.

Dans votre événement push, vous pouvez vérifier si vous devez afficher une notification ou non en examinant les clients de la 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 mettons en boucle en vérifiant le paramètre focused.

Dans notre événement push, nous utiliserions 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 ignorer l'affichage d'une notification si l'utilisateur est actuellement sur votre site. Mais que se passe-t-il si vous souhaitez quand même informer l'utilisateur qu'un événement s'est produit, mais qu'une notification lui est trop difficile ?

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

Supposons que nous ayons reçu un push, vérifié que notre application Web soit actuellement ciblée, puis que nous puissions "publier un message" sur chaque page ouverte, comme ceci:

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 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 sur votre page Web, les messages du service worker n'auront aucun effet.

Mettre en cache une page et ouvrir une fenêtre

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

Pour ce faire, votre service worker doit être configuré pour gérer les événements fetch. Toutefois, si vous implémentez un écouteur d'événements fetch, assurez-vous de l'utiliser dans votre événement push en mettant en cache la page et les éléments dont vous aurez besoin avant d'afficher votre notification.

Compatibilité du navigateur

L'événement notificationclose

Navigateurs pris en charge

  • 50
  • 17
  • 44
  • 16

Source

Clients.openWindow()

Navigateurs pris en charge

  • 40
  • 17
  • 44
  • 11.1

Source

ServiceWorkerRegistration.getNotifications()

Navigateurs pris en charge

  • 40
  • 17
  • 44
  • 16

Source

clients.matchAll()

Navigateurs pris en charge

  • 42
  • 17
  • 54
  • 11.1

Source

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

Étapes suivantes

Ateliers de programmation