Reutilizar o código do Cloud Functions como uma extensão do Firebase

1. Antes de começar

Uma extensão do Firebase executa uma tarefa específica ou um conjunto de tarefas em resposta a solicitações HTTP ou acionamento de eventos de outros produtos do Firebase e do Google, como Firebase Cloud Messaging, Cloud Firestore ou Pub/Sub.

O que você vai criar

Neste codelab, você criará uma extensão do Firebase para geohashing. Depois de implantada, a extensão converte as coordenadas X e Y em geohashes em resposta a eventos do Firestore ou usando invocações de função chamável. Isso pode ser usado como uma alternativa à implementação da biblioteca geofire em todas as plataformas de destino para armazenar dados, economizando tempo.

A extensão de geohash exibida no Console do Firebase

O que você aprenderá

  • Como transformar o código atual do Cloud Functions em uma extensão distribuível do Firebase
  • Como configurar um arquivo extension.yaml
  • Como armazenar strings confidenciais (chaves de API) em uma extensão
  • Como permitir que os desenvolvedores da extensão a configurem para atender às suas necessidades
  • Como testar e implantar a extensão

O que é necessário

  • CLI do Firebase (instalação e login)
  • Uma Conta do Google, como uma conta do Gmail
  • Node.js e npm
  • Seu ambiente de desenvolvimento favorito

2. Começar a configuração

Buscar o código

Tudo o que você precisa para essa extensão está em um repositório do GitHub. Para começar, abra o código no seu ambiente de desenvolvimento favorito.

  1. Descompacte o arquivo ZIP transferido por download.
  2. Para instalar as dependências necessárias, abra o terminal no diretório functions e execute o comando npm install.

Configurar o Firebase

Este codelab incentiva o uso de emuladores do Firebase. Se você quiser testar o desenvolvimento de extensões com um projeto real do Firebase, consulte Criar um projeto do Firebase. Este codelab usa o Cloud Functions. Portanto, se você estiver usando um projeto real do Firebase em vez dos emuladores, será necessário fazer upgrade para o plano de preços Blaze.

Quer pular para a próxima?

É possível fazer o download de uma versão completa do codelab. Se você tiver dificuldades no caminho ou quiser conferir a aparência de uma extensão concluída, confira a ramificação codelab-end do repositório do GitHub (link em inglês) ou faça o download do ZIP concluído.

3. Revisar o código

  • Abra o arquivo index.ts no arquivo ZIP. Observe que ele contém duas declarações do Cloud Functions.

O que essas funções fazem?

Essas funções de demonstração são usadas para geohash. Eles pegam um par de coordenadas e os transformam em um formato otimizado para consultas geográficas no Firestore. As funções simulam o uso de uma chamada de API. Assim, você pode saber mais sobre como lidar com tipos de dados confidenciais em extensões. Para mais informações, consulte a documentação sobre como executar consultas geográficas em dados no Firestore.

Constantes de função

As constantes são declaradas no início, na parte superior do arquivo index.ts. Algumas dessas constantes são referenciadas nos acionadores definidos da extensão.

index.ts (link em inglês)

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

Gatilho do Firestore

A primeira função no arquivo index.ts vai ficar assim:

index.ts (link em inglês)

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

Essa função é um gatilho do Firestore. Quando um evento de gravação ocorre no banco de dados, a função reage a esse evento pesquisando um campo xv e um campo yv e, se ambos existirem, ela calcula o geohash e grava a saída em um local de saída de documento especificado. O documento de entrada é definido pela constante users/{uid}, o que significa que a função lê todos os documentos gravados na coleção users/ e processa um geohash para esses documentos. Em seguida, gera o hash em um campo de hash no mesmo documento.

Funções chamáveis

A próxima função no arquivo index.ts vai ficar assim:

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 a função onCall. Indica que essa é uma função chamável, que pode ser chamada no código do aplicativo cliente. Essa função chamável usa os parâmetros x e y e retorna um geohash. Embora essa função não seja chamada diretamente neste codelab, ela está incluída aqui como um exemplo de algo a ser configurado na extensão do Firebase.

4. Configurar um arquivo extension.yaml

