Erste Schritte beim Erstellen einer Erweiterung

Auf dieser Seite werden Sie durch die Schritte geführt, die zum Erstellen einer einfachen Firebase-Erweiterung erforderlich sind, die Sie in Ihren Projekten installieren oder mit anderen teilen können. In diesem einfachen Beispiel für eine Firebase-Erweiterung wird Ihre Realtime Database auf Nachrichten überwacht und diese werden in Großbuchstaben umgewandelt.

1. Umgebung einrichten und Projekt initialisieren

Bevor Sie mit der Entwicklung einer Erweiterung beginnen können, müssen Sie eine Entwicklungsumgebung 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 diesen 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 in dieses Verzeichnis:

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

    firebase ext:dev:init

    Wählen Sie bei Aufforderung JavaScript als Sprache für Funktionen aus. Sie können aber auch TypeScript verwenden, wenn Sie Ihre eigene Erweiterung entwickeln. Wenn Sie gefragt werden, ob Sie Abhängigkeiten installieren möchten, antworten Sie mit „yes“. Übernehmen Sie die Standardeinstellungen für alle anderen Optionen. Mit diesem Befehl wird eine Skelett-Codebasis für eine neue Erweiterung eingerichtet, mit der Sie mit der Entwicklung Ihrer Erweiterung beginnen können.

2. Beispielerweiterung mit dem Emulator testen

Als die Firebase CLI das neue Erweiterungsverzeichnis initialisiert hat, wurde eine einfache Beispielfunktion und ein integration-tests-Verzeichnis erstellt, das die Dateien enthält, die zum Ausführen einer Erweiterung mit der Firebase-Emulatorsuite 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). Die Erweiterung besteht bisher aus einer einzelnen HTTP-getriggerten Funktion, greetTheWorld, die bei Zugriff die Nachricht „Hello World“ zurückgibt.

  3. Während der Emulator noch ausgeführt wird, können Sie die greetTheWorld-Funktion der Erweiterung testen, indem Sie die URL aufrufen, die beim Starten des Emulators 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 den Quellcode 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. Während 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 ausgeführt haben, können Sie mit dem Schreiben Ihrer eigenen Erweiterung beginnen.

Bearbeiten Sie als ersten Schritt die vordefinierten Erweiterungsmetadaten, damit sie die Erweiterung widerspiegeln, die Sie schreiben möchten, anstelle von greet-the-world. 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 Namenskonvention, die im Feld name verwendet wird: 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 für 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.

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

4. Cloud Functions-Funktion schreiben und als Erweiterungsressource deklarieren

