Primeiras etapas para criar uma extensão

Nesta página, apresentamos as etapas necessárias para criar uma extensão simples do Firebase, que você pode instalar nos seus projetos ou compartilhar com outras pessoas. Este exemplo simples de uma extensão do Firebase monitorará o Realtime Database em busca de mensagens e as converterá em letras maiúsculas.

1. Configurar o ambiente e inicializar um projeto

Antes de começar a criar uma extensão, configure um ambiente de criação com as ferramentas necessárias.

  1. Instale o Node.js 16 ou mais recente. Uma maneira de instalar o Node é usar o nvm (ou nvm-windows).

  2. Instale ou atualize para a versão mais recente da CLI do Firebase. Para instalar ou atualizar usando npm, execute este comando:

    npm install -g firebase-tools
    

Agora use a CLI do Firebase para inicializar um novo projeto de extensão:

  1. Crie um diretório para sua extensão e cd nele:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
    
  2. Execute o comando ext:dev:init da CLI do Firebase:

    firebase ext:dev:init
    

    Quando solicitado, escolha o JavaScript como linguagem das funções. No entanto, você também pode usar o TypeScript ao desenvolver sua própria extensão. Quando solicitado a instalar as dependências, responda "sim". Aceite os padrões de outras opções. Esse comando configurará uma base de código para uma nova extensão, de que você pode começar a desenvolver sua extensão.

2. Testar a extensão de exemplo usando o emulador

Quando a CLI do Firebase inicializou o novo diretório de extensões, ele criou uma função de exemplo simples e um diretório integration-tests que contém os arquivos necessários para executar uma extensão usando o pacote de emuladores do Firebase.

Tente executar a extensão de exemplo no emulador:

  1. Altere para o diretório integration-tests:

    cd functions/integration-tests
    
  2. Inicie o emulador com um projeto de demonstração:

    firebase emulators:start --project=demo-test
    

    O emulador carrega a extensão em um projeto "fictício" predefinido (demo-test). Até o momento, a extensão consiste em uma única função acionada por HTTP, greetTheWorld, que retorna uma mensagem "hello world" quando acessada.

  3. Com o emulador ainda em execução, tente a função greetTheWorld da extensão visitando o URL que ela imprimiu quando você a iniciou.

    Seu navegador exibe a mensagem "Hello World from greet-the-world".

  4. O código-fonte dessa função está no diretório functions da extensão. Abra o código-fonte no editor ou ambiente de desenvolvimento integrado de sua escolha:

    functions/index.js

    const functions = require("firebase-functions");
    
    exports.greetTheWorld = functions.https.onRequest((req, res) => {
      // Here we reference a user-provided parameter
      // (its value is provided by the user during installation)
      const consumerProvidedGreeting = process.env.GREETING;
    
      // And here we reference an auto-populated parameter
      // (its value is provided by Firebase after installation)
      const instanceId = process.env.EXT_INSTANCE_ID;
    
      const greeting = `${consumerProvidedGreeting} World from ${instanceId}`;
    
      res.send(greeting);
    });
    
  5. Enquanto o emulador estiver em execução, ele recarregará automaticamente todas as alterações feitas no código do Functions. Tente fazer uma pequena alteração na função greetTheWorld:

    functions/index.js

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

    Salve as mudanças. O emulador recarregará o código e, agora, quando você acessar o URL da função, verá a saudação atualizada.

3. Adicionar informações básicas ao extension.yaml

Agora que você tem um ambiente de desenvolvimento configurado e está executando o emulador de extensões, comece a criar sua própria extensão.

