Erste Schritte beim Erstellen einer Erweiterung

Auf dieser Seite werden die Schritte zum Erstellen einer einfachen Firebase-Erweiterung beschrieben, die Sie in Ihren Projekten installieren oder für andere freigeben können. In diesem einfachen Beispiel für eine Firebase-Erweiterung werden Nachrichten in Ihrer Realtime Database überwacht und in Großbuchstaben umgewandelt.

1. Umgebung einrichten und Projekt initialisieren

Bevor Sie mit dem Erstellen einer Erweiterung beginnen können, müssen Sie eine Buildumgebung mit den erforderlichen Tools einrichten.

  1. Installieren Sie Node.js 16 oder höher. Eine Möglichkeit zur Installation von Node ist die Verwendung von nvm (oder nvm-windows).

  2. Installieren Sie die Firebase CLI oder aktualisieren Sie sie auf die neueste Version. Führen Sie den folgenden Befehl aus, um npm zu installieren oder zu aktualisieren:

    npm install -g firebase-tools

Initialisieren Sie nun mit der Firebase CLI ein neues Erweiterungsprojekt:

  1. Erstellen Sie ein Verzeichnis für Ihre Erweiterung und cd darin:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
  2. Führen Sie den Befehl ext:dev:init der Firebase CLI aus:

    firebase ext:dev:init

    Wählen Sie auf Aufforderung JavaScript als Sprache für Funktionen aus. Sie können aber auch TypeScript verwenden, wenn Sie Ihre eigene Erweiterung entwickeln. Antworten Sie auf die Aufforderung zur Installation von Abhängigkeiten mit „Ja“. Übernehmen Sie für alle anderen Optionen die Standardeinstellungen. Mit diesem Befehl wird eine Skelett-Codebasis für eine neue Erweiterung eingerichtet, auf der Sie mit der Entwicklung Ihrer Erweiterung beginnen können.

2. Beispielerweiterung mit dem Emulator testen

Als die Firebase CLI das neue Verzeichnis „extensions“ initialisiert hat, wurden eine einfache Beispielfunktion und ein integration-tests-Verzeichnis erstellt, das die Dateien enthält, die zum Ausführen einer Erweiterung mit der Firebase-Emulator-Suite erforderlich sind.

Führen Sie die Beispielerweiterung im Emulator aus:

  1. Wechseln Sie in das Verzeichnis integration-tests:

    cd functions/integration-tests
  2. Starten Sie den Emulator mit einem Demoprojekt:

    firebase emulators:start --project=demo-test

    Der Emulator lädt die Erweiterung in ein vordefiniertes „Dummy“-Projekt (demo-test) hoch. Die Erweiterung besteht bisher aus einer einzigen HTTP-ausgelösten Funktion, greetTheWorld, die beim Zugriff die Nachricht „Hallo Welt“ zurückgibt.

  3. Testen Sie die greetTheWorld-Funktion der Erweiterung, während der Emulator noch läuft. Rufen Sie dazu die URL auf, die beim Starten der Erweiterung ausgegeben wurde.

    In Ihrem Browser wird die Meldung „Hello World from greet-the-world“ angezeigt.

  4. Der Quellcode für diese Funktion befindet sich im Verzeichnis functions der Erweiterung. Öffnen Sie die Quelle im Editor oder in der IDE Ihrer Wahl:

    functions/index.js

    const functions = require("firebase-functions/v1");
    
    exports.greetTheWorld = functions.https.onRequest((req, res) => {
      // Here we reference a user-provided parameter
      // (its value is provided by the user during installation)
      const consumerProvidedGreeting = process.env.GREETING;
    
      // And here we reference an auto-populated parameter
      // (its value is provided by Firebase after installation)
      const instanceId = process.env.EXT_INSTANCE_ID;
    
      const greeting = `${consumerProvidedGreeting} World from ${instanceId}`;
    
      res.send(greeting);
    });
    
  5. Solange der Emulator ausgeführt wird, werden alle Änderungen, die Sie an Ihrem Functions-Code vornehmen, automatisch neu geladen. Nehmen Sie eine kleine Änderung an der greetTheWorld-Funktion vor:

    functions/index.js

    const greeting = `${consumerProvidedGreeting} everyone, from ${instanceId}`;
    

    Speichern Sie die Änderungen. Der Emulator lädt Ihren Code neu. Wenn Sie jetzt die Funktions-URL aufrufen, wird die aktualisierte Begrüßung angezeigt.

3. Grundlegende Informationen zu „extension.yaml“ hinzufügen

Nachdem Sie eine Entwicklungsumgebung eingerichtet und den Erweiterungsemulator gestartet haben, können Sie mit dem Erstellen Ihrer eigenen Erweiterung beginnen.

