Reutiliza el código de Cloud Functions como una extensión de Firebase

1. Antes de comenzar

Una extensión de Firebase realiza una tarea o un conjunto de tareas específicos en respuesta a solicitudes HTTP o a eventos de activación de otros productos de Firebase y Google, como Firebase Cloud Messaging, Cloud Firestore o Pub/Sub.

Qué compilarás

En este codelab, crearás una extensión de Firebase para el geohashing. Una vez implementada, la extensión convierte las coordenadas X e Y en geohashes en respuesta a eventos de Firestore o mediante invocaciones de funciones que admiten llamadas. Esto se puede usar como alternativa a implementar la biblioteca de geofire en todas tus plataformas de destino para almacenar datos, lo que te ahorrará tiempo.

La extensión de geohash que se muestra en Firebase console

Qué aprenderás

  • Cómo tomar el código existente de Cloud Functions y convertirlo en una extensión de Firebase distribuible
  • Cómo configurar un archivo extension.yaml
  • Cómo almacenar cadenas sensibles (claves de API) en una extensión
  • Cómo permitir que los desarrolladores de la extensión la configuren según sus necesidades
  • Cómo probar e implementar la extensión

Requisitos

  • Firebase CLI (instalación y acceso)
  • Una Cuenta de Google, como una cuenta de Gmail
  • Node.js y npm
  • Tu entorno de desarrollo favorito

2. Prepárate

Obtén el código

Todo lo que necesitas para esta extensión se encuentra en un repositorio de GitHub. Para comenzar, toma el código y ábrelo en tu entorno de desarrollo favorito.

Descargar código fuente

  1. Descomprime el archivo ZIP que se descargó.
  2. Para instalar las dependencias requeridas, abre la terminal en el directorio functions y ejecuta el comando npm install.

Configura Firebase

En este codelab, se recomienda encarecidamente el uso de emuladores de Firebase. Si quieres probar el desarrollo de extensiones con un proyecto de Firebase real, consulta Cómo crear un proyecto de Firebase. En este codelab, se usa Cloud Functions. Por lo tanto, si utilizas un proyecto real de Firebase en lugar de los emuladores, debes actualizar al plan de precios Blaze.

¿Quieres avanzar?

Puedes descargar una versión completa del codelab. Si no puedes avanzar o si quieres ver cómo se ve una extensión completada, consulta la rama codelab-end del repositorio de GitHub o descarga el archivo ZIP completado.

3. Revisa el código

  • Abre el archivo index.ts del archivo ZIP. Ten en cuenta que contiene dos declaraciones de Cloud Functions.

¿Qué hacen estas funciones?

Estas funciones de demostración se usan para el geohashing. Toman un par de coordenadas y las convierten en un formato optimizado para consultas geográficas en Firestore. Las funciones simulan el uso de una llamada a la API para que puedas obtener más información sobre el manejo de tipos de datos sensibles en las extensiones. Para obtener más información, revisa la documentación sobre cómo ejecutar consultas de ubicación geográfica en datos en Firestore.

Constantes de función

Las constantes se declaran al principio, en la parte superior del archivo index.ts. Se hace referencia a algunas de estas constantes en los activadores definidos de la extensión.

index.ts;

import {firestore} from "firebase-functions";
import {initializeApp} from "firebase-admin/app";
import {GeoHashService, ResultStatusCode} from "./fake-geohash-service";
import {onCall} from "firebase-functions/v1/https";
import {fieldValueExists} from "./utils";

const documentPath = "users/{uid}";
const xField = "xv";
const yField = "yv";
const apiKey = "1234567890";
const outputField = "hash";

initializeApp();

const service = new GeoHashService(apiKey);

Activador de Firestore

La primera función en el archivo index.ts se ve de la siguiente manera:

index.ts;

export const locationUpdate = firestore.document(documentPath)
  .onWrite((change) => {
    // item deleted
    if (change.after == null) {
      return 0;
    }
    // double check that both values exist for computation
    if (
      !fieldValueExists(change.after.data(), xField) ||
      !fieldValueExists(change.after.data(), yField)
    ) {
      return 0;
    }
    const x: number = change.after.data()![xField];
    const y: number = change.after.data()![yField];
    const hash = service.convertToHash(x, y);
    // This is to check whether the hash value has changed. If
    // it hasn't, you don't want to write to the document again as it
    // would create a recursive write loop.
    if (fieldValueExists(change.after.data(), outputField)
      && change.after.data()![outputField] == hash) {
      return 0;
    }
    return change.after.ref
      .update(
        {
          [outputField]: hash.hash,
        }
      );
  });

