Reutilice su código de Cloud Functions como una extensión de Firebase

1. Antes de comenzar

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

lo que construirás

En este codelab, crearás una extensión de Firebase para geohashing . Una vez implementada, su extensión convierte las coordenadas X e Y en geohashes en respuesta a eventos de Firestore o mediante invocaciones de funciones invocables. Esto se puede utilizar como una alternativa a la implementación de la biblioteca geofire en todas sus plataformas de destino para almacenar datos, lo que le permitirá ahorrar tiempo.

La extensión geohash que se muestra en Firebase console

lo que 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 confidenciales (claves 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

Lo que necesitarás

  • Firebase CLI (instalar e iniciar sesión)
  • Una cuenta de Google, como una cuenta de Gmail.
  • Node.js y npm
  • Tu entorno de desarrollo favorito

2. Prepárate

Obtener el código

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

  1. Descomprima el archivo zip descargado.
  2. Para instalar las dependencias requeridas, abra la terminal en el directorio functions y ejecute el comando npm install .

Configurar base de fuego

Este codelab recomienda encarecidamente el uso de emuladores de Firebase. Si desea probar el desarrollo de extensiones con un proyecto de Firebase real, consulte crear un proyecto de Firebase . Este codelab usa Cloud Functions, por lo que si estás usando un proyecto real de Firebase en lugar de los emuladores, debes actualizar al plan de precios de Blaze .

¿Quieres saltar adelante?

Puede descargar una versión completa del codelab. Si te quedas atascado en el camino o si quieres ver cómo se ve una extensión completa, consulta la rama codelab-end del repositorio de GitHub o descarga el archivo zip completo.

3. Revisa el código

  • Abra el archivo index.ts desde el archivo zip. Observe que contiene dos declaraciones de Cloud Functions.

¿Qué hacen estas funciones?

Estas funciones de demostración se utilizan para 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 API para que pueda aprender más sobre el manejo de tipos de datos confidenciales en extensiones. Para obtener más información, consulte la documentación sobre cómo ejecutar consultas geográficas sobre datos en Firestore .

Constantes de función

Las constantes se declaran desde el 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 almacén de fuego

La primera función en el archivo index.ts se ve así:

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 disparador de Firestore . Cuando ocurre un evento de escritura en la base de datos, la función reacciona a ese evento buscando un campo xv y un campo yv y, si ambos campos existen, calcula el geohash y escribe la salida en una ubicación de salida de documento específica. El documento de entrada está definido por la constante users/{uid} , lo que significa que la función lee cada documento escrito en los users/ colección y luego procesa un geohash para esos documentos. Luego envía el hash a un campo hash en el mismo documento.

Funciones invocables

La siguiente función en el archivo index.ts se ve así:

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

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

4. Configure un archivo de extensión.yaml

Ahora que sabe qué hace el código de Cloud Functions en su extensión, está listo para empaquetarlo para su distribución. Cada extensión de Firebase viene con un archivo extension.yaml que describe qué hace la extensión y cómo se comporta.

Un archivo extension.yaml requiere algunos metadatos iniciales sobre su extensión. Cada uno de los siguientes pasos le ayuda a comprender qué significan todos los campos y por qué los necesita.

  1. Cree un archivo extension.yaml en el directorio raíz del proyecto que descargó anteriormente. Comience agregando 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 utiliza 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 de la extensión y los recursos específicos de la extensión utilizando ese ID de instancia. El número de versión indica la versión de su extensión. Debe seguir el control de versiones semántico y debe actualizarlo cada vez que realice cambios en la funcionalidad de la extensión. La versión de la especificación de extensión se usa para determinar qué especificación de extensiones de Firebase seguir; en este caso, se usa v1beta .

  1. Agregue 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 para mostrar es una representación amigable del nombre de su extensión cuando los desarrolladores interactúan con su extensión. La descripción ofrece una breve descripción general de lo que hace la extensión. Cuando la extensión se implementa en extensions.dev , se parece a esto:

Extensión Geohash Converter como se ve en extensions.dev

  1. Especifique la licencia para el código en su extensión.
...

license: Apache-2.0  # The license you want for the extension
  1. Indique 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 utiliza para que los usuarios sepan a quién comunicarse en caso de que tengan problemas con la extensión o quieran obtener más información al respecto. billingRequired es un parámetro obligatorio y debe establecerse en true , ya que todas las extensiones dependen de Cloud Functions, lo que requiere el plan Blaze.

Esto cubre 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 puede especificar en una extensión, consulte la documentación .

5. Convierta 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 con ellos. En este proyecto, esos recursos son Cloud Functions, que deben definirse 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 sus Cloud Functions no se declaran explícitamente como un recurso, no se pueden implementar cuando se implementa la extensión.

Ubicación de implementación definida por el usuario

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

extensión.yaml

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

  1. En el archivo extension.yaml , cree un objeto de recurso para la función locationUpdate . Agregue 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}