Jetzt können Sie mit dem Schreiben von Code beginnen. In diesem Schritt schreiben Sie eine Cloud Functions-Funktion, die die Kernaufgabe Ihrer Erweiterung ausführt: die Realtime Database nach Nachrichten zu beobachten und sie in Großbuchstaben zu konvertieren.

  1. Öffnen Sie den Quellcode für die Funktionen der Erweiterung (im Verzeichnis functions 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 durch HTTP ausgelöste Funktion, die ausgeführt wurde, wenn auf einen HTTP-Endpunkt zugegriffen wurde. Die neue Funktion wird durch Echtzeitdatenbankereignisse ausgelöst: Sie sucht nach neuen Elementen an einem bestimmten Pfad und schreibt, wenn ein neues Element gefunden wird, die Version des Werts in Großbuchstaben zurück in die Datenbank.

    Diese neue Datei verwendet übrigens die ECMAScript-Modulsyntax (import und export) anstelle von CommonJS (require). 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 Beispiel-Extension wurde greetTheWorld als einzige Cloud-Funktion der Extension deklariert. Da Sie sie 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 Emulatoren für Functions und Database aus. Übernehmen 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. So testen Sie Ihre aktualisierte Erweiterung:

    1. Öffnen Sie die Benutzeroberfläche des Datenbank-Emulators ü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 der Datenbankänderungen die Funktion makeuppercase der Erweiterung ausgelöst und der Nachricht 11 ein untergeordneter Datensatz mit dem Inhalt "upper": "RECIPE" hinzugefügt werden. Sehen Sie sich die Logs und die Datenbanktabs 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 Datensatz hinzufügen, sollte die Erweiterung ein uppercase-Feld mit dem Inhalt des original-Felds in Großbuchstaben hinzufügen.

Sie haben jetzt eine vollständige, wenn auch einfache Erweiterung, die auf einer RTDB-Instanz ausgeführt wird. In den folgenden Abschnitten werden Sie diese Erweiterung mit einigen zusätzlichen Funktionen optimieren. Anschließend erfahren Sie, wie Sie die Erweiterung für die Verteilung an andere vorbereiten und wie Sie sie im Extensions Hub veröffentlichen.

5. APIs und Rollen deklarieren

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

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

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

Ebenso deklarieren Sie die Google-APIs, die eine Erweiterung verwendet, 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 Google-APIs erforderlich, die nicht zu Firebase gehören, und ist für diese Anleitung nicht erforderlich.

6. Vom Nutzer konfigurierbare Parameter definieren

Die Funktion, die Sie in den letzten beiden Schritten erstellt haben, hat einen bestimmten RTDB-Speicherort auf eingehende Nachrichten überwacht. Manchmal ist es genau das, was Sie möchten, z. B. wenn Ihre Erweiterung auf einer Datenbankstruktur basiert, die Sie ausschließlich für Ihre Erweiterung verwenden. In den meisten Fällen möchten Sie jedoch, dass diese Werte von Nutzern konfiguriert werden können, die Ihre Erweiterung in ihren Projekten installieren. So können Nutzer Ihre Erweiterung verwenden, um mit ihrer vorhandenen Datenbankkonfiguration zu arbeiten.

Machen Sie den Pfad, den die Erweiterung auf neue Nachrichten überwacht, für den Nutzer konfigurierbar:

  1. Fügen Sie in der Datei extension.yaml einen params-Abschnitt 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
    

    Damit wird ein neuer String-Parameter definiert, den Nutzer festlegen müssen, wenn sie Ihre Erweiterung installieren.

  2. Gehen Sie in der Datei extension.yaml zurück zur Deklaration von makeuppercase 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 makeuppercase-Funktion überwacht dann den vom Nutzer angegebenen Pfad. Mit dieser Syntax können Sie an beliebiger Stelle in extension.yaml (und später auch in POSTINSTALL.md) auf einen beliebigen benutzerdefinierten Parameter verweisen.

  3. Sie können auch über den Funktionscode auf benutzerdefinierte Parameter zugreifen.

    In der Funktion, die Sie im letzten Abschnitt geschrieben haben, haben Sie den Pfad, auf den Änderungen überwacht werden sollen, fest codiert. Ändern Sie die Triggerdefinition so, dass auf den benutzerdefinierten Wert verwiesen wird:

    functions/index.js

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

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

  4. Es ist vielleicht enttäuschend, eine Codeänderung vorzunehmen, die keine Auswirkungen auf die Laufzeit hat. Die wichtige Erkenntnis 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 Protokollausgabe hinzu, um zu demonstrieren, 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 für Tests und die Entwicklung verwenden, überspringen Sie den Installationsprozess. Stattdessen geben Sie Werte für benutzerdefinierte Parameter über eine 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 Pfad oben unterscheidet sich vom Standardpfad und vom zuvor definierten Pfad. So können Sie beim Testen der aktualisierten Erweiterung sehen, dass Ihre Definition wirksam wird.

  6. Starten Sie den Emulator neu und rufen Sie die Benutzeroberfläche des Datenbankemulators noch einmal 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. Ereignishooks für benutzerdefinierte Logik bereitstellen

Als Erweiterungsautor haben Sie bereits gesehen, wie ein Firebase-Produkt die von Ihrer Erweiterung bereitgestellte Logik auslösen kann: Das Erstellen neuer Datensätze 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 Logik auslösen, die der Nutzer definiert.

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, benutzerdefinierte Vorverarbeitungen durchzuführen, bevor eine Erweiterung ihre Arbeit erledigt.

In diesem Leitfaden fügen Sie Ihrer Erweiterung einen asynchronen Hook hinzu, mit dem Nutzer eigene Verarbeitungsschritte definieren können, die ausgeführt werden, nachdem Ihre Erweiterung die Nachricht in Großbuchstaben in die Realtime Database geschrieben hat. Asynchrone Hooks verwenden Eventarc, um nutzerdefinierte Funktionen auszulösen. Erweiterungen deklarieren die Arten von Ereignissen, die sie ausgeben, und Nutzer wählen bei der Installation der Erweiterung aus, an welchen Ereignistypen sie interessiert sind. Wenn sie mindestens ein Ereignis auswählen, wird im Rahmen der Installation ein Eventarc-Channel für die Erweiterung bereitgestellt. Nutzer können dann ihre eigenen Cloud-Funktionen bereitstellen, die diesen Channel überwachen 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 eine 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 universell eindeutig sein. Verwenden Sie daher immer das folgende Format für die Benennung Ihrer Ereignisse: <publisher-id>.<extension-id>.<version>.<description>. (Sie haben noch keine Publisher-ID. Verwenden Sie daher vorerst test-publisher.)

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

    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 die Tatsache genutzt, 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 hat das Ereignis ein Feld subject, das einen Verweis auf den neu erstellten Wert enthält, und eine Nutzlast data, die die ursprünglichen und die in Großbuchstaben geschriebenen Nachrichten enthält. Benutzerdefinierte Funktionen, die durch das Ereignis ausgelöst werden, können diese Informationen nutzen.

  3. Normalerweise werden die Umgebungsvariablen EVENTARC_CHANNEL und EXT_SELECTED_EVENTS basierend auf den Optionen definiert, die der Nutzer während der Installation ausgewählt hat. Für Tests mit dem Emulator müssen Sie diese Variablen manuell in der Datei rtdb-uppercase-messages.env definieren:

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

Sie haben nun die erforderlichen Schritte ausgeführt, um Ihrer Erweiterung einen asynchronen Ereignishook hinzuzufügen.

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

  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 wartet die Funktion darauf, dass die Erweiterung ein complete-Ereignis veröffentlicht. Wenn sie ausgelöst wird, fügt sie der neu in Großbuchstaben geschriebenen Nachricht drei Ausrufezeichen hinzu.

  3. Starten Sie den Emulator neu. Der Emulator lädt die Funktionen der Erweiterung sowie die vom Nutzer definierte Nachbearbeitungsfunktion.

  4. Rufen Sie die Benutzeroberfläche des Datenbank-Emulators auf und 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, sollten die makeuppercase-Funktion der Erweiterung und die extraemphasis-Funktion des Nutzers nacheinander ausgelöst werden. Das Feld upper erhält dann den Wert RECIPE!!!.

8. Lebenszyklus-Event-Handler hinzufügen

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

Firebase Extensions verwendet Cloud Tasks, um Ihre Handler für Lebenszyklusereignisse auszuführen. Sie definieren Event-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 Handler für Lebenszyklusereignisse ausgeführt wird, wird dem Nutzer in der Firebase Console angezeigt, dass für die Erweiterungsinstanz eine Verarbeitung ansteht. Es liegt an Ihrer Handler-Funktion, den laufenden Status und den Abschluss von Aufgaben an den Nutzer zurückzumelden.

So fügen Sie einen Handler für Lebenszyklusereignisse hinzu, mit dem vorhandene Nachrichten nachgetragen werden:

  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.");
      }
    });
    

    Die Funktion verarbeitet nur wenige Datensätze, bevor sie sich wieder in die Aufgabenwarteschlange einreiht. Dies ist eine häufig verwendete Strategie für die Verarbeitung von Aufgaben, die nicht innerhalb des Zeitlimitfensters 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 Ihre 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 die Funktion dann als Handler für das onInstall-Lebenszyklusereignis:

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. Das Backfilling vorhandener Nachrichten ist zwar wünschenswert, die Erweiterung könnte aber auch ohne diese Funktion funktionieren. In solchen Fällen sollten Sie das Ausführen der Lifecycle-Event-Handler optional machen.

    Fügen Sie dazu einen neuen Parameter zu extension.yaml 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 am Anfang der Backfill-Funktion den Wert des Parameters DO_BACKFILL und beenden Sie die Funktion frühzeitig, wenn 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 jetzt in Großbuchstaben umgewandelt, wenn die Erweiterung installiert wird.

