Commencer à créer une extension

Cette page vous explique les étapes requises pour créer une extension Firebase simple, que vous pouvez installer dans vos projets ou partager avec d'autres utilisateurs. Cet exemple simple d'extension Firebase surveille les messages de votre base de données Realtime Database et les convertit en majuscules.

1. Configurer votre environnement et initialiser un projet

Avant de pouvoir commencer à créer une extension, vous devez configurer un environnement de compilation avec les outils requis.

  1. Installez Node.js 16 ou une version ultérieure. Une façon d'installer Node consiste à utiliser nvm (ou nvm-windows).

  2. Installez la dernière version de la CLI Firebase ou appliquez la mise à jour correspondante. Pour installer ou mettre à jour à l'aide de npm, exécutez la commande suivante:

    npm install -g firebase-tools

Utilisez maintenant la CLI Firebase pour initialiser un nouveau projet d'extension:

  1. Créez un répertoire pour votre extension et cd dans celui-ci:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
  2. Exécutez la commande ext:dev:init de la CLI Firebase:

    firebase ext:dev:init

    Lorsque vous y êtes invité, choisissez JavaScript comme langage pour les fonctions (mais notez que vous pouvez également utiliser TypeScript lorsque vous développez votre propre extension). Lorsque vous êtes invité à installer des dépendances, répondez "oui". (Acceptez les valeurs par défaut pour toutes les autres options.) Cette commande configure un code de base squelette pour une nouvelle extension, à partir duquel vous pouvez commencer à développer votre extension.

2. Essayer l'exemple d'extension à l'aide de l'émulateur

Lorsque la CLI Firebase a initialisé le nouveau répertoire d'extensions, elle a créé un exemple de fonction simple et un répertoire integration-tests contenant les fichiers nécessaires à l'exécution d'une extension à l'aide de la suite d'émulateurs Firebase.

Essayez d'exécuter l'exemple d'extension dans l'émulateur:

  1. Accédez au répertoire integration-tests :

    cd functions/integration-tests
  2. Démarrez l'émulateur avec un projet de démonstration:

    firebase emulators:start --project=demo-test

    L'émulateur charge l'extension dans un projet "factice" prédéfini (demo-test). L'extension se compose jusqu'à présent d'une seule fonction déclenchée par HTTP, greetTheWorld, qui renvoie un message "Hello World" lorsqu'elle est consultée.

  3. Lorsque l'émulateur est toujours en cours d'exécution, essayez la fonction greetTheWorld de l'extension en accédant à l'URL qu'elle a imprimée lorsque vous l'avez démarrée.

    Votre navigateur affiche le message "Hello World from greet-the-world" (Bonjour le monde depuis greet-the-world).

  4. Le code source de cette fonction se trouve dans le répertoire functions de l'extension. Ouvrez la source dans l'éditeur ou l'IDE de votre choix:

    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. Lorsque l'émulateur est en cours d'exécution, il recharge automatiquement toutes les modifications que vous apportez à votre code Functions. Essayez d'apporter une légère modification à la fonction greetTheWorld:

    functions/index.js

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

    Enregistrez les modifications. L'émulateur rechargera votre code. Désormais, lorsque vous accéderez à l'URL de la fonction, vous verrez le message d'accueil mis à jour.

3. Ajouter des informations de base à extension.yaml

Maintenant que vous avez configuré un environnement de développement et que vous exécutez l'émulateur d'extensions, vous pouvez commencer à écrire votre propre extension.

Pour commencer, modifiez les métadonnées d'extension prédéfinies afin qu'elles reflètent l'extension que vous souhaitez écrire au lieu de greet-the-world. Ces métadonnées sont stockées dans le fichier extension.yaml.

  1. Ouvrez extension.yaml dans votre éditeur et remplacez l'intégralité du fichier par ce qui suit:

    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
    

    Notez la convention d'attribution de nom utilisée dans le champ name: les extensions Firebase officielles sont nommées avec un préfixe indiquant le produit Firebase principal sur lequel l'extension fonctionne, suivi d'une description de son fonctionnement. Vous devez utiliser la même convention dans vos propres extensions.

  2. Étant donné que vous avez modifié le nom de votre extension, vous devez également mettre à jour la configuration de l'émulateur avec le nouveau nom:

    1. Dans functions/integration-tests/firebase.json, remplacez greet-the-world par rtdb-uppercase-messages.
    2. functions/integration-tests/extensions/greet-the-world.env a été renommé functions/integration-tests/extensions/rtdb-uppercase-messages.env.