Esta función es un activador de Firestore. Cuando ocurre un evento de escritura en la base de datos, la función reacciona a ese evento mediante la búsqueda de un campo xv y un campo yv y, si ambos campos existen, calcula el geohash y escribe el resultado en una ubicación de salida del documento especificada. La constante users/{uid} define el documento de entrada, lo que significa que la función lee todos los documentos escritos en la colección users/ y, luego, procesa un geohash para esos documentos. Luego, genera el hash en un campo de hash en el mismo documento.

Funciones que admiten llamadas

La siguiente función en el archivo index.ts se ve de la siguiente manera:

index.ts

export const callableHash = onCall((data, context) => {
  if (context.auth == undefined) {
    return {error: "Only authorized users are allowed to call this endpoint"};
  }
  const x = data[xField];
  const y = data[yField];
  if (x == undefined || y == undefined) {
    return {error: "Either x or y parameter was not declared"};
  }
  const result = service.convertToHash(x, y);
  if (result.status != ResultStatusCode.ok) {
    return {error: `Something went wrong ${result.message}`};
  }
  return {result: result.hash};
});

Observa la función onCall. Indica que esta es una función que admite llamadas, a la que se puede llamar desde el código de tu aplicación cliente. Esta función que se puede llamar toma los parámetros x y y y muestra un geohash. Aunque no se llamará a esta función directamente en este codelab, se incluye aquí como ejemplo de algo que se debe configurar en la extensión de Firebase.

4. Configura un archivo extension.yaml

Ahora que sabes qué hace el código de Cloud Functions en tu extensión, puedes empaquetarlo para su distribución. Cada extensión de Firebase incluye un archivo extension.yaml que describe lo que hace y su comportamiento.

Un archivo extension.yaml requiere algunos metadatos iniciales sobre tu extensión. Cada uno de los siguientes pasos te ayudará a comprender qué significan todos los campos y por qué los necesitas.

  1. Crea un archivo extension.yaml en el directorio raíz del proyecto que descargaste antes. Comienza por agregar lo siguiente:
name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

El nombre de la extensión se usa como base del ID de instancia de la extensión (los usuarios pueden instalar varias instancias de una extensión, cada una con su propio ID). Luego, Firebase genera el nombre de las cuentas de servicio y los recursos específicos de la extensión con ese ID de instancia. El número de versión indica la versión de tu extensión. Debe cumplir con el control de versiones semántico y debes actualizarlo cada vez que realices cambios en la funcionalidad de la extensión. La versión de la especificación de la extensión se usa para determinar qué especificación de Extensiones de Firebase debe seguirse. En este caso, se usa v1beta.

  1. Agrega algunos detalles fáciles de usar al archivo YAML:
...

displayName: Latitude and longitude to GeoHash converter
description: A converter for changing your Latitude and longitude coordinates to geohashes.

El nombre visible es una representación amigable del nombre de tu extensión cuando los desarrolladores interactúan con ella. La descripción proporciona una breve descripción general de lo que hace la extensión. Cuando la extensión se implementa en extensions.dev, se ve de la siguiente manera:

Extensión de Geohash Converter como se muestra en extensions.dev

  1. Especifica la licencia del código de tu extensión.
...

license: Apache-2.0  # The license you want for the extension
  1. Indica quién escribió la extensión y si se requiere facturación para instalarla:
...

author:
  authorName: AUTHOR_NAME
  url: https://github.com/Firebase

billingRequired: true

La sección author se usa para que los usuarios sepan con quién comunicarse en caso de que tengan problemas con la extensión o quieran más información sobre ella. billingRequired es un parámetro obligatorio y se debe establecer en true, ya que todas las extensiones dependen de Cloud Functions, que requiere el plan Blaze.

Aquí se incluye la cantidad mínima de campos requeridos en el archivo extension.yaml para identificar esta extensión. Para obtener más detalles sobre otra información de identificación que puedes especificar en una extensión, consulta la documentación.

