Comienza a crear una extensión

En esta página, se explican los pasos necesarios para compilar una extensión simple de Firebase, que puedes instalar en tus proyectos o compartir con otras personas. En este ejemplo sencillo de una extensión de Firebase, se supervisarán los mensajes de Realtime Database y se convertirán en mayúsculas.

1. Configura el entorno y, luego, inicializa un proyecto

Antes de comenzar a compilar una extensión, deberás configurar un entorno de compilación con las herramientas necesarias.

  1. Instala Node.js 16 o una versión más reciente. Para ello, puedes usar nvm (o nvm-windows).

  2. Instala la versión más reciente de Firebase CLI. Para instalarla o actualizarla con npm, ejecuta el siguiente comando:

    npm install -g firebase-tools

Ahora, usa Firebase CLI para inicializar un nuevo proyecto de extensión:

  1. Crea un directorio para la extensión y usa el comando cd para acceder a él:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
  2. Ejecuta el comando ext:dev:init de Firebase CLI:

    firebase ext:dev:init

    Cuando se te solicite, elige JavaScript como lenguaje para las funciones (pero ten en cuenta que también puedes usar TypeScript cuando desarrolles tu propia extensión) y, cuando se te solicite instalar las dependencias, responde “sí” (acepta los valores predeterminados para cualquier otra opción). Este comando configurará una base de código para una extensión nueva, a partir de la cual podrás comenzar a desarrollar tu extensión.

2. Prueba la extensión de ejemplo con el emulador

Cuando Firebase CLI inicializó el nuevo directorio de extensión, creó una función de ejemplo simple y un directorio integration-tests que contiene los archivos necesarios para ejecutar la extensión con Firebase Emulator Suite.

Intenta ejecutar la extensión de ejemplo en el emulador:

  1. Cambia al directorio integration-tests:

    cd functions/integration-tests
  2. Inicia el emulador con un proyecto de demostración:

    firebase emulators:start --project=demo-test

    El emulador carga la extensión en un proyecto ficticio predefinido (demo-test). Hasta ahora, la extensión consta de una sola función activada por HTTP, greetTheWorld, que muestra un mensaje de “Hello World” cuando se accede a ella.

  3. Con el emulador en ejecución, prueba la función greetTheWorld de la extensión. Para ello, visita la URL que imprimió cuando la iniciaste.

    El navegador muestra el mensaje “Hello World from greet-the-world”.

  4. El código fuente de esta función se encuentra en el directorio functions de la extensión. Ábrelo en el editor o IDE que prefieras:

    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. Mientras se esté ejecutando el emulador, se volverán a cargar automáticamente los cambios que realices en el código de Functions. Intenta realizar un pequeño cambio en la función greetTheWorld:

    functions/index.js

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

    Guarda los cambios. El emulador volverá a cargar el código y, cuando visites la URL de la función, verás el saludo actualizado.

3. Agrega información básica a extension.yaml

Ahora que configuraste un entorno de desarrollo y ejecutaste el emulador de extensiones, puedes comenzar a escribir tu propia extensión.

Como primer paso sencillo, edita los metadatos de extensión predefinidos para que reflejen la extensión que quieres escribir en lugar de greet-the-world. Estos metadatos se almacenan en el archivo extension.yaml.

  1. Abre extension.yaml en un editor y reemplaza todo el contenido del archivo por lo siguiente:

    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
    

    Ten en cuenta la convención de nombres que se usa en el campo name: Las extensiones oficiales de Firebase se nombran con un prefijo que indica el producto principal de Firebase en el que opera la extensión, seguido de una descripción de lo que hace. Sigue la misma convención en tus extensiones.

  2. Como cambiaste el nombre de tu extensión, también debes actualizar la configuración del emulador con el nuevo nombre:

    1. En functions/integration-tests/firebase.json, cambia greet-the-world a rtdb-uppercase-messages.
    2. Cambia el nombre de functions/integration-tests/extensions/greet-the-world.env a functions/integration-tests/extensions/rtdb-uppercase-messages.env.

Todavía quedan algunos restos de la extensión greet-the-world en el código de la extensión, pero déjalos por ahora. Los actualizarás en las próximas secciones.

4. Escribe una Cloud Function y declárala como un recurso de la extensión

