将 Cloud Functions 代码用作 Firebase Extensions 扩展程序

1. 准备工作

Firebase Extensions 扩展程序可用来执行一个或一组特定任务,以响应 HTTP 请求或来自其他 Firebase 和 Google 产品(如 Firebase Cloud Messaging、Cloud Firestore 或 Pub/Sub)的触发性事件。

构建内容

在此 Codelab 中,您将构建一个用于地理哈希的 Firebase 扩展程序。部署后,扩展程序会响应 Firestore 事件或通过可调用函数调用将 X 坐标和 Y 坐标转换为地理哈希。您可以选择使用此功能,而不是在所有目标平台上实现地理围栏库来存储数据,从而节省时间。

Firebase 控制台中显示的地理哈希扩展程序

学习内容

  • 如何将现有的 Cloud Functions 代码转换为可分发的 Firebase 扩展程序
  • 如何设置 extension.yaml 文件
  • 如何在扩展程序中存储敏感字符串(API 密钥)
  • 如何允许扩展程序的开发者根据自己的需求配置扩展程序
  • 如何测试和部署扩展程序

所需条件

  • Firebase CLI(安装和登录)
  • Google 账号,例如 Gmail 账号
  • Node.js 和 npm
  • 您喜爱的开发环境

2. 进行设置

获取代码

您可以在 GitHub 代码库中找到所需的有关此扩展程序的所有内容。首先,请获取代码并在您常用的开发环境中打开。

  1. 解压缩下载的 ZIP 文件。
  2. 如需安装所需的依赖项,请在 functions 目录中打开终端,然后运行 npm install 命令。

设置 Firebase

此 Codelab 强烈建议使用 Firebase 模拟器。如果您想使用真实的 Firebase 项目试用扩展程序开发,请参阅创建 Firebase 项目。此 Codelab 使用 Cloud Functions,因此如果您使用的是真实的 Firebase 项目而不是模拟器,则需要升级到 Blaze 定价方案

想跳过吗?

您可以下载已完成的 Codelab。如果您在学习过程中遇到困难,或者想了解完成的扩展程序是什么样的,请查看 GitHub 代码库codelab-end 分支,或下载完成的 zip 文件。

3. 查看代码

  • 打开 ZIP 文件中的 index.ts 文件。请注意,它包含两个 Cloud Functions 声明。

这些函数的作用是什么?

这些演示函数用于地理哈希。它们会获取一对坐标,并将其转换为针对 Firestore 中的地理位置查询进行了优化的格式。这些函数会模拟 API 调用的使用,以便您详细了解如何在扩展程序中处理敏感数据类型。如需了解详情,请参阅有关在 Firestore 中对数据运行地理位置查询的文档。

函数常量

常量在 index.ts 文件的顶部提前声明。其中一些常量在扩展程序定义的触发器中引用。

index.ts

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

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

initializeApp();

const service = new GeoHashService(apiKey);

Firestore 触发器

index.ts 文件中的第一个函数如下所示:

index.ts

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

此函数是一个 Firestore 触发器。当数据库中发生写入事件时,该函数会通过搜索 xv 字段和 yv 字段来对该事件做出反应,如果这两个字段都存在,则计算地理哈希并将输出写入指定的文档输出位置。输入文档由 users/{uid} 常量定义,这意味着该函数会读取写入 users/ 集合的每个文档,然后处理这些文档的地理哈希。然后,它会将哈希输出到同一文档中的哈希字段。

Callable 函数

index.ts 文件中的下一个函数如下所示:

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

请注意 onCall 函数。它表示此函数是可调用函数,可从客户端应用代码中调用。此可调用函数接受 xy 参数,并返回地理哈希。虽然在此 Codelab 中不会直接调用此函数,但此处将其纳入,是为了展示如何在 Firebase 扩展程序中配置某些内容。

4. 设置 extension.yaml 文件

现在,您已经了解扩展程序中的 Cloud Functions 代码的作用,可以开始将其打包以进行分发了。每个 Firebase 扩展程序都附带一个 extension.yaml 文件,用于描述扩展程序的作用和行为。