Il reste encore des restes de l'extension greet-the-world dans le code de votre extension, mais laissez-les pour l'instant. Vous les mettrez à jour dans les prochaines sections.

4. Écrire une fonction Cloud et la déclarer en tant que ressource d'extension

Vous pouvez maintenant commencer à écrire du code. À cette étape, vous allez écrire une fonction Cloud qui effectue la tâche principale de votre extension, à savoir surveiller les messages dans votre base de données en temps réel et les convertir en majuscules.

  1. Ouvrez la source des fonctions de l'extension (dans le répertoire functions de l'extension) dans l'éditeur ou l'IDE de votre choix. Remplacez son contenu par ceci:

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

    L'ancienne fonction, que vous avez remplacée, était une fonction déclenchée par HTTP, qui s'exécutait lorsqu'un point de terminaison HTTP était accessible. La nouvelle fonction est déclenchée par des événements de base de données en temps réel: elle recherche de nouveaux éléments à un chemin d'accès particulier et, lorsqu'un est détecté, elle écrit la version en majuscules de la valeur dans la base de données.

    Par ailleurs, ce nouveau fichier utilise la syntaxe de module ECMAScript (import et export) au lieu de CommonJS (require). Pour utiliser des modules ES dans Node, spécifiez "type": "module" dans functions/package.json:

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      
    }
    
  2. Chaque fonction de votre extension doit être déclarée dans le fichier extension.yaml. L'exemple d'extension déclarait greetTheWorld comme seule fonction Cloud de l'extension. Maintenant que vous l'avez remplacé par makeuppercase, vous devez également mettre à jour sa déclaration.

    Ouvrez extension.yaml et ajoutez un champ resources:

    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. Étant donné que votre extension utilise désormais Realtime Database comme déclencheur, vous devez mettre à jour la configuration de l'émulateur pour exécuter l'émulateur RTDB avec l'émulateur Cloud Functions:

    1. Si l'émulateur est toujours en cours d'exécution, arrêtez-le en appuyant sur Ctrl+C.

    2. Depuis le répertoire functions/integration-tests, exécutez la commande suivante:

      firebase init emulators

      Lorsque vous y êtes invité, ignorez la configuration d'un projet par défaut, puis sélectionnez les émulateurs Functions et Database. Acceptez les ports par défaut et autorisez l'outil de configuration à télécharger les fichiers requis.

    3. Redémarrez l'émulateur:

      firebase emulators:start --project=demo-test
  4. Essayez votre extension mise à jour:

    1. Ouvrez l'interface utilisateur de l'émulateur de base de données à l'aide du lien imprimé par l'émulateur lorsque vous l'avez démarré.

    2. Modifiez le nœud racine de la base de données:

      • Champ:messages
      • Type : json
      • Valeur : {"11": {"original": "recipe"}}

      Si tout est configuré correctement, lorsque vous enregistrez les modifications apportées à la base de données, la fonction makeuppercase de l'extension doit se déclencher et ajouter un enregistrement enfant au message 11 avec le contenu "upper": "RECIPE". Consultez les journaux et les onglets de base de données de l'UI de l'émulateur pour confirmer les résultats attendus.

    3. Essayez d'ajouter d'autres enfants au nœud messages ({"original":"any text"}). Chaque fois que vous ajoutez un enregistrement, l'extension doit ajouter un champ uppercase contenant le contenu en majuscules du champ original.

Vous disposez désormais d'une extension complète, mais simple, qui fonctionne sur une instance RTDB. Dans les sections suivantes, vous allez affiner cette extension avec quelques fonctionnalités supplémentaires. Vous préparerez ensuite l'extension à être distribuée à d'autres utilisateurs, puis vous apprendrez à la publier sur Extensions Hub.

5. Déclarer des API et des rôles