Ahora puedes comenzar a escribir código. En este paso, escribirás una Cloud Function que realice la tarea principal de tu extensión, que es supervisar Realtime Database en busca de mensajes y convertirlos en mayúsculas.

  1. Abre el código fuente de las funciones de la extensión (en el directorio functions de la extensión) en el editor o IDE que prefieras. Reemplaza su contenido por lo siguiente:

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

    La función antigua, que se reemplazó, era una activada por HTTP que se ejecutaba cuando se accedía a un extremo HTTP. La función nueva se activa mediante eventos de la base de datos en tiempo real: detecta elementos nuevos en una ruta de acceso específica y, cuando los detecta, escribe la versión en mayúsculas del valor en la base de datos.

    Por cierto, este archivo nuevo usa sintaxis del módulo de ECMAScript (import y export) en lugar de CommonJS (require). Para usar módulos de ES en Node, especifica "type": "module" en functions/package.json:

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      
    }
    
  2. Cada función de tu extensión debe declararse en el archivo extension.yaml. En la extensión de ejemplo, se declaró greetTheWorld como la única Cloud Function de la extensión. Ahora que la reemplazaste por makeuppercase, también debes actualizar su declaración.

    Abre extension.yaml y agrega un campo 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. Dado que tu extensión ahora usa Realtime Database como activador, debes actualizar la configuración del emulador para ejecutar el emulador de RTDB junto con el de Cloud Functions:

    1. Si el emulador sigue ejecutándose, presiona Ctrl + C para detenerlo.

    2. Desde el directorio functions/integration-tests, ejecuta el siguiente comando:

      firebase init emulators

      Cuando se te solicite, omite la configuración de un proyecto predeterminado y, luego, selecciona los emuladores de Functions y Database. Acepta los puertos predeterminados y permite que la herramienta de configuración descargue los archivos necesarios.

    3. Reinicia el emulador:

      firebase emulators:start --project=demo-test
  4. Prueba la extensión actualizada:

    1. Abre la IU del emulador de Database con el vínculo que imprimió el emulador cuando lo iniciaste.

    2. Edita el nodo raíz de la base de datos:

      • Campo: messages
      • Tipo: json
      • Valor: {"11": {"original": "recipe"}}

      Si todo está configurado correctamente, cuando guardes los cambios de la base de datos, la función makeuppercase de la extensión debería activarse y agregar un registro secundario al mensaje 11 con el contenido "upper": "RECIPE". Observa los registros y las pestañas de la base de datos de la IU del emulador para confirmar los resultados esperados.

    3. Intenta agregar algunos elementos secundarios más al nodo messages ({"original":"any text"}). Cada vez que agregas un registro nuevo, la extensión debe agregar un campo uppercase con el contenido en mayúsculas del campo original.

Ahora tienes una extensión completa (pero sencilla) que opera en una instancia de RTDB. En las siguientes secciones, definirás mejor esta extensión con algunas funciones adicionales. Tendrás la extensión lista para distribuirla a otras personas y, por último, aprenderás a publicar tu extensión en Extensions Hub.

5. Declara APIs y roles

Firebase otorga a cada instancia de una extensión instalada acceso limitado al proyecto y a sus datos mediante una cuenta de servicio por instancia. Cada cuenta tiene el conjunto mínimo de permisos necesarios para operar. Por este motivo, debes declarar de forma explícita cualquier rol de IAM que requiera tu extensión. Cuando los usuarios instalan tu extensión, Firebase crea una cuenta de servicio con estos roles otorgados y la usa para ejecutar la extensión.

No es necesario declarar roles para activar los eventos de un producto, pero debes declarar una función que interactúe con ella de otro modo. Debido a que la función que agregaste en el último paso escribe en Realtime Database, debes agregar la siguiente declaración a extension.yaml:

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

De manera similar, declaras las APIs de Google que usan una extensión en el campo apis. Cuando los usuarios instalen tu extensión, se les preguntará si desean habilitar automáticamente estas APIs en su proyecto. Por lo general, esto solo es necesario para las APIs de Google que no son de Firebase y no es necesario en esta guía.

6. Define parámetros configurables por el usuario

La función que creaste en los últimos dos pasos supervisó una ubicación RTDB específica para los mensajes entrantes. A veces, lo que deseas realmente es supervisar una ubicación específica, por ejemplo, cuando tu extensión opera en una estructura de base de datos que se usa exclusivamente para la extensión. Sin embargo, la mayoría de las veces, querrás que los usuarios que instalen la extensión en sus proyectos puedan configurar estos valores. De esta manera, los usuarios pueden usar la extensión para trabajar con la configuración de su base de datos existente.

