将您的 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 控制台中显示的 Geohash 扩展程序

学习内容

  • 如何将现有 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 扩展程序规范,在本例中使用 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 和哈希字段的用户文档的用户集合。

清理以避免冲突

  • 完成测试后,卸载扩展程序 - 您以后要更新扩展程序代码并且不希望与当前的扩展程序发生冲突。

扩展程序支持同时安装同一个扩展程序的多个版本,因此卸载扩展程序可以确保与以前安装的某个扩展程序不发生冲突。

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 中。这是云中的一个特殊位置,用于存储加密的 Secret,并防止它们被意外泄露。这需要开发者支付使用此服务的费用,但会为其 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

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

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

您可以在实际 Firebase 项目中安装您的扩展程序。建议使用测试项目进行测试。如果您要测试扩展程序的端到端流程,或者 Firebase 模拟器套件尚不支持您的扩展程序的触发器,请使用此测试工作流(请参阅 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 Extensions 扩展程序!

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

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

后续操作