Como uma primeira etapa simples, edite os metadados de extensão predefinidos para refletir a extensão que você quer gravar em vez de greet-the-world. Esses metadados são armazenados no arquivo extension.yaml.

  1. Abra extension.yaml no seu editor e substitua todo o conteúdo do arquivo pelo seguinte:

    name: rtdb-uppercase-messages
    version: 0.0.1
    specVersion: v1beta  # Firebase Extensions specification version; don't change
    
    # Friendly display name for your extension (~3-5 words)
    displayName: Convert messages to upper case
    
    # Brief description of the task your extension performs (~1 sentence)
    description: >-
      Converts messages in RTDB to upper case
    
    author:
      authorName: Your Name
      url: https://your-site.example.com
    
    license: Apache-2.0  # Required license
    
    # Public URL for the source code of your extension
    sourceUrl: https://github.com/your-name/your-repo
    

    A convenção de nomenclatura usada no campo name: as extensões oficiais do Firebase são nomeadas com um prefixo que indica o produto principal do Firebase em que a extensão opera, seguida por uma descrição do que a extensão faz. Use a mesma convenção nas suas próprias extensões.

  2. Como você mudou o nome da extensão, atualize a configuração do emulador com o novo nome:

    1. Em functions/integration-tests/firebase.json, mude greet-the-world para rtdb-uppercase-messages.
    2. Renomeie functions/integration-tests/extensions/greet-the-world.env como functions/integration-tests/extensions/rtdb-uppercase-messages.env.

Ainda existem algumas partes da extensão greet-the-world no código da extensão, mas não as mude por enquanto. Vamos fazer isso nas próximas seções.

4. Escrever uma função do Cloud e declará-la como um recurso de extensão

Agora você pode começar a escrever código. Nesta etapa, você vai escrever uma função do Cloud que executa a tarefa principal da sua extensão, que é observar o Realtime Database em busca de mensagens e convertê-las em maiúsculas.

  1. Abra o código-fonte das funções da extensão (no diretório functions da extensão) no editor ou ambiente de desenvolvimento integrado de sua escolha. Substitua o conteúdo pelo seguinte:

    functions/index.js

    import { database, logger } from "firebase-functions/v1";
    
    const app = initializeApp();
    
    // Listens for new messages added to /messages/{pushId}/original and creates an
    // uppercase version of the message to /messages/{pushId}/uppercase
    // for all databases in 'us-central1'
    export const makeuppercase = database
      .ref("/messages/{pushId}/uppercase")
      .onCreate(async (snapshot, context) => {
        // Grab the current value of what was written to the Realtime Database.
        const original = snapshot.val();
    
        // Convert it to upper case.
        logger.log("Uppercasing", context.params.pushId, original);
        const uppercase = original.toUpperCase();
    
        // Setting an "uppercase" sibling in the Realtime Database.
        const upperRef = snapshot.ref.parent.child("upper");
        await upperRef.set(uppercase);
    });
    

    A antiga função substituída era uma função acionada por HTTP, executada quando um endpoint HTTP era acessado. A nova função é acionada por eventos do banco de dados em tempo real: ela verifica novos itens em um caminho específico e, quando é detectada, grava a versão em maiúsculas do valor de volta no banco de dados.

    A propósito, esse novo arquivo usa a sintaxe do módulo ECMAScript (import e export) em vez de CommonJS (require). Para usar módulos ES no Node, especifique "type": "module" em functions/package.json:

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      …
    }
    
  2. Todas as funções da extensão precisam ser declaradas no arquivo extension.yaml. A extensão de exemplo declarou greetTheWorld como a única função do Cloud da extensão. Agora que você a substituiu por makeuppercase, também é necessário atualizar a declaração.

    Abra extension.yaml e adicione um campo resources:

    resources:
      - name: makeuppercase
        type: firebaseextensions.v1beta.function
        properties:
          eventTrigger:
            eventType: providers/google.firebase.database/eventTypes/ref.create
            # DATABASE_INSTANCE (project's default instance) is an auto-populated
            # parameter value. You can also specify an instance.
            resource: projects/_/instances/${DATABASE_INSTANCE}/refs/messages/{pushId}/original
          runtime: "nodejs18"
    
  3. Como sua extensão agora está usando o Realtime Database como acionador, você precisa atualizar a configuração do emulador para executar o emulador RTDB com o emulador do Cloud Functions:

    1. Se o emulador ainda estiver em execução, pressione Ctrl+C para interrompê-lo.

    2. No diretório functions/integration-tests, execute o seguinte comando:

      firebase init emulators
      

      Quando solicitado, pule a configuração de um projeto padrão e selecione os emuladores do Functions e do Database. Aceite as portas padrão e permita que a ferramenta de configuração faça o download dos arquivos necessários.

    3. Reinicie o emulador:

      firebase emulators:start --project=demo-test
      
  4. Teste sua extensão atualizada:

    1. Abra a IU do emulador do Database usando o link impresso pelo emulador quando você o iniciou.

    2. Edite o nó raiz do banco de dados:

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

      Se tudo estiver configurado corretamente, quando você salvar as alterações no banco de dados, a função makeuppercase da extensão será acionada e adicionará um registro filho à mensagem 11 com o conteúdo "upper": "RECIPE". Confira os registros e as guias do banco de dados da IU do emulador para confirmar os resultados esperados.

    3. Tente adicionar mais alguns filhos ao nó messages ({"original":"any text"}). Sempre que você adicionar um novo registro, a extensão precisará adicionar um campo uppercase que contenha o conteúdo em maiúsculas do campo original.