Haz que la ruta de acceso que la extensión detecta para los mensajes nuevos sea configurable por el usuario:

  1. En el archivo extension.yaml, agrega una sección llamada 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
    

    Este código define un nuevo parámetro de cadena que los usuarios deberán configurar cuando instalen tu extensión.

  2. En el archivo extension.yaml, vuelve a la declaración makeuppercase y cambia el campo resource a lo siguiente:

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

    El token ${param:MESSAGE_PATH} es una referencia al parámetro que acabas de definir. Cuando se ejecute tu extensión, este token se reemplazará por cualquier valor que el usuario haya configurado para ese parámetro, con el resultado de que la función makeuppercase escuchará la ruta que especificó el usuario. Puedes usar esta sintaxis para hacer referencia a cualquier parámetro definido por el usuario en cualquier parte de extension.yaml (y en POSTINSTALL.md, hablaremos de esto más adelante).

  3. También puedes acceder a parámetros definidos por el usuario desde el código de tus funciones.

    En la función que escribiste en la última sección, codificaste de forma fija la ruta para supervisar los cambios. En su lugar, cambia la definición del activador para hacer referencia al valor definido por el usuario:

    functions/index.js

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

    Ten en cuenta que, en Extensiones de Firebase, este cambio es solo para fines de documentación. Cuando se implementa una Cloud Function como parte de una extensión, esta usa la definición del activador de la extension.yaml y pasa por alto el valor especificado en la definición de la función. Sin embargo, es una buena idea documentar en tu código de dónde proviene este valor.

  4. Quizás te parezca decepcionante hacer un cambio de código que no surte efecto en el tiempo de ejecución, pero la lección importante que debes aprender es que puedes acceder a cualquier parámetro definido por el usuario en el código de tu función y usarlo como un valor común en la lógica de la función. Como referencia de esta función, agrega la siguiente instrucción de registro para demostrar que realmente accedes al valor que definió el usuario:

    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. Por lo general, se les pide a los usuarios que proporcionen valores para los parámetros cuando instalan una extensión. Sin embargo, cuando usas el emulador para pruebas y desarrollo, omites el proceso de instalación, por lo que, en su lugar, se proporcionan valores para los parámetros definidos por el usuario mediante un archivo env.

    Abre functions/integration-tests/extensions/rtdb-uppercase-messages.env y reemplaza la definición GREETING por lo siguiente:

    MESSAGE_PATH=/msgs/{pushId}/original
    

    Ten en cuenta que la ruta anterior es diferente de la predeterminada y de la ruta de acceso que definiste antes. Esto es solo para comprobar durante las pruebas de la extensión actualizada que su definición surte efecto.

  6. Ahora, reinicia el emulador y vuelve a visitar la IU del emulador de la base de datos.

    Edita el nodo raíz de la base de datos con la ruta de acceso que definiste antes:

    • Campo: msgs
    • Tipo: json
    • Valor: {"11": {"original": "recipe"}}

    Cuando guardes los cambios de la base de datos, la función makeuppercase de la extensión debería activarse como lo hacía antes, pero ahora también debería imprimir el parámetro definido por el usuario en el registro de la consola.

7. Proporciona enlaces de eventos para la lógica definida por el usuario

Como autor de la extensión, ya viste cómo un producto de Firebase puede activar la lógica proporcionada por la extensión: la creación de registros nuevos en Realtime Database activa la función makeuppercase. Las extensiones pueden tener una relación análoga con los usuarios que la instalan: las extensiones pueden activar la lógica que definen los usuarios.

Una extensión puede proporcionar hooks síncronos, asíncronos o de ambos tipos. Los hooks síncronos ofrecen a los usuarios una forma de realizar tareas que bloquean la finalización de una de las funciones de la extensión. Esto puede ser útil, por ejemplo, para brindarles a los usuarios una forma de realizar un procesamiento previo personalizado antes de que una extensión haga su trabajo.

En esta guía, agregarás un hook asíncrono a tu extensión, lo que permitirá a los usuarios definir sus propios pasos de procesamiento para que se ejecuten después de que tu extensión escriba el mensaje en mayúsculas en Realtime Database. Los hooks asíncronos usan Eventarc para activar funciones definidas por el usuario. Las extensiones declaran los tipos de eventos que emiten y, cuando los usuarios instalan la extensión, eligen qué tipos de eventos les interesan. Si eligen al menos un evento, Firebase aprovisionará un canal de Eventarc para la extensión como parte del proceso de instalación. Luego, los usuarios pueden implementar sus propias Cloud Functions que escuchan en ese canal y se activan cuando la extensión publica eventos nuevos.