extension.yaml 文件需要包含一些有关扩展程序的初始元数据。以下每个步骤都有助于您了解所有字段的含义以及您需要这些字段的原因。

  1. 在您之前下载的项目的根目录中创建 extension.yaml 文件。首先,添加以下内容:
name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

扩展程序的名称用作扩展程序实例 ID 的基础(用户可以安装扩展程序的多个实例,每个实例都有自己的 ID)。然后,Firebase 会使用该实例 ID 生成扩展程序的服务账号和特定于扩展程序的资源的名称。版本号表示扩展程序的版本。它必须遵循语义化版本控制,并且您需要在每次更改扩展程序的功能时更新它。扩展程序规范版本用于确定要遵循的 Firebase 扩展程序规范,在本例中,使用的是 v1beta

  1. 向 YAML 文件添加一些便于用户理解的详细信息:
...

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

显示名称是开发者与扩展程序互动时,扩展程序名称的易记表示形式。说明简要介绍了扩展程序的用途。当扩展程序部署到 extensions.dev 时,它看起来如下所示:

Geohash Converter 扩展程序,如 extensions.dev 中所示

  1. 指定扩展程序中代码的许可。
...

license: Apache-2.0  # The license you want for the extension
  1. 指明扩展程序的作者以及安装该扩展程序是否需要结算:
...

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

billingRequired: true

author 部分用于告知用户,如果他们在使用扩展程序时遇到问题或想了解有关扩展程序的更多信息,应与谁联系。billingRequired 是必需的参数,必须设置为 true,因为所有扩展程序都依赖于 Cloud Functions,而 Cloud Functions 需要使用 Blaze 方案。

这涵盖了 extension.yaml 文件中用于标识相应扩展程序的最低字段数。如需详细了解您可以在扩展程序中指定的其他标识信息,请参阅相关文档

5. 将 Cloud Functions 代码转换为扩展程序资源

扩展程序资源是指 Firebase 在扩展程序安装期间在项目中创建的项目。然后,扩展程序会拥有这些资源,并具有一个可操作这些资源的特定服务账号。在此项目中,这些资源是 Cloud Functions,必须在 extension.yaml 文件中定义,因为扩展程序不会自动从 functions 文件夹中的代码创建资源。如果未将 Cloud Functions 明确声明为资源,则在部署扩展程序时无法部署这些函数。

用户定义的部署位置

  1. 允许用户指定要部署此扩展程序的位置,并决定是将扩展程序托管在更靠近其最终用户的位置,还是更靠近其数据库的位置。在 extension.yaml 文件中,添加用于选择位置的选项。

extension.yaml

现在,您可以开始编写函数资源的配置了。

  1. extension.yaml 文件中,为 locationUpdate 函数创建一个资源对象。将以下内容附加到 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}

您将 name 定义为项目的 index.ts 文件中定义的函数名称。您需要指定所部署函数的 type,目前应始终为 firebaseextensions.v1beta.function。然后,您需要定义此函数的 properties。您定义的第一个属性是与此函数关联的 eventTrigger。为了与扩展程序当前支持的功能保持一致,您可以使用 providers/cloud.firestore/eventTypes/document.writeeventType,该字段可在为扩展程序编写 Cloud Functions 函数 文档中找到。您将 resource 定义为文档的位置。由于您当前的目标是镜像代码中已有的内容,因此文档路径会监听 users/{uid},并在其前面加上默认数据库位置。

  1. 扩展程序需要 Firestore 数据库的读取和写入权限。在 extension.yaml 文件的末尾,指定扩展程序应具有哪些 IAM 角色才能使用开发者 Firebase 项目中的数据库。
roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

datastore.user 角色来自扩展程序支持的 IAM 角色列表。由于扩展程序将进行读取和写入操作,因此 datastore.user 角色非常适合。

  1. 还必须添加可调用函数。在 extension.yaml 文件中,在 resources 属性下创建一个新资源。以下属性是可调用函数的特有属性:
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