5. Convierte el código de Cloud Functions en un recurso de Extensiones

Un recurso de extensión es un elemento que Firebase crea en el proyecto durante la instalación de una extensión. Luego, la extensión es propietaria de esos recursos y tiene una cuenta de servicio específica que opera en ellos. En este proyecto, esos recursos son Cloud Functions, que se deben definir en el archivo extension.yaml porque la extensión no creará recursos automáticamente a partir del código en la carpeta de funciones. Si tus Cloud Functions no se declaran explícitamente como un recurso, no se podrán implementar cuando se implemente la extensión.

Ubicación de implementación definida por el usuario

  1. Permite que el usuario especifique la ubicación en la que desea implementar esta extensión y decida si es mejor alojar la extensión más cerca de sus usuarios finales o de su base de datos. En el archivo extension.yaml, incluye la opción para elegir una ubicación.

extension.yaml

Ya está todo listo para escribir la configuración del recurso de la función.

  1. En el archivo extension.yaml, crea un objeto de recurso para la función locationUpdate. Agrega lo siguiente al archivo extension.yaml:
resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}

Define name como el nombre de la función definido en el archivo index.ts del proyecto. Especificas el type de la función que se implementará, que por ahora siempre debe ser firebaseextensions.v1beta.function. Luego, defines el properties de esta función. La primera propiedad que defines es el eventTrigger asociado con esta función. Para duplicar lo que la extensión admite actualmente, usa el eventType de providers/cloud.firestore/eventTypes/document.write, que se encuentra en la documentación Escribe Cloud Functions para tu extensión. Define resource como la ubicación de los documentos. Dado que tu objetivo actual es duplicar lo que existe en el código, la ruta de acceso del documento escucha users/{uid}, con la ubicación predeterminada de la base de datos que la precede.

  1. La extensión necesita permisos de lectura y escritura para la base de datos de Firestore. Al final del archivo extension.yaml, especifica los roles de IAM a los que debe tener acceso la extensión para trabajar con la base de datos en el proyecto de Firebase del desarrollador.
roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

El rol datastore.user proviene de la lista de roles de IAM admitidos para extensiones. Como la extensión será de lectura y escritura, el rol de datastore.user es una buena opción en este caso.

  1. También se debe agregar la función que admite llamadas. En el archivo extension.yaml, crea un recurso nuevo en la propiedad resources. Estas propiedades son específicas de una función que se puede llamar:
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

Aunque el recurso anterior usaba un eventTrigger, aquí debes usar un httpsTrigger, que abarca funciones que admiten llamadas y funciones HTTPS.

Verificación de código

Fue mucha configuración para que tu extension.yaml coincida con todo lo que hace el código en tu archivo index.ts. El archivo extension.yaml completo debería verse de la siguiente manera en este momento:

extension.yaml

name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

displayName: Latitude and Longitude to GeoHash converter
description: A converter for changing your Latitude and Longitude coordinates to geohashes.

license: Apache-2.0  # The license you want for the extension

author:
  authorName: Sparky
  url: https://github.com/Firebase

billingRequired: true

resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

Verificación de estado

En este punto, tienes configuradas las partes funcionales iniciales de la extensión, por lo que puedes probarla con los emuladores de Firebase.

  1. Si aún no lo hiciste, llama a npm run build en la carpeta de funciones del proyecto de extensiones descargado.
  2. Crea un directorio nuevo en tu sistema host y conéctalo a tu proyecto de Firebase con firebase init.
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
    This command creates a `firebase.json` file in the directory. In the following steps, you push the configuration specified in this file to Firebase.
  1. Desde el mismo directorio, ejecuta firebase ext:install. Reemplaza /path/to/extension por la ruta de acceso absoluta al directorio que contiene tu archivo extension.yaml.
firebase ext:install /path/to/extension
    This command does two things:
  • Te solicitará que especifiques la configuración de la instancia de extensión y crea un archivo *.env que contiene la información de configuración de la instancia.
  • Agrega la instancia de extensión a la sección extensions de tu firebase.json. Esto actúa como una asignación del ID de instancia a la versión de la extensión.
  • Como implementarás el proyecto de forma local, puedes especificar que deseas usar un archivo local en lugar de Secret Manager de Google Cloud.