Agora você tem uma extensão completa, embora simples, que opera em uma instância do RTDB. Nas seções a seguir, você refinará essa extensão com alguns recursos adicionais. Depois, prepare a extensão para distribuir a outras pessoas e aprenda a publicá-la no Extensions Hub.

5. Declarar APIs e papéis

O Firebase concede a cada instância de uma extensão instalada acesso limitado ao projeto e aos dados dele usando uma conta de serviço por instância. Cada conta tem o conjunto mínimo de permissões necessárias para operar. Por esse motivo, você precisa declarar explicitamente todos os papéis do IAM exigidos pela extensão. Quando os usuários instalarem a extensão, o Firebase criará uma conta de serviço com esses papéis e a usará para executar a extensão.

Você não precisa declarar um papel para acionar os eventos de um produto, mas precisa declarar um papel para interagir com ele. Como a função adicionada na última etapa grava no Realtime Database, você precisa adicionar a seguinte declaração ao extension.yaml:

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

Da mesma forma, você declara as APIs do Google que uma extensão usa no campo apis. Quando os usuários instalarem sua extensão, eles receberão uma mensagem perguntando se querem ativar automaticamente essas APIs para o projeto. Isso normalmente é necessário apenas para APIs do Google que não são do Firebase e não é necessário para este guia.

6. Definir parâmetros configuráveis pelo usuário

A função que você criou nas últimas duas etapas assistiu a um local RTDB específico para mensagens recebidas. Às vezes, assistir a um local específico é o que você quer, como quando sua extensão opera em uma estrutura de banco de dados que você usa exclusivamente para ela. No entanto, na maioria das vezes, convém tornar esses valores configuráveis pelos usuários que instalam sua extensão nos projetos. Dessa forma, os usuários podem usar sua extensão para trabalhar com a configuração existente do banco de dados.