Sigue estos pasos para agregar un hook asíncrono:

  1. En el archivo extension.yaml, agrega la siguiente sección, que declara el tipo de evento que emite la extensión:

    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.
    

    Los tipos de eventos deben ser únicos a nivel universal. Para garantizar la exclusividad, siempre asigna un nombre al evento con el siguiente formato: <publisher-id>.<extension-id>.<version>.<description> (como aún no tienes un ID de publicador, sola usa test-publisher por ahora).

  2. Al final de la función makeuppercase, agrega código que publique un evento del tipo que acabas de declarar:

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

    Este código de ejemplo aprovecha el hecho de que la variable de entorno EVENTARC_CHANNEL se define solo cuando el usuario habilitó al menos un tipo de evento. Si no se definió EVENTARC_CHANNEL, el código no intenta publicar ningún evento.

    Puedes adjuntar información adicional a un evento de Eventarc. En el ejemplo anterior, el evento tiene un campo subject que contiene una referencia al valor recién creado y una carga útil data que contiene los mensajes originales y en mayúsculas. Las funciones definidas por el usuario que se activan después del evento pueden usar esta información.

  3. Por lo general, las variables de entorno EVENTARC_CHANNEL y EXT_SELECTED_EVENTS se definen según las opciones que el usuario seleccionó durante la instalación. Para realizar pruebas con el emulador, define manualmente estas variables en el archivo rtdb-uppercase-messages.env:

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

En este punto, completaste los pasos necesarios para agregar un hook de evento asíncrono a tu extensión.

Para probar la nueva función que acabas de implementar, en los próximos pasos, asume el rol de un usuario que instala la extensión:

  1. Desde el directorio functions/integration-tests, inicializa un proyecto de Firebase nuevo:

    firebase init functions

    Cuando se te solicite, rechaza la configuración de un proyecto predeterminado, selecciona JavaScript como el lenguaje de Cloud Functions y, luego, instala las dependencias necesarias. Este proyecto representa el proyecto de un usuario, que tiene tu extensión instalada.

  2. Edita integration-tests/functions/index.js y pega el siguiente código:

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

    Este es un ejemplo de una función de procesamiento posterior que puede escribir un usuario. En este caso, la función espera a que la extensión publique un evento complete y, cuando se activa, agrega tres signos de exclamación al mensaje en mayúsculas.

  3. Reinicia el emulador. El emulador cargará las funciones de la extensión y la función de procesamiento posterior que definió el “usuario”.

  4. Visita la IU del emulador de la base de datos y edita el nodo raíz de la base de datos con la ruta que definiste antes:

    • Campo: msgs
    • Tipo: json
    • Valor: {"11": {"original": "recipe"}}

    Cuando guardas los cambios de la base de datos, la función makeuppercase de la extensión y la función extraemphasis del usuario deben activarse en secuencia, lo que hace que el campo upper obtenga el valor RECIPE!!!.

8. Agrega controladores de eventos de ciclo de vida

La extensión que escribiste hasta ahora procesa los mensajes a medida que se crean. Pero ¿qué pasa si tus usuarios ya tienen una base de datos de mensajes cuando instalan la extensión? Las Extensiones de Firebase tienen una función llamada hooks de evento de ciclo de vida, que puedes usar para activar acciones cuando se instala, actualiza o reconfigura tu extensión. En esta sección, usarás hooks de evento de ciclo de vida para reabastecer la base de datos de mensajes existente de un proyecto con mensajes en mayúsculas cuando un usuario instale tu extensión.

Las Extensiones de Firebase usan Cloud Tasks para ejecutar los controladores de eventos del ciclo de vida. Defines los controladores de eventos con Cloud Functions. Cuando una instancia de la extensión alcanza uno de los eventos de ciclo de vida admitidos, si definiste un controlador, se agregará al controlador a una cola de Cloud Tasks. Luego, Cloud Tasks ejecutará el controlador de forma asíncrona. Mientras se ejecute un controlador de eventos de ciclo de vida, Firebase console informará al usuario que la instancia de extensión tiene una tarea de procesamiento en curso. Depende de tu función de controlador informar el estado en curso y la finalización de la tarea al usuario.

