Créer un serveur de notifications push

Dans cet atelier de programmation, vous allez créer un serveur de notifications push. Le serveur gérera la liste des abonnements push et leur enverra des notifications.

Le code client est déjà terminé. Dans cet atelier de programmation, vous allez travailler sur les fonctionnalités côté serveur.

Les notifications de l'application Glitch intégrée sont automatiquement bloquées. Vous ne pourrez donc pas prévisualiser l'application sur cette page. Voici ce que vous devez faire:

  1. Cliquez sur Remixer pour modifier pour rendre le projet modifiable.
  2. Pour prévisualiser le site, appuyez sur Afficher l'application, puis sur Plein écran plein écran.

L'application en direct s'ouvre dans un nouvel onglet Chrome. Dans Glitch intégré, cliquez sur Afficher le code source pour afficher à nouveau le code.

Au cours de cet atelier de programmation, modifiez le code dans Glitch intégré sur cette page. Actualisez le nouvel onglet avec votre application en direct pour voir les modifications.

Se familiariser avec l'application de démarrage et son code

Commencez par examiner l'UI cliente de l'application.

Dans le nouvel onglet Chrome:

  1. Appuyez sur Ctrl+Maj+J (ou Cmd+Option+J sur Mac) pour ouvrir DevTools. Cliquez sur l'onglet Console.

  2. Essayez de cliquer sur les boutons de l'interface utilisateur (consultez la console de développement Chrome pour obtenir la sortie).

    • Enregistrer un service worker enregistre un service worker pour le champ d'application de l'URL de votre projet Glitch. Désenregistrer le service worker supprime le service worker. Si un abonnement push y est associé, il sera également désactivé.

    • S'abonner à la diffusion push crée un abonnement push. Il n'est disponible que lorsqu'un service worker a été enregistré et qu'une constante VAPID_PUBLIC_KEY est présente dans le code client (nous y reviendrons plus tard). Vous ne pouvez donc pas encore cliquer dessus.

    • Lorsque vous disposez d'un abonnement push actif, Notify current subscription (Notifier l'abonnement actuel) demande au serveur d'envoyer une notification à son point de terminaison.

    • Notifier tous les abonnements indique au serveur d'envoyer une notification à tous les points de terminaison d'abonnement de sa base de données.

      Notez que certains de ces points de terminaison peuvent être inactifs. Il est toujours possible qu'un abonnement disparaisse au moment où le serveur lui envoie une notification.

Voyons ce qui se passe côté serveur. Pour afficher les messages du code du serveur, consultez le journal Node.js dans l'interface Glitch.

  • Dans l'application Glitch, cliquez sur Tools -> Logs (Outils -> Journaux).

    Un message semblable à Listening on port 3000 s'affichera probablement.

    Si vous avez essayé de cliquer sur Notifier l'abonnement actuel ou Notifier tous les abonnements dans l'interface utilisateur de l'application en ligne, le message suivant s'affiche également:

    TODO: Implement sendNotifications()
    Endpoints to send to:  []