Usted define el name como el nombre de la función definida en el archivo index.ts del proyecto. Usted especifica el type de función que se está implementando, que siempre debe ser firebaseextensions.v1beta.function , por ahora. Luego, define las properties de esta función. la primera propiedad que define es el eventTrigger asociado con esta función. Para reflejar lo que la extensión admite actualmente, use el eventType de evento de providers/cloud.firestore/eventTypes/document.write , que se encuentra en Write Cloud Functions para la documentación de su extensión . El resource se define como la ubicación de los documentos. Dado que su objetivo actual es reflejar lo que existe en el código, la ruta del documento escucha a users/{uid} , con la ubicación predeterminada de la base de datos precediendo.

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

La función datastore.user proviene de la lista de funciones de IAM admitidas para extensiones . Dado que la extensión será de lectura y escritura, el rol datastore.user encaja bien aquí.

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

Aunque el recurso anterior usaba un eventTrigger , aquí usa un httpsTrigger , que cubre tanto funciones invocables como funciones HTTPS.

verificación de código

Fue mucha configuración para que su extension.yaml coincidiera con todo lo realizado por el código en su archivo index.ts . Así es como debería verse el archivo extension.yaml completo en este momento:

extensión.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.

Comprobación del estado

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

  1. Si aún no lo ha hecho, llame npm run build en la carpeta de funciones del proyecto de extensiones descargado.
  2. Cree un nuevo directorio en su sistema host y conecte ese directorio a su proyecto de Firebase usando 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, ejecute firebase ext:install . Reemplace /path/to/extension con la ruta absoluta al directorio que contiene su archivo extension.yaml .
firebase ext:install /path/to/extension
    This command does two things:
  • Le solicita que especifique la configuración para 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 su firebase.json . Esto actúa como un mapa del ID de instancia a la versión de extensión.
  • Dado que está implementando el proyecto localmente, puede especificar que desea utilizar un archivo local en lugar de Google Cloud Secret Manager.

Captura de pantalla del proceso de instalación de la extensión que muestra que el archivo local se utiliza para los secretos al instalar esta extensión

  1. Inicie los emuladores de Firebase con la nueva configuración:
firebase emulators:start
  1. Después de ejecutar emulators:start , navegue hasta la pestaña Firestore en la vista web de los emuladores.
  2. Agregue 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 logró instalar la extensión, la extensión crea un nuevo campo llamado hash en el documento.

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

Limpiar para evitar conflictos

  • Una vez que haya terminado de realizar la prueba, desinstale la extensión; actualizará el código de la extensión y no querrá entrar en conflicto con la extensión actual más adelante.

Las extensiones permiten instalar varias versiones de la misma extensión a la vez, por lo que al desinstalarlas se asegura de que no haya conflictos con una extensión instalada previamente.

firebase ext:uninstall geohash-ext

La solución actual funciona, pero como se mencionó al comienzo del proyecto, hay una clave API codificada para simular la comunicación con un servicio. ¿Cómo se puede utilizar la clave API del usuario final en lugar de la proporcionada originalmente? Sigue leyendo para descubrirlo.

6. Haga que la extensión sea configurable por el usuario.

En este punto del codelab, tiene una extensión que está configurada para usar con la configuración obstinada de las funciones que ya ha escrito, pero ¿qué pasa si su usuario quiere usar latitud y longitud en lugar de y y x para los campos que indican el ubicación en un plano cartesiano? Además, ¿cómo se puede lograr que el usuario final proporcione su propia clave API, en lugar de permitirle consumir la clave API proporcionada? Podrías superar rápidamente la cuota de esa API. En este caso, usted configura y utiliza parámetros.

Definir parámetros básicos en el archivo extension.yaml

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

  1. En el archivo extension.yaml , agregue el siguiente código, que utiliza los parámetros de campo XFIELD e YFIELD . Estos parámetros se encuentran dentro de la propiedad YAML params previamente definida:

extensión.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 nombra el parámetro de una manera que sea visible para usted, el productor de la extensión. Utilice este valor más adelante cuando especifique los valores de los parámetros.
  • La etiqueta es un identificador legible por humanos para el desarrollador que le permite saber qué hace el parámetro.
  • descripción proporciona una descripción detallada del valor. Dado que esto admite rebajas, puede vincular a documentación adicional o puede resaltar palabras que podrían ser importantes para el desarrollador.
  • El tipo define el mecanismo de entrada sobre cómo 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, consulte la documentación .
  • validationRegex restringe la entrada del desarrollador a un determinado valor de expresión regular (en el ejemplo, se basa en las pautas simples de nombres de campo que se encuentran aquí ); y si eso falla...
  • validationErrorMessage alerta al desarrollador sobre el valor del error.
  • El valor predeterminado es cuál sería el valor si el desarrollador no ingresara ningún texto.
  • requerido significa que el desarrollador no está obligado a ingresar ningún texto.
  • inmutable permite al desarrollador actualizar esta extensión y cambiar este valor. En este caso, el desarrollador debería poder cambiar los nombres de los campos a medida que cambien sus requisitos.
  • El ejemplo proporciona una idea de cómo podría verse una entrada válida.

¡Había mucho que entender!

  1. Tiene 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

Definir parámetros sensibles