Bearbeiten Sie als ersten Schritt die vordefinierten Erweiterungsmetadaten, damit sie die gewünschte Erweiterung statt greet-the-world widerspiegeln. Diese Metadaten werden in der Datei extension.yaml gespeichert.

  1. Öffnen Sie extension.yaml in Ihrem Editor und ersetzen Sie den gesamten Inhalt der Datei durch Folgendes:

    name: rtdb-uppercase-messages
    version: 0.0.1
    specVersion: v1beta  # Firebase Extensions specification version; don't change
    
    # Friendly display name for your extension (~3-5 words)
    displayName: Convert messages to upper case
    
    # Brief description of the task your extension performs (~1 sentence)
    description: >-
      Converts messages in RTDB to upper case
    
    author:
      authorName: Your Name
      url: https://your-site.example.com
    
    license: Apache-2.0  # Required license
    
    # Public URL for the source code of your extension
    sourceUrl: https://github.com/your-name/your-repo
    

    Beachten Sie die Benennungskonvention im Feld name: Offizielle Firebase-Erweiterungen haben ein Präfix, das das primäre Firebase-Produkt angibt, für das die Erweiterung verwendet wird, gefolgt von einer Beschreibung der Funktion der Erweiterung. Sie sollten dieselbe Konvention in Ihren eigenen Erweiterungen verwenden.

  2. Da Sie den Namen Ihrer Erweiterung geändert haben, sollten Sie auch die Emulatorkonfiguration mit dem neuen Namen aktualisieren:

    1. Ändern Sie in functions/integration-tests/firebase.json den Wert greet-the-world in rtdb-uppercase-messages.
    2. Benennen Sie functions/integration-tests/extensions/greet-the-world.env in functions/integration-tests/extensions/rtdb-uppercase-messages.env um.

In Ihrem Erweiterungscode sind noch einige Überreste der greet-the-world-Erweiterung vorhanden. Lassen Sie sie vorerst. Sie aktualisieren diese in den nächsten Abschnitten.

4. Cloud-Funktion schreiben und als Erweiterungsressource deklarieren

Jetzt können Sie mit dem Schreiben von Code beginnen. In diesem Schritt schreiben Sie eine Cloud Function, die die Hauptaufgabe Ihrer Erweiterung ausführt, nämlich in Ihrer Realtime Database nach Nachrichten zu suchen und sie in Großbuchstaben zu konvertieren.

  1. Öffnen Sie die Quelle für die Funktionen der Erweiterung (im functions-Verzeichnis der Erweiterung) im Editor oder in der IDE Ihrer Wahl. Ersetzen Sie den Inhalt durch Folgendes:

    functions/index.js

    import { database, logger } from "firebase-functions/v1";
    
    const app = initializeApp();
    
    // Listens for new messages added to /messages/{pushId}/original and creates an
    // uppercase version of the message to /messages/{pushId}/uppercase
    // for all databases in 'us-central1'
    export const makeuppercase = database
      .ref("/messages/{pushId}/uppercase")
      .onCreate(async (snapshot, context) => {
        // Grab the current value of what was written to the Realtime Database.
        const original = snapshot.val();
    
        // Convert it to upper case.
        logger.log("Uppercasing", context.params.pushId, original);
        const uppercase = original.toUpperCase();
    
        // Setting an "uppercase" sibling in the Realtime Database.
        const upperRef = snapshot.ref.parent.child("upper");
        await upperRef.set(uppercase);
    });
    

    Die alte Funktion, die Sie ersetzt haben, war eine HTTP-ausgelöste Funktion, die ausgeführt wurde, wenn auf einen HTTP-Endpunkt zugegriffen wurde. Die neue Funktion wird durch Echtzeit-Datenbankereignisse ausgelöst: Sie prüft, ob neue Elemente an einem bestimmten Pfad vorhanden sind, und schreibt bei einem positiven Ergebnis die Großbuchstabenversion des Werts zurück in die Datenbank.

    In dieser neuen Datei wird übrigens die ECMAScript-Modulsyntax (import und export) anstelle von CommonJS (require) verwendet. Wenn Sie ES-Module in Node verwenden möchten, geben Sie "type": "module" in functions/package.json an:

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      
    }
    
  2. Jede Funktion in Ihrer Erweiterung muss in der Datei extension.yaml deklariert werden. In der Beispielerweiterung wurde greetTheWorld als einzige Cloud-Funktion der Erweiterung deklariert. Da Sie sie jetzt durch makeuppercase ersetzt haben, müssen Sie auch die Deklaration aktualisieren.

    Öffnen Sie extension.yaml und fügen Sie ein resources-Feld hinzu:

    resources:
      - name: makeuppercase
        type: firebaseextensions.v1beta.function
        properties:
          eventTrigger:
            eventType: providers/google.firebase.database/eventTypes/ref.create
            # DATABASE_INSTANCE (project's default instance) is an auto-populated
            # parameter value. You can also specify an instance.
            resource: projects/_/instances/${DATABASE_INSTANCE}/refs/messages/{pushId}/original
          runtime: "nodejs18"
    
  3. Da Ihre Erweiterung jetzt Realtime Database als Trigger verwendet, müssen Sie die Emulatorkonfiguration aktualisieren, damit der RTDB-Emulator zusammen mit dem Cloud Functions-Emulator ausgeführt wird:

    1. Wenn der Emulator noch ausgeführt wird, beenden Sie ihn mit Strg + C.

    2. Führen Sie im Verzeichnis functions/integration-tests den folgenden Befehl aus:

      firebase init emulators

      Überspringen Sie bei Aufforderung die Einrichtung eines Standardprojekts und wählen Sie dann die Funktionen und Datenbankemulatoren aus. Akzeptieren Sie die Standardports und erlauben Sie dem Einrichtungstool, alle erforderlichen Dateien herunterzuladen.

    3. Starten Sie den Emulator neu:

      firebase emulators:start --project=demo-test
  4. Testen Sie die aktualisierte Erweiterung:

    1. Öffnen Sie die Benutzeroberfläche des Datenbankemulators über den Link, den der Emulator beim Starten ausgegeben hat.

    2. Bearbeiten Sie den Stammknoten der Datenbank:

      • Feld:messages
      • Typ: json
      • Wert: {"11": {"original": "recipe"}}

      Wenn alles richtig eingerichtet ist, sollte beim Speichern Ihrer Datenbankänderungen die makeuppercase-Funktion der Erweiterung ausgelöst werden und Nachricht 11 einen untergeordneten Eintrag mit dem Inhalt "upper": "RECIPE" hinzufügen. Sehen Sie sich die Protokolle und die Datenbank-Tabs der Emulator-Benutzeroberfläche an, um die erwarteten Ergebnisse zu bestätigen.

    3. Fügen Sie dem Knoten messages ({"original":"any text"}) weitere untergeordnete Elemente hinzu. Wenn Sie einen neuen Eintrag hinzufügen, sollte die Erweiterung ein uppercase-Feld mit dem in Großbuchstaben geschriebenen Inhalt des original-Felds hinzufügen.

