开始构建扩展程序

本页面将引导您完成构建一个简单的 Firebase Extensions 扩展程序所需的步骤,您可以将此扩展程序安装在项目中或与他人分享。这个简单的 Firebase Extensions 扩展程序示例将监控您的 Realtime Database 消息,并将其转换为大写形式。

1. 设置环境并初始化项目

您需要先使用所需工具设置构建环境,然后才能开始构建扩展程序。

  1. 安装 Node.js 16 或更高版本。安装 Node 的一种方法是使用 nvm(或 nvm-windows)。

  2. 安装或更新到 Firebase CLI 的最新版本。如需使用 npm 进行安装或更新,请运行下面的命令:

    npm install -g firebase-tools
    

现在,使用 Firebase CLI 初始化一个新的扩展程序项目:

  1. 为您的扩展程序创建一个目录,并 cd(切换到)该目录:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
    
  2. 运行 Firebase CLI 的 ext:dev:init 命令:

    firebase ext:dev:init
    

    出现提示时,选择 JavaScript 作为函数语言(不过请注意,您在开发自己的扩展程序时也可以使用 TypeScript),之后,在系统要求您安装依赖项时,选择“是”。(其他所有选项接受默认值即可)。该命令会为新扩展程序设置一个框架型代码库,您可以开始基于该代码库开发自己的扩展程序。

2. 使用模拟器试用示例扩展程序

在 Firebase CLI 初始化新的扩展程序目录时,便创建了一个简单的示例函数和一个 integration-tests 目录,其中包含使用 Firebase Emulator Suite 运行扩展程序所需的文件。

尝试在模拟器中运行示例扩展程序:

  1. 切换到 integration-tests 目录:

    cd functions/integration-tests
    
  2. 用一个演示项目启动模拟器:

    firebase emulators:start --project=demo-test
    

    模拟器会将扩展程序加载到一个预定义的“虚拟”项目 (demo-test) 中。至此,该扩展程序只包含一个 HTTP 触发的函数 (greetTheWorld),会在有用户访问该扩展程序时返回一条“hello world”消息。

  3. 保持模拟器运行,试用一下该扩展程序的 greetTheWorld 函数:访问启动扩展程序时显示的网址。

    您的浏览器会显示“Hello World from greet-the-world”这样一条消息。

  4. 此函数的源代码位于扩展程序的 functions 目录中。您可以在自己偏好的编辑器或 IDE 中打开该源代码:

    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. 在模拟器运行时,它会自动重新加载您对 Functions 函数代码所做的任何更改。您可以试着对 greetTheWorld 函数稍作更改:

    functions/index.js

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

    保存更改。模拟器将重新加载您的代码。现在,当您访问函数网址时,便会看到更新后的问候语。

3. 向 extension.yaml 添加基本信息

现在,您已经设置了开发环境,并且在运行扩展程序模拟器,接下来便可以开始编写自己的扩展程序了。

通常,作为第一步,您可以修改预定义的扩展程序元数据,以反映您要编写的扩展程序(而不是 greet-the-world)。这些元数据存储在 extension.yaml 文件中。

  1. 在您的编辑器中打开 extension.yaml,并将文件的全部内容替换为下面的代码:

    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
    

    请注意 name 字段中采用的命名惯例:官方 Firebase 扩展程序的名称会带有一个前缀,指示主要运行该扩展程序的 Firebase 产品,后跟描述扩展程序用途的简要说明。您在自己的扩展程序中也应该采用相同的惯例。

  2. 既然您更改了扩展程序的名称,则应使用新名称相应更新模拟器配置:

    1. functions/integration-tests/firebase.json 中,将 greet-the-world 更改为 rtdb-uppercase-messages
    2. functions/integration-tests/extensions/greet-the-world.env 重命名为 functions/integration-tests/extensions/rtdb-uppercase-messages.env

