S'abonner à un utilisateur

La première étape consiste à obtenir l'autorisation de l'utilisateur pour lui envoyer des messages push, puis nous pouvons obtenir un PushSubscription.

L'API JavaScript pour ce faire est relativement simple. Voyons donc le flux logique.

Détection de fonctionnalités

Nous devons d'abord vérifier si le navigateur actuel est compatible avec la messagerie push. Nous pouvons vérifier si la méthode push est prise en charge à l'aide de deux vérifications simples.

  1. Recherchez serviceWorker sur navigator.
  2. Recherchez PushManager dans window.
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

Bien que la compatibilité des navigateurs avec les service workers et les messages push augmente rapidement, il est toujours recommandé de détecter les fonctionnalités pour les deux et de les améliorer progressivement.

Enregistrer un service worker

Grâce à la détection de fonctionnalités, nous savons que les service workers et le push sont compatibles. L'étape suivante consiste à "enregistrer" notre service worker.

Lorsque nous enregistrons un service worker, nous indiquons au navigateur où se trouve notre fichier de service worker. Le fichier n'est toujours que du code JavaScript, mais le navigateur lui "donne accès" aux API du service worker, y compris à la méthode push. Pour être plus précis, le navigateur exécute le fichier dans un environnement de service worker.

Pour enregistrer un service worker, appelez navigator.serviceWorker.register() en transmettant le chemin d'accès à notre fichier. Par exemple :

function registerServiceWorker() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      console.log('Service worker successfully registered.');
      return registration;
    })
    .catch(function (err) {
      console.error('Unable to register service worker.', err);
    });
}

Cette fonction indique au navigateur que nous disposons d'un fichier de service worker et de son emplacement. Dans ce cas, le fichier du service worker se trouve dans /service-worker.js. En arrière-plan, le navigateur effectue les étapes suivantes après avoir appelé register() :

  1. Téléchargez le fichier du service worker.

  2. Exécutez le code JavaScript.

  3. Si tout fonctionne correctement et qu'aucune erreur ne se produit, la promesse renvoyée par register() est résolue. En cas d'erreurs, la promesse est rejetée.

Si register() refuse, vérifiez que votre code JavaScript ne contient pas de fautes de frappe ni d'erreurs dans les outils pour les développeurs Chrome.

Lorsque register() est résolu, une exception ServiceWorkerRegistration est renvoyée. Nous utiliserons cet enregistrement pour accéder à l'API PushManager.

Compatibilité du navigateur avec l'API PushManager

Navigateurs pris en charge

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

Source

Demander une autorisation

Nous avons enregistré notre service worker et sommes prêts à inscrire l'utilisateur. L'étape suivante consiste à obtenir l'autorisation de l'utilisateur pour lui envoyer des messages push.

L'API permettant d'obtenir une autorisation est relativement simple. L'inconvénient est que l'API a récemment remplacé la prise en charge d'un rappel par la génération d'une promesse. Le problème est que nous ne pouvons pas savoir quelle version de l'API est implémentée par le navigateur actuel. Vous devez donc implémenter les deux et les gérer.

function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error("We weren't granted permission.");
    }
  });
}

Dans le code ci-dessus, l'extrait de code important est l'appel à Notification.requestPermission(). Cette méthode affiche une invite à l'utilisateur :

Requête d'autorisation dans Chrome sur ordinateur et mobile

Une fois que l'utilisateur a interagi avec l'invite d'autorisation en appuyant sur "Autoriser", "Bloquer" ou en la fermant, le résultat s'affiche sous forme de chaîne: 'granted', 'default' ou 'denied'.

Dans l'exemple de code ci-dessus, la promesse renvoyée par askPermission() est résolue si l'autorisation est accordée. Dans le cas contraire, une erreur entraîne le rejet de la promesse.