Captura de pantalla del proceso de instalación de la extensión que muestra que se usa el archivo local para los secretos cuando se instala esta extensión

  1. Inicia los emuladores de Firebase con la nueva configuración:
firebase emulators:start
  1. Después de ejecutar emulators:start, navega a la pestaña de Firestore en la WebView de los emuladores.
  2. Agrega un documento a la colección users con un campo de número xv y un campo de número yv.

Un cuadro de diálogo que se muestra en los emuladores de Firebase para iniciar una colección con el ID de la colección que contiene la frase

  1. Si instalaste correctamente la extensión, esta creará un campo nuevo llamado hash en el documento.

La colección usuarios con un documento de usuario que tiene un campo xv, yv y hash.

Realiza una limpieza para evitar conflictos

  • Una vez que hayas terminado la prueba, desinstala la extensión. Actualizarás el código de la extensión y no querrás entrar en conflicto con la extensión actual.

Las extensiones permiten instalar varias versiones de la misma extensión a la vez, por lo que, cuando las desinstalas, te aseguras de que no haya conflictos con una extensión instalada anteriormente.

firebase ext:uninstall geohash-ext

La solución actual funciona, pero como se mencionó al comienzo del proyecto, hay una clave de API hard-coded para simular la comunicación con un servicio. ¿Cómo puedes usar la clave de API del usuario final en lugar de la que se proporcionó originalmente? Continúa leyendo y descúbrelo.

6. Haz que el usuario pueda configurar la extensión

En este punto del codelab, tienes una extensión que está configurada para usar con la configuración definida de las funciones que ya escribiste, pero ¿qué sucede si el usuario quiere usar latitud y longitud en lugar de y y x para los campos que indican la ubicación en un plano cartesiano? Además, ¿cómo puedes hacer que el usuario final proporcione su propia clave de API, en lugar de permitir que consuma la clave de API proporcionada? Podrías sobrepasar rápidamente la cuota para esa API. En este caso, configuras y usas parámetros.

Define los parámetros básicos en el archivo extension.yaml

Comienza por convertir los elementos para los que los desarrolladores puedan tener una configuración personalizada. El primero serían los parámetros XFIELD y YFIELD.

  1. En el archivo extension.yaml, agrega el siguiente código, que usa los parámetros de campo XFIELD y YFIELD. Estos parámetros se encuentran dentro de la propiedad YAML params definida anteriormente:

extension.yaml

params:
  - param: XFIELD
    label: The X Field Name
    description: >-
      The X Field is also known as the **longitude** value. What does
      your Firestore instance refer to as the X value or the longitude
      value. If no value is specified, the extension searches for
      field 'xv'.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: xv
    required: false
    immutable: false
    example: xv
  - param: YFIELD
    label: The Y Field Name
    description: >-
      The Y Field is also known as the **latitude** value. What does
      your Firestore instance refer to as the Y value or the latitude
      value. If no value is specified, the extension searches for
      field 'yv'.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: yv
    required: false
    immutable: false
    example: yv
  • param asigna un nombre al parámetro de una manera que tú, el productor de la extensión, puedas ver. Usa este valor más adelante cuando especifiques los valores de los parámetros.
  • label es un identificador legible por humanos para el desarrollador que le permite saber qué hace el parámetro.
  • description proporciona una descripción detallada del valor. Dado que admite Markdown, puede vincular a documentación adicional o destacar palabras que podrían ser importantes para el desarrollador.
  • type define el mecanismo de entrada sobre la forma en que un usuario establecería el valor del parámetro. Existen muchos tipos, incluidos string, select, multiSelect, selectResource y secret. Para obtener más información sobre cada una de estas opciones, consulta la documentación.
  • validationRegex restringe la entrada del desarrollador a un valor de regex determinado (en el ejemplo, se basa en los lineamientos de nombres de campos simples que se encuentran aquí). Si eso falla…
  • validationErrorMessage alerta al desarrollador sobre el valor de error.
  • default es el valor que tendría si el desarrollador no ingresara texto.
  • obligatorio significa que el desarrollador no tiene la obligación de ingresar texto.
  • inmutable permite al desarrollador actualizar esta extensión y cambiar este valor. En este caso, el desarrollador debería poder modificar los nombres de campo a medida que cambian los requisitos.
  • example brinda una idea de cómo se vería una entrada válida.