至此,您的扩展程序代码中仍有 greet-the-world 扩展程序的部分残留元素,但现在暂时不必理会。在接下来的几个部分中,您会更新掉这些内容。

4. 编写一个 Cloud Functions 函数并声明将其作为扩展程序资源使用

现在,您可以开始编写一些代码了。在此步骤中,您将编写一个 Cloud Functions 函数来执行扩展程序的核心任务,即监控您的 Realtime Database 消息,并将其转换为大写形式。

  1. 在您偏好的编辑器或 IDE 中打开扩展程序函数的源代码(在扩展程序的 functions 目录中)。将其内容替换为下面的代码:

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

    您替换掉的旧函数是 HTTP 触发的函数,会在用户访问 HTTP 端点时运行。而这个新函数是通过 Realtime Database 数据库事件触发的:它会监控特定路径上的新增项,并在检测到新增项时将相应值的大写版本写回数据库。

    此外,这个新文件是使用 ECMAScript 模块语法(importexport),而不是 CommonJS (require)。如需在 Node 中使用 ES 模块,请在 functions/package.json 中指定 "type": "module"

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      …
    }
    
  2. 扩展程序中的每个函数都必须在 extension.yaml 文件中声明。示例扩展程序声明了 greetTheWorld 作为该扩展程序的唯一 Cloud Functions 函数;现在,既然您将其替换成了 makeuppercase,您也需要相应更新该声明。

    打开 extension.yaml 并添加一个 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. 由于您的扩展程序现在是使用 Realtime Database 作为触发器,因此您需要更新您的模拟器配置,以便将 RTDB 模拟器与 Cloud Functions 模拟器一起运行:

    1. 如果模拟器仍在运行,可以按 Ctrl-C 键将其停止运行。

    2. functions/integration-tests 目录运行以下命令:

      firebase init emulators
      

      在系统询问时,跳过设置默认项目的步骤,然后选择 Functions 和 Database 模拟器。接受默认端口,并允许设置工具下载任何必需的文件。

    3. 重启模拟器:

      firebase emulators:start --project=demo-test
      
  4. 试用更新后的扩展程序:

    1. 使用启动模拟器时显示的链接打开 Database 模拟器界面。

    2. 修改数据库的根节点:

      • 字段messages
      • 类型json
      • {"11": {"original": "recipe"}}

      如果一切设置正确无误,在您保存数据库更改时,应会触发扩展程序的 makeuppercase 函数,在消息 11 中添加一条内容为 "upper": "RECIPE" 的子记录。查看相关日志及模拟器界面的数据库标签页,核实是否获得了预期结果。

    3. 尝试向 messages 节点 ({"original":"any text"}) 添加更多子项。每当您添加一条新记录,该扩展程序都会添加一个 uppercase 字段,其中包含 original 字段的相应大写内容。

现在,您有了一个完整的在 RTDB 实例上运行的简单扩展程序。在接下来的部分中,您将通过一些额外的功能来优化该扩展程序。之后,您就可以将扩展程序分发给其他人,最后,还可以将扩展程序发布到 Extensions Hub 上。

5. 声明 API 和角色

Firebase 通过使用实例级服务帐号向已安装的每个扩展程序实例授予对项目及其数据的有限访问权限。每个服务帐号只会拥有运行扩展程序实例所需的最小权限集。因此,您必须明确声明您的扩展程序需要使用的所有 IAM 角色;在用户安装您的扩展程序时,Firebase 会创建一个授予这些角色的服务帐号,并使用该服务帐号运行扩展程序。

虽然您无需声明角色便可触发产品事件,但您若要以其他方式与产品交互,就必须声明所需的角色。由于您在上一步中添加的函数会对 Realtime Database 执行写入操作,因此您需要向 extension.yaml 添加以下声明:

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

同样,您也可以在 apis 字段中声明扩展程序使用的 Google API。在用户安装您的扩展程序时,系统会询问他们是否要为自己的项目自动启用这些 API。通常情况下,只有非 Firebase 的 Google API 需要声明,因此本指南是不需要进行此项声明的。

