Nachrichten mit Web-Push-Bibliotheken senden

Matt Gaunt

Eines der Probleme bei der Arbeit mit Web-Push-Mitteilungen ist, dass das Auslösen einer Push-Mitteilung extrem umständlich ist. Um eine Push-Nachricht auszulösen, muss eine Anwendung eine POST-Anfrage an einen Push-Dienst senden, die dem Web-Push-Protokoll entspricht. Wenn Sie Push-Mitteilungen in allen Browsern verwenden möchten, müssen Sie VAPIDs (auch Anwendungsserverschlüssel genannt) verwenden. Dazu müssen Sie einen Header mit einem Wert festlegen, der nachweist, dass Ihre Anwendung Nutzer benachrichtigen kann. Damit Daten mit einer Push-Nachricht gesendet werden können, müssen die Daten verschlüsselt und bestimmte Header hinzugefügt werden, damit der Browser die Nachricht richtig entschlüsseln kann.

Das Hauptproblem beim Auslösen von Push-Benachrichtigungen besteht darin, dass es bei Problemen schwierig ist, die Ursache zu ermitteln. Mit der Zeit und einer breiteren Browserunterstützung wird es besser, aber es ist noch lange nicht einfach. Aus diesem Grund empfehlen wir dringend, eine Bibliothek für die Verschlüsselung, Formatierung und Auslösung Ihrer Push-Nachricht zu verwenden.

Im nächsten Abschnitt erfahren Sie mehr über die Bibliotheken. Im Moment konzentrieren wir uns auf die Verwaltung von Abos und die Verwendung einer vorhandenen Web-Push-Bibliothek, um Push-Anfragen zu senden.

In diesem Abschnitt verwenden wir die Web-Push-Knotenbibliothek. Andere Sprachen haben zwar Unterschiede, aber sie sind nicht allzu sehr verschieden. Wir konzentrieren uns auf Node, da es sich um JavaScript handelt und es für Leser am zugänglichsten sein sollte.

Wir führen die folgenden Schritte aus:

  1. Sende ein Abo an unser Backend und speichere es.
  2. Gespeicherte Abos abrufen und eine Push-Nachricht auslösen.

Abos werden gespeichert

Das Speichern und Abfragen von PushSubscription-Elementen aus einer Datenbank hängt von der serverseitigen Sprache und der Datenbankauswahl ab. Es kann jedoch hilfreich sein, ein Beispiel zu sehen, wie das geht.

Auf der Demo-Webseite wird PushSubscription mit einer einfachen POST-Anfrage an unser Back-End 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) {

In diesem Pfad prüfen wir das Abo, um sicherzustellen, dass die Anfrage in Ordnung ist und nicht mit Müll gefüllt 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. Das ist eine einfache dateibasierte Datenbank. Sie können aber jede beliebige Datenbank verwenden. Wir verwenden diese Methode nur, da sie keine Einrichtung erfordert. Für die Produktion sollten Sie etwas Zuverlässigeres verwenden. (Ich bleibe lieber beim guten 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

Wenn es um das Senden einer Push-Nachricht geht, benötigen wir letztendlich ein Ereignis, das den Prozess zum Senden einer Nachricht an Nutzer auslöst. Eine gängige Methode ist die Erstellung einer Administratorseite, auf der Sie die Push-Nachricht konfigurieren und auslösen können. Sie könnten jedoch ein Programm zur lokalen Ausführung oder einen anderen Ansatz erstellen, der es Ihnen ermöglicht, auf die Liste der PushSubscriptions zuzugreifen und den Code zum Auslösen der Push-Nachricht auszuführen.

Unsere Demo verfügt über eine Seite, auf der Sie eine Push-Benachrichtigung auslösen können. Da es sich nur um eine Demo handelt, ist es eine öffentliche Seite.

Ich werde jeden Schritt durchgehen, der erforderlich ist, damit die Demo funktioniert. Es sind einfache Schritte, damit alle mitkommen können, auch diejenigen, die noch keine Erfahrung mit Node haben.

Beim Besprechen des Abonnierens eines Nutzers haben wir erwähnt, dass den Optionen subscribe() eine applicationServerKey hinzugefügt wird. Dieser private Schlüssel wird auf dem Backend benötigt.

In der Demo werden diese Werte unserer Node-Anwendung so hinzugefügt (langweiliger Code, ich weiß, aber ich möchte nur, dass Sie wissen, dass es keine Zauberei gibt):

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

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

npm install web-push --save

Dann fordern wir in unserem Node-Script das web-push-Modul so an:

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

Jetzt können wir das web-push-Modul 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 einen „mailto:“-String hinzugefügt haben. Dieser String muss entweder eine URL oder eine mailto-E-Mail-Adresse sein. Diese Information wird im Rahmen der Anfrage zum Auslösen eines Pushes an den Web-Push-Dienst gesendet. So kann ein Web-Push-Dienst, wenn er den Absender kontaktieren muss, einige Informationen dazu haben.

Damit ist das Modul web-push einsatzbereit. Der nächste Schritt besteht darin, eine Push-Nachricht auszulösen.

In der Demo wird das fiktive Admin-Steuerfeld verwendet, um Push-Nachrichten auszulösen.

Screenshot der Seite „Verwaltung“

Durch Klicken auf die Schaltfläche "Trigger Push Message" wird eine POST-Anfrage an /api/trigger-push-msg/ gesendet. Das ist das Signal für unser Back-End, dass Push-Nachrichten gesendet werden sollen. 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 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 angegebene 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;
    }
  });
};

Beim Aufruf von webpush.sendNotification() wird ein Promise zurückgegeben. Wenn die Nachricht erfolgreich gesendet wurde, wird das Versprechen erfüllt und wir müssen nichts unternehmen. Wenn das Versprechen abgelehnt wird, müssen Sie den Fehler untersuchen, da Sie dadurch erfahren, ob die PushSubscription noch gültig ist.

Um die Art des Fehlers eines Push-Dienstes zu ermitteln, sollten Sie sich den Statuscode ansehen. Fehlermeldungen variieren je nach Push-Dienst und einige sind hilfreicher als andere.

In diesem Beispiel wird nach den Statuscodes 404 und 410 gesucht, den HTTP-Statuscodes für „Nicht gefunden“ und „Nicht mehr verfügbar“. Wenn wir eine solche Meldung 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 throw err wir einfach, wodurch das von triggerPushMsg() zurückgegebene Versprechen abgelehnt wird.

Einige der anderen Statuscodes werden im nächsten Abschnitt behandelt, wenn wir uns das Web-Push-Protokoll genauer ansehen.

Nach dem Durchlauf der Abos 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 Schritte zur Implementierung bereits besprochen:

  1. Erstellen Sie eine API, um Abos von unserer Website an unser Back-End 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 über das gefälschte Admin-Dashboard aufgerufen wird).
  3. Alle Abos aus unserem Backend abrufen und mit einer der Web-Push-Bibliotheken eine Nachricht an jedes Abo senden.

Die Schritte zur Implementierung von Push sind unabhängig von Ihrem Back-End (Node, PHP, Python usw.) identisch.

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

Weitere Informationen

Codelabs