In diesem Codelab erstellen Sie einen Server für Push-Benachrichtigungen. Der Server verwaltet eine Liste von Push-Abos und sendet Benachrichtigungen an diese.
Der Clientcode ist bereits fertig. In diesem Codelab arbeiten Sie an den serverseitigen Funktionen.
Beispiel-App neu zusammenstellen und in einem neuen Tab ansehen
Benachrichtigungen werden für die eingebettete Glitch App automatisch blockiert, sodass du auf dieser Seite keine Vorschau der App anzeigen kannst. Gehen Sie stattdessen wie folgt vor:
- Klicke auf Zum Bearbeiten Remix, damit das Projekt bearbeitet werden kann.
- Wenn Sie sich eine Vorschau der Website ansehen möchten, klicken Sie auf App ansehen. Drücken Sie dann
Vollbild
Die Live-App wird in einem neuen Chrome-Tab geöffnet. Klicken Sie im eingebetteten Glitch auf View Source (Quelle anzeigen), um den Code noch einmal anzuzeigen.
Während du dieses Codelab durcharbeitest, kannst du den Code im eingebetteten Glitch auf dieser Seite ändern. Aktualisieren Sie den neuen Tab mit Ihrer Live-App, um die Änderungen zu sehen.
Mit der Start-App und ihrem Code vertraut machen
Sehen Sie sich zuerst die Client-Benutzeroberfläche der App an.
Auf dem neuen Chrome-Tab:
Drücken Sie „Strg + Umschalttaste + J“ (oder „Befehlstaste + Option + J“ auf einem Mac), um die Entwicklertools zu öffnen. Klicken Sie auf den Tab Console.
Klicken Sie auf Schaltflächen in der Benutzeroberfläche. Die Ausgabe finden Sie in der Chrome-Entwicklerkonsole.
Mit Service Worker registrieren wird ein Service Worker für den Bereich der URL deines Glitch-Projekts registriert. Durch Registrierung eines Service Workers aufheben wird der Service Worker entfernt. Wenn ein Push-Abo damit verknüpft ist, wird auch das Push-Abo deaktiviert.
Mit Subscribe to push wird ein Push-Abo erstellt. Sie ist nur verfügbar, wenn ein Service Worker registriert wurde und im Clientcode die Konstante
VAPID_PUBLIC_KEY
vorhanden ist (mehr dazu später). Sie können also noch nicht darauf klicken.Wenn Sie ein aktives Push-Abo haben, fordert Sie mit Aktuelles Abo benachrichtigen an, dass der Server eine Benachrichtigung an seinen Endpunkt sendet.
Alle Abos benachrichtigen weist den Server an, eine Benachrichtigung an alle Aboendpunkte in der Datenbank zu senden.
Beachten Sie, dass einige dieser Endpunkte möglicherweise inaktiv sind. Es kann immer vorkommen, dass ein Abonnement entfernt wird, sobald der Server eine Benachrichtigung an das Abonnement sendet.
Sehen wir uns an, was auf der Serverseite passiert. Nachrichten vom Servercode findest du im Node.js-Protokoll auf der Glitch-Oberfläche.
Klicke in der Glitch App auf Tools -> Logs:
Sie sehen wahrscheinlich eine Meldung wie
Listening on port 3000
.Wenn du versucht hast, auf der Benutzeroberfläche der Live-App auf Aktuelles Abo benachrichtigen oder Alle Abos benachrichtigen zu klicken, wird die folgende Meldung angezeigt:
TODO: Implement sendNotifications() Endpoints to send to: []
Schauen wir uns jetzt etwas Code an.
public/index.js
enthält den vollständigen Clientcode. Er führt eine Funktionserkennung durch, registriert und hebt die Registrierung des Service Workers auf und steuert das Abo des Nutzers für Push-Benachrichtigungen. Außerdem werden Informationen zu neuen und gelöschten Abos an den Server gesendet.Da Sie nur an der Serverfunktion arbeiten, bearbeiten Sie diese Datei nicht (abgesehen davon, dass die Konstante
VAPID_PUBLIC_KEY
ausgefüllt wird).public/service-worker.js
ist ein einfacher Service Worker, der Push-Ereignisse erfasst und Benachrichtigungen anzeigt./views/index.html
enthält die Anwendungs-UI..env
enthält die Umgebungsvariablen, die Glitch beim Start in Ihren Anwendungsserver lädt. Sie geben in.env
Authentifizierungsdetails zum Senden von Benachrichtigungen ein.server.js
ist die Datei, in der Sie in diesem Codelab den Großteil Ihrer Arbeit erledigen.Mit dem Startcode wird ein einfacher Express-Webserver erstellt. Es gibt vier TODO-Elemente für Sie, die in Codekommentaren mit
TODO:
gekennzeichnet sind. Folgende Schritte sind erforderlich:In diesem Codelab wirst du diese TODO-Elemente nacheinander durcharbeiten.
VAPID-Details generieren und laden
Ihr erstes TODO-Element besteht darin, VAPID-Details zu generieren, sie den Node.js-Umgebungsvariablen hinzuzufügen und den Client- und Servercode mit den neuen Werten zu aktualisieren.
Hintergrund
Wenn Nutzer Benachrichtigungen abonnieren, müssen sie der Identität der App und ihres Servers vertrauen können. Nutzer müssen sich auch darauf verlassen können, dass eine Benachrichtigung von derselben App stammt, über die das Abo eingerichtet wurde. Außerdem müssen sie darauf vertrauen können, dass niemand anderes den Benachrichtigungsinhalt lesen kann.
Das Protokoll, das Push-Benachrichtigungen sicher und privat macht, heißt Voluntary Application Server Identification for Web Push (VAPID). VAPID nutzt Public-Key-Kryptografie, um die Identität von Apps, Servern und Aboendpunkten zu überprüfen und Benachrichtigungsinhalte zu verschlüsseln.
In dieser App verwenden Sie das web-push npm-Paket, um VAPID-Schlüssel zu generieren und Benachrichtigungen zu verschlüsseln und zu senden.
Implementierung
Generieren Sie in diesem Schritt ein Paar VAPID-Schlüssel für Ihre App und fügen Sie sie den Umgebungsvariablen hinzu. Laden Sie die Umgebungsvariablen auf den Server und fügen Sie den öffentlichen Schlüssel als Konstante im Clientcode hinzu.
Mit der Funktion
generateVAPIDKeys
derweb-push
-Bibliothek können Sie ein VAPID-Schlüsselpaar erstellen.Entfernen Sie in server.js die Kommentare aus den folgenden Codezeilen:
server.js
// Generate VAPID keys (only do this once). /* * const vapidKeys = webpush.generateVAPIDKeys(); * console.log(vapidKeys); */ const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys);
Nachdem Ihre App durch Glitch neu gestartet wurde, werden die generierten Schlüssel im Node.js-Protokoll in der Glitch-Oberfläche ausgegeben (nicht in der Chrome-Konsole). Um die VAPID-Schlüssel anzuzeigen, wählen Sie Tools -> Protokolle auf der Glitch-Oberfläche.
Achten Sie darauf, Ihre öffentlichen und privaten Schlüssel aus demselben Schlüsselpaar zu kopieren.
Glitch startet deine App jedes Mal neu, wenn du deinen Code bearbeitest. Daher scrollt das erste von dir generierte Schlüsselpaar möglicherweise aus der Ansicht, wenn mehr Ausgabe folgt.
Kopieren Sie die VAPID-Schlüssel und fügen Sie sie in .env ein. Setzen Sie die Schlüssel in doppelte Anführungszeichen (
"..."
).Für
VAPID_SUBJECT
können Sie"mailto:test@test.test"
eingeben..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"
Kommentieren Sie diese beiden Codezeilen in server.js noch einmal aus, da Sie VAPID-Schlüssel nur einmal generieren müssen.
server.js
// Generate VAPID keys (only do this once). /* const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys); */ const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys);
Laden Sie in server.js die VAPID-Details aus den Umgebungsvariablen.
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 }
Kopieren Sie den öffentlichen Schlüssel und fügen Sie ihn in den Clientcode ein.
Geben Sie in public/index.js denselben Wert für
VAPID_PUBLIC_KEY
ein, den Sie in die Umgebung-Datei kopiert haben:public/index.js
// Copy from .env const VAPID_PUBLIC_KEY = ''; const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...'; ````
Funktionen zum Senden von Benachrichtigungen implementieren
Hintergrund
In dieser App verwenden Sie das web-push npm-Paket, um Benachrichtigungen zu senden.
Dieses Paket verschlüsselt Benachrichtigungen automatisch, wenn webpush.sendNotification()
angerufen wird. Du musst dir also keine Gedanken darüber machen.
Web-Push akzeptiert mehrere Optionen für Benachrichtigungen – Sie können beispielsweise Header an die Nachricht anhängen und die Inhaltscodierung festlegen.
In diesem Codelab werden nur zwei Optionen verwendet, die durch die folgenden Codezeilen definiert werden:
let options = {
TTL: 10000; // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails; // VAPID keys from .env
};
Mit der Option TTL
(Gültigkeitsdauer) wird ein Zeitlimit für eine Benachrichtigung festgelegt. So kann der Server vermeiden, einem Nutzer eine Benachrichtigung zu senden, wenn sie nicht mehr relevant ist.
Die Option vapidDetails
enthält die VAPID-Schlüssel, die Sie aus den Umgebungsvariablen geladen haben.
Implementierung
Ändern Sie in server.js die Funktion sendNotifications
so:
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);
});
}
Da webpush.sendNotification()
ein Promise zurückgibt, kannst du ganz einfach eine Fehlerbehandlung hinzufügen.
Ändern Sie in server.js die Funktion sendNotifications
noch einmal:
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} `);
});
});
}
Neue Abos verarbeiten
Hintergrund
Wenn der Nutzer Push-Benachrichtigungen abonniert, geschieht Folgendes:
Der Nutzer klickt auf Abonnieren, um Push-Benachrichtigungen zu erhalten.
Der Client verwendet die
VAPID_PUBLIC_KEY
-Konstante (den öffentlichen VAPID-Schlüssel des Servers), um ein eindeutiges, serverspezifischessubscription
-Objekt zu generieren. Dassubscription
-Objekt sieht so aus:{ "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...", "expirationTime": null, "keys": { "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...", "auth": "0IyyvUGNJ9RxJc83poo3bA" } }
Der Client sendet eine
POST
-Anfrage an die URL/add-subscription
, einschließlich des Abos als String im JSON-Format im Text.Der Server ruft den String
subscription
aus dem Text der POST-Anfrage ab, parst ihn wieder in JSON und fügt ihn der Abodatenbank hinzu.Die Datenbank speichert Abos mit ihren eigenen Endpunkten als Schlüssel:
{
"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: { ... }
},
}
Jetzt steht das neue Abo dem Server zum Senden von Benachrichtigungen zur Verfügung.
Implementierung
Anfragen für neue Abos gehen über die Route /add-subscription
ein, die eine POST-URL ist. In server.js sehen Sie einen Stub-Route-Handler:
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);
});
In Ihrer Implementierung muss dieser Handler:
- Rufen Sie das neue Abo aus dem Text der Anfrage ab.
- Auf die Datenbank der aktiven Abonnements zugreifen
- Fügen Sie das neue Abo der Liste der aktiven Abos hinzu.
So verarbeiten Sie neue Abos:
Ändern Sie in server.js den Routing-Handler für
/add-subscription
so: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);
});
Umgang mit Abo-Kündigungen
Hintergrund
Der Server weiß nicht immer, wann ein Abo inaktiv wird. Beispielsweise könnte ein Abo gelöscht werden, wenn der Browser den Service Worker herunterfährt.
Der Server kann jedoch über die Benutzeroberfläche der App ermitteln, ob Abos gekündigt wurden. In diesem Schritt implementieren Sie eine Funktion zum Entfernen eines Abos aus der Datenbank.
Auf diese Weise wird vermieden, dass der Server eine Reihe von Benachrichtigungen an nicht vorhandene Endpunkte sendet. Bei einer einfachen Test-App ist dies natürlich nicht wichtig, aber in größerem Maßstab wird es wichtig.
Implementierung
Anfragen zum Kündigen von Abos werden an die POST-URL /remove-subscription
gesendet.
Der Stub-Route-Handler in server.js sieht so aus:
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);
});
In Ihrer Implementierung muss dieser Handler:
- Rufen Sie den Endpunkt des gekündigten Abos aus dem Text der Anfrage ab.
- Auf die Datenbank der aktiven Abonnements zugreifen
- Entfernen Sie das gekündigte Abo aus der Liste der aktiven Abos.
Der Text der POST-Anfrage vom Client enthält den Endpunkt, den Sie entfernen müssen:
{
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9..."
}
Um Abo-Kündigungen zu handhaben:
Ändern Sie in server.js den Routing-Handler für
/remove-subscription
so: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);
});