¡Eso fue mucho para entender!

  1. Tienes tres parámetros más para agregar al archivo extension.yaml antes de agregar un parámetro especial.
  - param: INPUTPATH
    label: The input document to listen to for changes
    description: >-
      This is the document where you write an x and y value to. Once
      that document has received a value, it notifies the extension to
      calculate a geohash and store that in an output document in a certain
      field. This accepts function [wildcard parameters](https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters)
    type: string
    validationRegex: ^[^/]+(/[^/]*/[^/]*)*/[^/]+$
    validationErrorMessage: >-
      This must point to a document path, not a collection path from the root
      of the database. It must also not start or end with a '/' character.
    required: true
    immutable: false
    example: users/{uid}
  - param: OUTPUTFIELD
    label: Geohash field
    description: >-
      This specifies the field in the output document to store the geohash in.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    required: false
    default: hash
    immutable: false
    example: hash

Define parámetros sensibles

Ahora, debes administrar la clave de API que especifica el usuario. Esta es una cadena sensible que no se debe almacenar en texto sin formato en la función. En su lugar, almacena este valor en Cloud Secret Manager. Esta es una ubicación especial en la nube que almacena secretos encriptados y evita que se filtren accidentalmente. Esto requiere que el desarrollador pague por el uso de este servicio, pero agrega una capa adicional de seguridad a sus claves de API y, potencialmente, limita la actividad fraudulenta. La documentación para el usuario le informa al desarrollador que se trata de un servicio pagado para que no haya sorpresas en la facturación. En general, el uso es similar al de los otros recursos de cadenas mencionados anteriormente. La única diferencia es el tipo, que se llama secret.

  • En el archivo extension.yaml, agrega el siguiente código:

extension.yaml

  - param: APIKEY
    label: GeohashService API Key
    description: >-
      Your geohash service API Key. Since this is a demo, and not a real
      service, you can use : 1234567890.
    type: secret
    required: true
    immutable: false

Actualiza los atributos resource para usar parámetros

Como se mencionó anteriormente, el recurso (no la función) define cómo se observa el recurso, por lo que se debe actualizar el recurso locationUpdate para usar el nuevo parámetro.

  • En el archivo extension.yaml, agrega el siguiente código:

extension.yaml

## Change from this
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}]

## To this
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/${INPUTPATH}

Revisa el archivo extension.yaml.

  • Revisa el archivo extension.yaml. Debería verse algo similar a esto:

extension.yaml

name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

displayName: Latitude and Longitude to GeoHash converter
description: A converter for changing your Latitude and Longitude coordinates to geohashes.

license: Apache-2.0  # The license you want to use for the extension

author:
  authorName: Sparky
  url: https://github.com/Firebase

billingRequired: true

params:
  - param: XFIELD
    label: The X Field Name
    description: >-
      The X Field is also known as the **longitude** value. What does
      your Firestore instance refer to as the X value or the longitude
      value. If you don't provide a value for this field, the extension will use 'xv' as the default value.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: xv
    required: false
    immutable: false
    example: xv
  - param: YFIELD
    label: The Y Field Name
    description: >-
      The Y Field is also known as the **latitude** value. What does
      your Firestore instance refer to as the Y value or the latitude
      Value. If you don't provide a value for this field, the extension will use 'yv' as the default value.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: yv
    required: false
    immutable: false
    example: yv
  - param: INPUTPATH
    label: The input document to listen to for changes
    description: >-
      This is the document where you write an x and y value to. Once
      that document has been modified, it notifies the extension to
      compute a geohash and store that in an output document in a certain
      field. This accepts function [wildcard parameters](https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters)
    type: string
    validationRegex: ^[^/]+(/[^/]*/[^/]*)*/[^/]+$
    validationErrorMessage: >-
      This must point to a document path, not a collection path from the root
      of the database. It must also not start or end with a '/' character.
    required: true
    immutable: false
    example: users/{uid}
  - param: OUTPUTFIELD
    label: Geohash field
    description: >-
      This specifies the field in the output document to store the geohash in.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    required: false
    default: hash
    immutable: false
    example: hash
  - param: APIKEY
    label: GeohashService API Key
    description: >-
      Your geohash service API Key. Since this is a demo, and not a real
      service, you can use : 1234567890.
    type: secret
    required: true
    immutable: false

resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/${INPUTPATH}
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

Cómo acceder a los parámetros en el código

Ahora que todos los parámetros están configurados en el archivo extension.yaml, agrégalos al archivo index.ts.

  • En el archivo index.ts, reemplaza los valores predeterminados por process.env.PARAMETER_NAME, que recupera los valores de parámetros adecuados y los propaga en el código de la función implementado en el proyecto de Firebase del desarrollador.

index.ts

// Replace this:
const documentPath = "users/{uid}";
const xField = "xv";
const yField = "yv";
const apiKey = "1234567890";
const outputField = "hash";

// with this:
const documentPath = process.env.INPUTPATH!; // this value is ignored since its read from the resource
const xField = process.env.XFIELD!;
const yField = process.env.YFIELD!;
const apiKey = process.env.APIKEY!;
const outputField = process.env.OUTPUTFIELD!;

Por lo general, debes realizar verificaciones nulas con los valores de las variables de entorno, pero en este caso, confías en que los valores de los parámetros se copian correctamente. Ahora, el código está configurado para funcionar con los parámetros de la extensión.

7. Crear documentación para el usuario

Antes de probar el código en emuladores o en el mercado de extensiones de Firebase, se debe documentar la extensión para que los desarrolladores sepan qué obtienen cuando la usan.

  1. Comienza por crear el archivo PREINSTALL.md, que se usa para describir la funcionalidad, los requisitos previos para la instalación y las posibles implicaciones de facturación.

PREINSTALL.md

Use this extension to automatically convert documents with a latitude and
longitude to a geohash in your database. Additionally, this extension includes a callable function that allows users to make one-time calls
to convert an x,y coordinate into a geohash.

Geohashing is supported for latitudes between 90 and -90 and longitudes
between 180 and -180.

#### Third Party API Key

This extension uses a fictitious third-party API for calculating the
geohash. You need to supply your own API keys. (Since it's fictitious,
you can use 1234567890 as an API key).

#### Additional setup

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

After installing this extension, you'll need to:

- Update your client code to point to the callable geohash function if you
want to perform arbitrary geohashes.

Detailed information for these post-installation tasks are provided after
you install this extension.

