Server für Push-Benachrichtigungen erstellen

In diesem Codelab erstellen Sie einen Push-Benachrichtigungsserver. 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.

Benachrichtigungen von der eingebetteten Glitch-App werden automatisch blockiert. Sie können sich die App auf dieser Seite also nicht in der Vorschau ansehen. Gehen Sie stattdessen wie folgt vor:

  1. Klicke auf Remix zum Bearbeiten, um das Projekt bearbeitbar zu machen.
  2. Wenn Sie sich eine Vorschau der Website ansehen möchten, klicken Sie auf App ansehen und dann auf Vollbild Vollbild.

Die Live-App wird in einem neuen Chrome-Tab geöffnet. Klicken Sie im eingebetteten Glitch auf Quellcode ansehen, um den Code wieder einzublenden.

Nehmen Sie während dieses Codelabs Änderungen am Code im eingebetteten Glitch auf dieser Seite vor. Aktualisieren Sie den neuen Tab mit Ihrer Live-App, um die Änderungen zu sehen.

Start-App und Code kennenlernen

Sehen wir uns zuerst die Client-Benutzeroberfläche der App an.

Auf dem neuen Chrome-Tab:

  1. Drücken Sie Strg + Umschalttaste + J (oder Befehlstaste + Optionstaste + J auf einem Mac), um die Entwicklertools zu öffnen. Klicken Sie auf den Tab Console.

  2. Klicken Sie auf Schaltflächen in der Benutzeroberfläche und prüfen Sie die Ausgabe in der Chrome-Entwicklerkonsole.

    • Mit Service Worker registrieren wird ein Service Worker für den Bereich der URL deines Glitch-Projekts registriert. Wenn Sie Service Worker abmelden auswählen, wird der Service Worker entfernt. Wenn ein Push-Abo verknüpft ist, wird auch dieses deaktiviert.

    • Mit Push abonnieren wird ein Push-Abo erstellt. Sie ist nur verfügbar, wenn ein Service Worker registriert wurde und im Clientcode eine VAPID_PUBLIC_KEY-Konstante vorhanden ist (mehr dazu später). Sie können also noch nicht darauf klicken.

    • Wenn Sie ein aktives Push-Abo haben, wird mit Aktuelles Abo benachrichtigen angefordert, 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.

      Einige dieser Endpunkte sind möglicherweise inaktiv. Es ist immer möglich, dass ein Abo verschwindet, bis der Server eine Benachrichtigung an das Abo sendet.

Sehen wir uns an, was auf Serverseite passiert. Nachrichten aus dem Servercode finden Sie im Node.js-Log in der Glitch-Benutzeroberfläche.

  • Klicken Sie in der Glitch App auf Tools -> Logs (Tools -> Protokolle).

    Es wird wahrscheinlich eine Meldung wie Listening on port 3000 angezeigt.

    Wenn Sie in der Live-App-Benutzeroberfläche auf Aktuelles Abo benachrichtigen oder Alle Abos benachrichtigen geklickt haben, wird außerdem 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 die Funktionserkennung durch, registriert und deregistriert den Dienst-Worker und steuert die Anmeldung 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 (außer der Konstante VAPID_PUBLIC_KEY).

  • public/service-worker.js ist ein einfacher Service Worker, der Push-Ereignisse erfasst und Benachrichtigungen anzeigt.

  • /views/index.html enthält die App-Benutzeroberfläche.

  • .env enthält die Umgebungsvariablen, die Glitch beim Starten in Ihren App-Server lädt. Geben Sie in .env die Authentifizierungsdetails für das 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 gehen Sie diese TODO-Elemente nacheinander durch.

VAPID-Details generieren und laden

Als Erstes müssen Sie VAPID-Details generieren, sie den Node.js-Umgebungsvariablen hinzufügen und den Client- und Servercode mit den neuen Werten aktualisieren.

Hintergrund

Wenn Nutzer Benachrichtigungen abonnieren, müssen sie der Identität der App und ihres Servers vertrauen. Nutzer müssen außerdem sicher sein können, dass eine Benachrichtigung von derselben App stammt, über die das Abo eingerichtet wurde. Außerdem muss er darauf vertrauen können, dass niemand sonst den Inhalt der Benachrichtigung lesen kann.

Das Protokoll, das Push-Benachrichtigungen sicher und privat macht, heißt Voluntary Application Server Identification for Web Push (VAPID). VAPID verwendet die 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