Examinons maintenant du code.

  • public/index.js contient le code client finalisé. Il effectue la détection des fonctionnalités, enregistre et désenregistre le service worker, et contrôle l'abonnement de l'utilisateur aux notifications push. Il envoie également des informations sur les nouveaux abonnements et les abonnements supprimés au serveur.

    Comme vous ne travaillerez que sur la fonctionnalité du serveur, vous ne modifierez pas ce fichier (à l'exception de la constante VAPID_PUBLIC_KEY).

  • public/service-worker.js est un service worker simple qui capture les événements push et affiche les notifications.

  • /views/index.html contient l'UI de l'application.

  • .env contient les variables d'environnement que Glitch charge dans votre serveur d'application au démarrage. Vous renseignerez .env avec les informations d'authentification pour envoyer des notifications.

  • server.js est le fichier dans lequel vous allez effectuer la majeure partie de votre travail pendant cet atelier de programmation.

    Le code de départ crée un serveur Web Express simple. Vous devez effectuer quatre tâches, qui sont indiquées dans les commentaires de code par TODO:. Vos tâches sont les suivantes :

    Dans cet atelier de programmation, vous allez examiner ces éléments TODO un par un.

Générer et charger les détails VAPID

La première tâche à accomplir consiste à générer les informations VAPID, à les ajouter aux variables d'environnement Node.js et à mettre à jour le code client et serveur avec les nouvelles valeurs.

Contexte

Lorsque les utilisateurs s'abonnent aux notifications, ils doivent faire confiance à l'identité de l'application et de son serveur. Les utilisateurs doivent également être sûrs que, lorsqu'ils reçoivent une notification, elle provient de la même application que celle qui a configuré l'abonnement. Ils doivent également être sûrs que personne d'autre ne peut lire le contenu de la notification.

Le protocole qui rend les notifications push sécurisées et privées s'appelle Voluntary Application Server Identification for Web Push (VAPID). VAPID utilise la cryptographie à clé publique pour valider l'identité des applications, des serveurs et des points de terminaison d'abonnement, et pour chiffrer le contenu des notifications.

Dans cette application, vous allez utiliser le package npm web-push pour générer des clés VAPID, et pour chiffrer et envoyer des notifications.

Implémentation

Au cours de cette étape, vous allez générer une paire de clés VAPID pour votre application et les ajouter aux variables d'environnement. Chargez les variables d'environnement sur le serveur et ajoutez la clé publique en tant que constante dans le code client.

  1. Utilisez la fonction generateVAPIDKeys de la bibliothèque web-push pour créer une paire de clés VAPID.

    Dans server.js, supprimez les commentaires autour des lignes de code suivantes:

    server.js

    // Generate VAPID keys (only do this once).
    /*
     * const vapidKeys = webpush.generateVAPIDKeys();
     * console.log(vapidKeys);
     */

    const vapidKeys = webpush.generateVAPIDKeys();
    console
    .log(vapidKeys);
  2. Une fois que Glitch a redémarré votre application, il affiche les clés générées dans le journal Node.js de l'interface Glitch (et non dans la console Chrome). Pour afficher les clés VAPID, sélectionnez Tools -> Logs (Outils -> Journaux) dans l'interface Glitch.

    Assurez-vous de copier vos clés publique et privée à partir de la même paire de clés.

    Glitch redémarre votre application chaque fois que vous modifiez votre code. La première paire de clés que vous générez peut donc disparaître à l'écran à mesure que d'autres résultats s'affichent.

  3. Dans .env, copiez et collez les clés VAPID. Placez les clés entre guillemets doubles ("...").

    Pour VAPID_SUBJECT, vous pouvez saisir "mailto:test@test.test".

    .env

    # process.env.SECRET
    VAPID_PUBLIC_KEY
    =
    VAPID_PRIVATE_KEY
    =
    VAPID_SUBJECT
    =
    VAPID_PUBLIC_KEY
    ="BN3tWzHp3L3rBh03lGLlLlsq..."
    VAPID_PRIVATE_KEY
    ="I_lM7JMIXRhOk6HN..."
    VAPID_SUBJECT
    ="mailto:test@test.test"
  4. Dans server.js, commentez à nouveau ces deux lignes de code, car vous n'avez besoin de générer des clés VAPID qu'une seule fois.

    server.js

    // Generate VAPID keys (only do this once).
    /*
    const vapidKeys = webpush.generateVAPIDKeys();
    console.log(vapidKeys);
    */

    const vapidKeys = webpush.generateVAPIDKeys();
    console
    .log(vapidKeys);
  5. Dans server.js, chargez les informations sur la propriété VAPID à partir des variables d'environnement.

    server.js

    const vapidDetails = {
     
    // TODO: Load VAPID details from environment variables.
      publicKey
    : process.env.VAPID_PUBLIC_KEY,
      privateKey
    : process.env.VAPID_PRIVATE_KEY,
      subject
    : process.env.VAPID_SUBJECT
    }
  6. Copiez et collez également la clé publique dans le code client.

    Dans public/index.js, saisissez la même valeur pour VAPID_PUBLIC_KEY que celle que vous avez copiée dans le fichier .env:

    public/index.js

    // Copy from .env
    const VAPID_PUBLIC_KEY = '';
    const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...';
    ````

Implémenter une fonctionnalité permettant d'envoyer des notifications

Contexte

Dans cette application, vous allez utiliser le package npm web-push pour envoyer des notifications.

Ce package chiffre automatiquement les notifications lorsque webpush.sendNotification() est appelé. Vous n'avez donc pas à vous en soucier.

web-push accepte plusieurs options pour les notifications. Par exemple, vous pouvez joindre des en-têtes au message et spécifier l'encodage du contenu.

Dans cet atelier de programmation, vous n'utiliserez que deux options, définies par les lignes de code suivantes:

let options = {
  TTL
: 10000; // Time-to-live. Notifications expire after this.
  vapidDetails
: vapidDetails; // VAPID keys from .env
};

L'option TTL (time-to-live) définit un délai d'expiration pour une notification. Cela permet au serveur d'éviter d'envoyer une notification à un utilisateur lorsqu'elle n'est plus pertinente.

L'option vapidDetails contient les clés VAPID que vous avez chargées à partir des variables d'environnement.

Implémentation

Dans server.js, modifiez la fonction sendNotifications comme suit:

server.js

function sendNotifications(database, endpoints) {
 
// TODO: Implement functionality to send notifications.
  console
.log('TODO: Implement sendNotifications()');
  console
.log('Endpoints to send to: ', endpoints);
  let notification
= JSON.stringify(createNotification());
  let options
= {
    TTL
: 10000, // Time-to-live. Notifications expire after this.
    vapidDetails
: vapidDetails // VAPID keys from .env
 
};
  endpoints
.map(endpoint => {
    let subscription
= database[endpoint];
    webpush
.sendNotification(subscription, notification, options);
 
});
}

Comme webpush.sendNotification() renvoie une promesse, vous pouvez facilement ajouter une gestion des erreurs.

Dans server.js, modifiez à nouveau la fonction sendNotifications:

server.js

function sendNotifications(database, endpoints) {
  let notification
= JSON.stringify(createNotification());
  let options
= {
    TTL
: 10000; // Time-to-live. Notifications expire after this.
    vapidDetails
: vapidDetails; // VAPID keys from .env
 
};
  endpoints
.map(endpoint => {
    let subscription
= database[endpoint];
    webpush
.sendNotification(subscription, notification, options);
    let id
= endpoint.substr((endpoint.length - 8), endpoint.length);
    webpush
.sendNotification(subscription, notification, options)
   
.then(result => {
      console
.log(`Endpoint ID: ${id}`);
      console
.log(`Result: ${result.statusCode} `);
   
})
   
.catch(error => {
      console
.log(`Endpoint ID: ${id}`);
      console
.log(`Error: ${error.body} `);
   
});
 
});
}

Gérer les nouveaux abonnements

Contexte

Voici ce qui se passe lorsque l'utilisateur s'abonne aux notifications push:

  1. L'utilisateur clique sur S'abonner aux notifications push.

  2. Le client utilise la constante VAPID_PUBLIC_KEY (clé VAPID publique du serveur) pour générer un objet subscription unique, spécifique au serveur. L'objet subscription se présente comme suit:

       {
         
    "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
         
    "expirationTime": null,
         
    "keys":
         
    {
           
    "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
           
    "auth": "0IyyvUGNJ9RxJc83poo3bA"
         
    }
       
    }
  3. Le client envoie une requête POST à l'URL /add-subscription, y compris l'abonnement au format JSON concaténé dans le corps.

  4. Le serveur récupère l'subscription concaténée à partir du corps de la requête POST, la convertit à nouveau en JSON et l'ajoute à la base de données des abonnements.

    La base de données stocke les abonnements à l'aide de leurs propres points de terminaison comme clé:

    {
     
"https://fcm...1234": {
        endpoint
: "https://fcm...1234",
        expirationTime
: ...,
        keys
: { ... }
     
},
     
"https://fcm...abcd": {
        endpoint
: "https://fcm...abcd",
        expirationTime
: ...,
        keys
: { ... }
     
},
     
"https://fcm...zxcv": {
        endpoint
: "https://fcm...zxcv",
        expirationTime
: ...,
        keys
: { ... }
     
},
   
}

Le nouveau serveur est désormais disponible pour envoyer des notifications.

Implémentation

Les requêtes de nouveaux abonnements sont acheminées vers la route /add-subscription, qui est une URL POST. Un gestionnaire de route fictif s'affiche dans server.js:

server.js

app.post('/add-subscription', (request, response) => {
 
// TODO: implement handler for /add-subscription
  console
.log('TODO: Implement handler for /add-subscription');
  console
.log('Request body: ', request.body);
  response
.sendStatus(200);
});

Dans votre implémentation, ce gestionnaire doit:

  • Récupérez le nouvel abonnement dans le corps de la requête.
  • Accéder à la base de données des abonnements actifs.
  • Ajoutez le nouvel abonnement à la liste des abonnements actifs.

Pour gérer les nouveaux abonnements:

  • Dans server.js, modifiez le gestionnaire de route pour /add-subscription comme suit:

    server.js

    app.post('/add-subscription', (request, response) => {
     
// TODO: implement handler for /add-subscription
      console
.log('TODO: Implement handler for /add-subscription');
      console
.log('Request body: ', request.body);
      let subscriptions
= Object.assign({}, request.session.subscriptions);
      subscriptions
[request.body.endpoint] = request.body;
      request
.session.subscriptions = subscriptions;
      response
.sendStatus(200);
   
});

Gérer les résiliations d'abonnements

Contexte

Le serveur ne sait pas toujours quand un abonnement devient inactif. Par exemple, un abonnement peut être effacé lorsque le navigateur arrête le service worker.

Le serveur peut toutefois savoir si des abonnements sont résiliés via l'interface utilisateur de l'application. Dans cette étape, vous allez implémenter une fonctionnalité permettant de supprimer un abonnement de la base de données.

Le serveur évite ainsi d'envoyer un tas de notifications à des points de terminaison inexistants. Bien sûr, cela n'a pas vraiment d'importance pour une application de test simple, mais cela devient important à plus grande échelle.

Implémentation

Les demandes de résiliation d'abonnement sont envoyées à l'URL POST /remove-subscription.

Le gestionnaire de route fictif dans server.js se présente comme suit:

server.js

app.post('/remove-subscription', (request, response) => {
 
// TODO: implement handler for /remove-subscription
  console
.log('TODO: Implement handler for /remove-subscription');
  console
.log('Request body: ', request.body);
  response
.sendStatus(200);
});

Dans votre implémentation, ce gestionnaire doit:

  • Récupérez le point de terminaison de l'abonnement résilié dans le corps de la requête.
  • Accéder à la base de données des abonnements actifs.
  • Supprimez l'abonnement annulé de la liste des abonnements actifs.

Le corps de la requête POST du client contient le point de terminaison que vous devez supprimer:

{
 
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9..."
}

Pour gérer les résiliations d'abonnements:

  • Dans server.js, modifiez le gestionnaire de route pour /remove-subscription comme suit:

    server.js

  app.post('/remove-subscription', (request, response) => {
   
// TODO: implement handler for /remove-subscription
    console
.log('TODO: Implement handler for /remove-subscription');
    console
.log('Request body: ', request.body);
    let subscriptions
= Object.assign({}, request.session.subscriptions);
   
delete subscriptions[request.body.endpoint];
    request
.session.subscriptions = subscriptions;
    response
.sendStatus(200);
 
});