Firebase accorde à chaque instance d'une extension installée un accès limité au projet et à ses données à l'aide d'un compte de service par instance. Chaque compte dispose de l'ensemble minimal d'autorisations nécessaires à son fonctionnement. Pour cette raison, vous devez déclarer explicitement tous les rôles IAM requis par votre extension. Lorsque les utilisateurs installent votre extension, Firebase crée un compte de service auquel ces rôles sont attribués et l'utilise pour exécuter l'extension.

Vous n'avez pas besoin de déclarer de rôles pour déclencher les événements d'un produit, mais vous devez déclarer un rôle pour interagir avec lui. Étant donné que la fonction que vous avez ajoutée à l'étape précédente écrit dans Realtime Database, vous devez ajouter la déclaration suivante à extension.yaml:

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

De même, vous déclarez les API Google utilisées par une extension dans le champ apis. Lorsque les utilisateurs installent votre extension, ils sont invités à activer automatiquement ces API pour leur projet. Cela n'est généralement nécessaire que pour les API Google autres que Firebase et n'est pas nécessaire pour ce guide.

6. Définir des paramètres configurables par l'utilisateur

La fonction que vous avez créée au cours des deux dernières étapes a surveillé un emplacement RTDB spécifique pour les messages entrants. Parfois, vous souhaitez vraiment surveiller un emplacement spécifique, par exemple lorsque votre extension fonctionne sur une structure de base de données que vous utilisez exclusivement pour votre extension. Toutefois, la plupart du temps, vous devrez configurer ces valeurs par les utilisateurs qui installent votre extension dans leurs projets. Les utilisateurs pourront ainsi utiliser votre extension pour travailler avec leur configuration de base de données existante.

Faites en sorte que le chemin d'accès que l'extension surveille pour les nouveaux messages soit configurable par l'utilisateur:

  1. Dans le fichier extension.yaml, ajoutez une section params:

    - 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
    

    Cela définit un nouveau paramètre de chaîne que les utilisateurs seront invités à définir lorsqu'ils installeront votre extension.

  2. Toujours dans le fichier extension.yaml, revenez à votre déclaration makeuppercase et remplacez le champ resource par le code suivant:

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

    Le jeton ${param:MESSAGE_PATH} fait référence au paramètre que vous venez de définir. Lorsque votre extension s'exécute, ce jeton est remplacé par la valeur que l'utilisateur a configurée pour ce paramètre. La fonction makeuppercase écoute alors le chemin d'accès spécifié par l'utilisateur. Vous pouvez utiliser cette syntaxe pour faire référence à n'importe quel paramètre défini par l'utilisateur n'importe où dans extension.yaml (et dans POSTINSTALL.md, nous y reviendrons plus tard).

  3. Vous pouvez également accéder aux paramètres définis par l'utilisateur à partir du code de vos fonctions.

    Dans la fonction que vous avez écrite dans la section précédente, vous avez codé en dur le chemin d'accès à surveiller pour détecter les modifications. Modifiez la définition du déclencheur pour qu'elle fasse référence à la valeur définie par l'utilisateur:

    functions/index.js

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

    Notez que dans les extensions Firebase, ce changement est purement à des fins de documentation: lorsqu'une fonction Cloud est déployée dans le cadre d'une extension, elle utilise la définition du déclencheur du fichier extension.yaml et ignore la valeur spécifiée dans la définition de la fonction. Toutefois, il est recommandé de documenter dans votre code la source de cette valeur.

  4. Vous pouvez être déçu de modifier le code sans effet d'exécution, mais la leçon importante à retenir est que vous pouvez accéder à n'importe quel paramètre défini par l'utilisateur dans le code de votre fonction et l'utiliser comme une valeur ordinaire dans la logique de la fonction. Pour faire référence à cette fonctionnalité, ajoutez l'instruction de journalisation suivante pour montrer que vous accédez bien à la valeur définie par l'utilisateur:

    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. Normalement, les utilisateurs sont invités à fournir des valeurs pour les paramètres lorsqu'ils installent une extension. Toutefois, lorsque vous utilisez l'émulateur pour les tests et le développement, vous ignorez le processus d'installation. Vous fournissez donc des valeurs pour les paramètres définis par l'utilisateur à l'aide d'un fichier env.

    Ouvrez functions/integration-tests/extensions/rtdb-uppercase-messages.env et remplacez la définition de GREETING par ce qui suit:

    MESSAGE_PATH=/msgs/{pushId}/original
    

    Notez que le chemin d'accès ci-dessus est différent du chemin d'accès par défaut et du chemin d'accès que vous avez défini précédemment. Il s'agit simplement de vous prouver que votre définition prend effet lorsque vous essayez votre extension mise à jour.

  6. Redémarrez l'émulateur, puis accédez à nouveau à l'interface utilisateur de l'émulateur de base de données.

    Modifiez le nœud racine de la base de données à l'aide du chemin que vous avez défini ci-dessus:

    • Champ:msgs
    • Type : json
    • Valeur : {"11": {"original": "recipe"}}

    Lorsque vous enregistrez les modifications apportées à la base de données, la fonction makeuppercase de l'extension doit se déclencher comme auparavant, mais elle doit également imprimer le paramètre défini par l'utilisateur dans le journal de la console.