Ahora, necesita administrar la clave API que especifica el usuario. Esta es una cadena confidencial que no debe almacenarse en texto sin formato en la función. En su lugar, almacene este valor en el administrador de secretos de la nube . Se trata de una ubicación especial en la nube que almacena secretos cifrados 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 sobre sus claves API y limita potencialmente la actividad fraudulenta. La documentación de usuario alerta al desarrollador de que se trata de un servicio de pago, para que no haya sorpresas en la facturación. En general, el uso es similar al de otros recursos de cadenas mencionados anteriormente. La única diferencia es el tipo que se llama secret .

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

extensión.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

Actualizar 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 el recurso locationUpdate debe actualizarse para poder utilizar el nuevo parámetro.

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

extensión.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}

Verifique el archivo extension.yaml

  • Revise el archivo extension.yaml . Debería verse así:

extensión.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.

Acceder a los parámetros en el código.

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

  • En el archivo index.ts , reemplace los valores predeterminados con process.env.PARAMETER_NAME , que recupera los valores de parámetros apropiados y los completa en el código de función implementado en el proyecto 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!;

Normalmente, desea realizar comprobaciones nulas con los valores de las variables de entorno, pero en este caso, confía en que los valores de los parámetros se copian correctamente. El código ahora está configurado para funcionar con los parámetros de extensión.

7. Crear documentación de usuario.

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

  1. Comience creando el archivo PREINSTALL.md , que se utiliza para describir la funcionalidad, los requisitos previos para la instalación y las posibles implicaciones de facturación.

PREINSTALAR.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 al escribir el README.md para este proyecto, utilice el método conveniente:
firebase ext:info . --markdown > README.md

Esto combina el contenido de su archivo PREINSTALL.md y detalles adicionales sobre su extensión de su archivo extension.yaml .

Finalmente, informe al desarrollador de la extensión sobre algunos detalles adicionales sobre la extensión que acaba de instalar. El desarrollador puede obtener algunas instrucciones e información adicionales después de completar la instalación y puede obtener algunas tareas detalladas posteriores a la instalación, como configurar el código del cliente aquí.

  1. Cree un archivo POSTINSTALL.md y luego incluya 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, puede monitorear la actividad de su extensión instalada, incluidas comprobaciones de su 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 su implementación junto con la extensión cuando se implemente en un emulador o directamente en Firebase.

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

Instalar y probar con los emuladores de Firebase

  1. Cree un nuevo directorio en su sistema host y conecte ese directorio a su proyecto de Firebase usando firebase init .
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. Desde ese directorio, ejecute firebase ext:install para instalar la extensión. Reemplace /path/to/extension con la ruta absoluta al directorio que contiene su archivo extension.yaml . Esto inicia el proceso de instalación de su extensión y crea un archivo .env que contiene sus configuraciones antes de enviar la configuración a Firebase o a los emuladores.
firebase ext:install /path/to/extension
  • Dado que está implementando el proyecto localmente, especifique que desea utilizar un archivo local en lugar de Google Cloud Secret Manager.

da928c65ffa8ce15.png

  1. Inicie el conjunto de emuladores locales:
firebase emulators:start

Instalar y probar con un proyecto real de Firebase

Puede instalar su extensión en un proyecto real de Firebase. Se recomienda utilizar un proyecto de prueba para sus pruebas. Utilice este flujo de trabajo de prueba si desea probar el flujo de un extremo a otro de su extensión o si el activador de su extensión aún no es compatible con el conjunto de emuladores de Firebase (consulte la opción Emulador de extensiones ). Actualmente, los emuladores admiten funciones activadas por solicitudes HTTP y funciones activadas por eventos en segundo plano para Cloud Firestore, Realtime Database y Pub/Sub.

  1. Cree un nuevo directorio en su sistema host y conecte ese directorio a su proyecto de Firebase usando firebase init .
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. Luego, desde ese directorio, ejecute firebase ext:install para instalar la extensión. Reemplace /path/to/extension con la ruta absoluta al directorio que contiene su archivo extension.yaml . Esto inicia el proceso de instalación de su extensión y crea un archivo .env que contiene sus configuraciones antes de enviar la configuración a Firebase o a los emuladores.
firebase ext:install /path/to/extension
  • Dado que desea realizar la implementación en Firebase directamente y utilizar Google Cloud Secret Manager, debe activar la API de Secret Manager antes de instalar la extensión.
  1. Implemente en su proyecto de Firebase.
firebase deploy

Pruebe la extensión

  1. Después de ejecutar firebase deploy o firebase emulators:start , navegue hasta la pestaña Firestore de Firebase console o la vista web de los emuladores, según corresponda.
  2. Agregue un documento a la colección especificada por el campo x y el campo 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 emuladores de Firebase para agregar un registro de Firestore

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

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

8. ¡Felicitaciones!

¡Has convertido con éxito tu primera función de nube en una extensión de Firebase!

Agregó un archivo extension.yaml y lo configuró para que los desarrolladores puedan seleccionar cómo les gustaría que se implementara su extensión. Luego creó documentación de usuario que proporciona orientación sobre lo que los desarrolladores de la extensión deben hacer antes de configurarla y qué pasos podrían necesitar seguir después de haberla instalado correctamente.

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

¿Que sigue?