Para agregar un controlador de eventos de ciclo de vida que reabastezca los mensajes existentes, haz lo siguiente:

  1. Define una nueva Cloud Function que se active mediante eventos de la lista de tareas en cola:

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

    Ten en cuenta que la función solo procesa algunos registros antes de volver a agregarse a la lista de tareas en cola. Esta es una estrategia de uso general para abordar las tareas de procesamiento que no se pueden completar dentro del tiempo de espera de una función de Cloud Functions. Esta estrategia es una buena opción, ya que no puedes predecir cuántos mensajes podría tener un usuario en su base de datos cuando instala tu extensión.

  2. En el archivo extension.yaml, declara tu función de reabastecimiento como un recurso de extensión que tenga la propiedad taskQueueTrigger:

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

    Luego, declara la función como el controlador del evento del ciclo de vida onInstall:

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. Si bien reabastecer los mensajes es bueno, la extensión aún podría funcionar sin este proceso. En situaciones como esta, debes hacer que la ejecución de los controladores de eventos de ciclo de vida sea opcional.

    Para ello, agrega un parámetro nuevo a 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
    

    Luego, al comienzo de la función de reabastecimiento, verifica el valor del parámetro DO_BACKFILL y sal antes si no está configurado:

    functions/index.js

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

Con los cambios anteriores, la extensión ahora convertirá los mensajes existentes en mayúsculas cuando se instale.

Hasta este punto, usaste el emulador de extensiones para desarrollar tu extensión y probar los cambios en curso. Sin embargo, el emulador de extensiones omite el proceso de instalación, por lo que, para probar tu controlador de eventos onInstall, deberás instalar la extensión en un proyecto real. También es así, ya que, con la adición de esta función de reabastecimiento automático, la extensión del instructivo ahora está completa en código.

9. Implementa la extensión en un proyecto real de Firebase

Si bien el emulador de extensiones es una gran herramienta para iterar rápidamente en una extensión durante el desarrollo, en algún momento querrás probarla en un proyecto real.

Para ello, primero configura un proyecto nuevo con algunos servicios habilitados:

  1. En Firebase console, agrega un proyecto nuevo.
  2. Actualiza tu proyecto al plan Blaze (de pago por uso). Cloud Functions para Firebase requiere que tu proyecto tenga una cuenta de facturación, por lo que también necesitas una cuenta de facturación para instalar una extensión.
  3. En el proyecto nuevo, habilita Realtime Database.
  4. Debido a que quieres probar la capacidad de tu extensión para reabastecer los datos existentes en la instalación, importa algunos datos de muestra a tu instancia de base de datos en tiempo real:
    1. Descarga algunos datos de origen de RTDB.
    2. En la página de Realtime Database de Firebase console, haz clic en (más) > Importar JSON y selecciona el archivo que acabas de descargar.
  5. Si quieres permitir que la función de reabastecimiento use el método orderByChild, configura la base de datos para indexar mensajes en el valor de upper:

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

Ahora, instala tu extensión desde la fuente local al nuevo proyecto:

  1. Crea un directorio nuevo para tu proyecto de Firebase:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. Inicializa un proyecto de Firebase en el directorio de trabajo:

    firebase init database

    Cuando se te solicite, selecciona el proyecto que acabas de crear.

  3. Instala la extensión en tu proyecto de Firebase local:

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

    Aquí puedes ver cómo es la experiencia del usuario cuando se instala una extensión con la herramienta de Firebase CLI. Asegúrate de seleccionar “sí” cuando la herramienta de configuración te pregunte si quieres reabastecer tu base de datos existente.

    Después de seleccionar las opciones de configuración, Firebase CLI guardará tu configuración en el directorio extensions y registrará la ubicación del código fuente de la extensión en el archivo firebase.json. En conjunto, estos dos registros se denominan manifiesto de extensiones. Los usuarios pueden usar el manifiesto para guardar la configuración de sus extensiones y, luego, implementarla en diferentes proyectos.

  4. Implementa la configuración de la extensión en tu proyecto en vivo:

    firebase deploy --only extensions

Si todo resulta bien, Firebase CLI debe subir la extensión a tu proyecto y, luego, instalarla. Cuando se complete la instalación, se ejecutará la tarea de reabastecimiento y, en unos minutos, tu base de datos se actualizará con mensajes en mayúsculas. Agrega algunos nodos nuevos a la base de datos de mensajes y asegúrate de que la extensión también funcione para los mensajes nuevos.