Sie haben jetzt eine vollständige, aber einfache Erweiterung, die auf einer RTDB-Instanz ausgeführt wird. In den folgenden Abschnitten optimieren Sie diese Erweiterung mit einigen zusätzlichen Funktionen. Anschließend bereiten Sie die Erweiterung für den Vertrieb vor und erfahren, wie Sie sie im Erweiterungs-Hub veröffentlichen.

5. APIs und Rollen deklarieren

Firebase gewährt jeder Instanz einer installierten Erweiterung über ein pro Instanz erstelltes Dienstkonto eingeschränkten Zugriff auf das Projekt und seine Daten. Jedes Konto hat die Mindestberechtigungen, die für die Funktion erforderlich sind. Aus diesem Grund müssen Sie alle IAM-Rollen, die für Ihre Erweiterung erforderlich sind, explizit angeben. Wenn Nutzer Ihre Erweiterung installieren, erstellt Firebase ein Dienstkonto mit diesen Rollen und verwendet es, um die Erweiterung auszuführen.

Sie müssen keine Rollen deklarieren, um Ereignisse eines Produkts auszulösen. Sie müssen jedoch eine Rolle deklarieren, um anderweitig damit zu interagieren. Da die im letzten Schritt hinzugefügte Funktion in die Realtime Database schreibt, müssen Sie extension.yaml die folgende Deklaration hinzufügen:

roles:
  - role: firebasedatabase.admin
    reason: Allows the extension to write to RTDB.

Ebenso deklarieren Sie die Google APIs, die von einer Erweiterung verwendet werden, im Feld apis. Wenn Nutzer Ihre Erweiterung installieren, werden sie gefragt, ob sie diese APIs automatisch für ihr Projekt aktivieren möchten. Dies ist in der Regel nur für nicht Firebase-spezifische Google APIs erforderlich und in diesem Leitfaden nicht erforderlich.

6. Vom Nutzer konfigurierbare Parameter definieren

Die Funktion, die Sie in den letzten beiden Schritten erstellt haben, hat an einem bestimmten RTDB-Speicherort nach eingehenden Nachrichten gesucht. Manchmal ist es aber tatsächlich sinnvoll, einen bestimmten Speicherort zu beobachten, z. B. wenn Ihre Erweiterung auf einer Datenbankstruktur basiert, die Sie ausschließlich für Ihre Erweiterung verwenden. In den meisten Fällen sollten Sie diese Werte jedoch von Nutzern konfigurierbar machen, die Ihre Erweiterung in ihren Projekten installieren. So können Nutzer Ihre Erweiterung mit ihrer vorhandenen Datenbankkonfiguration verwenden.