虽然之前的资源使用了 eventTrigger,但这里使用了 httpsTrigger,它涵盖了可调用函数和 HTTPS 函数。

代码检查

为了使 extension.yamlindex.ts 文件中的代码所做的一切保持一致,您需要进行大量配置。此时,完成的 extension.yaml 文件应如下所示:

extension.yaml

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

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

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

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

billingRequired: true

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

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

状态检查

至此,您已设置好扩展程序的初始功能组件,因此可以使用 Firebase 模拟器实际试用一下!

  1. 如果您尚未在下载的扩展程序项目的函数文件夹中调用 npm run build,请先调用。
  2. 在主机系统上创建一个新目录,并使用 firebase init 将该目录连接到您的 Firebase 项目。
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. 在同一目录中,运行 firebase ext:install。将 /path/to/extension 替换为包含 extension.yaml 文件的目录的绝对路径。
firebase ext:install /path/to/extension
    This command does two things:
  • 系统会提示您指定扩展实例的配置,并创建一个包含该实例配置信息的 *.env 文件。
  • 它会将扩展程序实例添加到 firebase.jsonextensions 部分。此文件充当实例 ID 与扩展程序版本的映射。
  • 由于您要在本地部署项目,因此可以指定要使用本地文件,而不是 Google Cloud Secret Manager。

扩展程序安装过程的屏幕截图,显示了在安装此扩展程序时,本地文件正用于存储密钥

  1. 使用新配置启动 Firebase 模拟器:
firebase emulators:start
  1. 运行 emulators:start 后,在模拟器的 WebView 中导航到 Firestore 标签页。
  2. users 集合添加一个文档,其中包含 xv 数字字段和 yv 数字字段。

在 Firebase 模拟器中显示的对话框,用于启动包含短语的集合 ID 的集合

  1. 如果您成功安装了扩展程序,该扩展程序会在文档中创建一个名为 hash 的新字段。

包含具有 xv、yv 和 hash 字段的用户文档的用户集合。

清理相关资源以避免发生冲突

  • 完成测试后,请卸载该扩展程序,因为您将更新扩展程序代码,不希望以后与当前扩展程序发生冲突。

扩展程序允许同时安装同一扩展程序的多个版本,因此通过卸载,您可以确保不会与之前安装的扩展程序发生冲突。

firebase ext:uninstall geohash-ext

当前解决方案可以正常运行,但正如项目开始时所提到的,其中包含一个硬编码的 API 密钥,用于模拟与服务的通信。如何使用最终用户的 API 密钥,而不是最初提供的 API 密钥?欢迎阅读下文,一探究竟。

6. 使扩展程序可供用户配置

在此 Codelab 的这一步中,您已配置一个扩展程序,该扩展程序可用于您已编写的函数的意见性设置,但如果用户希望使用纬度和经度(而非 yx)来表示笛卡尔平面上的位置,该怎么办?此外,如何让最终用户提供自己的 API 密钥,而不是让他们使用您提供的 API 密钥?您可能会很快超出相应 API 的配额。在这种情况下,您需要设置和使用参数。

extension.yaml 文件中定义基本参数

首先转换开发者可能需要自定义配置的项。第一个是 XFIELDYFIELD 参数。

  1. extension.yaml 文件中,添加以下代码,该代码使用 XFIELDYFIELD 字段形参。这些参数位于之前定义的 params YAML 属性内:

extension.yaml

