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

1. 准备工作

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

构建内容

在此 Codelab 中,您将构建一个用于地理编码的 Firebase 扩展程序。部署后,您的扩展程序会将 X 和 Y 坐标转换为 Geohash,以响应 Firestore 事件或通过 Callable 函数调用。这可以用作在所有目标平台上实现 GeoFire 库以存储数据的替代方案,从而节省时间。

Firebase 控制台中显示的地理编码扩展程序

学习内容

  • 如何将现有 Cloud Functions 函数代码转换为可分发的 Firebase Extensions 扩展程序
  • 如何设置 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 字段来响应该事件,如果这两个字段都存在,该函数会计算 Geohash 并将输出写入指定的文档输出位置。输入文档由 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 函数。表明此函数是一个可调用的函数,可在客户端应用代码中调用。此 Callable 函数接受 xy 参数,并返回 Geohash。虽然此 Codelab 不会直接调用此函数,但这里给出了要在 Firebase 扩展程序中配置的内容示例。

4. 设置 extension.yaml 文件

现在,您已了解扩展程序中 Cloud Functions 函数代码的用途,接下来可以将其打包以进行分发。每个 Firebase Extensions 扩展程序都带有一个 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 Extensions 扩展程序规范,在本例中,使用的是 v1beta

  1. 向 YAML 文件添加一些方便用户使用的详细信息:
...

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

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

Extensions.dev 上显示的 Geohash Converter 扩展程序

  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. 还必须添加 Callable 函数。在 extension.yaml 文件中,在 resources 属性下创建一个新资源。以下属性专用于 Callable 函数:
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

虽然前面的资源使用了 eventTrigger,但此处使用的是 httpsTrigger,它同时涵盖了 Callable 函数和 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. 在所下载的扩展程序项目的 functions 文件夹中调用 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。

扩展程序安装流程的屏幕截图,展示了安装此扩展程序时本地文件正用于 Secret

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

Firebase Emulators 中显示的一个对话框,用于通过包含此词组的集合 ID 启动集合

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

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

清理以避免冲突

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

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

firebase ext:uninstall geohash-ext

当前的解决方案行之有效,但如项目开头所述,有一个硬编码的 API 密钥来模拟与服务通信。您该如何使用最终用户的 API 密钥,而不是最初提供的 API 密钥?请阅读下文,一探究竟。

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

在此 Codelab 的这一阶段,您已配置一个扩展程序,用于对您已编写的函数进行主观设置,但如果您的用户想要为表示笛卡尔平面上位置的字段使用纬度和经度(而不是 y 和 x),该怎么办?此外,您如何让最终用户提供自己的 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 会以您(即扩展程序制作者)可见的方式为参数命名。稍后指定参数值时,请使用此值。
  • label 是人类可读的标识符,用于告知开发者参数的用途。
  • description:提供值的详细说明。由于此功能支持 Markdown,因此可以链接到其他文档,也可以突出显示对开发者可能很重要的字词。
  • type 用于定义用户设置参数值的输入机制。存在多种类型,包括 stringselectmultiSelectselectResourcesecret。如需详细了解每种选项,请参阅文档
  • validationRegex 将开发者条目限制为某个特定的正则表达式值(在此示例中,该值基于此处的简单字段名称准则);如果失败了...
  • validationErrorMessage 会向开发者发出失败值提醒。
  • default 是开发者未输入任何文本时的值。
  • required 表示开发者无需输入任何文本。
  • immutable:允许开发者更新此扩展程序并更改此值。在这种情况下,开发者应能够根据其要求更改字段名称。
  • 示例可让您大致了解有效输入可能是什么样的。

太明白了!

  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 Extensions Marketplace 中测试代码之前,您需要记录扩展程序,以便开发者知道使用扩展程序时会获得什么。

  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 Emulator Suite 尚不支持您的扩展程序的触发器,请使用此测试工作流(请参阅 Extensions 模拟器选项)。模拟器目前支持针对 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 控制台的 Firestore 标签页或模拟器的 WebView。
  2. 将文档添加到由 x 字段和 y 字段指定的集合中。在本例中,更新后的文档位于 u/{uid},其 x 字段为 xvy 字段为 yv

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

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

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

8. 恭喜!

您已成功将第一个 Cloud Functions 函数转换为 Firebase Extension!

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

您现在已经知道将 Firebase 函数转换为可分发的 Firebase Extensions 所需的关键步骤。

后续操作