Dans cet atelier de programmation, vous allez créer un serveur de notifications push. Le serveur gérera une liste d'abonnements push et leur enverra des notifications.
Le code client est déjà terminé. Dans cet atelier de programmation, vous travaillerez sur la fonctionnalité côté serveur.
Remixer l'application exemple et l'afficher dans un nouvel onglet
Les notifications sont automatiquement bloquées dans l'application Glitch intégrée. Vous ne pourrez donc pas prévisualiser l'application sur cette page. Voici comment procéder:
- Cliquez sur Remix to Edit (Remixer pour modifier) pour rendre le projet modifiable.
- Pour prévisualiser le site, appuyez sur Afficher l'application, puis sur Plein écran
.
L'application en ligne s'ouvre dans un nouvel onglet Chrome. Dans le Glitch intégré, cliquez sur View Source (Afficher la source) pour afficher à nouveau le code.
Tout au long de cet atelier de programmation, modifiez le code dans le Glitch intégré présenté sur cette page. Actualisez le nouvel onglet contenant votre application en ligne 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:
Appuyez sur Ctrl+Maj+J (ou Cmd+Option+J sur Mac) pour ouvrir les outils de développement. Cliquez sur l'onglet Console.
Essayez de cliquer sur les boutons de l'interface utilisateur (consultez la console pour les développeurs Chrome pour obtenir des résultats).
L'option Register service worker (Enregistrer un service worker) enregistre un service worker pour le champ d'application de l'URL de votre projet Glitch. L'option Annuler l'enregistrement du service worker supprime le service worker. Si un abonnement push lui est associé, il sera également désactivé.
L'option S'abonner au service push crée un abonnement push. Elle 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 cliquer dessus pour l'instant.Lorsque vous disposez d'un abonnement push actif, la fonction Notifier l'abonnement actuel demande que le serveur envoie une notification à son point de terminaison.
L'option 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 lorsque le serveur lui envoie une notification.
Voyons ce qui se passe côté serveur. Pour voir 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).
Vous verrez probablement un message tel que
Listening on port 3000
.Si vous avez essayé de cliquer sur Recevoir une notification pour les abonnements actuels ou Envoyer une notification à tous les abonnements dans l'interface en direct de l'application, le message suivant s'affiche également:
TODO: Implement sendNotifications() Endpoints to send to: []
Examinons maintenant quelques lignes de code.
public/index.js
contient le code client finalisé. Il détecte les fonctionnalités, enregistre le service worker et l'annule, et contrôle l'abonnement de l'utilisateur aux notifications push. Il envoie également au serveur des informations sur les abonnements nouveaux et supprimés.Étant donné que vous allez uniquement travailler sur la fonctionnalité serveur, vous n'allez pas modifier ce fichier (vous devrez remplir 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'applications au démarrage. Vous allez renseigner.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 au cours de cet atelier de programmation.Le code de démarrage crée un serveur Web Express simple. Vous avez quatre éléments de tâche à effectuer, marqués de
TODO:
dans les commentaires du code. Vos tâches sont les suivantes :Chargez les détails VAPID à partir de variables d'environnement.
Implémentez une fonctionnalité permettant d'envoyer des notifications.
Dans cet atelier de programmation, vous allez effectuer les tâches suivantes un par un.
Générer et charger des informations VAPID
La première étape consiste à générer des informations VAPID, à les ajouter aux variables d'environnement Node.js, puis à mettre à jour le code client et serveur avec les nouvelles valeurs.
Contexte
Lorsque les utilisateurs s'abonnent aux notifications, ils doivent pouvoir faire confiance à l'identité de l'application et de son serveur. Les utilisateurs doivent également s'assurer que lorsqu'ils reçoivent une notification, elle provient de l'application qui a configuré l'abonnement. Ils doivent également s'assurer que personne d'autre ne peut lire le contenu des notifications.
Le protocole qui assure la sécurité et la confidentialité des notifications push est appelé Voluntary Application Server Identification for Web Push (VAPID). Le VAPID utilise la cryptographie à clé publique pour valider l'identité des applications, des serveurs et des points de terminaison des abonnements, et pour chiffrer le contenu des notifications.
Dans cette application, vous allez utiliser le package web-push npm pour générer des clés VAPID, 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.
Utilisez la fonction
generateVAPIDKeys
de la bibliothèqueweb-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);
Une fois que Glitch a redémarré votre application, les clés générées sont enregistré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.
Veillez à 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. Par conséquent, il est possible que la première paire de clés générée ne soit plus visible, tandis que d'autres résultats sont générés par la suite.
Dans le fichier .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"
Dans server.js, mettez à nouveau ces deux lignes de code en commentaire, car vous n'avez besoin de générer les 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);
Dans server.js, chargez les informations 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 }
Copiez et collez également la clé publique dans le code client.
Dans public/index.js, saisissez pour
VAPID_PUBLIC_KEY
la même valeur 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é pour envoyer des notifications
Contexte
Dans cette application, vous utiliserez le package web-push npm 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 de notification, 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 avec 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
(durée de vie) définit un délai d'expiration pour une notification. Cela permet au serveur d'éviter d'envoyer une notification à un utilisateur lorsque celle-ci 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:
L'utilisateur clique sur S'abonner au service push.
Le client utilise la constante
VAPID_PUBLIC_KEY
(la clé VAPID publique du serveur) pour générer un objetsubscription
unique, spécifique au serveur. L'objetsubscription
se présente comme suit:{ "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...", "expirationTime": null, "keys": { "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...", "auth": "0IyyvUGNJ9RxJc83poo3bA" } }
Le client envoie une requête
POST
à l'URL/add-subscription
, en incluant l'abonnement sous forme de fichier JSON concaténé dans le corps.Le serveur récupère la chaîne
subscription
dans le corps de la requête POST, l'analyse au format JSON et l'ajoute à la base de données "subscriptions".La base de données stocke les abonnements en utilisant 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: { ... }
},
}
Désormais, le nouvel abonnement est disponible sur le serveur pour l'envoi de notifications.
Implémentation
Les requêtes de nouveaux abonnements sont transmises par la route /add-subscription
, qui est une URL POST. Un gestionnaire de routage bouchon 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édez à 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 routes 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 annulations d'abonnement
Contexte
Le serveur ne saura pas toujours quand un abonnement devient inactif. Par exemple, les données d'un abonnement peuvent être effacées lorsque le navigateur arrête le service worker.
Toutefois, le serveur peut être informé des abonnements résiliés via l'interface utilisateur de l'application. Au cours de cette étape, vous allez implémenter une fonctionnalité permettant de supprimer un abonnement de la base de données.
De cette façon, le serveur évite d'envoyer un grand nombre de notifications à des points de terminaison inexistants. Évidemment, cela n'a pas vraiment d'importance avec une application de test simple, mais cela devient important à plus grande échelle.
Implémentation
Les demandes d'annulation d'abonnements sont envoyées à l'URL POST /remove-subscription
.
Le gestionnaire de routage des bouchons 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édez à 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'abonnement:
Dans server.js, modifiez le gestionnaire de routes 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);
});