Envoyer des messages à l'aide de bibliothèques Web Push

L'un des problèmes rencontrés avec les notifications push Web est que le déclenchement d'un message push est extrêmement complexe. Pour déclencher un message push, une application doit envoyer une requête POST à un service push conformément au protocole Web Push. Pour utiliser le push sur tous les navigateurs, vous devez utiliser VAPID (également appelé "clés de serveur d'application"), ce qui nécessite essentiellement de définir un en-tête avec une valeur prouvant que votre application peut envoyer un message à un utilisateur. Pour envoyer des données avec un message push, les données doivent être chiffrées et des en-têtes spécifiques doivent être ajoutés pour que le navigateur puisse déchiffrer correctement le message.

Le principal problème lié au déclenchement du mode push est que si vous rencontrez un problème, il est difficile de le diagnostiquer. Cette situation s'améliore avec le temps et une compatibilité plus large avec les navigateurs, mais ce n'est pas une mince affaire. C'est pourquoi nous vous recommandons vivement d'utiliser une bibliothèque pour gérer le chiffrement, la mise en forme et le déclenchement de votre message push.

Si vous voulez vraiment savoir ce que font les bibliothèques, nous en parlerons dans la section suivante. Pour l'instant, nous allons examiner la gestion des abonnements et l'utilisation d'une bibliothèque Web push existante pour effectuer les requêtes push.

Dans cette section, nous allons utiliser la bibliothèque Node web-push. Les autres langues présentent des différences, mais elles ne sont pas trop différentes. Nous examinons Node, car il s'agit de JavaScript et qu'il devrait être le plus accessible pour les lecteurs.

Voici les étapes à suivre:

  1. Envoyez un abonnement à notre backend et enregistrez-le.
  2. Récupérez les abonnements enregistrés et déclenchez un message push.

Enregistrement des abonnements...

L'enregistrement et l'interrogation des PushSubscription à partir d'une base de données varient en fonction de la langue côté serveur et de la base de données choisies, mais il peut être utile de voir un exemple de la façon dont cela peut être fait.

Sur la page Web de démonstration, le PushSubscription est envoyé à notre backend via une simple requête POST:

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

Dans notre démonstration, le serveur Express possède un écouteur de requêtes correspondant pour le point de terminaison /api/save-subscription/:

app.post('/api/save-subscription/', function (req, res) {

Dans ce parcours, nous validons l'abonnement juste pour nous assurer que la requête est correcte et qu'elle n'est pas remplie de données indésirables :

const isValidSaveRequest = (req, res) => {
  // Check the request body has at least an endpoint.
  if (!req.body || !req.body.endpoint) {
    // Not a valid subscription.
    res.status(400);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'no-endpoint',
          message: 'Subscription must have an endpoint.',
        },
      }),
    );
    return false;
  }
  return true;
};

Si l'abonnement est valide, nous devons l'enregistrer et renvoyer une réponse JSON appropriée:

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