Un cas limite que vous devez gérer est si l'utilisateur clique sur le bouton « Bloquer ». Si cela se produit, votre application Web ne pourra plus demander l'autorisation à l'utilisateur. Ils devront "débloquer" manuellement votre application en modifiant son état d'autorisation, qui se trouve dans un panneau de paramètres. Réfléchissez bien à la manière dont vous demandez l'autorisation à l'utilisateur et au moment où elle l'est. En effet, s'il clique sur "Bloquer", il n'est pas facile d'annuler cette décision.

La bonne nouvelle, c'est que la plupart des utilisateurs peuvent accorder leur autorisation à condition qu'ils savent pourquoi l'autorisation est demandée.

Nous verrons plus tard comment certains sites populaires demandent des autorisations.

Abonner un utilisateur avec PushManager

Une fois notre service worker enregistré et que nous avons obtenu l'autorisation, nous pouvons abonner un utilisateur en appelant registration.pushManager.subscribe().

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

Lorsque vous appelez la méthode subscribe(), vous transmettez un objet options, qui se compose de paramètres obligatoires et facultatifs.

Voyons toutes les options que nous pouvons transmettre.

Options userVisibleOnly

Lorsque le push a été ajouté pour la première fois aux navigateurs, il n'était pas clair si les développeurs pouvaient envoyer un message push sans afficher de notification. On parle généralement de push silencieux, car l'utilisateur ne sait pas qu'un événement s'est produit en arrière-plan.

Le problème était que les développeurs pouvaient faire des choses malveillantes, comme suivre en permanence la position d'un utilisateur à son insu.

Pour éviter ce scénario et laisser aux auteurs de la spécification le temps de réfléchir à la meilleure façon de prendre en charge cette fonctionnalité, l'option userVisibleOnly a été ajoutée. Transmettre une valeur de true constitue un accord symbolique avec le navigateur selon lequel l'application Web affichera une notification chaque fois qu'un push sera reçu (c'est-à-dire qu'il n'y aura pas de push silencieux).

Pour le moment, vous devez transmettre la valeur true. Si vous n'incluez pas la clé userVisibleOnly ou la carte false, l'erreur suivante s'affiche :

Chrome n'est actuellement compatible qu'avec l'API Push pour les abonnements qui entraînent des messages visibles par l'utilisateur. Pour ce faire, appelez pushManager.subscribe({userVisibleOnly: true}) à la place. Pour en savoir plus, consultez la page https://goo.gl/yqv4Q4.

Il semble actuellement que les notifications push silencieuses ne seront jamais implémentées dans Chrome. Au lieu de cela, les auteurs de spécifications explorent la notion d'API de budget qui permettra aux applications Web d'envoyer un certain nombre de messages push silencieux en fonction de l'utilisation d'une application Web.

Option applicationServerKey

Nous avons brièvement mentionné les "clés de serveur d'application" dans la section précédente. Les "clés de serveur d'application" sont utilisées par un service push pour identifier l'application qui abonne un utilisateur et s'assurer que la même application envoie des messages à cet utilisateur.

Les clés de serveur d'applications sont une paire de clés publique et privée propre à votre application. La clé privée doit rester secrète pour votre application, et la clé publique peut être partagée librement.

L'option applicationServerKey transmise dans l'appel subscribe() est la clé publique de l'application. Le navigateur transmet cette information à un service push lors de l'abonnement de l'utilisateur. Cela signifie que le service push peut associer la clé publique de votre application à l'PushSubscription de l'utilisateur.

Le schéma ci-dessous illustre ces étapes.

  1. Votre application Web est chargée dans un navigateur et vous appelez subscribe() en transmettant votre clé publique de serveur d'application.
  2. Le navigateur envoie ensuite une requête réseau à un service de transfert qui génère un point de terminaison, l'associe à la clé publique de l'application et le renvoie au navigateur.
  3. Le navigateur ajoutera ce point de terminaison à PushSubscription, qui est renvoyé via la promesse subscribe().

Illustration de la clé publique du serveur d'application utilisée dans la méthode subscribe.