6. 定义用户可配置的参数

您在前面两个步骤创建的函数会监控特定 RTDB 位置的传入消息。有时,您确实只需要监控某一特定位置,比如,您的扩展程序可能是在专用于该扩展程序的数据库结构上运行。但在大多数情况下,您可能希望安装您扩展程序的用户能够在他们自己的项目中配置这些值。这样,用户就可以使用您的扩展程序来处理现有的数据库设置。

将该扩展程序要监控新消息的路径设置为可供用户进行配置:

  1. extension.yaml 文件中,添加一个 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
    

    这会定义一个新的字符串参数,在用户安装您的扩展程序时,系统会提示他们设置该参数。

  2. 还是在 extension.yaml 文件中,回到 makeuppercase 声明,将 resource 字段更改为以下内容:

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

    ${param:MESSAGE_PATH} 令牌是对您刚才定义的参数的引用。在您的扩展程序运行时,此令牌将替换为用户为该参数配置的任何值,这样 makeuppercase 函数便会监听用户指定的路径。您可以使用此语法在 extension.yaml(以及 POSTINSTALL.md,下文会详细说明)中的任意位置引用任何用户定义的参数。

  3. 您也可以通过函数代码访问用户定义的参数。

    在您在上一部分编写的函数中,您对要监控更改内容的路径是进行的硬编码。现在,将触发器定义更改为引用用户定义的值:

    functions/index.js

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

    请注意,在 Firebase Extensions 扩展程序中,进行此更改操作只是出于记载目的:在扩展程序中部署了 Cloud Functions 函数后,扩展程序便会使用 extension.yaml 文件中的触发器定义,而会忽略函数定义中指定的值。不过,最好还是在代码中记录该值的来源。

  4. 您可能会因所做的代码更改没有任何运行时影响而略感失望,但是您因此了解了一个重要的知识点,即您可以通过函数代码访问任何用户定义的参数,并将其作为函数逻辑中的普通值使用。为进一步演示此功能,可以添加下面的日志语句,证明您确实在访问用户定义的值:

    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. 通常,在用户安装扩展程序时,系统会提示他们提供参数值。不过,如果您是使用模拟器进行测试和开发,则可以跳过安装过程,而是使用 env 文件提供用户定义的参数值。

    打开 functions/integration-tests/extensions/rtdb-uppercase-messages.env 并将 GREETING 定义替换为下面的内容:

    MESSAGE_PATH=/msgs/{pushId}/original
    

    请注意,上面的路径与默认路径以及与您之前定义的路径都不同;这只是为了向您演示,您的定义在更新后的扩展程序中所起到的作用。

  6. 现在,重启模拟器,然后重新打开数据库模拟器界面。

    使用上面定义的路径修改数据库的根节点:

    • 字段msgs
    • 类型json
    • {"11": {"original": "recipe"}}

    保存数据库更改时,扩展程序的 makeuppercase 函数应会像之前一样触发,而且还会将用户定义的参数输出到控制台日志。

7. 为用户定义的逻辑提供事件钩子

作为扩展程序的创作者,您已经了解了 Firebase 产品如何触发您的扩展程序提供的逻辑:在 Realtime Database 中创建新记录会触发您的 makeuppercase 函数。而安装您的扩展程序的用户与您的扩展程序之间也有着类似的关系:您的扩展程序可以触发用户定义的逻辑。

扩展程序可以提供同步钩子和/或异步钩子。借助同步钩子,用户可以执行会阻止某个扩展程序函数完成的任务。这在某些情况下会很有用,例如,用户可以在扩展程序开始工作之前执行一些自定义的预处理操作。