Bis zu diesem Punkt haben Sie den Erweiterungsemulator verwendet, um Ihre Erweiterung zu entwickeln und laufende Änderungen zu testen. Der Erweiterungsemulator überspringt jedoch den Installationsvorgang. Wenn Sie den onInstall-Event-Handler testen möchten, müssen Sie die Erweiterung in einem echten Projekt installieren. Das ist aber auch gut so, denn mit der Ergänzung dieser automatischen Backfill-Funktion ist die Erweiterung für die Anleitung jetzt vollständig.

9. In einem echten Firebase-Projekt bereitstellen

Der Extensions-Emulator ist zwar ein hervorragendes Tool, um während der Entwicklung schnell an einer Erweiterung zu arbeiten, aber irgendwann möchten Sie sie 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. Daher benötigen Sie auch ein Rechnungskonto, um eine Erweiterung zu installieren.
  3. Aktivieren Sie die Realtime Database in Ihrem neuen Projekt.
  4. Da Sie testen möchten, ob Ihre Erweiterung vorhandene Daten bei der Installation nachträglich einfügen kann, importieren Sie einige Beispieldaten in Ihre Echtzeitdatenbankinstanz:
    1. Laden Sie einige RTDB-Startdaten herunter.
    2. Klicken Sie in der Firebase Console auf der Seite „Realtime Database“ auf das Dreipunkt-Menü  > JSON importieren und wählen Sie die Datei aus, die Sie gerade heruntergeladen haben.
  5. Damit die Backfill-Funktion die orderByChild-Methode verwenden kann, müssen Sie die Datenbank so konfigurieren, dass Nachrichten anhand des Werts von upper indexiert werden:

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