Lorsque vous souhaitez envoyer ultérieurement un message push, vous devez créer un en-tête Authorization contenant des informations signées avec la clé privée de votre serveur d'applications. Lorsque le service push reçoit une requête d'envoi d'un message push, il peut valider cet en-tête Authorization signé en recherchant la clé publique associée au point de terminaison qui reçoit la requête. Si la signature est valide, le service de transfert par poussée sait qu'elle doit provenir du serveur d'application avec la clé privée correspondante. Il s'agit essentiellement d'une mesure de sécurité qui empêche toute autre personne d'envoyer des messages aux utilisateurs d'une application.

Utilisation de la clé privée du serveur d'application lors de l'envoi d'un message

Techniquement, applicationServerKey est facultatif. Cependant, l'implémentation la plus simple sur Chrome l'exige, et d'autres navigateurs pourraient l'exiger à l'avenir. Cette étape est facultative dans Firefox.

La spécification qui définit ce que doit être la clé de serveur d'application est la spécification VAPID. Chaque fois que vous lisez quelque chose qui fait référence à des "clés de serveur d'application" ou à des "clés VAPID", n'oubliez pas qu'il s'agit de la même chose.

Créer des clés de serveur d'application

Vous pouvez créer un ensemble public et privé de clés de serveur d'application en accédant à web-push-codelab.glitch.me ou en utilisant la ligne de commande web-push pour générer des clés en procédant comme suit :

    $ npm install -g web-push
    $ web-push generate-vapid-keys

Vous n'avez besoin de créer ces clés qu'une seule fois pour votre application. Veillez simplement à conserver la clé privée de manière privée. (Oui, je viens de le dire.)

Autorisations et subscribe()

L'appel de subscribe() a un effet secondaire. Si votre application Web ne dispose pas des autorisations permettant d'afficher des notifications au moment de l'appel de subscribe(), le navigateur les demandera à votre place. C'est utile si votre interface utilisateur fonctionne avec ce flux, mais si vous souhaitez avoir plus de contrôle (et je pense que la plupart des développeurs le feront), conservez l'API Notification.requestPermission() que nous avons utilisée précédemment.

Qu'est-ce qu'un PushSubscription ?

Nous appelons subscribe() et transmettons certaines options. En retour, nous obtenons une promesse qui se résout en PushSubscription, ce qui génère un code semblable à celui-ci:

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

L'objet PushSubscription contient toutes les informations requises pour envoyer un message push à cet utilisateur. Si vous imprimez le contenu à l'aide de JSON.stringify(), vous verrez ce qui suit :

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

endpoint est l'URL des services push. Pour déclencher un message push, envoyez une requête POST à cette URL.

L'objet keys contient les valeurs utilisées pour chiffrer les données de message envoyées avec un message push (que nous verrons plus loin dans cette section).

Réabonnement régulier pour éviter l'expiration

Lorsque vous vous abonnez aux notifications push, vous recevez souvent une PushSubscription.expirationTime de null. En théorie, cela signifie que l'abonnement n'expire jamais (contrairement à ce qui se passe lorsque vous recevez un DOMHighResTimeStamp, qui indique le moment exact où l'abonnement expire). En pratique, cependant, il est courant que les navigateurs laissent les abonnements expirer, par exemple si aucune notification push n'a été reçue pendant une longue période ou si le navigateur détecte que l'utilisateur n'utilise pas d'application disposant de l'autorisation de notifications push. Pour éviter cela, vous pouvez réabonner l'utilisateur à chaque notification reçue, comme illustré dans l'extrait suivant. Pour ce faire, vous devez envoyer des notifications suffisamment fréquemment pour que le navigateur ne mette pas automatiquement fin à l'abonnement. Vous devez donc peser très soigneusement les avantages et les inconvénients des besoins légitimes en notifications par rapport au spam involontaire de l'utilisateur afin que l'abonnement ne prenne pas fin. En fin de compte, vous ne devez pas essayer de lutter contre le navigateur dans ses efforts pour protéger l'utilisateur des abonnements aux notifications oubliés depuis longtemps.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Envoyer un abonnement à votre serveur

Une fois que vous avez un abonnement push, vous devez l'envoyer à votre serveur. Le choix vous appartient, mais un petit conseil consiste à utiliser JSON.stringify() pour obtenir toutes les données nécessaires à partir de l'objet d'abonnement. Vous pouvez également obtenir le même résultat manuellement comme suit :

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth'),
  },
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