在本指南中,您将向扩展程序添加一个异步钩子,这样用户便可以定义自己的处理步骤,以在扩展程序将转换为大写形式的消息写入 Realtime Database 后运行这些步骤。异步钩子使用 Eventarc 来触发用户定义的函数。扩展程序会声明所发出的事件类型;在用户安装扩展程序时,可以自行选择需要的事件类型。如果用户选择了至少一个事件,Firebase 会在安装过程中为该扩展程序预配一个 Eventarc 渠道。之后,用户可以部署自己的 Cloud Functions 函数来监听该渠道,并会在扩展程序发布新事件时触发函数。

请按照以下步骤添加异步钩子:

  1. extension.yaml 文件中,添加以下部分,用于声明该扩展程序发出的一种事件类型:

    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.
    

    事件类型必须采用通用唯一标识符;为确保唯一性,请始终使用以下格式命名事件:<publisher-id>.<extension-id>.<version>.<description>。(您目前还没有发布商 ID,现在使用 test-publisher 即可。)

  2. makeuppercase 函数的末尾,添加一些代码,用于发布您刚才声明的类型的事件:

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

    此代码示例正是利用了只有在用户至少启用了一种事件类型时才会定义 EVENTARC_CHANNEL 环境变量这一特性;那么,如果未定义 EVENTARC_CHANNEL,代码便不会尝试发布任何事件。

    您还可以在 Eventarc 事件中附加额外信息。在上面的示例中,事件有一个 subject 字段(包含对新创建的值的引用)和一个 data 载荷(包含原始消息和转换为大写形式的消息)。通过该事件触发的用户定义的函数便可以使用这些信息。

  3. 通常,EVENTARC_CHANNELEXT_SELECTED_EVENTS 环境变量是根据用户在安装过程中选择的选项定义的。如需使用模拟器进行测试,请在 rtdb-uppercase-messages.env 文件中手动定义这两个变量:

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

至此,您已完成向扩展程序添加异步事件钩子所需的步骤。

为了试用您刚刚实现的这项新功能,在接下来的几个步骤中,您将扮演安装扩展程序的用户角色:

  1. functions/integration-tests 目录中,初始化一个新的 Firebase 项目:

    firebase init functions
    

    出现提示时,跳过设置默认项目的步骤,选择 JavaScript 作为 Cloud Functions 函数语言,然后安装所需的依赖项。此项目代表安装了您的扩展程序的“用户”项目。

  2. 修改 integration-tests/functions/index.js 并粘贴以下代码:

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

    这是一个用户可能会编写的后处理函数示例。在本例中,该函数会监听扩展程序以便发布 complete 事件,并且触发后会在新转换的大写形式消息中添加三个感叹号。

  3. 重启模拟器。模拟器将加载扩展程序的函数以及“用户”定义的后处理函数。

  4. 打开数据库模拟器界面,使用上面定义的路径修改数据库的根节点:

    • 字段msgs
    • 类型json
    • {"11": {"original": "recipe"}}

    保存数据库更改时,扩展程序的 makeuppercase 函数和用户的 extraemphasis 函数应会依次触发,因此 upper 字段会获得 RECIPE!!! 值。

8. 添加生命周期事件处理脚本

至此,您编写的扩展程序会在有消息被创建时对其进行相应处理。但是,如果您的用户在安装扩展程序时已经拥有消息数据库,该怎么办?Firebase Extensions 提供一项称为“生命周期事件钩子”的功能,可用于在用户安装、更新或重新配置扩展程序时触发相应操作。在本部分中,您将在用户安装扩展程序时,使用生命周期事件钩子用大写形式的消息回填项目的现有消息数据库。

Firebase Extensions 使用 Cloud Tasks 来运行生命周期事件处理脚本。您可以使用 Cloud Functions 函数定义事件处理脚本;如果您定义了处理脚本,每当您的扩展程序实例触发了某个受支持的生命周期事件,都会向 Cloud Tasks 队列添加该处理脚本。Cloud Tasks 随后会异步执行该处理脚本。在生命周期事件处理脚本运行时,Firebase 控制台会向用户报告扩展程序实例有正在进行的处理任务。您的处理脚本函数则会向用户报告正在进行的状态和任务完成情况。

