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

L'un des points de contrariété liés à l'utilisation de la fonction push Web est que le déclenchement d'un message push est extrêmement "difficile". Pour déclencher un message push, une application doit envoyer une requête POST à un service push en suivant le protocole Web push. Pour utiliser le mode push dans tous les navigateurs, vous devez utiliser VAPID (ou clés de serveur d'applications), 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, celles-ci doivent être chiffrées et des en-têtes spécifiques doivent être ajoutés pour que le navigateur puisse déchiffrer le message correctement.

Le principal problème lié au déclenchement d'une transmission push est que si vous rencontrez un problème, il est difficile de le diagnostiquer. Ce phénomène s'améliore avec le temps et la compatibilité avec les navigateurs est élargie, mais c'est loin d'être facile. 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 en savoir plus sur le fonctionnement des bibliothèques, nous verrons cela dans la section suivante. Pour l'instant, nous allons nous intéresser à 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 de nœuds web-push. Les autres langues auront des différences, mais elles ne seront pas trop différentes. Nous nous intéresserons à Node, car il s'agit d'un script JavaScript qui devrait être le plus accessible aux lecteurs.

Nous allons procéder comme suit:

  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 de PushSubscription à partir d'une base de données varient en fonction du langage côté serveur et du choix de la base de données, mais il peut être utile de voir un exemple de procédure.

Sur la page Web de démonstration, le PushSubscription est envoyé au 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.');
      }
    });
}

Le serveur Express de notre démonstration dispose d'un écouteur de requêtes correspondant pour le point de terminaison /api/save-subscription/:

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

Dans cette route, nous validons l'abonnement uniquement pour nous assurer que la requête est correcte et qu'elle n'est pas remplie de mémoire:

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 base de données simple basée sur des fichiers, mais vous pouvez vous servir de la base de données de votre choix. Nous l'utilisons uniquement car elle 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

Lorsqu'il s'agit d'envoyer un message push, nous avons au final 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. Toutefois, vous pouvez 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 « J'aime » qui vous permet de déclencher un push. Puisqu'il s'agit d'une démo, c'est une page publique.

Je vais passer en revue chaque étape nécessaire au fonctionnement de la démonstration. Il s'agira de petits pas que tout le monde pourra suivre, y compris tous les nouveaux sur Node.

Lorsque nous avons parlé de l'abonnement d'un utilisateur, nous avons parlé de l'ajout d'un applicationServerKey aux options subscribe(). C'est sur le backend que nous aurons besoin de cette clé privée.

Dans la démonstration, ces valeurs sont ajoutées à l'application Node comme suit (du 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 avons besoin du 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 du serveur d'application. (Rappelez-vous qu'elles sont également appelées "clés VAPID", car il s'agit du 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 en fait envoyée au service Web push dans le cadre de la requête de déclenchement d'une transmission. En effet, si un service Web Push doit entrer en contact avec l'expéditeur, il dispose de certaines informations qui lui permettront de le faire.

Maintenant que le module web-push est prêt à l'emploi, l'étape suivante consiste à déclencher un message push.

La démonstration utilise le panneau d'administration fictif pour déclencher des messages push.

Capture d'écran de la page Administration.

En cliquant sur le bouton "Trigger Push Message" (Déclencher le message push), une requête POST est envoyée à /api/trigger-push-msg/, qui est le signal permettant à notre backend d'envoyer des messages push. Nous créons donc la route dans 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 dans 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 bien été envoyé, la promesse se résout et vous n'avez rien à faire. Si la promesse est rejetée, vous devez examiner l'erreur, car elle vous indiquera si PushSubscription est toujours valide ou non.

Pour déterminer le type d'erreur provenant d'un service push, il est préférable d'examiner le code d'état. Les messages d'erreur varient d'un service d'envoi à l'autre et certains sont plus utiles que d'autres.

Dans cet exemple, il recherche les codes d'état 404 et 410, qui sont les codes d'état HTTP "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.

Dans le cas d'une autre erreur, nous n'utilisons que la méthode throw err, ce qui permet de rejeter la promesse renvoyée par triggerPushMsg().

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

Après avoir parcouru les abonnements en boucle, 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 de l'implémentation:

  1. Créez une API pour envoyer les abonnements de 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 panneau d'administration fictif).
  3. Récupérez tous les abonnements de notre backend et envoyez un message à chaque abonnement à l'aide de l'une des bibliothèques Web push.

Quel que soit votre backend (nœud, PHP, Python, etc.), la procédure d'implémentation du mode push sera identique.

Ensuite, que nous apportent exactement ces bibliothèques web-push ?

Étapes suivantes

Ateliers de programmation