Torne o caminho que a extensão observa para novas mensagens configurável pelo usuário:

  1. No arquivo extension.yaml, adicione uma seção params:

    - param: MESSAGE_PATH
      label: Message path
      description: >-
        What is the path at which the original text of a message can be found?
      type: string
      default: /messages/{pushId}/original
      required: true
      immutable: false
    

    Isso define um novo parâmetro de string que vai solicitar que os usuários façam uma definição ao instalar sua extensão.

  2. Ainda no arquivo extension.yaml, volte para a declaração makeuppercase e altere o campo resource para o seguinte:

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

    O token ${param:MESSAGE_PATH} é uma referência ao parâmetro que você acabou de definir. Quando a extensão for executada, esse token será substituído por qualquer valor configurado pelo usuário para esse parâmetro. Como resultado, a função makeuppercase detectará o caminho especificado pelo usuário. É possível usar essa sintaxe para referenciar qualquer parâmetro definido pelo usuário em qualquer lugar em extension.yaml (e em POSTINSTALL.md. Falaremos sobre isso mais adiante).

  3. Você também pode acessar parâmetros definidos pelo usuário usando seu código de funções.

    Na função que você escreveu na última seção, você codificou o caminho para monitorar as alterações. Altere a definição do acionador para referenciar o valor definido pelo usuário:

    functions/index.js

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

    Observe que, nas Extensões do Firebase, essa alteração é meramente documental: quando uma função do Cloud é implantada como parte de uma extensão, ela usa a definição do acionador da extension.yaml e ignora o valor especificado na definição da função. No entanto, é uma boa ideia documentar em seu código de onde vem esse valor.

  4. Pode ser frustrante fazer uma alteração de código que não tenha efeito no ambiente de execução, mas a lição importante é que você pode acessar qualquer parâmetro definido pelo usuário no código de função e usá-lo como um valor comum na lógica da função. Como referência a esse recurso, adicione a seguinte declaração de registro para demonstrar que você está de fato acessando o valor definido pelo usuário:

    functions/index.js

    export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate(
      async (snapshot, context) => {
        logger.log("Found new message at ", snapshot.ref);
    
        // Grab the current value of what was written to the Realtime Database.
        ...
    
  5. Normalmente, os usuários precisam fornecer valores de parâmetros ao instalar uma extensão. No entanto, ao usar o emulador para testes e desenvolvimento você ignora o processo de instalação, o que, por sua vez, faz com que você forneça valores para parâmetros definidos pelo usuário usando um arquivo env.

    Abra functions/integration-tests/extensions/rtdb-uppercase-messages.env e substitua a definição de GREETING pelo seguinte:

    MESSAGE_PATH=/msgs/{pushId}/original
    

    O caminho acima é diferente do caminho padrão e do caminho definido anteriormente. Isso serve apenas para você testar quando a extensão atualizada entrar em vigor.

  6. Agora, reinicie o emulador e acesse novamente a IU do emulador do banco de dados.

    Edite o nó raiz do banco de dados usando o caminho definido acima:

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

    Quando você salva as alterações no banco de dados, a função makeuppercase da extensão precisa ser acionada como antes, mas agora também precisa imprimir o parâmetro definido pelo usuário no registro do console.

7. Fornecer ganchos de evento para a lógica definida pelo usuário

Como autor de extensão, você já viu como um produto do Firebase pode acionar a lógica fornecida pela extensão: a criação de novos registros no Realtime Database aciona a função makeuppercase. A extensão pode ter uma relação análoga com os usuários que a instalam: a extensão pode acionar a lógica definida pelo usuário.

Uma extensão pode fornecer ganchos síncronos, ganchos assíncronos ou ambos. Os ganchos síncronos dão aos usuários uma maneira de realizar tarefas que bloqueiam a conclusão de uma das funções da extensão. Isso pode ser útil, por exemplo, para oferecer aos usuários uma maneira de executar o pré-processamento personalizado antes que uma extensão faça o trabalho.

Neste guia, você adicionará um hook assíncrono à extensão, o que permitirá que os usuários definam as próprias etapas de processamento para serem executadas depois que a extensão gravar a mensagem em maiúsculas no Realtime Database. Os ganchos assíncronos usam o Eventarc para acionar funções definidas pelo usuário. As extensões declaram os tipos de eventos que emitem e, quando os usuários instalam a extensão, escolhem em quais tipos de evento estão interessados. Se eles escolherem pelo menos um evento, o Firebase provisionará um canal do Eventarc para a extensão como parte do processo de instalação. Os usuários podem implantar as próprias funções do Cloud que detectam nesse canal e são acionadas quando a extensão publica novos eventos.

Siga estas etapas para adicionar um hook assíncrono:

  1. No arquivo extension.yaml, adicione a seguinte seção, que declara o tipo de evento que a extensão emite:

    events:
      - type: test-publisher.rtdb-uppercase-messages.v1.complete
        description: >-
          Occurs when message uppercasing completes. The event subject will contain
          the RTDB URL of the uppercase message.
    

    Os tipos de evento precisam ser universalmente exclusivos. Para garantir a exclusividade, sempre nomeie seus eventos usando o seguinte formato: <publisher-id>.<extension-id>.<version>.<description>. Você ainda não tem um ID de editor. Use test-publisher por enquanto.

  2. No final da função makeuppercase, adicione um código que publique um evento do tipo que você acabou de declarar:

    functions/index.js

    // Import the Eventarc library:
    import { initializeApp } from "firebase-admin/app";
    import { getEventarc } from "firebase-admin/eventarc";
    
    const app = initializeApp();
    
    // In makeuppercase, after upperRef.set(uppercase), add:
    
    // Set eventChannel to a newly-initialized channel, or `undefined` if events
    // aren't enabled.
    const eventChannel =
      process.env.EVENTARC_CHANNEL &&
      getEventarc().channel(process.env.EVENTARC_CHANNEL, {
        allowedEventTypes: process.env.EXT_SELECTED_EVENTS,
      });
    
    // If events are enabled, publish a `complete` event to the configured
    // channel.
    eventChannel &&
      eventChannel.publish({
        type: "test-publisher.rtdb-uppercase-messages.v1.complete",
        subject: upperRef.toString(),
        data: {
          "original": original,
          "uppercase": uppercase,
        },
      });
    

    Esse código de exemplo aproveita o fato de que a variável de ambiente EVENTARC_CHANNEL é definida apenas quando o usuário ativa pelo menos um tipo de evento. Se EVENTARC_CHANNEL não estiver definido, o código não tentará publicar nenhum evento.

    Você pode anexar informações extras a um evento do Eventarc. No exemplo acima, o evento tem um campo subject que contém uma referência ao valor recém-criado e um payload data que contém as mensagens originais e em maiúsculas. As funções definidas pelo usuário que acionam o evento podem usar essas informações.

  3. Normalmente, as variáveis de ambiente EVENTARC_CHANNEL e EXT_SELECTED_EVENTS são definidas com base nas opções que o usuário selecionou durante a instalação. Para testar com o emulador, defina manualmente essas variáveis no arquivo rtdb-uppercase-messages.env:

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

Neste ponto, você concluiu as etapas necessárias para adicionar um gancho de evento assíncrono à sua extensão.

Para testar esse novo recurso recém-implementado, nas próximas etapas, assuma o papel de um usuário que esteja instalando a extensão:

  1. No diretório functions/integration-tests, inicialize um novo projeto do Firebase:

    firebase init functions
    

    Quando solicitado, recuse para configurar um projeto padrão, selecione JavaScript como a linguagem do Cloud Functions e instale as dependências necessárias. Ele representa o projeto de um usuário, que tem a extensão instalada.

  2. Edite integration-tests/functions/index.js e cole o seguinte código:

    import { logger } from "firebase-functions/v1";
    import { onCustomEventPublished } from "firebase-functions/v2/eventarc";
    
    import { initializeApp } from "firebase-admin/app";
    import { getDatabase } from "firebase-admin/database";
    
    const app = initializeApp();
    
    export const extraemphasis = onCustomEventPublished(
      "test-publisher.rtdb-uppercase-messages.v1.complete",
      async (event) => {
        logger.info("Received makeuppercase completed event", event);
    
        const refUrl = event.subject;
        const ref = getDatabase().refFromURL(refUrl);
        const upper = (await ref.get()).val();
        return ref.set(`${upper}!!!`);
      }
    );
    

    Este é um exemplo de função de pós-processamento que um usuário pode escrever. Nesse caso, a função detecta a extensão para publicar um evento complete e, quando acionada, adiciona três pontos de exclamação à mensagem recém-em maiúsculas.

  3. Reinicie o emulador. O emulador carregará as funções da extensão e a função de pós-processamento definida pelo "usuário".

  4. Acesse a IU do emulador do banco de dados e edite o nó raiz do banco de dados usando o caminho definido acima:

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

    Quando você salva as mudanças no banco de dados, a função makeuppercase da extensão e a função extraemphasis do usuário precisam ser acionadas em sequência, fazendo com que o campo upper receba o valor RECIPE!!!.

8. Adicionar manipuladores de eventos de ciclo de vida

A extensão que você criou até agora processa as mensagens à medida que elas são criadas. Mas e se os usuários já tiverem um banco de dados de mensagens ao instalar a extensão? As extensões do Firebase têm um recurso chamado hooks de evento de ciclo de vida que pode ser usado para acionar ações quando a extensão é instalada, atualizada ou reconfigurada. Nesta seção, você usará hooks de evento de ciclo de vida para preencher o banco de dados de mensagens de um projeto com mensagens em maiúsculas, quando um usuário instalar sua extensão.

As Extensões do Firebase usam o Cloud Tasks para executar os manipuladores de eventos do ciclo de vida. Você define manipuladores de eventos usando o Cloud Functions. Sempre que uma instância da extensão atingir um dos eventos de ciclo de vida compatíveis, se você tiver definido um gerenciador, ele o adicionará a uma fila do Cloud Tasks. O Cloud Tasks executará o gerenciador de forma assíncrona. Enquanto um manipulador de eventos do ciclo de vida estiver em execução, o Console do Firebase informará ao usuário que a instância de extensão tem uma tarefa de processamento em andamento. Cabe à sua função de gerenciador relatar o status contínuo e a conclusão da tarefa de volta ao usuário.

Para adicionar um gerenciador de evento do ciclo de vida que preencha mensagens existentes, faça o seguinte:

  1. Defina uma nova função do Cloud acionada por eventos da fila de tarefas:

    functions/index.js

    import { tasks } from "firebase-functions/v1";
    
    import { getDatabase } from "firebase-admin/database";
    import { getExtensions } from "firebase-admin/extensions";
    import { getFunctions } from "firebase-admin/functions";
    
    export const backfilldata = tasks.taskQueue().onDispatch(async () => {
      const batch = await getDatabase()
        .ref(process.env.MESSAGE_PATH)
        .parent.parent.orderByChild("upper")
        .limitToFirst(20)
        .get();
    
      const promises = [];
      for (const key in batch.val()) {
        const msg = batch.child(key);
        if (msg.hasChild("original") && !msg.hasChild("upper")) {
          const upper = msg.child("original").val().toUpperCase();
          promises.push(msg.child("upper").ref.set(upper));
        }
      }
      await Promise.all(promises);
    
      if (promises.length > 0) {
        const queue = getFunctions().taskQueue(
          "backfilldata",
          process.env.EXT_INSTANCE_ID
        );
        return queue.enqueue({});
      } else {
        return getExtensions()
          .runtime()
          .setProcessingState("PROCESSING_COMPLETE", "Backfill complete.");
      }
    });
    

    A função só processa alguns registros antes de se adicionar de volta à fila de tarefas. Essa é uma estratégia usada com frequência para lidar com tarefas de processamento que não podem ser concluídas dentro da janela de tempo limite de uma função do Cloud. Como não é possível prever quantas mensagens um usuário pode ter no banco de dados ao instalar sua extensão, essa estratégia é adequada.

  2. No arquivo extension.yaml, declare sua função de preenchimento como um recurso de extensão que tem a propriedade taskQueueTrigger:

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

    Em seguida, declare a função como o gerenciador do evento de ciclo de vida onInstall:

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. Embora o preenchimento das mensagens atuais seja bom, a extensão ainda pode funcionar sem ela. Em situações como essa, a execução dos manipuladores de eventos do ciclo de vida é opcional.

    Para fazer isso, adicione um novo parâmetro a extension.yaml:

    - param: DO_BACKFILL
      label: Backfill existing messages
      description: >-
        Generate uppercase versions of existing messages?
      type: select
      required: true
      options:
        - label: Yes
          value: true
        - label: No
          value: false
    

    Em seguida, no início da função de preenchimento, verifique o valor do parâmetro DO_BACKFILL e saia antecipadamente se ele não estiver definido:

    functions/index.js

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

Com as alterações acima, a extensão agora converterá as mensagens existentes em maiúsculas, quando for instalada.

Até agora, você usou o emulador de extensão para desenvolvê-la e testar alterações contínuas. No entanto, o emulador de extensão ignora o processo de instalação. Portanto, para testar o manipulador de eventos onInstall, você precisa instalar a extensão em um projeto real. No entanto, isso também acontece porque, com a adição desse recurso de preenchimento automático, a extensão do tutorial agora está completa no código.

9. Implantar em um projeto real do Firebase

Embora o emulador de extensões seja uma ótima ferramenta para iterar rapidamente uma extensão durante o desenvolvimento, convém testá-la em um projeto real.

Para fazer isso, primeiro configure um novo projeto com alguns serviços ativados:

  1. No Console do Firebase, adicione um novo projeto.
  2. Faça upgrade do seu projeto para o plano Blaze de pagamento por utilização. O Cloud Functions para Firebase exige que seu projeto tenha uma conta de faturamento. Portanto, você também precisa ter uma conta de faturamento para instalar uma extensão.
  3. No novo projeto, ative o Realtime Database.
  4. Como você quer testar a capacidade da extensão de preencher os dados atuais na instalação, importe alguns dados de amostra para a instância do banco de dados em tempo real:
    1. Faça o download de alguns dados de RTDB de sugestão.
    2. Na página "Banco de dados em tempo real" do Console do Firebase, clique em (mais) > Importar JSON e selecione o arquivo que você acabou de salvar.
  5. Para permitir que a função de preenchimento use o método orderByChild, configure o banco de dados para indexar mensagens no valor de upper:

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

Agora instale a extensão da fonte local no novo projeto:

  1. Crie um novo diretório para seu projeto do Firebase:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. Inicialize um novo projeto do Firebase no diretório de trabalho:

    firebase init database
    

    Quando solicitado, selecione o projeto que você acabou de criar.

  3. Instale a extensão no seu projeto local do Firebase:

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

    Confira como é a experiência do usuário ao instalar uma extensão usando a ferramenta CLI do Firebase. Selecione "sim" quando a ferramenta de configuração perguntar se você quer preencher o banco de dados existente.

    Depois que você selecionar as opções de configuração, a CLI do Firebase salvará sua configuração no diretório extensions e registrará o local da origem da extensão no arquivo firebase.json. Coletivamente, esses dois registros são chamados de manifesto de extensões. Os usuários podem usar o manifesto para salvar a configuração de extensões e implantá-lo em projetos diferentes.

  4. Implante a configuração da extensão no projeto ativo:

    firebase deploy --only extensions
    

Se tudo correr bem, a CLI do Firebase precisará fazer o upload da extensão para o projeto e instalá-la. Após a conclusão da instalação, a tarefa de preenchimento será executada e, em alguns minutos, o banco de dados será atualizado com mensagens em maiúsculas. Adicione alguns nós ao banco de dados de mensagens e verifique se a extensão também está funcionando para novas mensagens.

10. Escrever a documentação

Antes de compartilhar sua extensão com os usuários, verifique se você está fornecendo documentação suficiente para que eles sejam bem-sucedidos.

Quando você inicializou o projeto de extensão, a CLI do Firebase criou versões de stub da documentação mínima necessária. Atualize esses arquivos para refletir com precisão a extensão que você criou.

extension.yaml

Como você já atualizou este arquivo ao desenvolver a extensão, não precisa fazer mais atualizações no momento.

No entanto, não esqueça a importância da documentação contida nesse arquivo. Além das informações de identificação fundamentais de uma extensão, como nome, descrição, autor e local oficial do repositório, o arquivo extension.yaml contém a documentação do usuário referente a cada recurso e parâmetro configurável pelo usuário. Essas informações são exibidas aos usuários no console do Firebase, no Hub de extensões e na CLI do Firebase.

PREINSTALL.md

Nesse arquivo, forneça as informações que o usuário precisa antes de instalar a extensão: descreva brevemente o que a extensão faz, explique os pré-requisitos e forneça ao usuário informações sobre as implicações de faturamento da instalação da extensão. Se você tiver um site com informações adicionais, este também é um bom lugar para vinculá-lo.

O texto desse arquivo é exibido para o usuário no Hub de extensões e pelo comando firebase ext:info.

Aqui está um exemplo de um arquivo PREINSTALL:

Use this extension to automatically convert strings to upper case when added to
a specified Realtime Database path.

This extension expects a database layout like the following example:

    "messages": {
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
    }

When you create new string records, this extension creates a new sibling record
with upper-cased text:

    MESSAGE_ID: {
      "original": MESSAGE_TEXT,
      "upper": UPPERCASE_MESSAGE_TEXT,
    }

#### Additional setup

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

#### Billing

To install an extension, your project must be on the
[Blaze (pay as you go) plan](https://firebase.google.com/pricing).

- This extension uses other Firebase and Google Cloud Platform services, which
  have associated charges if you exceed the service's no-cost tier:
  - Realtime Database
  - Cloud Functions (Node.js 10+ runtime)
    [See FAQs](https://firebase.google.com/support/faq#extensions-pricing)
- If you enable events,
  [Eventarc fees apply](https://cloud.google.com/eventarc/pricing).

POSTINSTALL.md

Esse arquivo contém informações úteis para os usuários depois que eles instalam a extensão: por exemplo, etapas de configuração de acompanhamento, um exemplo da extensão em ação e assim por diante.

O conteúdo de POSTINSTALL.md é exibido no console do Firebase depois que uma extensão é configurada e instalada. Você pode fazer referência aos parâmetros do usuário neste arquivo, e eles serão substituídos pelos valores configurados.

Confira um exemplo de arquivo pós-instalação da extensão do tutorial:

### See it in action

You can test out this extension right away!

1.  Go to your
    [Realtime Database dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/database/${param:PROJECT_ID}/data) in the Firebase console.

1.  Add a message string to a path that matches the pattern `${param:MESSAGE_PATH}`.

1.  In a few seconds, you'll see a sibling node named `upper` that contains the
    message in upper case.

### Using the extension

We recommend adding data by pushing -- for example,
`firebase.database().ref().push()` -- because pushing assigns an automatically
generated ID to the node in the database. During retrieval, these nodes are
guaranteed to be ordered by the time they were added. Learn more about reading
and writing data for your platform (iOS, Android, or Web) in the
[Realtime Database documentation](https://firebase.google.com/docs/database/).

### Monitoring

As a best practice, you can
[monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor)
of your installed extension, including checks on its health, usage, and logs.

CHANGELOG.md

Você também precisa documentar as alterações feitas entre as versões de uma extensão no arquivo CHANGELOG.md.

Como a extensão de exemplo nunca foi publicada anteriormente, o registro de alterações tem apenas uma entrada:

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

A maioria das extensões também fornece um arquivo README para beneficiar os usuários que visitam o repositório da extensão. É possível gravar esse arquivo manualmente ou gerar uma leitura usando o comando.

Para os fins deste guia, pule a gravação de um arquivo leia-me.

Documentação adicional

A documentação discutida acima é o conjunto mínimo de documentação que você deve fornecer aos usuários. Muitas extensões exigem uma documentação mais detalhada para que os usuários possam usá-las. Quando esse for o caso, escreva outros documentos e hospede-os em um local para onde os usuários possam direcioná-los.

Para fins deste guia, pule a etapa de gravação para uma documentação mais extensa.

11. Publicar no Extensions Hub

Agora que sua extensão está completa e documentada, você está pronto para compartilhá-la com o mundo no Extensions Hub. Como este é apenas um tutorial, não faça isso. Comece a escrever sua própria extensão usando o que você aprendeu aqui e no restante da documentação do editor de Extensões do Firebase, examinando a fonte das extensões oficiais escritas pelo Firebase.

Quando estiver tudo pronto para publicar seu trabalho no Extensions Hub, siga estas etapas:

  1. Se você estiver publicando sua primeira extensão, registre-se como uma editora de extensões. Ao se registrar como editor de extensões, você cria um ID do editor que permite aos usuários identificar rapidamente você como autor das extensões.
  2. Hospede o código-fonte da sua extensão em um local verificável publicamente. Quando o código estiver disponível em uma fonte verificável, o Firebase poderá publicar a extensão diretamente desse local. Isso ajuda a garantir que você esteja publicando a versão lançada da extensão e ajuda os usuários permitindo que eles examinem o código que estão instalando nos projetos.

    Atualmente, isso significa disponibilizar sua extensão em um repositório público do GitHub.

  3. Faça upload da extensão no Extensions Hub usando o comando firebase ext:dev:upload.

  4. Acesse o painel do editor no Console do Firebase, encontre a extensão que você acabou de enviar e clique em "Publicar no Hub de extensões". Isso pede uma análise à nossa equipe de revisão, o que pode levar alguns dias. Se aprovada, a extensão será publicada no Extensions Hub. Se rejeitada, você receberá uma mensagem explicando o motivo. Assim, você poderá resolver os problemas informados e reenviar para análise.