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

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

关于此 Codelab

subject上次更新时间:5月 11, 2023
account_circleAlexander Nohe 编写

1. 准备工作

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

构建内容

在此 Codelab 中,您将构建一个用于地理编码的 Firebase 扩展程序。部署后,您的扩展程序会在响应 Firestore 事件或通过可调用函数调用时将 X 坐标和 Y 坐标转换为地理哈希。这可以用作在所有目标平台上实现 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 字段来响应该事件,如果这两个字段都存在,则会计算地理哈希并将输出写入到指定的文档输出位置。输入文档由 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 Extension 中要配置的内容示例。

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 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 上部署扩展程序后,其界面应如下所示:

Geohash 转换器扩展程序(如 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,它涵盖了 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。

扩展程序安装过程的屏幕截图,显示在安装此扩展程序时,系统会使用本地文件存储密钥

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

Firebase 模拟器中显示的对话框,用于启动收集,收集 ID 包含字词

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

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

清理相关资源以避免冲突

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

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

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 以扩展程序生产者可见的方式为参数命名。稍后在指定参数值时使用此值。
  • label 是供开发者了解参数用途的人类可读标识符。
  • description 用于详细说明该值。由于此功能支持 Markdown,因此可以链接到其他文档,也可以突出显示对开发者可能很重要的字词。
  • type 用于定义用户设置参数值的输入机制。存在许多类型,包括 stringselectmultiSelectselectResourcesecret。如需详细了解每种选项,请参阅文档
  • validationRegex 会将开发者条目限制为特定正则表达式值(在此示例中,该值基于此处提供的简单字段名称准则);如果验证失败...
  • validationErrorMessage 会向开发者发出失败值提醒。
  • default 是指如果开发者未输入任何文本,则该值的默认值。
  • required 表示开发者无需输入任何文本。
  • 不可变:开发者可以更新此扩展程序并更改此值。在这种情况下,开发者应能够根据其需求更改字段名称。
  • 示例可让您大致了解有效输入可能是什么样的。

我了解了!

  1. 在添加特殊参数之前,您还需要向 extension.yaml 文件添加 3 个参数。
  - 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 文件的目录的绝对路径。这会启动扩展程序的安装流程,并在将配置推送到 Firebase 或模拟器之前创建包含配置的 .env 文件。
firebase ext:install /path/to/extension
  • 由于您是在本地部署项目,因此请指定您想使用本地文件,而不是 Google Cloud Secret Manager。

da928c65ffa8ce15.png

  1. 启动 Local Emulator Suite:
firebase emulators:start

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

您可以在实际 Firebase 项目中安装您的扩展程序。建议您使用测试项目进行测试。如果您希望测试扩展程序的端到端流程,或者 Firebase Emulator Suite 尚不支持您的扩展程序的触发器,请使用此测试工作流(请参阅扩展程序模拟器选项)。模拟器目前支持针对 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 文件的目录的绝对路径。这会启动扩展程序的安装流程,并在将配置推送到 Firebase 或模拟器之前创建包含配置的 .env 文件。
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 扩展程序所需的关键步骤。

后续操作