Server für Push-Benachrichtigungen erstellen

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 vollständig. In diesem Codelab arbeiten Sie mit der serverseitigen Funktion.

Remix der Beispiel-App erstellen und in einem neuen Tab ansehen

Benachrichtigungen werden von der eingebetteten Glitch App automatisch blockiert, sodass du auf dieser Seite keine Vorschau der App ansehen kannst. Gehen Sie stattdessen so vor:

  1. Klicke auf Zu bearbeitende Remixe, damit das Projekt bearbeitet werden kann.
  2. Um die Website als Vorschau anzusehen, wählen Sie App ansehen und dann Vollbild Vollbild aus.

Die Live-App wird in einem neuen Chrome-Tab geöffnet. Klicke im eingebetteten Glitch auf View Source, um den Code wieder anzuzeigen.

Nimm beim Durcharbeiten dieses Codelab Änderungen am Code der eingebetteten Glitch auf dieser Seite vor. Aktualisieren Sie den neuen Tab mit Ihrer Live-App, um die Änderungen zu sehen.

Start-App und Code

Sehen Sie sich zuerst die Client-UI der App an.

Im neuen Chrome-Tab:

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

  2. Klicken Sie auf Schaltflächen in der Benutzeroberfläche. Die Ausgabe ist in der Chrome-Entwicklerkonsole verfügbar.

    • Service Worker registrieren registriert einen Service Worker für den Bereich deiner Glitch-Projekt-URL Wenn Sie die Registrierung eines Service Workers aufheben, wird er entfernt. Wenn damit ein Push-Abo verknüpft ist, wird auch das Push-Abo deaktiviert.

    • Subscribe to Push erstellt ein Push-Abo. 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, werden Sie durch Aktuelles Abo benachrichtigen darüber informiert, dass der Server eine Benachrichtigung an seinen Endpunkt sendet.

    • Alle Abos benachrichtigen weist den Server an, eine Benachrichtigung an alle Aboendpunkte in seiner Datenbank zu senden.

      Einige dieser Endpunkte sind möglicherweise inaktiv. Es ist immer möglich, dass ein Abo nach dem Senden einer Benachrichtigung durch den Server ausgeblendet wird.

Schauen wir uns an, was serverseitig vor sich geht. Nachrichten vom Servercode findest du im Node.js-Protokoll auf der Glitch-Oberfläche.

  • Klicke in der Glitch App auf Tools -> Logs (Tools -> Logs).

    Wahrscheinlich wird eine Meldung wie „Listening on port 3000“ angezeigt.

    Wenn Sie versucht haben, in der Live-App-Benutzeroberfläche auf Aktuelles Abo benachrichtigen oder Alle Abos benachrichtigen zu klicken, wird auch die folgende Meldung angezeigt:

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

