Nachrichten mit Web-Push-Bibliotheken senden

Matt Gaunt

Einer der Probleme bei der Arbeit mit Web-Push ist, dass das Auslösen von Push-Nachrichten extrem kompliziert ist. Zum Auslösen einer Push-Nachricht muss eine Anwendung eine POST-Anfrage an einen Push-Dienst gemäß dem Web-Push-Protokoll stellen. Um Push in allen Browsern zu verwenden, müssen Sie VAPID (auch Anwendungsserverschlüssel genannt) verwenden. Dazu muss im Grunde ein Header mit einem Wert festgelegt werden, der beweist, dass Ihre Anwendung einem Nutzer Nachrichten senden kann. Zum Senden von Daten mit einer Push-Nachricht müssen die Daten verschlüsselt und bestimmte Header hinzugefügt werden, damit der Browser die Nachricht korrekt entschlüsseln kann.

Das Hauptproblem beim Auslösen eines Push-Vorgangs besteht darin, dass es schwierig ist, das Problem zu diagnostizieren, wenn ein Problem auftritt. Mit der Zeit und der umfassenden Browserunterstützung wird diese Funktion zwar noch besser, ist aber alles andere als einfach. Aus diesem Grund empfehlen wir Ihnen dringend, für die Verschlüsselung, Formatierung und Auslösung Ihrer Push-Nachrichten eine Bibliothek zu verwenden.

Wenn Sie wirklich wissen möchten, was die Bibliotheken tun, behandeln wir das im nächsten Abschnitt. Zuerst sehen wir uns die Verwaltung von Abos und die Verwendung einer vorhandenen Web-Push-Bibliothek für Push-Anfragen an.

In diesem Abschnitt verwenden wir die web-push-Knotenbibliothek. Andere Sprachen werden sich unterscheiden, aber nicht allzu sehr ähnlich. Wir sehen uns Node.js an, da es sich um JavaScript handelt und für Leser am besten zugänglich sein sollte.

Wir werden die folgenden Schritte ausführen:

  1. Senden Sie ein Abo an unser Backend und speichern Sie es.
  2. Gespeicherte Abos abrufen und Push-Nachricht auslösen

Abos werden gespeichert

Das Speichern und Abfragen von PushSubscriptions aus einer Datenbank hängt von Ihrer serverseitigen Sprache und Datenbankauswahl ab. Es kann jedoch nützlich sein, ein Beispiel dafür zu sehen.

Auf der Demowebseite wird die PushSubscription mit einer einfachen POST-Anfrage an unser Backend gesendet:

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
    .then(function (response) {
      if (!response.ok) {
        throw new Error('Bad status code from server.');
      }

      return response.json();
    })
    .then(function (responseData) {
      if (!(responseData.data && responseData.data.success)) {
        throw new Error('Bad response from server.');
      }
    });
}

Der Express-Server in unserer Demo hat einen übereinstimmenden Anfrage-Listener für den Endpunkt /api/save-subscription/:

app.post('/api/save-subscription/', function (req, res) {

Auf dieser Route validieren wir das Abo nur, um sicherzustellen, dass die Anfrage in Ordnung ist und nicht voll mit Speichermüll ist:

const isValidSaveRequest = (req, res) => {
  // Check the request body has at least an endpoint.
  if (!req.body || !req.body.endpoint) {
    // Not a valid subscription.
    res.status(400);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'no-endpoint',
          message: 'Subscription must have an endpoint.',
        },
      }),
    );
    return false;
  }
  return true;
};

Wenn das Abo gültig ist, müssen wir es speichern und eine entsprechende JSON-Antwort zurückgeben:

return saveSubscriptionToDatabase(req.body)
  .then(function (subscriptionId) {
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({data: {success: true}}));
  })
  .catch(function (err) {
    res.status(500);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'unable-to-save-subscription',
          message:
            'The subscription was received but we were unable to save it to our database.',
        },
      }),
    );
  });