Agora que você sabe o que o código do Cloud Functions na sua extensão faz, está pronto para empacotá-lo para distribuição. Cada extensão do Firebase vem com um arquivo extension.yaml que descreve o que a extensão faz e como se comporta.

Um arquivo extension.yaml requer alguns metadados iniciais sobre sua extensão. Cada uma das etapas a seguir ajuda a entender o que todos os campos significam e por que você precisa deles.

  1. Crie um arquivo extension.yaml no diretório raiz do projeto que você salvou antes. Comece adicionando o seguinte:
name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

O nome da extensão é usado como a base do ID da instância da extensão (os usuários podem instalar várias instâncias de uma extensão, cada uma com seu próprio ID). Em seguida, o Firebase gera o nome das contas de serviço e os recursos específicos da extensão usando esse ID de instância. O número da versão indica a versão da sua extensão. Ela precisa seguir o controle de versões semântico, e você precisa atualizá-la sempre que fizer alterações na funcionalidade da extensão. A versão de especificação da extensão é usada para determinar qual especificação de extensões do Firebase deve ser seguida. Nesse caso, a v1beta é usada.

  1. Adicione alguns detalhes fáceis de usar ao arquivo YAML:
...

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

O nome de exibição é uma representação amigável do nome da sua extensão quando os desenvolvedores interagem com ela. A descrição oferece uma breve visão geral do que a extensão faz. Quando a extensão é implantada em extensions.dev, o código é semelhante ao seguinte:

Extensão Geohash Converter conforme vista em extensions.dev

  1. Especifique a licença do código na sua extensão.
...

license: Apache-2.0  # The license you want for the extension
  1. Indique quem criou a extensão e se é necessário faturamento para instalá-la:
...

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

billingRequired: true

A seção author é usada para informar aos usuários com quem eles devem entrar em contato se tiverem problemas com a extensão ou quiserem mais informações sobre ela. billingRequired é um parâmetro obrigatório e precisa ser definido como true, já que todas as extensões dependem do Cloud Functions, que exige o plano Blaze.

Isso cobre o número mínimo de campos necessários no arquivo extension.yaml para identificar essa extensão. Para mais detalhes sobre outras informações de identificação que podem ser especificadas em uma extensão, consulte a documentação.

5. Converter o código do Cloud Functions em um recurso de extensões

Um recurso de extensão é um item que o Firebase cria no projeto durante a instalação de uma extensão. A extensão é proprietária desses recursos e tem uma conta de serviço específica que opera neles. Neste projeto, esses recursos são o Cloud Functions, que precisa ser definido no arquivo extension.yaml porque a extensão não vai criar recursos automaticamente a partir do código na pasta de funções. Se as funções do Cloud não forem explicitamente declaradas como um recurso, elas não poderão ser implantadas quando a extensão for implantada.

Local de implantação definido pelo usuário

  1. Permita que o usuário especifique o local em que quer implantar essa extensão e decida se é melhor hospedar a extensão mais perto dos usuários finais ou do banco de dados. No arquivo extension.yaml, inclua a opção para escolher um local.

extension.yaml (link em inglês)

Agora está tudo pronto para você gravar a configuração do recurso da função.

  1. No arquivo extension.yaml, crie um objeto de recurso para a função locationUpdate. Anexe o seguinte ao arquivo 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}

Defina name como o nome da função definido no arquivo index.ts do projeto. Especifique o type da função que está sendo implantada, que precisa ser sempre firebaseextensions.v1beta.function por enquanto. Em seguida, defina o properties dessa função. a primeira propriedade definida é o eventTrigger associado a essa função. Para espelhar o que a extensão oferece suporte no momento, use o eventType de providers/cloud.firestore/eventTypes/document.write, encontrado na documentação Gravar funções do Cloud para sua extensão. Defina o resource como o local dos documentos. Como a meta atual é espelhar o que existe no código, o caminho do documento detecta users/{uid}, com o local padrão do banco de dados antes dele.

  1. A extensão precisa de permissões de leitura e gravação no banco de dados do Firestore. No final do arquivo extension.yaml, especifique os papéis do IAM aos quais a extensão precisa ter acesso para trabalhar com o banco de dados no projeto do Firebase do desenvolvedor.
roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

O papel datastore.user vem da lista de papéis do IAM compatíveis com extensões. Como a extensão fará a leitura e a gravação, o papel datastore.user é adequado para esse caso.

  1. A função chamável também precisa ser adicionada. No arquivo extension.yaml, crie um novo recurso na propriedade de recursos. Estas propriedades são específicas para uma função chamável:
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

Embora o recurso anterior tenha usado uma eventTrigger, aqui você usa um httpsTrigger, que abrange funções chamáveis e HTTPS.

Verificação de código

Foram necessárias muitas configurações para fazer com que o extension.yaml corresponda a tudo o que foi feito pelo código no arquivo index.ts. O arquivo extension.yaml completo ficará assim:

extension.yaml (link em inglês)

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.

Verificação de status

Neste ponto, você tem as partes funcionais iniciais da extensão configuradas para que possa testá-la usando os emuladores do Firebase.

  1. Chame npm run build na pasta de funções do projeto de extensões baixado, caso ainda não tenha feito isso.
  2. Crie um novo diretório no seu sistema host e o conecte ao seu projeto do 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. No mesmo diretório, execute firebase ext:install. Substitua /path/to/extension pelo caminho absoluto para o diretório que contém o arquivo extension.yaml.
firebase ext:install /path/to/extension
    This command does two things:
  • Ele solicita que você especifique a configuração da instância de extensão e cria um arquivo *.env que contém as informações de configuração da instância.
  • Ele adiciona a instância de extensão à seção extensions do firebase.json. Isso funciona como um mapa do ID da instância para a versão da extensão.
  • Como você está implantando o projeto localmente, especifique que quer usar um arquivo local em vez do Secret Manager do Google Cloud.

Captura de tela do processo de instalação da extensão mostrando que o arquivo local está sendo usado para secrets ao instalar a extensão

  1. Inicie os emuladores do Firebase com a nova configuração:
firebase emulators:start
  1. Depois de executar emulators:start, navegue até a guia Firestore na WebView dos emuladores.
  2. Adicione um documento à coleção users com um campo de número xv e um campo de número yv.

Uma caixa de diálogo mostrada nos emuladores do Firebase para iniciar uma coleção com o ID da coleção que contém a frase

  1. Se você instalou a extensão, ela cria um novo campo chamado hash no documento.

A coleção "users" com um documento de usuário com campos "xv", "yv" e "hash".

Fazer limpeza para evitar conflitos

  • Quando terminar de testar, desinstale a extensão. Você vai atualizar o código da extensão e não quer entrar em conflito com a extensão atual mais tarde.

As extensões permitem que várias versões da mesma extensão sejam instaladas de uma só vez. Por isso, ao desinstalar, você garante que não haja conflitos com uma extensão instalada anteriormente.

firebase ext:uninstall geohash-ext

A solução atual funciona, mas, conforme mencionado no início do projeto, há uma chave de API codificada para simular a comunicação com um serviço. Como usar a chave de API do usuário final em vez da que foi fornecida originalmente? Continue lendo para descobrir.

6. Tornar o usuário da extensão configurável

Neste ponto do codelab, você tem uma extensão que está configurada para uso com a configuração opinativa das funções que já escreveu. Mas e se o usuário quiser usar latitude e longitude em vez de y e x para os campos que indicam a localização em um plano cartesiano? Além disso, o que fazer para que o usuário final informe a própria chave de API, em vez de permitir que ele consuma essa chave? Você pode ultrapassar rapidamente a cota dessa API. Nesse caso, você configura e usa parâmetros.

Defina os parâmetros básicos no arquivo extension.yaml

Comece convertendo os itens para os quais os desenvolvedores podem ter uma configuração personalizada. O primeiro seria os parâmetros XFIELD e YFIELD.

  1. No arquivo extension.yaml, adicione o código a seguir, que usa os parâmetros de campo XFIELD e YFIELD. Esses parâmetros ficam dentro da propriedade YAML params definida anteriormente:

