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, Node zu installieren, 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 die Erweiterung und fügen Sie cd hinzu:

    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
    

    Wenn Sie dazu aufgefordert werden, wählen Sie JavaScript als Sprache für Funktionen aus. Sie können aber auch TypeScript verwenden, wenn Sie Ihre eigene Erweiterung entwickeln. Wenn Sie aufgefordert werden, Abhängigkeiten zu installieren, antworten Sie mit „Ja“. (Übernehmen Sie die Standardwerte für alle anderen Optionen.) Mit diesem Befehl wird eine Codebasis für eine neue Erweiterung eingerichtet, von der aus Sie mit der Entwicklung Ihrer Erweiterung beginnen können.

2. Beispielerweiterung mit dem Emulator testen

Als die Firebase CLI das neue Erweiterungsverzeichnis initialisiert hat, wurden eine einfache Beispielfunktion und ein integration-tests-Verzeichnis mit den Dateien erstellt, die zum Ausführen einer Erweiterung mithilfe 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. Wenn der Emulator noch ausgeführt wird, testen Sie die greetTheWorld-Funktion der Erweiterung. Rufen Sie dazu die URL auf, die beim Start 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, sehen Sie die aktualisierte Begrüßung.

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 im Feld name verwendete Namenskonvention: Offizielle Firebase-Erweiterungen werden mit einem Präfix benannt, das das primäre Firebase-Produkt angibt, für das die Erweiterung ausgeführt 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. Diese werden Sie in den nächsten Abschnitten aktualisieren.

4. Cloud-Funktion schreiben und als Erweiterungsressource deklarieren

Jetzt können Sie anfangen, Code zu schreiben. 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. Die Beispielerweiterung hat greetTheWorld als einzige Cloud Functions-Funktion der Erweiterung deklariert. Nachdem Sie sie durch makeuppercase ersetzt haben, müssen Sie auch deren 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
      

      Wenn Sie dazu aufgefordert werden, überspringen Sie 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 Ihre 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 korrekt eingerichtet ist, sollte beim Speichern der Datenbankänderungen die Funktion makeuppercase der Erweiterung ausgelöst werden und der Nachricht 11 einen untergeordneten Datensatz mit dem Inhalt "upper": "RECIPE" hinzufügen. Sehen Sie sich die Logs und die Datenbank-Tabs der Emulator-UI 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 verfügt über die für den Betrieb erforderlichen Mindestberechtigungen. 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 normalerweise nur für Nicht-Firebase-Google-APIs erforderlich und für diesen Leitfaden nicht erforderlich.

6. Benutzerdefinierte Parameter definieren

Die Funktion, die Sie in den letzten beiden Schritten erstellt haben, hat an einem bestimmten RTDB-Speicherort nach eingehenden Nachrichten gesucht. Manchmal möchten Sie einen bestimmten Standort überwachen, z. B. wenn Ihre Erweiterung mit einer Datenbankstruktur arbeitet, die Sie exklusiv für Ihre Erweiterung verwenden. In den meisten Fällen sollten Sie diese Werte jedoch für Nutzer konfigurieren lassen, 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 Token ${param:MESSAGE_PATH} verweist 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 nun 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 die Datenbankänderungen speichern, sollte die Funktion makeuppercase der Erweiterung wie zuvor ausgelöst werden. Jetzt sollte sie aber auch den benutzerdefinierten Parameter im Konsolenlog ausgeben.

7. Ereignis-Hooks für benutzerdefinierte Logik bereitstellen

Als Autor einer Erweiterung haben Sie bereits gesehen, wie ein Firebase-Produkt die von Ihrer Erweiterung bereitgestellte Logik auslösen kann: Das Erstellen neuer Datensätze in Realtime Database löst die Funktion makeuppercase 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. Mit synchronen Hooks können Nutzer Aufgaben ausführen, die den Abschluss einer der Funktionen 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 über Eventarc ausgelöst. Erweiterungen deklarieren die Ereignistypen, die sie ausgeben, und wenn Nutzer die Erweiterung installieren, wählen sie die Ereignistypen aus, an denen sie interessiert sind. Wenn er mindestens ein Ereignis auswählt, wird von Firebase im Rahmen der Installation ein Eventarc-Kanal für die Erweiterung bereitgestellt. 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,
        },
      });
    

    Bei diesem Beispielcode wird die Tatsache genutzt, dass die Umgebungsvariable EVENTARC_CHANNEL nur definiert wird, wenn der Nutzer mindestens einen Ereignistyp aktiviert hat. Ist EVENTARC_CHANNEL nicht definiert, versucht der Code nicht, 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.

Übernimm in den nächsten Schritten die Rolle eines Nutzers, der die Erweiterung installiert, um diese neue Funktion auszuprobieren:

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

    firebase init functions
    

    Wenn Sie dazu aufgefordert werden, 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, auf 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 Nachbearbeitungsfunktion, 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 nach dem "Nutzer" definierte Nachbearbeitungsfunktion.

  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. Event-Handler werden mithilfe von Cloud Functions definiert. Wenn eine Instanz Ihrer Erweiterung eines der unterstützten Lebenszyklusereignisse erreicht, wird der Handler einer Cloud Tasks-Warteschlange hinzugefügt, sofern Sie einen Handler definiert haben. Cloud Tasks führt den Handler dann asynchron aus. Während ein Lebenszyklus-Event-Handler ausgeführt wird, meldet die Firebase Console dem Nutzer, dass bei der Erweiterungsinstanz eine Verarbeitungsaufgabe 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, eignet sich diese Strategie gut.

  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.

Bisher 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 Erweiterung der Anleitung 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 ein Upgrade Ihres Projekts auf den Blaze-Tarif mit Abrechnung nach Verbrauch 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 testen möchten, ob Ihre Erweiterung vorhandene Daten bei der Installation auffüllen kann, 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. Damit die Backfill-Funktion die Methode orderByChild verwenden kann, konfigurieren Sie die Datenbank so, dass Nachrichten mit 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. Diese beiden Einträge werden zusammen 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.

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

Erweiterung.yaml

Sie haben diese Datei bereits mit der Entwicklung dieser Erweiterung aktualisiert, daher müssen Sie im Moment keine weiteren Aktualisierungen vornehmen.

Vergessen Sie jedoch nicht die Wichtigkeit der in dieser Datei enthaltenen Dokumentation. Zusätzlich zu den wichtigen Identifizierungsinformationen einer Erweiterung – Name, Beschreibung, Autor und offizieller Repository-Speicherort – enthält die Datei extension.yaml eine an den Nutzer gerichtete Dokumentation für jede Ressource und jeden vom Nutzer konfigurierbaren 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 aufrufen. 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 Dokumentation schreiben und an einem Ort hosten, auf den 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 mit der Welt teilen. Aber da dies nur eine Anleitung ist, 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 überprüfbaren Ort. 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. Nach der Genehmigung wird die Erweiterung im Erweiterungs-Hub veröffentlicht. Wenn der 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.