Sehen wir uns nun den Code an.

  • public/index.js enthält den vollständigen Clientcode. Er führt eine Funktionserkennung durch, registriert den Service Worker und hebt seine Registrierung auf und steuert das Abo des Nutzers für Push-Benachrichtigungen. Außerdem sendet er Informationen über neue und gelöschte Abos an den Server.

    Da Sie nur an der Serverfunktion arbeiten, bearbeiten Sie diese Datei nicht (abgesehen vom Ausfüllen 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-UI.

  • .env enthält die Umgebungsvariablen, die Glitch beim Start in Ihren Anwendungsserver lädt. Sie füllen .env mit Authentifizierungsdetails für das Senden von Benachrichtigungen aus.

  • server.js ist die Datei, mit der Sie in diesem Codelab die meiste Arbeit ausführen.

    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 arbeiten Sie diese TODO-Elemente nacheinander durch.

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. Nutzer müssen sich außerdem sicher sein, dass sie von derselben App stammen, die das Abo eingerichtet hat, wenn sie eine Benachrichtigung erhalten. Sie müssen auch darauf vertrauen können, dass niemand den Inhalt der Benachrichtigung lesen kann.

Das Protokoll, das Push-Benachrichtigungen sicher und privat macht, wird als Voluntary Application Server Identification for Web Push (VAPID) bezeichnet. VAPID verwendet 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 sowie Benachrichtigungen zu verschlüsseln und zu senden.

Implementierung

Generieren Sie in diesem Schritt ein VAPID-Schlüsselpaar für Ihre Anwendung und fügen Sie es den Umgebungsvariablen hinzu. Laden Sie die Umgebungsvariablen in den Server und fügen Sie den öffentlichen Schlüssel als Konstante in den Clientcode ein.

  1. Verwende die Funktion generateVAPIDKeys der web-push-Bibliothek, um ein VAPID-Schlüsselpaar zu erstellen.

    Entfernen Sie in server.js die Kommentare bei 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 an das Node.js-Protokoll innerhalb der Glitch-Oberfläche ausgegeben (nicht an die Chrome-Konsole). Wenn du dir die VAPID-Schlüssel ansehen möchtest, wähle auf der Glitch-Oberfläche Tools -> Logs (Tools -> Logs) aus.

    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. Das erste von dir generierte Schlüsselpaar wird also möglicherweise aus dem Bild gescrollt, wenn weitere Ausgabedaten folgen.

  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. 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);
    
  5. 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
    }
    
  6. Kopieren Sie den public-Schlüssel und fügen Sie ihn in den Clientcode ein.

    Geben Sie unter 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 web-push npm-Paket zum Senden von Benachrichtigungen.

Dieses Paket verschlüsselt Benachrichtigungen automatisch, wenn webpush.sendNotification() aufgerufen wird. Du musst dir also keine Gedanken darüber machen.

Web-Push unterstützt mehrere Optionen für Benachrichtigungen. So können Sie beispielsweise Header an die Nachricht anhängen und die Inhaltscodierung angeben.

In diesem Codelab verwenden Sie nur zwei Optionen, die mit den folgenden Codezeilen definiert sind:

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 die Ablaufzeit einer Benachrichtigung festgelegt. So kann der Server vermeiden, dass eine Benachrichtigung an einen Nutzer gesendet wird, wenn diese 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

Das passiert, wenn der Nutzer Push-Benachrichtigungen abonniert:

  1. Der Nutzer klickt auf Abonnieren, um Push zu senden.

  2. Der Client verwendet die VAPID_PUBLIC_KEY-Konstante (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 URL /add-subscription, einschließlich des Abos als String-JSON im Text.

  4. Der Server ruft die als String markierte subscription aus dem Text der POST-Anfrage ab, parst sie in JSON und fügt sie 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 ist das neue Abo für den Server zum Senden von Benachrichtigungen verfügbar.

Implementierung

Anfragen für neue Abos gehen über die /add-subscription-Route ein, bei der es sich um eine POST-URL handelt. In server.js siehst du 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:

  • Rufen Sie das neue Abo aus dem Anfragetext ab.
  • Zugriff auf die Datenbank der aktiven Abos.
  • Fügen Sie das neue Abo der Liste der aktiven Abos hinzu.

So werden neue Abos verarbeitet:

  • Ändern Sie in server.js den Routen-Handler für /add-subscription wie folgt:

    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. Beispielsweise könnte ein Abo gelöscht werden, wenn der Browser den Service Worker herunterfährt.

Der Server kann jedoch in Erfahrung bringen, ob Abos über die App-Benutzeroberfläche gekündigt wurden. In diesem Schritt implementieren Sie eine Funktion zum Entfernen eines Abos aus der Datenbank.

Auf diese Weise vermeidet der Server, eine Menge Benachrichtigungen an nicht vorhandene Endpunkte zu senden. Bei einer einfachen Test-App spielt dies keine Rolle, aber in größerem Umfang wird es wichtig.

Implementierung

Anfragen zum Kündigen von Abos werden an die POST-URL /remove-subscription gesendet.

Der Stub-Routen-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.
  • Zugriff auf die Datenbank der aktiven Abos.
  • 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 wickelst du Abo-Kündigungen ab:

  • Ändern Sie in server.js den Routen-Handler für /remove-subscription wie folgt:

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