params:
  - param: XFIELD
    label: The X Field Name
    description: >-
      The X Field is also known as the **longitude** value. What does
      your Firestore instance refer to as the X value or the longitude
      value. If no value is specified, the extension searches for
      field 'xv'.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: xv
    required: false
    immutable: false
    example: xv
  - param: YFIELD
    label: The Y Field Name
    description: >-
      The Y Field is also known as the **latitude** value. What does
      your Firestore instance refer to as the Y value or the latitude
      value. If no value is specified, the extension searches for
      field 'yv'.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: yv
    required: false
    immutable: false
    example: yv
  • param 以您(扩展程序提供方)可见的方式命名参数。稍后指定参数值时,请使用此值。
  • 标签是开发者可读的标识符,用于告知开发者相应参数的用途。
  • description 提供了值的详细说明。由于此属性支持 Markdown,因此可以链接到其他文档,也可以突出显示对开发者可能很重要的字词。
  • type 定义了用户设置参数值的输入机制。存在多种类型,包括 stringselectmultiSelectselectResourcesecret。如需详细了解这些选项,请参阅文档
  • validationRegex 将开发者条目限制为特定的正则表达式值(在此示例中,该值基于此处提供的简单字段名称指南);如果该值无效...
  • validationErrorMessage 会向开发者提醒失败值。
  • 默认是指开发者未输入任何文本时该值应为何值。
  • 必需表示开发者无需输入任何文本。
  • 不可变允许开发者更新此扩展程序并更改此值。在这种情况下,开发者应该能够根据需求变化更改字段名称。
  • 示例可让您了解有效输入可能是什么样的。

您理解了很多内容!

  1. 在添加特殊参数之前,您还需要向 extension.yaml 文件添加三个参数。
  - 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

定义敏感参数

现在,您需要管理用户指定的 API 密钥。这是一个敏感字符串,不应以纯文本形式存储在函数中。请改为将此值存储在 Cloud Secret Manager 中。这是云端中用于存储加密密文的特殊位置,可防止密文意外泄露。这需要开发者付费使用此服务,但可以为他们的 API 密钥增加一层额外的安全保障,并可能限制欺诈活动。用户文档会提醒开发者这是一项付费服务,以免在结算时出现意外情况。总而言之,其使用方式与上述其他字符串资源类似。唯一的区别在于类型,即 secret

  • extension.yaml 文件中,添加以下代码:

extension.yaml

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

更新 resource 属性以使用参数

如前所述,资源(而非函数)定义了如何观测资源,因此需要更新 locationUpdate 资源才能使用新参数。

  • extension.yaml 文件中,添加以下代码:

extension.yaml

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

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

检查 extension.yaml 文件

  • 查看 extension.yaml 文件。输出应如下所示:

extension.yaml

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

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

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

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

billingRequired: true

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

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

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

在代码中访问参数

现在,所有参数都已在 extension.yaml 文件中配置完毕,接下来将它们添加到 index.ts 文件中。

  • index.ts 文件中,将默认值替换为 process.env.PARAMETER_NAME,后者会提取相应的参数值,并将其填充到部署在开发者 Firebase 项目中的函数代码中。

index.ts

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

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

通常,您需要对环境变量值执行 null 检查,但在这种情况下,您可以信任参数值已正确复制。代码现已配置为可与扩展程序参数搭配使用。

7. 创建用户文档

在模拟器上或 Firebase 扩展服务市场中测试代码之前,需要为扩展程序编写文档,以便开发者在使用扩展程序时了解自己将获得什么。

  1. 首先,创建 PREINSTALL.md 文件,用于描述功能、安装的任何前提条件以及潜在的结算影响。

PREINSTALL.md

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

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

#### Third Party API Key

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

#### Additional setup

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

After installing this extension, you'll need to:

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

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

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