7. Fournir des hooks d'événement pour la logique définie par l'utilisateur

En tant qu'auteur d'une extension, vous avez déjà vu comment un produit Firebase peut déclencher la logique fournie par votre extension: la création d'enregistrements dans Realtime Database déclenche votre fonction makeuppercase. Votre extension peut avoir une relation analogue avec les utilisateurs qui l'installent: votre extension peut déclencher une logique définie par l'utilisateur.

Une extension peut fournir des hooks synchrones, des hooks asynchrones ou les deux. Les hooks synchrones permettent aux utilisateurs d'effectuer des tâches qui bloquent l'exécution de l'une des fonctions de l'extension. Cela peut être utile, par exemple, pour permettre aux utilisateurs d'effectuer un prétraitement personnalisé avant qu'une extension ne s'exécute.

Dans ce guide, vous allez ajouter un hook asynchrone à votre extension, ce qui permettra aux utilisateurs de définir leurs propres étapes de traitement à exécuter une fois que votre extension aura écrit le message en majuscules dans la base de données en temps réel. Les hooks asynchrones utilisent Eventarc pour déclencher des fonctions définies par l'utilisateur. Les extensions déclarent les types d'événements qu'elles émettent. Lorsque les utilisateurs installent l'extension, ils choisissent les types d'événements qui les intéressent. S'il choisit au moins un événement, Firebase provisionnera un canal Eventarc pour l'extension lors du processus d'installation. Les utilisateurs peuvent ensuite déployer leurs propres fonctions cloud qui écoutent sur ce canal et se déclenchent lorsque l'extension publie de nouveaux événements.

Pour ajouter un hook asynchrone, procédez comme suit:

  1. Dans le fichier extension.yaml, ajoutez la section suivante, qui déclare le type d'événement que l'extension émet:

    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.
    

    Les types d'événements doivent être universellement uniques. Pour vous en assurer, nommez toujours vos événements en utilisant le format suivant : <publisher-id>.<extension-id>.<version>.<description>. (Vous n'avez pas encore d'ID d'éditeur. Utilisez donc test-publisher pour le moment.)

  2. À la fin de la fonction makeuppercase, ajoutez du code qui publie un événement du type que vous venez de déclarer:

    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,
        },
      });
    

    Cet exemple de code profite du fait que la variable d'environnement EVENTARC_CHANNEL n'est définie que lorsque l'utilisateur a activé au moins un type d'événement. Si EVENTARC_CHANNEL n'est pas défini, le code ne tente pas de publier d'événements.

    Vous pouvez joindre des informations supplémentaires à un événement Eventarc. Dans l'exemple ci-dessus, l'événement comporte un champ subject qui contient une référence à la valeur nouvellement créée, ainsi qu'une charge utile data qui contient les messages d'origine et en majuscules. Les fonctions définies par l'utilisateur qui déclenchent l'événement peuvent utiliser ces informations.

  3. Normalement, les variables d'environnement EVENTARC_CHANNEL et EXT_SELECTED_EVENTS sont définies en fonction des options sélectionnées par l'utilisateur lors de l'installation. Pour les tests avec l'émulateur, définissez manuellement ces variables dans le fichier rtdb-uppercase-messages.env:

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

À ce stade, vous avez suivi les étapes nécessaires pour ajouter un hook d'événement asynchrone à votre extension.