extension.yaml (link em inglês)

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 nomeia o parâmetro de modo que fique visível para você, o produtor da extensão. Use esse valor mais tarde ao especificar os valores de parâmetro.
  • label é um identificador legível para o desenvolvedor que informa o que o parâmetro faz.
  • description apresenta uma descrição detalhada do valor. Como é compatível com a marcação, ele pode vincular a documentação extra ou destacar palavras que podem ser importantes para o desenvolvedor.
  • type define o mecanismo de entrada como um usuário definiria o valor do parâmetro. Existem muitos tipos, incluindo string, select, multiSelect, selectResource e secret. Para saber mais sobre cada uma dessas opções, consulte a documentação.
  • O validationRegex restringe a entrada do desenvolvedor a um determinado valor de regex (no exemplo, ele se baseia nas diretrizes simples de nome de campo encontradas aqui). Se isso falhar...
  • validationErrorMessage alerta o desenvolvedor sobre o valor da falha.
  • default é o valor que seria se o desenvolvedor não inserisse texto.
  • obrigatório significa que o desenvolvedor não precisa inserir texto.
  • imutable permite que o desenvolvedor atualize essa extensão e mude esse valor. Nesse caso, o desenvolvedor precisa mudar os nomes dos campos à medida que os requisitos mudam.
  • example fornece uma ideia de como pode ser uma entrada válida.

Isso foi muito importante para entender!

  1. Há mais três parâmetros a serem adicionados ao arquivo extension.yaml antes de incluir um 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 sensíveis

Agora, você precisa gerenciar a chave de API especificada pelo usuário. Essa é uma string sensível que não deve ser armazenada em texto simples na função. Em vez disso, armazene esse valor no Cloud Secret Manager. Esse é um local especial na nuvem que armazena secrets criptografados e impede que eles sejam vazados acidentalmente. Isso exige que o desenvolvedor pague pelo uso desse serviço, mas adiciona uma camada extra de segurança às chaves de API e pode limitar atividades fraudulentas. A documentação do usuário alerta o desenvolvedor de que o serviço é pago, para que não haja surpresas no faturamento. No geral, o uso é semelhante aos outros recursos de string mencionados acima. A única diferença é o tipo chamado secret.

  • No arquivo extension.yaml, adicione o seguinte código:

extension.yaml (link em inglês)

  - 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

Atualizar os atributos resource para usar parâmetros

Como mencionado anteriormente, o recurso (não a função) define como o recurso é observado. Portanto, o recurso locationUpdate precisa ser atualizado para usar o novo parâmetro.

  • No arquivo extension.yaml, adicione o seguinte código:

extension.yaml (link em inglês)

## 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 o arquivo extension.yaml

  • Revise o arquivo extension.yaml. O código será semelhante a este:

extension.yaml (link em inglês)

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.

Acessar parâmetros no código

Agora que todos os parâmetros estão configurados no arquivo extension.yaml, adicione-os ao arquivo index.ts.

  • No arquivo index.ts, substitua os valores padrão por process.env.PARAMETER_NAME, que busca os valores de parâmetro apropriados e os preenche no código de função implantado no projeto do Firebase do desenvolvedor.

index.ts (link em inglês)

// 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, você quer realizar verificações de valores nulos com os valores das variáveis de ambiente, mas, nesse caso, você confia que os valores dos parâmetros foram copiados corretamente. O código agora está configurado para funcionar com os parâmetros de extensão.

7. Criar documentação do usuário

Antes de testar o código em emuladores ou no mercado de extensões do Firebase, a extensão precisa ser documentada para que os desenvolvedores saibam o que vão receber ao usá-la.

  1. Comece criando o arquivo PREINSTALL.md, que é usado para descrever a funcionalidade, os pré-requisitos de instalação e possíveis implicações no faturamento.

PREINSTALL.md (link em inglês)

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 economizar tempo ao gravar o README.md neste projeto, use o método de conveniência:
firebase ext:info . --markdown > README.md

Isso combina o conteúdo do arquivo PREINSTALL.md e outros detalhes sobre a extensão do arquivo extension.yaml.