- This extension uses other Firebase and Google Cloud Platform services,
which have associated charges if you exceed the service's no-cost tier:
 - Cloud Firestore
 - Cloud Functions (Node.js 16+ runtime. [See
FAQs](https://firebase.google.com/support/faq#extensions-pricing))
 - [Cloud Secret Manager](https://cloud.google.com/secret-manager/pricing)
  1. 为了节省为该项目编写 README.md 的时间,请使用便捷方法:
firebase ext:info . --markdown > README.md

此文件结合了 PREINSTALL.md 文件的内容以及 extension.yaml 文件中有关扩展程序的其他详细信息。

最后,向扩展程序的开发者告知刚刚安装的扩展程序的一些其他详细信息。开发者在完成安装后可能会收到一些额外的说明和信息,并且可能会收到一些详细的安装后任务,例如在此处设置客户端代码。

  1. 创建一个 POSTINSTALL.md 文件,然后添加以下安装后信息:

POSTINSTALL.md

Congratulations on installing the geohash extension!

#### Function information

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

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

监控

作为最佳实践,您可以监控已安装的扩展程序的活动,包括检查其健康状况、使用情况和日志。

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

这会重新编译函数,以便在将扩展程序部署到模拟器或直接部署到 Firebase 时,最新的源代码可以随扩展程序一起部署。

接下来,创建一个新目录,用于测试扩展程序。由于扩展程序是从现有函数开发的,因此请勿从配置扩展程序的文件夹中进行测试,因为该文件夹也会尝试部署函数和 Firebase 规则。

使用 Firebase 模拟器进行安装和测试

  1. 在主机系统上创建一个新目录,并使用 firebase init 将该目录连接到您的 Firebase 项目。
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. 在该目录中,运行 firebase ext:install 以安装扩展程序。将 /path/to/extension 替换为包含 extension.yaml 文件的目录的绝对路径。此命令会启动扩展程序的安装流程,并创建一个 .env 文件,其中包含您的配置,然后将配置推送到 Firebase 或模拟器。
firebase ext:install /path/to/extension
  • 由于您要在本地部署项目,因此请指定您要使用本地文件,而不是 Google Cloud Secret Manager。

da928c65ffa8ce15.png

  1. 启动本地模拟器套件:
firebase emulators:start

使用真实的 Firebase 项目进行安装和测试

您可以在实际 Firebase 项目中安装您的扩展程序。建议使用测试项目进行测试。如果您希望测试扩展程序的端到端流程,或者 Firebase 模拟器套件尚不支持您的扩展程序的触发器,请使用此测试工作流(请参阅扩展程序模拟器选项)。模拟器目前支持针对 Cloud Firestore、Realtime Database 和 Pub/Sub 的 HTTP 请求触发的函数和后台事件触发的函数。

  1. 在主机系统上创建一个新目录,并使用 firebase init 将该目录连接到您的 Firebase 项目。
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. 然后,在该目录下运行 firebase ext:install 以安装扩展程序。将 /path/to/extension 替换为包含 extension.yaml 文件的目录的绝对路径。此命令会启动扩展程序的安装流程,并创建一个 .env 文件,其中包含您的配置,然后将配置推送到 Firebase 或模拟器。
firebase ext:install /path/to/extension
  • 由于您想直接部署到 Firebase,并想使用 Google Cloud Secret Manager,因此需要在安装扩展程序之前激活 Secret Manager API
  1. 部署到您的 Firebase 项目。
firebase deploy

测试扩展程序

  1. 运行 firebase deployfirebase emulators:start 后,根据需要前往 Firebase 控制台或模拟器的 WebView 的 Firestore 标签页。
  2. 将文档添加到 x 字段和 y 字段指定的集合中。在本例中,更新后的文档位于 u/{uid},其中 x 字段的值为 xvy 字段的值为 yv

用于添加 Firestore 记录的 Firebase 模拟器界面

  1. 如果您成功安装了扩展程序,则在保存这两个字段后,该扩展程序会在文档中创建一个名为 hash 的新字段。

模拟器中的 Firestore 数据库界面,显示已添加的哈希

8. 恭喜!

您已成功将第一个 Cloud Functions 函数转换为 Firebase 扩展程序!

您添加了一个 extension.yaml 文件,并对其进行了配置,以便开发者可以选择他们希望如何部署您的扩展程序。然后,您创建了用户文档,其中提供了有关扩展程序开发者在设置扩展程序之前应执行的操作以及在成功安装扩展程序后可能需要采取的步骤的指南。

现在,您已了解将 Firebase 函数转换为可分发的 Firebase 扩展程序所需的主要步骤。

后续操作