如需添加用于回填现有消息的生命周期事件处理脚本,请执行以下操作:

  1. 定义一个通过任务队列事件触发的新 Cloud Functions 函数:

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

    请注意,该函数只会处理部分记录,然后会将自己重新添加到任务队列。在处理无法在 Cloud Functions 函数的超时时限内完成的处理任务时,这是一种常用的策略。由于您无法预测用户在安装您的扩展程序时数据库中可能已有多少条消息,因此非常适合采用该策略。

  2. extension.yaml 文件中,声明将该回填函数作为扩展程序资源使用,并添加 taskQueueTrigger 属性:

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

    然后,声明将该函数作为 onInstall 生命周期事件的处理脚本使用:

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. 虽然回填现有消息是一项很实用的操作,但可能即便您不执行此操作,也不影响扩展程序的正常运行。在这种情况下,您应将运行生命周期事件处理脚本设为一项可选操作。

    为此,您可以向 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
    

    然后,检查回填函数开头部分 DO_BACKFILL 参数的值,如果未设置该值,则提前退出:

    functions/index.js

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

进行上述更改后,现在该扩展程序在安装时会将现有消息都转换为大写形式。

至此,您已使用扩展程序模拟器开发扩展程序并对后续的各项更改进行测试。不过,扩展程序模拟器会跳过安装过程,因此,若要测试 onInstall 事件处理脚本,您则需要在真实项目中安装扩展程序。由于我们加入了自动回填功能,本教程中扩展程序的代码现在也是完整的!

9. 部署到真实的 Firebase 项目

虽然扩展程序模拟器是一款在开发过程中快速迭代扩展程序的绝佳工具,但在某些情况下,您可能需要在真实项目中试用您的扩展程序。

为此,请先设置一个启用了某些服务的新项目:

  1. Firebase 控制台中,添加一个新项目。
  2. 将您的项目升级为采用随用随付 Blaze 方案。Cloud Functions for Firebase 要求您的项目拥有结算帐号,因此您还需要有一个结算帐号才能安装扩展程序。
  3. 在新项目中,启用 Realtime Database
  4. 由于您要测试扩展程序在安装时回填现有数据的功能,因此请将一些示例数据导入您的 Realtime Database 实例:
    1. 下载一些种子 RTDB 数据
    2. 在 Firebase 控制台的“Realtime Database”页面中,点击 (更多)> 导入 JSON,然后选择您刚刚下载的文件。
  5. 为了让回填函数能够使用 orderByChild 方法,请将数据库配置为按 upper 的值将消息编入索引:

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

现在,通过本地源代码将扩展程序安装到新项目中:

  1. 为您的 Firebase 项目创建一个新目录:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. 在工作目录中初始化 Firebase 项目:

    firebase init database
    

    出现提示时,选择您刚刚创建的项目。

  3. 将该扩展程序安装到您的本地 Firebase 项目中:

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

    在这里,您可以了解使用 Firebase CLI 工具安装扩展程序时的用户体验。在配置工具询问您是否要回填现有数据库时,请务必选择“是”。

    在您选择配置选项后,Firebase CLI 会将您的配置保存在 extensions 目录中,并将扩展程序的源代码位置记录在 firebase.json 文件中。这两项记录统称为“扩展程序清单”。用户可以使用该清单保存其扩展程序配置,并将其部署到其他项目中。

  4. 将扩展程序配置部署到实际项目:

    firebase deploy --only extensions
    

如果一切顺利,Firebase CLI 应会将您的扩展程序上传到项目中并进行安装。安装完成后,回填任务将运行,几分钟后,您的数据库将使用大写形式的消息进行更新。不妨向消息数据库添加一些新节点,并确保对于这些新消息,该扩展程序也能正常工作。

10. 编写文档

在将您的扩展程序分享给用户之前,请确保您提供了充足的文档信息来帮助用户顺利使用您的扩展程序。