Installieren Sie die Erweiterung jetzt aus der lokalen Quelle in das neue 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 Nutzererfahrung bei der Installation einer Erweiterung mit dem Firebase CLI-Tool aussieht. Wählen Sie „Ja“ aus, wenn Sie vom Konfigurationstool gefragt werden, ob Sie Ihre vorhandene Datenbank mit Daten füllen möchten.

    Nachdem Sie 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 Datensätze als Erweiterungsmanifest bezeichnet. Nutzer können das Manifest verwenden, um die Konfiguration ihrer Erweiterungen zu speichern und in verschiedenen Projekten bereitzustellen.

  4. Stellen Sie die Erweiterungskonfiguration für Ihr Live-Projekt bereit:

    firebase deploy --only extensions

Im Normalfall sollte die Firebase CLI Ihre Erweiterung in Ihr Projekt hochladen und installieren. Nach Abschluss der Installation wird der Backfill-Vorgang ausgeführt und Ihre Datenbank wird innerhalb weniger Minuten mit Nachrichten in Großbuchstaben aktualisiert. Fügen Sie der Nachrichten-Datenbank 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 Sie genügend Dokumentation bereitstellen, damit sie erfolgreich verwendet werden kann.

Als Sie das Erweiterungsprojekt initialisiert haben, hat die Firebase CLI Platzhalterversionen der minimal erforderlichen Dokumentation erstellt. Aktualisieren Sie diese Dateien, damit sie die von Ihnen entwickelte Erweiterung korrekt widerspiegeln.