Den Pfad, in dem die Erweiterung nach neuen Nachrichten sucht, für Nutzer konfigurierbar machen:

  1. Fügen Sie in der Datei extension.yaml einen Abschnitt params hinzu:

    - param: MESSAGE_PATH
      label: Message path
      description: >-
        What is the path at which the original text of a message can be found?
      type: string
      default: /messages/{pushId}/original
      required: true
      immutable: false
    

    Dadurch wird ein neuer Stringparameter definiert, den Nutzer beim Installieren Ihrer Erweiterung festlegen müssen.

  2. Gehen Sie in der Datei extension.yaml zurück zur makeuppercase-Erklärung und ändern Sie das Feld resource in Folgendes:

    resource: projects/_/instances/${DATABASE_INSTANCE}/refs/${param:MESSAGE_PATH}
    

    Das ${param:MESSAGE_PATH}-Token ist ein Verweis auf den Parameter, den Sie gerade definiert haben. Wenn Ihre Erweiterung ausgeführt wird, wird dieses Token durch den Wert ersetzt, den der Nutzer für diesen Parameter konfiguriert hat. Die Funktion makeuppercase überwacht dann den vom Nutzer angegebenen Pfad. Mit dieser Syntax können Sie überall in extension.yaml (und in POSTINSTALL.md – dazu später mehr) auf benutzerdefinierte Parameter verweisen.

  3. Sie können auch über den Code Ihrer Funktionen auf benutzerdefinierte Parameter zugreifen.

    In der Funktion, die Sie im letzten Abschnitt geschrieben haben, haben Sie den Pfad, auf den Sie achten möchten, hartcodiert. Ändern Sie die Triggerdefinition so, dass sie stattdessen auf den benutzerdefinierten Wert verweist:

    functions/index.js

    export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate
    

    Bei Firebase-Erweiterungen dient diese Änderung nur der Dokumentation: Wenn eine Cloud-Funktion als Teil einer Erweiterung bereitgestellt wird, wird die Triggerdefinition aus der Datei extension.yaml verwendet und der in der Funktionsdefinition angegebene Wert ignoriert. Es empfiehlt sich jedoch, in Ihrem Code zu dokumentieren, woher dieser Wert stammt.

  4. Es mag enttäuschend sein, eine Codeänderung vorzunehmen, die keine Auswirkungen auf die Laufzeit hat. Wichtig ist jedoch, dass Sie in Ihrem Funktionscode auf jeden benutzerdefinierten Parameter zugreifen und ihn als normalen Wert in der Logik der Funktion verwenden können. Fügen Sie als Hinweis auf diese Funktion die folgende Protokollanweisung hinzu, um zu zeigen, dass Sie tatsächlich auf den vom Nutzer definierten Wert zugreifen:

    functions/index.js

    export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate(
      async (snapshot, context) => {
        logger.log("Found new message at ", snapshot.ref);
    
        // Grab the current value of what was written to the Realtime Database.
        ...
    
  5. Normalerweise werden Nutzer aufgefordert, Werte für Parameter anzugeben, wenn sie eine Erweiterung installieren. Wenn Sie den Emulator jedoch für Tests und die Entwicklung verwenden, überspringen Sie den Installationsprozess und geben stattdessen Werte für benutzerdefinierte Parameter mithilfe einer env-Datei an.

    Öffnen Sie functions/integration-tests/extensions/rtdb-uppercase-messages.env und ersetzen Sie die GREETING-Definition durch Folgendes:

    MESSAGE_PATH=/msgs/{pushId}/original
    

    Der obige Pfad unterscheidet sich vom Standardpfad und vom Pfad, den Sie zuvor definiert haben. Dies dient nur dazu, zu prüfen, ob Ihre Definition wirksam wird, wenn Sie die aktualisierte Erweiterung ausprobieren.

  6. Starten Sie jetzt den Emulator neu und rufen Sie noch einmal die Benutzeroberfläche des Datenbankemulators auf.

    Bearbeiten Sie den Stammknoten der Datenbank mit dem oben definierten Pfad:

    • Feld:msgs
    • Typ: json
    • Wert: {"11": {"original": "recipe"}}

    Wenn Sie Ihre Datenbankänderungen speichern, sollte die makeuppercase-Funktion der Erweiterung wie zuvor ausgelöst werden. Jetzt sollte sie aber auch den benutzerdefinierten Parameter in das Konsolenprotokoll ausgeben.

7. Ereignis-Hooks für benutzerdefinierte Logik bereitstellen

Als Entwickler einer Erweiterung haben Sie bereits gesehen, wie ein Firebase-Produkt die von Ihrer Erweiterung bereitgestellte Logik auslösen kann: Das Erstellen neuer Einträge in der Realtime Database löst Ihre makeuppercase-Funktion aus. Ihre Erweiterung kann eine ähnliche Beziehung zu den Nutzern haben, die sie installieren: Ihre Erweiterung kann eine vom Nutzer definierte Logik auslösen.

Eine Erweiterung kann synchrone Hooks, asynchrone Hooks oder beides bereitstellen. Mithilfe von synchronen Hooks können Nutzer Aufgaben ausführen, die den Abschluss einer Funktion der Erweiterung blockieren. Das kann beispielsweise nützlich sein, um Nutzern die Möglichkeit zu geben, eine benutzerdefinierte Vorverarbeitung durchzuführen, bevor eine Erweiterung ihre Arbeit beginnt.

In diesem Leitfaden fügen Sie Ihrer Erweiterung einen asynchronen Hook hinzu, mit dem Nutzer ihre eigenen Verarbeitungsschritte definieren können, die ausgeführt werden, nachdem Ihre Erweiterung die Nachricht in Großbuchstaben in die Realtime Database geschrieben hat. Bei asynchronen Hooks werden benutzerdefinierte Funktionen mit Eventarc ausgelöst. Erweiterungen deklarieren die Arten von Ereignissen, die sie senden. Wenn Nutzer die Erweiterung installieren, wählen sie die Ereignistypen aus, an denen sie interessiert sind. Wenn er mindestens ein Ereignis auswählt, stellt Firebase im Rahmen der Installation einen Eventarc-Kanal für die Erweiterung bereit. Nutzer können dann eigene Cloud-Funktionen bereitstellen, die auf diesem Kanal lauschen und ausgelöst werden, wenn die Erweiterung neue Ereignisse veröffentlicht.

So fügen Sie einen asynchronen Hook hinzu:

  1. Fügen Sie in der Datei extension.yaml den folgenden Abschnitt hinzu, in dem der Ereignistyp deklariert wird, den die Erweiterung ausgibt:

    events:
      - type: test-publisher.rtdb-uppercase-messages.v1.complete
        description: >-
          Occurs when message uppercasing completes. The event subject will contain
          the RTDB URL of the uppercase message.
    

    Ereignistypen müssen eindeutig sein. Verwenden Sie daher für Ereignisnamen immer das folgende Format: <publisher-id>.<extension-id>.<version>.<description>. Da du noch keine Publisher-ID hast, verwende vorerst test-publisher.

  2. Fügen Sie am Ende der Funktion makeuppercase Code hinzu, der ein Ereignis des gerade deklarierten Typs veröffentlicht:

    functions/index.js

    // Import the Eventarc library:
    import { initializeApp } from "firebase-admin/app";
    import { getEventarc } from "firebase-admin/eventarc";
    
    const app = initializeApp();
    
    // In makeuppercase, after upperRef.set(uppercase), add:
    
    // Set eventChannel to a newly-initialized channel, or `undefined` if events
    // aren't enabled.
    const eventChannel =
      process.env.EVENTARC_CHANNEL &&
      getEventarc().channel(process.env.EVENTARC_CHANNEL, {
        allowedEventTypes: process.env.EXT_SELECTED_EVENTS,
      });
    
    // If events are enabled, publish a `complete` event to the configured
    // channel.
    eventChannel &&
      eventChannel.publish({
        type: "test-publisher.rtdb-uppercase-messages.v1.complete",
        subject: upperRef.toString(),
        data: {
          "original": original,
          "uppercase": uppercase,
        },
      });
    

    In diesem Beispielcode wird davon ausgegangen, dass die Umgebungsvariable EVENTARC_CHANNEL nur definiert ist, wenn der Nutzer mindestens einen Ereignistyp aktiviert hat. Wenn EVENTARC_CHANNEL nicht definiert ist, wird im Code nicht versucht, Ereignisse zu veröffentlichen.

    Sie können einem Eventarc-Ereignis zusätzliche Informationen anhängen. Im obigen Beispiel enthält das Ereignis ein Feld subject mit einem Verweis auf den neu erstellten Wert und eine Nutzlast data mit der ursprünglichen Nachricht und der Nachricht in Großbuchstaben. Diese Informationen können von benutzerdefinierten Funktionen verwendet werden, die das Ereignis auslösen.

  3. Normalerweise werden die Umgebungsvariablen EVENTARC_CHANNEL und EXT_SELECTED_EVENTS anhand der Optionen definiert, die der Nutzer bei der Installation ausgewählt hat. Definieren Sie für den Test mit dem Emulator diese Variablen manuell in der Datei rtdb-uppercase-messages.env:

    EVENTARC_CHANNEL=locations/us-central1/channels/firebase
    EXT_SELECTED_EVENTS=test-publisher.rtdb-uppercase-messages.v1.complete
    

Sie haben jetzt alle Schritte ausgeführt, die zum Hinzufügen eines asynchronen Ereignis-Hooks zu Ihrer Erweiterung erforderlich sind.

Um diese neue Funktion auszuprobieren, die Sie gerade implementiert haben, übernehmen Sie in den nächsten Schritten die Rolle eines Nutzers, der die Erweiterung installiert:

  1. Initialisieren Sie im Verzeichnis functions/integration-tests ein neues Firebase-Projekt:

    firebase init functions

    Lehnen Sie die Einrichtung eines Standardprojekts ab, wählen Sie JavaScript als Cloud Functions-Sprache aus und installieren Sie die erforderlichen Abhängigkeiten. Dieses Projekt stellt das Projekt eines Nutzers dar, in dem Ihre Erweiterung installiert ist.

  2. Bearbeiten Sie integration-tests/functions/index.js und fügen Sie den folgenden Code ein:

    import { logger } from "firebase-functions/v1";
    import { onCustomEventPublished } from "firebase-functions/v2/eventarc";
    
    import { initializeApp } from "firebase-admin/app";
    import { getDatabase } from "firebase-admin/database";
    
    const app = initializeApp();
    
    export const extraemphasis = onCustomEventPublished(
      "test-publisher.rtdb-uppercase-messages.v1.complete",
      async (event) => {
        logger.info("Received makeuppercase completed event", event);
    
        const refUrl = event.subject;
        const ref = getDatabase().refFromURL(refUrl);
        const upper = (await ref.get()).val();
        return ref.set(`${upper}!!!`);
      }
    );
    

    Dies ist ein Beispiel für eine Funktion zur Nachbearbeitung, die ein Nutzer schreiben könnte. In diesem Fall überwacht die Funktion, ob die Erweiterung ein complete-Ereignis veröffentlicht. Wenn das der Fall ist, werden der Nachricht, die jetzt in Großbuchstaben geschrieben ist, drei Ausrufezeichen hinzugefügt.

  3. Starten Sie den Emulator neu. Der Emulator lädt die Funktionen der Erweiterung sowie die vom „Nutzer“ definierte Funktion zur Nachbearbeitung.

  4. Rufen Sie die Benutzeroberfläche des Datenbankemulators auf und bearbeiten Sie den Stammknoten der Datenbank über den oben definierten Pfad:

    • Feld:msgs
    • Typ: json
    • Wert: {"11": {"original": "recipe"}}

    Wenn Sie Ihre Datenbankänderungen speichern, sollten die makeuppercase-Funktion der Erweiterung und die extraemphasis-Funktion des Nutzers nacheinander ausgelöst werden, sodass das Feld upper den Wert RECIPE!!! erhält.

8. Lebenszyklusereignis-Handler hinzufügen

Die von Ihnen bisher geschriebene Erweiterung verarbeitet Nachrichten, sobald sie erstellt werden. Was ist aber, wenn Ihre Nutzer bereits eine Datenbank mit Nachrichten haben, wenn sie die Erweiterung installieren? Firebase Extensions bietet die Funktion Lifecycle-Ereignis-Hooks, mit der Sie Aktionen auslösen können, wenn Ihre Erweiterung installiert, aktualisiert oder neu konfiguriert wird. In diesem Abschnitt verwenden Sie Lebenszyklusereignis-Hooks, um die vorhandene Nachrichtendatenbank eines Projekts mit Großbuchstaben-Nachrichten zu füllen, wenn ein Nutzer Ihre Erweiterung installiert.

Firebase-Erweiterungen verwenden Cloud Tasks, um Ihre Lebenszyklusereignis-Handler auszuführen. Sie definieren Ereignis-Handler mit Cloud Functions. Wenn eine Instanz Ihrer Erweiterung eines der unterstützten Lebenszyklusereignisse erreicht und Sie einen Handler definiert haben, wird der Handler einer Cloud Tasks-Warteschlange hinzugefügt. Cloud Tasks führt den Handler dann asynchron aus. Während ein Lebenszyklusereignis-Handler ausgeführt wird, wird dem Nutzer in der Firebase Console angezeigt, dass für die Erweiterungs-Instanz ein Verarbeitungsvorgang läuft. Es liegt in der Verantwortung Ihrer Handlerfunktion, dem Nutzer den aktuellen Status und den Abschluss der Aufgabe zu melden.

So fügen Sie einen Lebenszyklus-Ereignis-Handler hinzu, der vorhandene Nachrichten nachträglich einfügt:

  1. Definieren Sie eine neue Cloud Functions-Funktion, die durch Ereignisse in der Aufgabenwarteschlange ausgelöst wird:

    functions/index.js

    import { tasks } from "firebase-functions/v1";
    
    import { getDatabase } from "firebase-admin/database";
    import { getExtensions } from "firebase-admin/extensions";
    import { getFunctions } from "firebase-admin/functions";
    
    export const backfilldata = tasks.taskQueue().onDispatch(async () => {
      const batch = await getDatabase()
        .ref(process.env.MESSAGE_PATH)
        .parent.parent.orderByChild("upper")
        .limitToFirst(20)
        .get();
    
      const promises = [];
      for (const key in batch.val()) {
        const msg = batch.child(key);
        if (msg.hasChild("original") && !msg.hasChild("upper")) {
          const upper = msg.child("original").val().toUpperCase();
          promises.push(msg.child("upper").ref.set(upper));
        }
      }
      await Promise.all(promises);
    
      if (promises.length > 0) {
        const queue = getFunctions().taskQueue(
          "backfilldata",
          process.env.EXT_INSTANCE_ID
        );
        return queue.enqueue({});
      } else {
        return getExtensions()
          .runtime()
          .setProcessingState("PROCESSING_COMPLETE", "Backfill complete.");
      }
    });
    

    Beachten Sie, dass die Funktion nur einige Einträge verarbeitet, bevor sie sich wieder der Aufgabenwarteschlange hinzufügt. Dies ist eine gängige Strategie, um mit Verarbeitungsaufgaben umzugehen, die nicht innerhalb des Zeitlimits einer Cloud-Funktion abgeschlossen werden können. Da Sie nicht vorhersagen können, wie viele Nachrichten ein Nutzer bereits in seiner Datenbank hat, wenn er Ihre Erweiterung installiert, ist diese Strategie gut geeignet.

  2. Deklarieren Sie in der Datei extension.yaml die Backfill-Funktion als Erweiterungsressource mit dem Attribut taskQueueTrigger:

    resources:
      - name: makeuppercase
        ...
      - name: backfilldata
        type: firebaseextensions.v1beta.function
        description: >-
          Backfill existing messages with uppercase versions
        properties:
          runtime: "nodejs18"
          taskQueueTrigger: {}
    

    Deklarieren Sie dann die Funktion als Handler für das Lebenszyklusereignis onInstall:

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. Das Backfilling vorhandener Nachrichten ist zwar praktisch, die Erweiterung funktioniert aber auch ohne. In solchen Fällen sollten Sie das Ausführen der Lebenszyklus-Ereignis-Handler optional machen.

    Fügen Sie dazu extension.yaml einen neuen Parameter hinzu:

    - param: DO_BACKFILL
      label: Backfill existing messages
      description: >-
        Generate uppercase versions of existing messages?
      type: select
      required: true
      options:
        - label: Yes
          value: true
        - label: No
          value: false
    

    Prüfen Sie dann zu Beginn der Backfill-Funktion den Wert des Parameters DO_BACKFILL und beenden Sie die Funktion, falls er nicht festgelegt ist:

    functions/index.js

    if (!process.env.DO_BACKFILL) {
      return getExtensions()
        .runtime()
        .setProcessingState("PROCESSING_COMPLETE", "Backfill skipped.");
    }
    

Durch die oben genannten Änderungen werden vorhandene Nachrichten nach der Installation der Erweiterung in Großbuchstaben umgewandelt.

Bislang haben Sie den Erweiterungsemulator verwendet, um Ihre Erweiterung zu entwickeln und laufende Änderungen zu testen. Der Erweiterungsemulator überspringt jedoch den Installationsprozess. Wenn Sie Ihren onInstall-Ereignishandler testen möchten, müssen Sie die Erweiterung in einem echten Projekt installieren. Das ist aber auch gut so, denn durch die zusätzliche Funktion für den automatischen Backfill ist die Anleitungserweiterung jetzt codefertig.

9. In einem echten Firebase-Projekt bereitstellen

Der Erweiterungsemulator ist ein hervorragendes Tool, um während der Entwicklung schnell Änderungen an einer Erweiterung vorzunehmen. Irgendwann sollten Sie sie jedoch in einem echten Projekt ausprobieren.

Richten Sie dazu zuerst ein neues Projekt mit einigen aktivierten Diensten ein:

  1. Fügen Sie in der Firebase Console ein neues Projekt hinzu.
  2. Führen Sie für Ihr Projekt ein Upgrade auf den Blaze-Tarif (Pay as you go) durch. Für Cloud Functions for Firebase ist ein Rechnungskonto für Ihr Projekt erforderlich. Sie benötigen also auch ein Rechnungskonto, um eine Erweiterung zu installieren.
  3. Aktivieren Sie in Ihrem neuen Projekt die Realtime Database.
  4. Da Sie die Fähigkeit Ihrer Erweiterung testen möchten, vorhandene Daten bei der Installation zu ergänzen, importieren Sie einige Beispieldaten in Ihre Echtzeit-Datenbankinstanz:
    1. Laden Sie einige RTDB-Startdaten herunter.
    2. Klicken Sie auf der Seite „Realtime Database“ (Realtime Database) der Firebase Console auf das Dreipunkt-Menü  > „JSON importieren“ und wählen Sie die Datei aus, die Sie gerade heruntergeladen haben.
  5. Wenn Sie die Backfill-Funktion für die orderByChild-Methode aktivieren möchten, konfigurieren Sie die Datenbank so, dass Nachrichten nach dem Wert von upper indexiert werden:

    {
      "rules": {
        ".read": false,
        ".write": false,
        "messages": {
          ".indexOn": "upper"
        }
      }
    }
    

Installieren Sie nun die Erweiterung aus der lokalen Quelle im neuen Projekt:

  1. Erstellen Sie ein neues Verzeichnis für Ihr Firebase-Projekt:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. Initialisieren Sie ein Firebase-Projekt im Arbeitsverzeichnis:

    firebase init database

    Wählen Sie bei Aufforderung das Projekt aus, das Sie gerade erstellt haben.

  3. Installieren Sie die Erweiterung in Ihrem lokalen Firebase-Projekt:

    firebase ext:install /path/to/rtdb-uppercase-messages

    Hier sehen Sie, wie die Installation einer Erweiterung mit der Firebase CLI abläuft. Wählen Sie „Ja“ aus, wenn Sie im Konfigurationstool gefragt werden, ob Sie Ihre vorhandene Datenbank auffüllen möchten.

    Nachdem Sie die Konfigurationsoptionen ausgewählt haben, speichert die Firebase CLI Ihre Konfiguration im Verzeichnis extensions und zeichnet den Speicherort der Erweiterungsquelle in der Datei firebase.json auf. Zusammen werden diese beiden Einträge als Manifest für Erweiterungen bezeichnet. Nutzer können die Erweiterungskonfiguration mit dem Manifest speichern und in verschiedenen Projekten bereitstellen.

  4. Bereitstellung der Erweiterungskonfiguration in Ihrem Liveprojekt:

    firebase deploy --only extensions

Wenn alles richtig funktioniert, sollte die Firebase CLI Ihre Erweiterung in Ihr Projekt hochladen und installieren. Nach Abschluss der Installation wird die Backfill-Aufgabe ausgeführt und innerhalb weniger Minuten wird Ihre Datenbank mit Großbuchstaben-Nachrichten aktualisiert. Fügen Sie der Nachrichtendatenbank einige neue Knoten hinzu und prüfen Sie, ob die Erweiterung auch für neue Nachrichten funktioniert.

10. Dokumentation schreiben

Bevor Sie Ihre Erweiterung für Nutzer freigeben, sollten Sie dafür sorgen, dass genügend Dokumentation vorhanden ist, damit sie erfolgreich eingesetzt werden kann.

Beim Initialisieren des Erweiterungsprojekts hat die Firebase CLI Stub-Versionen der Mindestdokumentation erstellt. Aktualisieren Sie diese Dateien, damit sie die von Ihnen erstellte Erweiterung korrekt widerspiegeln.

extension.yaml

Sie haben diese Datei bereits aktualisiert, während Sie diese Erweiterung entwickelt haben. Sie müssen sie also momentan nicht noch einmal aktualisieren.

Die in dieser Datei enthaltene Dokumentation ist jedoch sehr wichtig. Neben den wichtigen identifizierenden Informationen einer Erweiterung (Name, Beschreibung, Autor, offizieller Repository-Speicherort) enthält die extension.yaml-Datei auch eine für Nutzer sichtbare Dokumentation für jede Ressource und jeden benutzerkonfigurierbaren Parameter. Diese Informationen werden Nutzern in der Firebase Console, im Extensions Hub und in der Firebase CLI angezeigt.

PREINSTALL.md

Geben Sie in dieser Datei Informationen an, die Nutzer benötigen, bevor sie Ihre Erweiterung installieren: Beschreiben Sie kurz, wozu die Erweiterung dient, erläutern Sie alle Voraussetzungen und informieren Sie die Nutzer über die Abrechnungsfolgen der Installation der Erweiterung. Wenn Sie eine Website mit zusätzlichen Informationen haben, ist dies auch ein guter Ort, um sie zu verlinken.

Der Text dieser Datei wird dem Nutzer im Extensions Hub und über den Befehl firebase ext:info angezeigt.

Hier sehen Sie ein Beispiel für eine PREINSTALL-Datei:

Use this extension to automatically convert strings to upper case when added to
a specified Realtime Database path.

This extension expects a database layout like the following example:

    "messages": {
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
    }

When you create new string records, this extension creates a new sibling record
with upper-cased text:

    MESSAGE_ID: {
      "original": MESSAGE_TEXT,
      "upper": UPPERCASE_MESSAGE_TEXT,
    }

#### Additional setup

Before installing this extension, make sure that you've
[set up Realtime Database](https://firebase.google.com/docs/database/quickstart)
in your Firebase project.

#### Billing

To install an extension, your project must be on the
[Blaze (pay as you go) plan](https://firebase.google.com/pricing).

- This extension uses other Firebase and Google Cloud Platform services, which
  have associated charges if you exceed the service's no-cost tier:
  - Realtime Database
  - Cloud Functions (Node.js 10+ runtime)
    [See FAQs](https://firebase.google.com/support/faq#extensions-pricing)
- If you enable events,
  [Eventarc fees apply](https://cloud.google.com/eventarc/pricing).

POSTINSTALL.md

Diese Datei enthält Informationen, die für Nutzer nützlich sind, nachdem sie Ihre Erweiterung installiert haben. Dazu gehören beispielsweise weitere Einrichtungsschritte und ein Beispiel für die Verwendung der Erweiterung.

Der Inhalt von POSTINSTALL.md wird in der Firebase Console angezeigt, nachdem eine Erweiterung konfiguriert und installiert wurde. Sie können in dieser Datei auf Nutzerparameter verweisen. Sie werden dann durch die konfigurierten Werte ersetzt.

Hier ist eine Beispieldatei für die Erweiterung nach der Installation:

### See it in action

You can test out this extension right away!

1.  Go to your
    [Realtime Database dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/database/${param:PROJECT_ID}/data) in the Firebase console.

1.  Add a message string to a path that matches the pattern `${param:MESSAGE_PATH}`.

1.  In a few seconds, you'll see a sibling node named `upper` that contains the
    message in upper case.

### Using the extension

We recommend adding data by pushing -- for example,
`firebase.database().ref().push()` -- because pushing assigns an automatically
generated ID to the node in the database. During retrieval, these nodes are
guaranteed to be ordered by the time they were added. Learn more about reading
and writing data for your platform (iOS, Android, or Web) in the
[Realtime Database documentation](https://firebase.google.com/docs/database/).

### Monitoring

As a best practice, you can
[monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor)
of your installed extension, including checks on its health, usage, and logs.

CHANGELOG.md

Außerdem sollten Sie die Änderungen, die Sie zwischen den Releases einer Erweiterung vornehmen, in der Datei CHANGELOG.md dokumentieren.

Da die Beispielerweiterung noch nie veröffentlicht wurde, enthält das Änderungsprotokoll nur einen Eintrag:

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

Die meisten Erweiterungen enthalten auch eine Readme-Datei für Nutzer, die das Repository der Erweiterung besuchen. Sie können diese Datei manuell schreiben oder mit dem Befehl eine Readme-Datei generieren.

Überspringen Sie für diesen Leitfaden das Erstellen einer readme-Datei.

Zusätzliche Dokumentation

Die oben beschriebene Dokumentation ist die Mindestdokumentation, die Sie Nutzern zur Verfügung stellen sollten. Viele Erweiterungen erfordern eine detailliertere Dokumentation, damit Nutzer sie erfolgreich verwenden können. In diesem Fall sollten Sie zusätzliche Dokumentationen erstellen und an einem Ort hosten, auf den Sie Nutzer verweisen können.

Für die Zwecke dieses Leitfadens wird das Erstellen einer umfangreicheren Dokumentation übersprungen.

11. Im Erweiterungs-Hub veröffentlichen

Jetzt, da der Code Ihrer Erweiterung vollständig ist und sie dokumentiert wurde, können Sie sie im Erweiterungs-Hub veröffentlichen. Da es sich aber nur um eine Demo handelt, sollten Sie das nicht tun. Schreiben Sie jetzt Ihre eigene Erweiterung. Nutzen Sie dabei die Informationen in diesem Artikel und in der restlichen Firebase Extensions Publisher-Dokumentation und sehen Sie sich die Quelle der offiziellen, von Firebase erstellten Erweiterungen an.

So veröffentlichen Sie Ihre Arbeit im Erweiterungs-Hub:

  1. Wenn Sie Ihre erste Erweiterung veröffentlichen, registrieren Sie sich als Erweiterungsanbieter. Wenn Sie sich als Publisher von Erweiterungen registrieren, erstellen Sie eine Publisher-ID, anhand derer Nutzer Sie schnell als Autor Ihrer Erweiterungen identifizieren können.
  2. Hosten Sie den Quellcode Ihrer Erweiterung an einem öffentlich verifizierbaren Speicherort. Wenn Ihr Code aus einer überprüfbaren Quelle verfügbar ist, kann Firebase Ihre Erweiterung direkt an diesem Speicherort veröffentlichen. So können Sie sicherstellen, dass Sie die aktuell veröffentlichte Version Ihrer Erweiterung veröffentlichen. Außerdem können Nutzer den Code prüfen, den sie in ihren Projekten installieren.

    Derzeit bedeutet das, dass Sie Ihre Erweiterung in einem öffentlichen GitHub-Repository verfügbar machen müssen.

  3. Laden Sie Ihre Erweiterung mit dem Befehl firebase ext:dev:upload in den Erweiterungs-Hub hoch.

  4. Rufen Sie in der Firebase Console Ihr Publisher-Dashboard auf, suchen Sie die gerade hochgeladene Erweiterung und klicken Sie auf „Im Erweiterungs-Hub veröffentlichen“. Dies löst eine Überprüfung durch unser Team aus, die einige Tage dauern kann. Wenn die Erweiterung genehmigt wird, wird sie im Erweiterungs-Hub veröffentlicht. Wenn Ihr Antrag abgelehnt wird, erhalten Sie eine Nachricht mit einer Begründung. Sie können dann die gemeldeten Probleme beheben und den Antrag noch einmal zur Überprüfung einreichen.