In dieser Demo werden die Abos mit nedb gespeichert. Es ist eine einfache dateibasierte Datenbank, Sie können aber auch eine beliebige Datenbank verwenden. Wir verwenden sie nur, weil dafür keine Einrichtung erforderlich ist. Für die Produktion sollte ein zuverlässigeres Gerät verwendet werden. (Ich bleibe bei gutem alten MySQL.)

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function (resolve, reject) {
    db.insert(subscription, function (err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
}

Push-Nachrichten senden

Beim Senden von Push-Nachrichten benötigen wir letztendlich ein Ereignis, das das Senden einer Nachricht an Nutzer auslöst. Ein gängiger Ansatz besteht darin, eine Administratorseite zu erstellen, auf der Sie die Push-Nachricht konfigurieren und auslösen können. Sie könnten jedoch ein Programm erstellen, das lokal ausgeführt wird, oder einen anderen Ansatz, der den Zugriff auf die Liste der PushSubscription und das Ausführen des Codes zum Auslösen der Push-Nachricht ermöglicht.

Unsere Demo hat eine „Gefällt mir“-Seite für Administratoren, mit der Sie einen Push auslösen können. Da es sich nur um eine Demo handelt, ist es eine öffentliche Seite.

Ich gehe die einzelnen Schritte zur Ausführung der Demo durch. Es handelt sich um kleine Schritte, mit denen jeder Schritt befolgen kann, auch wenn er neu bei Node ist.

Als wir über das Abonnieren eines Nutzers gesprochen haben, ging es um das Hinzufügen eines applicationServerKey zu den subscribe()-Optionen. Wir benötigen diesen privaten Schlüssel am Backend.

In der Demo werden diese Werte so in unsere Node-App eingefügt (langweiliger Code, den ich kenne, aber Sie müssen nur wissen, dass es keine Magie gibt):

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

Als Nächstes müssen wir das Modul web-push für unseren Knotenserver installieren:

npm install web-push --save

Dann benötigen wir in unserem Node-Skript das web-push-Modul:

const webpush = require('web-push');

Jetzt können wir das Modul web-push verwenden. Zuerst müssen wir dem web-push-Modul unsere Anwendungsserverschlüssel mitteilen. (Denken Sie daran, dass sie auch als VAPID-Schlüssel bezeichnet werden, da dies der Name der Spezifikation ist.)

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey,
);

Beachten Sie, dass wir auch eine "mailto:"-Zeichenfolge eingefügt haben. Diese Zeichenfolge muss entweder eine URL oder eine Mailto-E-Mail-Adresse sein. Diese Information wird als Teil der Anfrage zum Auslösen eines Push-Vorgangs an den Web-Push-Dienst gesendet. Damit soll erreicht werden, dass ein Web-Push-Dienst über einige Informationen verfügt, die es ihm ermöglichen, mit dem Absender in Kontakt zu treten.

Damit ist das Modul web-push einsatzbereit. Im nächsten Schritt wird eine Push-Nachricht ausgelöst.

In der Demo werden Push-Nachrichten über das Admin-Steuerfeld ausgelöst.

Screenshot der Seite „Verwaltung“.

Wenn Sie auf die Schaltfläche "Push-Nachricht auslösen" klicken, wird eine POST-Anfrage an /api/trigger-push-msg/ gesendet. Dies ist das Signal für unser Back-End, Push-Nachrichten zu senden. Daher erstellen wir die Express-Route für diesen Endpunkt:

app.post('/api/trigger-push-msg/', function (req, res) {

Wenn diese Anfrage eingeht, rufen wir die Abos aus der Datenbank ab und lösen für jedes Abo eine Push-Nachricht aus.

return getSubscriptionsFromDatabase().then(function (subscriptions) {
  let promiseChain = Promise.resolve();

  for (let i = 0; i < subscriptions.length; i++) {
    const subscription = subscriptions[i];
    promiseChain = promiseChain.then(() => {
      return triggerPushMsg(subscription, dataToSend);
    });
  }

  return promiseChain;
});

Die Funktion triggerPushMsg() kann dann die Web-Push-Bibliothek verwenden, um eine Nachricht an das bereitgestellte Abo zu senden.

const triggerPushMsg = function (subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend).catch((err) => {
    if (err.statusCode === 404 || err.statusCode === 410) {
      console.log('Subscription has expired or is no longer valid: ', err);
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      throw err;
    }
  });
};

Der Aufruf von webpush.sendNotification() gibt ein Promise zurück. Wenn die Nachricht erfolgreich gesendet wurde, wird das Promise aufgelöst und wir müssen nichts weiter tun. Wenn das Promise abgelehnt wird, musst du den Fehler untersuchen, da er dir Aufschluss darüber gibt, ob PushSubscription noch gültig ist oder nicht.

Am besten sehen Sie sich den Statuscode an, um den Fehlertyp von einem Push-Dienst zu ermitteln. Die Fehlermeldungen variieren je nach Push-Dienst und einige sind hilfreicher als andere.

In diesem Beispiel wird nach den Statuscodes 404 und 410 gesucht, bei denen es sich um die HTTP-Statuscodes für „Nicht gefunden“ und „Entfernt“ handelt. Wenn wir eine davon erhalten, ist das Abo abgelaufen oder nicht mehr gültig. In diesen Fällen müssen wir die Abos aus unserer Datenbank entfernen.

Bei einem anderen Fehler wird nur throw err verwendet, wodurch das von triggerPushMsg() zurückgegebene Versprechen abgelehnt wird.

Auf einige andere Statuscodes gehen wir im nächsten Abschnitt ein, wenn wir uns das Web-Push-Protokoll genauer ansehen.

Nachdem wir die Abos in einer Schleife durchlaufen haben, müssen wir eine JSON-Antwort zurückgeben.

.then(() => {
res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({ data: { success: true } }));
})
.catch(function(err) {
res.status(500);
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({
    error: {
    id: 'unable-to-send-messages',
    message: `We were unable to send messages to all subscriptions : ` +
        `'${err.message}'`
    }
}));
});

Wir haben die wichtigsten Implementierungsschritte behandelt:

  1. Erstellen Sie eine API, um Abos von unserer Webseite an unser Backend zu senden, damit sie in einer Datenbank gespeichert werden können.
  2. Erstellen Sie eine API, um das Senden von Push-Nachrichten auszulösen (in diesem Fall eine API, die vom vorgetäuschten Admin-Steuerfeld aufgerufen wird).
  3. Rufen Sie alle Abos aus unserem Back-End ab und senden Sie jedem Abo mit einer der Web-Push-Bibliotheken eine Nachricht.

Unabhängig von Ihrem Back-End (Knoten, PHP, Python usw.) sind die Schritte zum Implementieren von Push immer identisch.

Als Nächstes fragen wir, was genau diese Web-Push-Bibliotheken für uns bewirken.

Weitere Informationen

Code-Labs