extension.yaml

Sie haben diese Datei bereits im Rahmen der Entwicklung dieser Erweiterung aktualisiert. Daher sind derzeit keine weiteren Aktualisierungen erforderlich.

Die in dieser Datei enthaltene Dokumentation ist jedoch nicht zu vernachlässigen. Zusätzlich zu den wichtigen Identifikationsinformationen einer Erweiterung – Name, Beschreibung, Autor, offizieller Repository-Speicherort – enthält die Datei extension.yaml die für Nutzer sichtbare 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 der Nutzer benötigt, bevor er Ihre Erweiterung installiert: Beschreiben Sie kurz, was die Erweiterung macht, erläutern Sie alle Voraussetzungen und informieren Sie den Nutzer über die Abrechnung, die mit der Installation der Erweiterung verbunden ist. Wenn Sie eine Website mit zusätzlichen Informationen haben, können Sie sie hier verlinken.

Der Text dieser Datei wird dem Nutzer im Extensions Hub und mit dem 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 nach der erfolgreichen Installation Ihrer Erweiterung nützlich sind, z. B. weitere Einrichtungsschritte oder 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 durch die konfigurierten Werte ersetzt.

Hier sehen Sie ein Beispiel für eine Datei, die nach der Installation der Tutorial-Erweiterung ausgeführt wird:

### 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

Sie sollten auch die Änderungen dokumentieren, die Sie zwischen den Versionen einer Erweiterung in der Datei CHANGELOG.md vornehmen.

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.

Für die Zwecke dieses Leitfadens wird keine README-Datei erstellt.

Zusätzliche Dokumentation

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

Für die Zwecke dieses Leitfadens wird auf eine ausführlichere Dokumentation verzichtet.

11. Im Erweiterungs-Hub veröffentlichen

Nachdem Ihre Erweiterung fertig programmiert und dokumentiert ist, können Sie sie im Erweiterungs-Hub mit anderen teilen. Da dies jedoch nur ein Tutorial ist, solltest du das nicht wirklich tun. Beginnen Sie mit dem Schreiben Ihrer eigenen Erweiterung. Nutzen Sie dazu die Informationen, die Sie hier und in der restlichen Dokumentation für Firebase Extensions-Publisher erhalten haben, und sehen Sie sich den Quellcode der offiziellen, von Firebase geschriebenen Erweiterungen an.

Wenn Sie Ihre Arbeit im Erweiterungs-Hub veröffentlichen möchten, gehen Sie so vor:

  1. Wenn Sie Ihre erste Erweiterung veröffentlichen, registrieren Sie sich als Erweiterungs-Publisher. Wenn Sie sich als Erweiterungs-Publisher registrieren, erstellen Sie eine Publisher-ID, mit der 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 über eine überprüfbare Quelle verfügbar ist, kann Firebase Ihre Erweiterung direkt von diesem Speicherort aus veröffentlichen. So stellen Sie sicher, dass Sie die aktuell veröffentlichte Version Ihrer Erweiterung veröffentlichen, und Nutzer können den Code, den sie in ihre Projekte installieren, prüfen.

    Derzeit bedeutet das, dass Sie Ihre Erweiterung in einem öffentlichen GitHub-Repository zur Verfügung stellen müssen.

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

  4. Rufen Sie das Publisher-Dashboard in der Firebase Console auf, suchen Sie nach der Erweiterung, die Sie gerade hochgeladen haben, und klicken Sie auf „Im Erweiterungs-Hub veröffentlichen“. Dadurch wird eine Überprüfung durch unsere Mitarbeiter angefordert, was einige Tage dauern kann. Bei Genehmigung wird die Erweiterung im Erweiterungs-Hub veröffentlicht. Wenn Ihr Antrag abgelehnt wird, erhalten Sie eine Nachricht mit dem Grund dafür. Sie können die gemeldeten Probleme dann beheben und den Antrag noch einmal zur Überprüfung einreichen.