In diesem Schritt generieren Sie ein VAPID-Schlüsselpaar für Ihre App und fügen sie den Umgebungsvariablen hinzu. Laden Sie die Umgebungsvariablen auf den Server und fügen Sie den öffentlichen Schlüssel als Konstante in den Clientcode ein.

  1. Verwende die generateVAPIDKeys-Funktion der web-push-Bibliothek, um ein VAPID-Schlüsselpaar zu 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);
  2. Nachdem Glitch Ihre App neu gestartet hat, werden die generierten Schlüssel im Node.js-Protokoll in der Glitch-Benutzeroberfläche ausgegeben (nicht in der Chrome-Konsole). Wenn du die VAPID-Schlüssel sehen möchtest, wähle in der Glitch-Benutzeroberfläche Tools -> Logs aus.

    Achten Sie darauf, dass Sie Ihre öffentlichen und privaten Schlüssel aus demselben Schlüsselpaar kopieren.

    Glitch startet Ihre App jedes Mal neu, wenn Sie Ihren Code bearbeiten. Das erste von Ihnen generierte Schlüsselpaar wird möglicherweise nicht mehr angezeigt, wenn weitere Ausgabe folgt.

  3. 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"
  4. Kommentiere in server.js diese beiden Codezeilen wieder, da du VAPID-Schlüssel nur einmal generieren musst.

    server.js

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

    const vapidKeys = webpush.generateVAPIDKeys();
    console
    .log(vapidKeys);
  5. Lade 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
    }
  6. Kopieren Sie auch 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 .env-Datei kopiert haben:

    public/index.js

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

Funktion zum Senden von Benachrichtigungen implementieren

Hintergrund

In dieser App verwenden Sie das npm-Paket „web-push“, um Benachrichtigungen zu senden.

Dieses Paket verschlüsselt Benachrichtigungen automatisch, wenn webpush.sendNotification() aufgerufen wird.

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 verwenden Sie nur zwei Optionen, die mit den 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, einer Nutzerin eine Benachrichtigung zu senden, die nicht mehr relevant ist.

Die Option vapidDetails enthält die VAPID-Schlüssel, die du aus den Umgebungsvariablen geladen hast.

Implementierung

Ändern Sie in server.js die Funktion sendNotifications folgendermaßen:

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 Versprechen zurückgibt, können Sie 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

So funktioniert es, wenn der Nutzer Push-Benachrichtigungen abonniert:

  1. Der Nutzer klickt auf Abonnieren, um Push-Benachrichtigungen zu erhalten.

  2. Der Client verwendet die Konstante VAPID_PUBLIC_KEY (den öffentlichen VAPID-Schlüssel des Servers), um ein eindeutiges, serverspezifisches subscription-Objekt zu generieren. Das subscription-Objekt sieht so aus:

       {
         
    "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
         
    "expirationTime": null,
         
    "keys":
         
    {
           
    "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
           
    "auth": "0IyyvUGNJ9RxJc83poo3bA"
         
    }
       
    }
  3. Der Client sendet eine POST-Anfrage an die /add-subscription-URL, einschließlich des Abos als JSON-String im Text.

  4. 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
: { ... }
     
},
   
}

Das neue Abo ist jetzt für den Server zum Senden von Benachrichtigungen verfügbar.

Implementierung

Anfragen für neue Abos werden an die Route /add-subscription weitergeleitet, eine POST-URL. In server.js sehen Sie einen Stub-Routen-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:

  • Rufe das neue Abo aus dem Textkörper der Anfrage ab.
  • Greifen Sie auf die Datenbank der aktiven Abonnements zu.
  • Fügen Sie das neue Abo der Liste der aktiven Abos hinzu.

So verwalten 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);
   
});

Abokündigungen verarbeiten

Hintergrund

Der Server weiß nicht immer, wann ein Abo inaktiv wird. Ein Abo kann beispielsweise gelöscht werden, wenn der Browser den Dienst-Worker beendet.

Der Server kann jedoch über die Benutzeroberfläche der App ermitteln, ob Abos gekündigt wurden. In diesem Schritt implementieren Sie eine Funktion, mit der ein Abo aus der Datenbank entfernt werden kann.

So vermeidet der Server, eine Reihe von Benachrichtigungen an nicht vorhandene Endpunkte zu senden. Bei einer einfachen Test-App spielt das natürlich keine Rolle, aber in größerem Maßstab wird es wichtig.

Implementierung

Anfragen zur Kündigung 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:

  • Rufe den Endpunkt des gekündigten Abos aus dem Anfragetext ab.
  • Greifen Sie auf die Datenbank der aktiven Abonnements zu.
  • 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..."
}

So verwalten Sie Abokündigungen:

  • Ändern Sie in server.js den Routen-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);
 
});