Cette démonstration utilise nedb pour stocker les abonnements. Il s'agit d'une simple base de données basée sur des fichiers, mais vous pouvez utiliser n'importe quelle base de données de votre choix. Nous n'utilisons que cette méthode, qui ne nécessite aucune configuration. Pour la production, vous devez utiliser quelque chose de plus fiable. (J'ai tendance à m'en tenir au bon vieux MySQL.)

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function (resolve, reject) {
    db.insert(subscription, function (err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
}

Envoyer des messages push

Pour envoyer un message push, nous avons finalement besoin d'un événement pour déclencher le processus d'envoi d'un message aux utilisateurs. Une approche courante consiste à créer une page d'administration qui vous permet de configurer et de déclencher le message push. Vous pouvez toutefois créer un programme à exécuter localement ou toute autre approche permettant d'accéder à la liste des PushSubscription et d'exécuter le code pour déclencher le message push.

Notre démonstration comporte une page "administrateur" qui vous permet de déclencher un push. Comme il s'agit d'une démonstration, il s'agit d'une page publique.

Je vais passer en revue chaque étape nécessaire au fonctionnement de la démonstration. Il s'agit d'étapes de base afin que tout le monde puisse suivre, y compris les débutants en Node.

Lorsque nous avons abordé l'abonnement d'un utilisateur, nous avons abordé l'ajout d'un applicationServerKey aux options subscribe(). Nous aurons besoin de cette clé privée côté backend.

Dans la démonstration, ces valeurs sont ajoutées à notre application Node comme suit (code ennuyeux, je sais, mais je veux juste que vous sachiez qu'il n'y a pas de magie) :

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

Nous devons ensuite installer le module web-push pour notre serveur de nœuds:

npm install web-push --save

Ensuite, dans notre script Node, nous exigeons le module web-push comme suit :

const webpush = require('web-push');

Nous pouvons maintenant commencer à utiliser le module web-push. Nous devons d'abord indiquer au module web-push les clés de notre serveur d'application. (N'oubliez pas qu'elles sont également appelées clés VAPID, car c'est le nom de la spécification.)

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey,
);

Notez que nous avons également inclus une chaîne "mailto:". Cette chaîne doit être une URL ou une adresse e-mail mailto. Cette information sera effectivement envoyée au service de push Web dans le cadre de la requête visant à déclencher un push. Cela permet, si un service de push Web doit contacter l'expéditeur, de disposer d'informations qui lui permettront de le faire.

Le module web-push est maintenant prêt à l'emploi. L'étape suivante consiste à déclencher un message push.

La démonstration se sert du panneau d'administration fictif pour déclencher l'envoi de messages push.

Capture d'écran de la page d'administration

Si vous cliquez sur le bouton "Trigger Push Message" (Déclencher un message push), une requête POST est envoyée à /api/trigger-push-msg/. C'est le signal pour que notre backend envoie des messages push. Nous créons donc la route express pour ce point de terminaison:

app.post('/api/trigger-push-msg/', function (req, res) {

Lorsque cette requête est reçue, nous récupérons les abonnements à partir de la base de données et, pour chacun d'eux, nous déclenchons un message push.

return getSubscriptionsFromDatabase().then(function (subscriptions) {
  let promiseChain = Promise.resolve();

  for (let i = 0; i < subscriptions.length; i++) {
    const subscription = subscriptions[i];
    promiseChain = promiseChain.then(() => {
      return triggerPushMsg(subscription, dataToSend);
    });
  }

  return promiseChain;
});

La fonction triggerPushMsg() peut ensuite utiliser la bibliothèque web-push pour envoyer un message à l'abonnement fourni.

const triggerPushMsg = function (subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend).catch((err) => {
    if (err.statusCode === 404 || err.statusCode === 410) {
      console.log('Subscription has expired or is no longer valid: ', err);
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      throw err;
    }
  });
};

L'appel à webpush.sendNotification() renvoie une promesse. Si le message a été envoyé avec succès, la promesse sera résolue et nous n'aurons rien à faire. Si la promesse est refusée, vous devez examiner l'erreur, car elle vous indiquera si l'PushSubscription est toujours valide ou non.

Pour déterminer le type d'erreur d'un service push, il est préférable de consulter le code d'état. Les messages d'erreur varient selon les services de push, et certains sont plus utiles que d'autres.

Dans cet exemple, il vérifie les codes d'état 404 et 410, qui sont les codes d'état HTTP pour "Introuvable" et "Déplacé". Si nous recevons l'un de ces messages, cela signifie que l'abonnement a expiré ou n'est plus valide. Dans ce cas, nous devons supprimer les abonnements de notre base de données.

En cas d'erreur, nous throw err, ce qui entraînera le rejet de la promesse renvoyée par triggerPushMsg().

Nous aborderons certains des autres codes d'état dans la section suivante, lorsque nous examinerons le protocole Web Push plus en détail.

Après avoir parcouru les abonnements, nous devons renvoyer une réponse JSON.

.then(() => {
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-send-messages',
    message: `We were unable to send messages to all subscriptions : ` +
        `'${err.message}'`
    }
}));
});

Nous avons passé en revue les principales étapes d'implémentation:

  1. Créez une API pour envoyer des abonnements depuis notre page Web vers notre backend afin qu'il puisse les enregistrer dans une base de données.
  2. Créez une API pour déclencher l'envoi de messages push (dans ce cas, une API appelée à partir du faux panneau d'administration).
  3. Récupérez tous les abonnements à partir de notre backend et envoyez un message à chaque abonnement avec l'une des bibliothèques Web Push.

Quel que soit votre backend (Node, PHP, Python, etc.), les étapes d'implémentation du push seront les mêmes.

À présent, que font exactement ces bibliothèques web-push pour nous ?

Étapes suivantes

Ateliers de programmation