Kompetenzen Portfolio Referenzen Lebenslauf Blog Termin buchen

Wie du Push-Nachrichten über Firebase verschickst — für Backend-Entwickler

Gepostet von: Felix Dziekan in: Blog am 

Das hier ist ein kurzer Überblick darüber, wie du das Senden von Push-Nachrichten an einen Client über Firebase testest — geschrieben für die Situation, in der ich immer wieder lande: Das Backend soll Push-Benachrichtigungen verschicken, und es gibt noch keine App, an die man sie schicken könnte.

Du solltest dem folgen können, ohne genauer zu wissen, wie Firebase Cloud Messaging intern funktioniert.

Für wen das ist

Für jeden Backend-Entwickler, der Push-Nachrichten anbinden muss, nicht weiß wie, und keine App hat, mit der er den kompletten Stack testen kann. Was der Normalfall ist — die mobile App wird meistens parallel gebaut, oder von einem anderen Team, oder wurde noch gar nicht angefangen.

Warten kannst du nicht, denn ohne echtes Device-Token kannst du keine einzige Zeile deines Sendecodes überprüfen.

Die Grundlage: Das Token kannst du nicht fälschen

Um eine Push-Nachricht über Firebase an einen bestimmten Nutzer zu schicken, brauchst du ein Token. Ohne eines zu senden ist nicht möglich, und ich habe keinen Weg gefunden, eines zu fälschen — der einzige Weg ist also, ein echtes zu bekommen.

HINWEIS: Push-Tokens laufen nicht nach einem Timer ab. Sie ändern sich nur von Zeit zu Zeit, zum Beispiel wenn ein Nutzer ein neues Gerät bekommt oder die Websitedaten löscht.

Ein Token wird an einen Client ausgegeben, der sich für Push-Nachrichten registriert hat. Dieser Client kann eine App sein — oder eine Webseite. Und weil du gerade ein Backend-Blog liest, geht die Webseite deutlich schneller.

Der Web-Client vertritt die App, die du noch nicht hast

Die zwei Dateien, die du brauchst

Ein Web-Push-Client sind zwei Dateien, und sie müssen im selben Ordner liegen:

  • firebasetest.html — die Seite, die du im Browser öffnest. Sie fragt nach der Erlaubnis für Benachrichtigungen und loggt das Token in die Konsole.
  • firebase-messaging-sw.js — der Service Worker im Hintergrund, der eingehende Nachrichten behandelt.

Hier die Seite:

 



FCM token




 

Und der Service Worker, der den Compat-Build nutzt, weil Service Worker ES-Module nicht auf dieselbe Weise verwenden können:

 

importScripts('https://www.gstatic.com/firebasejs/9.15.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/9.15.0/firebase-messaging-compat.js');

firebase.initializeApp({
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_PROJECT.firebaseapp.com",
  projectId: "YOUR_PROJECT",
  storageBucket: "YOUR_PROJECT.appspot.com",
  messagingSenderId: "YOUR_SENDER_ID",
  appId: "YOUR_APP_ID",
});

const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
  console.log('[sw] background message', payload);
  self.registration.showNotification('Background Message Title', {
    body: 'Background Message body.',
  });
});

 

Die Config muss in beiden Dateien stehen, und sie muss identisch sein. Das ist der Fehler, den ich am häufigsten gesehen habe: Die Seite wird aktualisiert, der Service Worker vergessen, und dann wundert man sich eine Stunde lang, warum das Token zu einem anderen Projekt gehört.

Wo du die Werte herbekommst

Die Firebase-Config steht unter Project settings → General, weiter unten im Abschnitt Your apps. Wenn dort noch keine Web-App liegt, leg eine an und wähl "Web".

Der vapidKey steht woanders: Project settings → Cloud Messaging → Web configuration → Web Push certificates. Wenn dort kein Key ist, erzeug einen. Das ist ein eigener Wert, unabhängig von allem im Config-Objekt, und ihn wegzulassen ist der zweithäufigste Grund, warum getToken scheitert.

Der Teil, an dem alle hängenbleiben: Du brauchst HTTPS

Das funktioniert nicht, wenn du die Datei einfach öffnest. Du brauchst eine echte HTTPS-Verbindung.