Por fim, informe o desenvolvedor da extensão sobre alguns detalhes adicionais relacionados à extensão que acabou de ser instalada. O desenvolvedor poderá obter algumas instruções e informações adicionais depois de concluir a instalação e poderá obter algumas tarefas de pós-instalação detalhadas, como a configuração do código do cliente aqui.

  1. Crie um arquivo POSTINSTALL.md e inclua as seguintes informações de pós-instalação:

POSTINSTALL.md (link em inglês)

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

Monitoramento

Como prática recomendada, você pode monitorar a atividade da extensão instalada, incluindo verificações de integridade, uso e 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

Isso recompila as funções para que o código-fonte mais recente esteja pronto para implantação junto com a extensão quando ela é implantada em um emulador ou diretamente no Firebase.

Em seguida, crie um novo diretório para testar a extensão. Como a extensão foi desenvolvida com base nas funções atuais, não teste na pasta em que a extensão foi configurada, porque isso também tenta implantar as funções e as regras do Firebase junto com ela.

Instalar e testar com os emuladores do Firebase

  1. Crie um novo diretório no seu sistema host e o conecte ao seu projeto do Firebase usando firebase init.
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. Nesse diretório, execute firebase ext:install para instalar a extensão. Substitua /path/to/extension pelo caminho absoluto para o diretório que contém o arquivo extension.yaml. Isso inicia o processo de instalação da extensão e cria um arquivo .env que contém as configurações antes de enviá-la ao Firebase ou aos emuladores.
firebase ext:install /path/to/extension
  • Como você está implantando o projeto localmente, especifique que quer usar um arquivo local em vez do Secret Manager do Google Cloud.

da928c65ffa8ce15.png

  1. Inicie o pacote de emuladores locais:
firebase emulators:start

Instalar e testar com um projeto real do Firebase

É possível instalar sua extensão em um projeto real do Firebase. É recomendável usar um projeto de teste. Use este fluxo de trabalho de teste se quiser testar o fluxo completo da sua extensão ou se o gatilho da sua extensão ainda não for compatível com o pacote de emuladores do Firebase. Consulte a opção de emulador de extensões. Atualmente, os emuladores são compatíveis com funções acionadas por solicitação HTTP e acionadas por eventos em segundo plano para o Cloud Firestore, o Realtime Database e o Pub/Sub.

  1. Crie um novo diretório no seu sistema host e o conecte ao seu projeto do Firebase usando firebase init.
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. Em seguida, nesse diretório, execute firebase ext:install para instalar a extensão. Substitua /path/to/extension pelo caminho absoluto para o diretório que contém o arquivo extension.yaml. Isso inicia o processo de instalação da extensão e cria um arquivo .env que contém as configurações antes de enviá-la ao Firebase ou aos emuladores.
firebase ext:install /path/to/extension
  • Como você quer implantar diretamente no Firebase e usar o Secret Manager do Google Cloud, ative a API Secret Manager antes de instalar a extensão.
  1. Implante no seu projeto do Firebase.
firebase deploy

Testar a extensão

  1. Depois de executar firebase deploy ou firebase emulators:start, acesse a guia do Firestore no Console do Firebase ou no WebView dos emuladores, conforme apropriado.
  2. Adicione um documento à coleção especificada pelos campos x e y. Nesse caso, os documentos atualizados estão localizados em u/{uid}, com um campo x de xv e um campo y de yv.

Tela de emuladores do Firebase para adicionar um registro do Firestore

  1. Se a extensão for instalada corretamente, ela criará um novo campo chamado hash no documento depois que você salvar os dois campos.

Tela do banco de dados do Firestore em um emulador mostrando o hash adicionado

8. Parabéns!

Você converteu sua primeira função do Cloud em uma extensão do Firebase.

Você adicionou um arquivo extension.yaml e o configurou para que os desenvolvedores possam selecionar como querem que sua extensão seja implantada. Em seguida, você criou uma documentação do usuário que fornece orientações sobre o que os desenvolvedores da extensão precisam fazer antes de configurar a extensão e quais etapas eles precisam seguir depois de instalar a extensão.

Agora você sabe as principais etapas necessárias para converter uma função do Firebase em uma extensão do Firebase distribuível.

Qual é a próxima etapa?