Pour tester cette nouvelle fonctionnalité que vous venez d'implémenter, dans les prochaines étapes, endossez le rôle d'un utilisateur qui installe l'extension:

  1. Dans le répertoire functions/integration-tests, initialisez un nouveau projet Firebase:

    firebase init functions

    Lorsque vous y êtes invité, refusez de configurer un projet par défaut, sélectionnez JavaScript comme langage Cloud Functions et installez les dépendances requises. Ce projet représente le projet d'un utilisateur, dans lequel votre extension est installée.

  2. Modifiez integration-tests/functions/index.js et collez le code suivant:

    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}!!!`);
      }
    );
    

    Il s'agit d'un exemple de fonction de post-traitement qu'un utilisateur peut écrire. Dans ce cas, la fonction écoute l'extension pour publier un événement complete et, lorsqu'elle est déclenchée, ajoute trois points d'exclamation au message mis en majuscules.

  3. Redémarrez l'émulateur. L'émulateur charge les fonctions de l'extension ainsi que la fonction de post-traitement définie par l'utilisateur.

  4. Accédez à l'interface utilisateur de l'émulateur de base de données et modifiez le nœud racine de la base de données à l'aide du chemin que vous avez défini ci-dessus:

    • Champ:msgs
    • Type : json
    • Valeur : {"11": {"original": "recipe"}}

    Lorsque vous enregistrez les modifications apportées à la base de données, la fonction makeuppercase de l'extension et la fonction extraemphasis de l'utilisateur doivent se déclencher de manière séquentielle, ce qui permet au champ upper d'obtenir la valeur RECIPE!!!.

8. Ajouter des gestionnaires d'événements de cycle de vie

L'extension que vous avez écrite jusqu'à présent traite les messages au fur et à mesure de leur création. Mais que se passe-t-il si vos utilisateurs disposent déjà d'une base de données de messages lorsqu'ils installent l'extension ? Les extensions Firebase disposent d'une fonctionnalité appelée hooks d'événement de cycle de vie que vous pouvez utiliser pour déclencher des actions lorsque votre extension est installée, mise à jour ou reconfigurée. Dans cette section, vous allez utiliser des hooks d'événement de cycle de vie pour remplir la base de données de messages existante d'un projet avec des messages en majuscules lorsqu'un utilisateur installe votre extension.

Les extensions Firebase utilisent Cloud Tasks pour exécuter vos gestionnaires d'événements de cycle de vie. Vous définissez des gestionnaires d'événements à l'aide de fonctions Cloud. Chaque fois qu'une instance de votre extension atteint l'un des événements de cycle de vie compatibles, si vous avez défini un gestionnaire, il est ajouté à une file d'attente Cloud Tasks. Cloud Tasks exécute ensuite le gestionnaire de manière asynchrone. Lorsqu'un gestionnaire d'événements de cycle de vie est en cours d'exécution, la console Firebase indique à l'utilisateur qu'une tâche de traitement est en cours pour l'instance d'extension. C'est à votre fonction de gestionnaire de signaler l'état en cours et l'avancement de la tâche à l'utilisateur.

Pour ajouter un gestionnaire d'événements de cycle de vie qui remplit les messages existants, procédez comme suit:

  1. Définissez une fonction Cloud déclenchée par des événements de file d'attente de tâches:

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

    Notez que la fonction ne traite que quelques enregistrements avant de s'ajouter à nouveau à la file d'attente de tâches. Il s'agit d'une stratégie couramment utilisée pour gérer les tâches de traitement qui ne peuvent pas être effectuées dans le délai avant expiration d'une fonction Cloud. Étant donné que vous ne pouvez pas prédire le nombre de messages qu'un utilisateur peut déjà avoir dans sa base de données lorsqu'il installe votre extension, cette stratégie est adaptée.

  2. Dans le fichier extension.yaml, déclarez votre fonction de remplissage comme une ressource d'extension avec la propriété taskQueueTrigger:

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

    Déclarez ensuite la fonction comme gestionnaire de l'événement de cycle de vie onInstall:

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. Bien que le remplissage des messages existants soit utile, l'extension peut toujours fonctionner sans cela. Dans ce cas, vous devez rendre l'exécution des gestionnaires d'événements de cycle de vie facultative.

    Pour ce faire, ajoutez un nouveau paramètre à extension.yaml:

    - 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
    

    Ensuite, au début de la fonction de remplissage, vérifiez la valeur du paramètre DO_BACKFILL et quittez prématurément si elle n'est pas définie:

    functions/index.js

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

Avec les modifications ci-dessus, l'extension convertira désormais les messages existants en majuscules lorsqu'elle sera installée.

Jusqu'à présent, vous avez utilisé l'émulateur d'extension pour développer votre extension et tester les modifications en cours. Toutefois, l'émulateur d'extension ignore le processus d'installation. Pour tester votre gestionnaire d'événements onInstall, vous devez donc installer l'extension dans un projet réel. Cela est tout aussi bien, car avec l'ajout de cette fonctionnalité de remplissage automatique, l'extension du tutoriel est maintenant complète.

9. Déployer dans un projet Firebase réel

Bien que l'émulateur d'extensions soit un excellent outil pour itérer rapidement sur une extension pendant le développement, vous devrez à un moment donné l'essayer dans un projet réel.

Pour ce faire, commencez par configurer un nouveau projet avec certains services activés:

  1. Dans la console Firebase, ajoutez un projet.
  2. Mettez à niveau votre projet vers le forfait Blaze avec paiement à l'usage. Cloud Functions for Firebase nécessite que votre projet dispose d'un compte de facturation. Vous avez donc également besoin d'un compte de facturation pour installer une extension.
  3. Dans votre nouveau projet, activez Realtime Database.
  4. Comme vous souhaitez tester la capacité de votre extension à remplir les données existantes lors de l'installation, importez des exemples de données dans votre instance de base de données en temps réel :
    1. Téléchargez des données de référence RTDB.
    2. Sur la page "Realtime Database" (Base de données en temps réel) de la console Firebase, cliquez sur (Plus) > Importer un fichier JSON, puis sélectionnez le fichier que vous venez de télécharger.
  5. Pour permettre à la fonction de remplissage de la base de données d'utiliser la méthode orderByChild, configurez la base de données pour qu'elle indexe les messages en fonction de la valeur de upper:

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

Installez maintenant votre extension à partir d'une source locale dans le nouveau projet:

  1. Créez un répertoire pour votre projet Firebase:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. Initialisez un projet Firebase dans le répertoire de travail:

    firebase init database

    Lorsque vous y êtes invité, sélectionnez le projet que vous venez de créer.

  3. Installez l'extension dans votre projet Firebase local:

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

    Vous pouvez voir ici à quoi ressemble l'expérience utilisateur lors de l'installation d'une extension à l'aide de l'outil CLI Firebase. Assurez-vous de sélectionner "Oui" lorsque l'outil de configuration vous demande si vous souhaitez remplir votre base de données existante.

    Une fois que vous avez sélectionné les options de configuration, la CLI Firebase enregistre votre configuration dans le répertoire extensions et enregistre l'emplacement de la source de l'extension dans le fichier firebase.json. Au total, ces deux enregistrements sont appelés fichier manifeste d'extensions. Les utilisateurs peuvent utiliser le fichier manifeste pour enregistrer la configuration de leurs extensions et la déployer dans différents projets.

  4. Déployez la configuration de votre extension dans votre projet en ligne:

    firebase deploy --only extensions

Si tout se passe bien, la CLI Firebase devrait importer votre extension dans votre projet et l'installer. Une fois l'installation terminée, la tâche de remplissage est exécutée et, en quelques minutes, votre base de données est mise à jour avec des messages en majuscules. Ajoutez des nœuds à la base de données des messages et assurez-vous que l'extension fonctionne également pour les nouveaux messages.

10. Rédiger la documentation

Avant de partager votre extension avec les utilisateurs, assurez-vous de leur fournir suffisamment de documentation pour qu'ils puissent l'utiliser.

Lorsque vous avez initialisé le projet d'extension, la CLI Firebase a créé des versions bouchon de la documentation minimale requise. Modifiez ces fichiers pour qu'ils reflètent précisément l'extension que vous avez créée.

extension.yaml

Vous avez déjà mis à jour ce fichier lorsque vous avez développé cette extension. Vous n'avez donc pas besoin d'apporter d'autres modifications pour le moment.

Toutefois, ne négligez pas l'importance de la documentation contenue dans ce fichier. En plus des informations d'identification essentielles d'une extension (nom, description, auteur, emplacement du dépôt officiel), le fichier extension.yaml contient une documentation destinée aux utilisateurs pour chaque ressource et paramètre configurable par l'utilisateur. Ces informations sont présentées aux utilisateurs dans la console Firebase, le hub des extensions et la CLI Firebase.

PREINSTALL.md

Dans ce fichier, fournissez les informations dont l'utilisateur a besoin avant d'installer votre extension: décrivez brièvement son fonctionnement, expliquez les conditions préalables et fournissez des informations sur les conséquences de l'installation de l'extension sur la facturation. Si vous disposez d'un site Web contenant des informations supplémentaires, vous pouvez également y ajouter un lien.

Le texte de ce fichier est présenté à l'utilisateur dans Extensions Hub et par la commande firebase ext:info.

Voici un exemple de fichier PREINSTALL:

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

Ce fichier contient des informations utiles pour les utilisateurs après l'installation de votre extension: par exemple, les étapes de configuration ultérieures, un exemple de l'extension en action, etc.

Le contenu de POSTINSTALL.md s'affiche dans la console Firebase une fois une extension configurée et installée. Vous pouvez faire référence à des paramètres utilisateur dans ce fichier. Ils seront remplacés par les valeurs configurées.

Voici un exemple de fichier post-installation pour l'extension du tutoriel:

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

Vous devez également documenter les modifications que vous apportez entre les versions d'une extension dans le fichier CHANGELOG.md.

Comme l'exemple d'extension n'a jamais été publié auparavant, le journal des modifications ne comporte qu'une seule entrée:

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

La plupart des extensions fournissent également un fichier "readme" à l'attention des utilisateurs qui accèdent au dépôt de l'extension. Vous pouvez écrire ce fichier manuellement ou générer un fichier "readme" à l'aide de la commande.

Pour les besoins de ce guide, ignorez la création d'un fichier "readme".

Documentation supplémentaire

La documentation décrite ci-dessus est l'ensemble minimal de documentation que vous devez fournir aux utilisateurs. De nombreuses extensions nécessitent une documentation plus détaillée pour que les utilisateurs puissent les utiliser. Dans ce cas, vous devez rédiger une documentation supplémentaire et l'héberger à un endroit où vous pourrez rediriger les utilisateurs.

Pour les besoins de ce guide, ne rédigez pas de documentation plus détaillée.

11. Publier sur Extensions Hub

Maintenant que le code de votre extension est terminé et documenté, vous pouvez la partager avec le monde entier sur le Hub des extensions. Mais comme il s'agit d'un tutoriel, ne le faites pas. Commencez à écrire votre propre extension en vous appuyant sur ce que vous avez appris ici et dans le reste de la documentation de l'éditeur d'extensions Firebase, et en examinant la source des extensions officielles écrites par Firebase.

Lorsque vous êtes prêt à publier votre travail sur Extensions Hub, procédez comme suit:

  1. Si vous publiez votre première extension, inscrivez-vous en tant qu'éditeur d'extensions. Lorsque vous vous enregistrez en tant qu'éditeur d'extensions, vous créez un ID d'éditeur qui permet aux utilisateurs de vous identifier rapidement comme l'auteur de vos extensions.
  2. Hébergez le code source de votre extension dans un emplacement publiquement vérifiable. Lorsque votre code est disponible à partir d'une source vérifiable, Firebase peut publier votre extension directement à partir de cet emplacement. Cela vous permet de vous assurer que vous publiez la version actuelle de votre extension, et aide les utilisateurs à examiner le code qu'ils installent dans leurs projets.

    Actuellement, cela signifie que vous devez rendre votre extension disponible dans un dépôt GitHub public.

  3. Importez votre extension dans le Hub des extensions à l'aide de la commande firebase ext:dev:upload.

  4. Accédez à votre tableau de bord d'éditeur dans la console Firebase, recherchez l'extension que vous venez d'importer, puis cliquez sur "Publier dans le Hub des extensions". Notre équipe d'examinateurs examinera votre demande, ce qui peut prendre quelques jours. Si elle est approuvée, l'extension sera publiée sur Extensions Hub. Si votre demande est refusée, vous recevrez un message vous expliquant le motif. Vous pourrez alors résoudre les problèmes signalés et renvoyer votre demande pour examen.