初始化扩展程序项目时,Firebase CLI 会创建至少需要提供的文档的存根版本。请更新这些文件,以准确反映您构建的扩展程序。

extension.yaml

由于您在开发扩展程序的过程中就已经更新了此文件,因此您现在无需再进行任何更新。

不过,请勿忽视此文件中包含的文档信息的重要性。除了扩展程序的关键识别信息(名称、说明、作者、官方代码库位置)之外,extension.yaml 文件还包含一些面向用户的文档信息(每项资源以及用户可配置的参数)。这些信息会在 Firebase 控制台、Extensions Hub 和 Firebase CLI 中向用户显示。

PREINSTALL.md

在该文件中,您需要向用户提供在他们安装您的扩展程序之前需要了解的信息:简要说明扩展程序的用途,阐明所有前提条件,并让用户了解安装该扩展程序在结算方面有何影响。如果您的网站包含额外信息,也非常适合在该文件中链接这些信息。

该文件的文本内容会在 Extensions Hub 向用户显示,亦可通过 firebase ext:info 命令进行查看。

下面是一个示例 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

该文件包含在用户成功安装您的扩展程序后对他们比较有用的信息:例如,后续设置步骤、实际扩展程序的示例等。

在配置和安装扩展程序后,POSTINSTALL.md 的内容会显示在 Firebase 控制台中。您可以在该文件中引用用户参数,它们将被替换为配置的值。

下面是本教程中扩展程序的安装后文件示例:

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

您还应在 CHANGELOG.md 文件中记录对扩展程序各个版本所做的更改。

由于示例扩展程序从未发布过,因此这里更新日志只有一个条目:

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

大多数扩展程序还会提供自述文件,以便用户访问扩展程序的代码库。你可以手动编写该文件,也可以通过相关命令生成该文件。

在本指南中,我们会跳过编写自述文件的步骤。

其他文档

上述几个文档是您至少应向用户提供的文档。许多扩展程序需要提供更详细的文档信息,以便用户顺利使用扩展程序。在这种情况下,您需要编写更多文档,并将这些文档托管在用户可以导航到的某个位置。

在本指南中,我们会跳过编写更详尽的文档信息这一步骤。

11. 发布到 Extensions Hub 上

现在您已完整编写并记录扩展程序的代码,接下来您可以通过 Extensions Hub 与世界各地的用户分享您的扩展程序了。但由于我们这里只是一个教程用扩展程序,因此请不要实际去这样操作。您可以使用在本教程学到的知识,并参考 Firebase Extensions 发布商文档的其余内容以及 Firebase 官方编写的扩展程序的源代码,开始着手编写您自己的扩展程序。

在准备好发布您的扩展程序后,可通过以下方法进行此操作:

  1. 如果您是在发布自己的第一个扩展程序,请先注册成为扩展程序发布商。在您注册成为扩展程序发布商时,需要创建发布商 ID,该 ID 可让用户快速确定您是扩展程序的创作者。
  2. 将扩展程序的源代码托管在一个可供公开验证的位置。如果您的代码可从可验证的来源获得,Firebase 就可以直接从该位置发布您的扩展程序。这样做有助于确保您是在发布扩展程序的当前已发布版本,并让用户能够检查他们将安装到自己项目中的代码。

    目前,这表示您需要将自己的扩展程序放置在一个公共 GitHub 代码库中。

  3. 使用 firebase ext:dev:upload 命令将您的扩展程序上传到 Extensions Hub。

  4. 前往 Firebase 控制台中的发布商信息中心,找到您刚刚上传的扩展程序,然后点击“发布到 Extensions Hub”。后续将需要我们的审核人员进行审核,审核流程可能需要几天时间来完成。如果审核通过,扩展程序便会发布到 Extensions Hub。如果发布申请遭拒,您会收到一条说明原因的消息;之后,您可以相应解决该消息中阐明的问题,并重新提交扩展程序以供再次审核。