L'envoi de l'abonnement s'effectue sur la page Web comme suit :

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
    .then(function (response) {
      if (!response.ok) {
        throw new Error('Bad status code from server.');
      }

      return response.json();
    })
    .then(function (responseData) {
      if (!(responseData.data && responseData.data.success)) {
        throw new Error('Bad response from server.');
      }
    });
}

Le serveur de nœud reçoit cette demande et enregistre les données dans une base de données pour une utilisation ultérieure.

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

  return saveSubscriptionToDatabase(req.body)
    .then(function (subscriptionId) {
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify({data: {success: true}}));
    })
    .catch(function (err) {
      res.status(500);
      res.setHeader('Content-Type', 'application/json');
      res.send(
        JSON.stringify({
          error: {
            id: 'unable-to-save-subscription',
            message:
              'The subscription was received but we were unable to save it to our database.',
          },
        }),
      );
    });
});

Avec les informations PushSubscription sur notre serveur, nous pouvons envoyer un message à notre utilisateur à tout moment.

Réabonnement régulier pour éviter l'expiration

Lorsque vous vous abonnez aux notifications push, vous recevez souvent un PushSubscription.expirationTime de null. En théorie, cela signifie que l'abonnement n'expire jamais (contrairement à ce qui se passe lorsque vous recevez un DOMHighResTimeStamp, qui indique le moment exact où l'abonnement expire). En pratique, cependant, il est courant que les navigateurs laissent les abonnements expirer, par exemple si aucune notification push n'a été reçue pendant une longue période ou si le navigateur détecte que l'utilisateur n'utilise pas l'application disposant de l'autorisation de notifications push. Pour éviter cela, l'un des moyens consiste à réabonner l'utilisateur à chaque notification reçue, comme illustré dans l'extrait suivant. Pour ce faire, vous devez envoyer des notifications suffisamment fréquemment pour que le navigateur ne mette pas automatiquement fin à l'abonnement. Vous devez donc peser très soigneusement les avantages et les inconvénients des besoins légitimes en notifications par rapport au spam de l'utilisateur afin que l'abonnement ne prenne pas fin. En fin de compte, vous ne devez pas essayer de lutter contre le navigateur dans ses efforts pour protéger l'utilisateur des abonnements aux notifications oubliés depuis longtemps.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Questions fréquentes

Voici quelques questions courantes que les utilisateurs se posent à ce stade :

Puis-je modifier le service push utilisé par un navigateur ?

Non. Le service de notification push est sélectionné par le navigateur et, comme nous l'avons vu avec l'appel subscribe(), le navigateur envoie des requêtes réseau au service de notification push pour récupérer les détails qui constituent le PushSubscription.

Chaque navigateur utilise un service de transfert de données différent. N'ont-ils pas des API différentes ?

Tous les services push attendront la même API.

Cette API courante, appelée protocole Web Push, décrit la requête réseau que votre application doit effectuer pour déclencher un message push.

Si j'abonne un utilisateur sur son ordinateur, le sera-t-il également sur son téléphone ?

Malheureusement, non. Un utilisateur doit s'inscrire pour les notifications push sur chaque navigateur sur lequel il souhaite recevoir des messages. Notez également que cette opération nécessite que l'utilisateur accorde une autorisation sur chaque appareil.

Étapes suivantes

Ateliers de programmation