10. Escribe la documentación

Antes de compartir la extensión con los usuarios, asegúrate de proporcionar suficiente documentación para que tengan éxito.

Cuando inicializaste el proyecto de extensión, Firebase CLI creó versiones de stub de la documentación mínima requerida. Actualiza estos archivos para reflejar con precisión la extensión que compilaste.

extension.yaml

Ya actualizaste este archivo porque desarrollaste esta extensión, por lo que no necesitas realizar más actualizaciones en este momento.

Sin embargo, no pases por alto la importancia de la documentación contenida en este archivo. Además de la información de identificación crucial de una extensión (nombre, descripción, autor, ubicación oficial del repositorio), el archivo extension.yaml contiene documentación para el usuario sobre cada recurso y parámetro que el usuario puede configurar. Esta información se muestra a los usuarios en Firebase console, Extensions Hub y Firebase CLI.

PREINSTALL.md

En este archivo, brinda la información que el usuario necesita antes de instalar tu extensión: describe brevemente lo que hace, explica los requisitos previos y proporciona detalles sobre las implicaciones de facturación que podría tener instalar la extensión. Si tienes un sitio web con información adicional, también puedes vincularlo aquí.

El texto de este archivo se muestra al usuario en Extensions Hub y mediante el comando firebase ext:info.

A continuación, se muestra un ejemplo de un archivo 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

Este archivo contiene información útil para los usuarios después de que hayan instalado correctamente tu extensión (por ejemplo, los próximos pasos de configuración, un ejemplo de la extensión en acción, etcétera).

El contenido de POSTINSTALL.md se muestra en Firebase console después de que se configura e instala una extensión. Puedes hacer referencia a los parámetros del usuario en este archivo, que se reemplazarán por los valores configurados.

Este es un ejemplo de archivo posterior a la instalación para la extensión del instructivo:

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

También debes documentar los cambios que realices entre las versiones de una extensión en el archivo CHANGELOG.md.

Dado que la extensión de ejemplo nunca se publicó antes, el registro de cambios tiene solo una entrada:

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

La mayoría de las extensiones también proporcionan un archivo readme para el beneficio de los usuarios que visitan el repositorio de la extensión. puedes escribir este archivo de forma manual o generar una lectura con el comando.

Para los fines de esta guía, no escribas un archivo README.

Documentación adicional

La documentación que se analizó antes es el conjunto mínimo de documentación que debes proporcionar a los usuarios. Muchas extensiones requieren documentación más detallada para que los usuarios las usen de forma correcta. Cuando este sea el caso, debes escribir documentación adicional y alojarla en un lugar al que puedas dirigir a los usuarios.

A los efectos de esta guía, no escribas documentación más extensa.

11. Publica la extensión en Extensions Hub

Ahora que la extensión está completa y documentada, tienes todo listo para compartirla con el mundo en Extensions Hub, pero, como este es solo un instructivo, no lo hagas. Comienza a escribir tu propia extensión con lo que aprendiste aquí y en el resto de la documentación para publicadores de Extensiones de Firebase, y examina el código fuente de las extensiones oficiales escritas por Firebase.

Cuando tengas todo listo para publicar tu trabajo en Extensions Hub, sigue estos pasos:

  1. Si publicas tu primera extensión, regístrate como publicador de extensiones. Cuando te registras como publicador de extensiones, se crea un ID de publicador que les permite a los usuarios identificarte rápidamente como autor de tus extensiones.
  2. Aloja el código fuente de tu extensión en una ubicación comprobable de forma pública. Cuando el código está disponible en una fuente verificable, Firebase puede publicar la extensión directamente desde esta ubicación. Esto te permite asegurarte de publicar la versión actual de tu extensión y ayuda a los usuarios, ya que les permite examinar el código que instalan en sus proyectos.

    Por el momento, esto significa que tu extensión está disponible en un repositorio público de GitHub.

  3. Sube tu extensión a Extensions Hub con el comando firebase ext:dev:upload.

  4. Ve al panel del editor en Firebase console, busca la extensión que acabas de subir y haz clic en “Publicar en Extensions Hub” para solicitar una revisión a nuestro personal de revisión, lo que puede tardar algunos días. Si se aprueba, la extensión se publicará en Extensions Hub. De lo contrario, recibirás un mensaje en el que se explicará el motivo. Podrás abordar los problemas informados y volver a enviar la extensión para que se revise.