Das Firebase-SDK nutzt Service Worker, und Browser erlauben Service Worker nur über eine verschlüsselte Verbindung. firebasetest.html aus deinem Dateisystem zu öffnen bringt dir überhaupt nichts.

Es gibt eine Ausnahme, die man kennen sollte: Browser behandeln localhost als sicheren Kontext. Wenn du die zwei Dateien also von einem lokalen Webserver auf localhost ausliefern kannst, funktioniert das. Es hilft nicht, wenn dein Setup einen Container unter einem anderen Hostnamen als localhost erreichbar macht.

Für den allgemeinen Fall brauchst du eine öffentliche HTTPS-URL. Zwei Wege, die ich gegangen bin:

  • Firebase Hosting. Wenn du ohnehin in Firebase bist, gibt dir firebase deploy in ein paar Minuten eine HTTPS-URL, und es ist kostenlos. Das ist meistens der schnellste Weg.
  • Ein Tunnel. ngrok oder ähnliches setzt eine öffentliche HTTPS-URL vor deinen lokalen Server, womit du auch OAuth-Callbacks löst — dieses Setup habe ich separat aufgeschrieben.

Das Token holen

Öffne deine-url mit geöffneten Entwicklertools. Die Seite fragt nach der Erlaubnis für Benachrichtigungen. Erteil sie, und das Token erscheint in der Konsole.

Ein Device-Token sieht ungefähr so aus:

 

dVH2yuOwfj0_5leeFv-f49:APA91bE...

 

Zwei Dinge kosten hier Zeit. Wenn du in Chrome bist, prüf, dass kein Filter in der Konsole aktiv ist — ein Filter aus einer früheren Sitzung versteckt dein Token, und die Seite sieht aus, als hätte sie nichts getan. Und wenn du irgendwann Benachrichtigungen für diese Domain abgelehnt hast, merkt sich der Browser das und fragt nie wieder. Setz die Seitenberechtigungen zurück und lade neu.

Das Token aus dem Backend nutzen

Leg das Token in deine lokale Umgebungsdatei statt in den Code — .env.dev.local oder Äquivalent, etwas, das per gitignore ausgeschlossen ist. Es ist ein Wert, der zu deinem Browser auf deiner Maschine gehört, und für alle anderen ist er falsch.

Dann schick von dort, wo dein Sendecode liegt. In einem Symfony-Projekt mit dem Firebase-Bundle ungefähr so:

 

$message = CloudMessage::withTarget('token', $deviceToken)
    ->withNotification(Notification::create('Title', 'Body'));

$this->messaging->send($message);

 

Pack das in einen Console-Command, damit du es wiederholt abfeuern kannst, ohne die Anwendung anzufassen. Dieser Command wird das, wogegen du tatsächlich entwickelst.

Wenn nichts ankommt

Geh in dein Firebase-Projekt und öffne Messaging → Reports. Die Zahl der gesendeten Nachrichten sollte bei jedem Lauf des Commands um eins steigen.

Diese eine Prüfung teilt das Problem sauber in zwei Hälften. Wenn die Zahl steigt, hat Firebase deine Nachricht angenommen und das Problem liegt auf der Client-Seite — Berechtigungen, der Service Worker, ein geschlossener Browser. Wenn sie nicht steigt, hat dein Backend Firebase nie erfolgreich erreicht, und das Problem sind Zugangsdaten oder das Payload.

Ohne diese Prüfung debuggst du zwei Systeme gleichzeitig, und so wird aus einem Zweistundenjob ein Zweitagejob.

Noch etwas zu den Zugangsdaten

Die Web-Config in diesen zwei Dateien ist absichtlich öffentlich — sie landet ohnehin im Browser. Das Service-Account-JSON, mit dem dein Backend Nachrichten sendet, ist das Gegenteil: Es ist eine echte Zugangsberechtigung, es darf nie ins Repository und nie in eine Image-Schicht.

Mount es zur Laufzeit und halt es aus git raus. Weil die Web-Config öffentlich ist, gehen Leute mit der ganzen Sache lässig um — und das sind zwei sehr verschiedene Dateien.

Sie brauchen einen Senior-Entwickler, der liefert?

Über 20 Jahre. Produktionssysteme. Echte Deadlines. Ob Architekt, Entwickler oder DevOps-Engineer — ich habe alle drei Rollen übernommen, oft im selben Projekt. Sparen wir uns den Agentur-Overhead und sprechen direkt.