#### 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:
 - Cloud Firestore
 - Cloud Functions (Node.js 16+ runtime. [See
FAQs](https://firebase.google.com/support/faq#extensions-pricing))
 - [Cloud Secret Manager](https://cloud.google.com/secret-manager/pricing)
  1. Para ahorrar tiempo en la escritura del README.md de este proyecto, usa el método útil:
firebase ext:info . --markdown > README.md

Esto combina el contenido del archivo PREINSTALL.md y los detalles adicionales sobre la extensión del archivo extension.yaml.

Por último, infórmale al desarrollador de la extensión algunos detalles adicionales sobre la extensión que se acaba de instalar. Es posible que el desarrollador reciba instrucciones y más información después de completar la instalación, así como tareas detalladas posteriores a la instalación, como configurar el código del cliente aquí.

  1. Crea un archivo POSTINSTALL.md y, luego, incluye la siguiente información posterior a la instalación:

POSTINSTALL.md

Congratulations on installing the geohash extension!

#### Function information

* **Firestore Trigger** - ${function:locationUpdate.name} was installed
and is invoked when both an x field (${param:XFIELD}) and y field
(${param:YFIELD}) contain a value.

* **Callable Trigger** - ${function:callableHash.name} was installed and
can be invoked by writing the following client code:
 ```javascript
import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions();
const geoHash = httpsCallable(functions, '${function:callableHash.name}');
geoHash({ ${param:XFIELD}: -122.0840, ${param:YFIELD}: 37.4221 })
  .then((result) => {
    // Read result of the Cloud Function.
    /** @type {any} */
    const data = result.data;
    const error = data.error;
    if (error != null) {
        console.error(`callable error : ${error}`);
    }
    const result = data.result;
    console.log(result);
  });

Supervisión

Como práctica recomendada, puedes supervisar la actividad de la extensión instalada, incluidas las verificaciones de estado, uso y registros.

The output rendering looks something like this when it's deployed:

<img src="img/82b54a5c6ca34b3c.png" alt="A preview of the latitude and longitude geohash converter extension in the firebase console"  width="957.00" />


## Test the extension with the full configuration
Duration: 03:00


It's time to make sure that the user-configurable extension is working the way it is intended.

* Change into the functions folder and ensure that the latest compiled version of the extensions exists. In the extensions project functions directory, call:

```console
npm run build

Esto vuelve a compilar las funciones para que el código fuente más reciente esté listo para implementarse junto con la extensión cuando se implemente en un emulador o directamente en Firebase.

A continuación, crea un directorio nuevo para probar la extensión. Dado que la extensión se desarrolló a partir de funciones existentes, no realices pruebas desde la carpeta en la que se configuró la extensión, ya que esto también intenta implementar las funciones y las reglas de Firebase junto con ella.

Instala y prueba con los emuladores de Firebase

  1. Crea un directorio nuevo en tu sistema host y conéctalo a tu proyecto de Firebase con firebase init.
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. Desde ese directorio, ejecuta firebase ext:install para instalar la extensión. Reemplaza /path/to/extension por la ruta de acceso absoluta al directorio que contiene tu archivo extension.yaml. Esto inicia el proceso de instalación de tu extensión y crea un archivo .env que contiene tus parámetros de configuración antes de enviarlos a Firebase o a los emuladores.
firebase ext:install /path/to/extension
  • Dado que implementarás el proyecto de manera local, especifica que te gustaría usar un archivo local en lugar de Secret Manager de Google Cloud.

da928c65ffa8ce15.png

  1. Inicia Local Emulator Suite:
firebase emulators:start

Instala y prueba con un proyecto real de Firebase

Puedes instalar tu extensión en un proyecto real de Firebase. Se recomienda usar un proyecto de prueba. Usa este flujo de trabajo de prueba si quieres probar el flujo de extremo a extremo de tu extensión o si el activador de la extensión aún no es compatible con Firebase Emulator Suite (consulta la opción del emulador de extensiones). Actualmente, los emuladores admiten funciones que se activan por medio de solicitudes HTTP y por eventos en segundo plano para Cloud Firestore, Realtime Database y Pub/Sub.

  1. Crea un directorio nuevo en tu sistema host y conéctalo a tu proyecto de Firebase con firebase init.
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. Luego, desde ese directorio, ejecuta firebase ext:install para instalar la extensión. Reemplaza /path/to/extension por la ruta de acceso absoluta al directorio que contiene tu archivo extension.yaml. Esto inicia el proceso de instalación de tu extensión y crea un archivo .env que contiene tus parámetros de configuración antes de enviarlos a Firebase o a los emuladores.
firebase ext:install /path/to/extension
  • Como quieres realizar implementaciones directamente en Firebase y usar Secret Manager de Google Cloud, debes activar la API de Secret Manager antes de instalar la extensión.
  1. Realiza la implementación en tu proyecto de Firebase.
firebase deploy

Prueba la extensión

  1. Después de ejecutar firebase deploy o firebase emulators:start, navega a la pestaña de Firestore de Firebase console o la WebView de los emuladores, según corresponda.
  2. Agrega un documento a la colección especificada por los campos x y y. En este caso, los documentos actualizados se encuentran en u/{uid} con un campo x de xv y un campo y de yv.

Pantalla de los emuladores de Firebase para agregar un registro de Firestore

  1. Si pudiste instalar la extensión correctamente, esta crea un nuevo campo en el documento llamado hash después de guardar los dos campos.

Pantalla de la base de datos de Firestore desde un emulador que muestra el hash agregado

8. ¡Felicitaciones!

Convertiste correctamente tu primera Cloud Function en una extensión de Firebase.

Agregaste un archivo extension.yaml y lo configuraste para que los desarrolladores puedan seleccionar cómo desean que se implemente tu extensión. Luego, creaste documentación para el usuario que brinda orientación sobre lo que los desarrolladores de la extensión deben hacer antes de configurarla y los pasos que podrían tener que seguir después de instalarla correctamente.

Ahora conoces los pasos clave necesarios para convertir una función de Firebase en una extensión de Firebase distribuible.

Próximos pasos