向扩展程序添加用户钩子

您可以允许安装您的扩展程序的用户在扩展程序执行过程中插入自己的自定义逻辑。可以通过两种方法实现此目的:

  • Eventarc 事件:若要让用户能够异步响应事件,您可以将事件发布到 Eventarc。例如,用户可以部署事件处理脚本函数,在长时间运行的任务完成后发送通知;此外,用户还可以定义自己的后处理函数。

  • 同步钩子:若要让用户能够向您的扩展程序添加屏蔽逻辑,您可以在扩展程序的预定义操作点添加同步钩子。在这些操作点,您需要运行用户提供的函数,并且必须在函数完成运行后才能进行后续操作。预处理任务通常属于此类别。

扩展程序可以使用任一方法,也可以结合使用这两种方法。

Eventarc 事件

如需从扩展程序发布事件,请执行以下操作:

  1. extension.yaml 文件中声明您要发布的事件类型:

    events:
      - type: publisher-id.extension-name.version.event-name
        description: event-description
      - type: publisher-id.extension-name.version.another-event-name
        description: another-event-description
    

    type 标识符由数个以英文句点分隔的字段组成。发布商 ID、扩展程序名称和事件名称字段是必填项;另外建议填写版本字段。为您发布的每种事件类型选择一个唯一的描述性事件名称。

    例如,storage-resize-images 扩展程序声明了单一事件类型:

    events:
      - type: firebase.extensions.storage-resize-images.v1.complete
        description: |
          Occurs when image resizing completes. The event will contain further
          details about specific formats and sizes.
    

    用户可以在安装扩展程序时选择要订阅哪些事件。

  2. 在扩展程序函数中,从 Admin SDK 导入 Eventarc API,并使用用户的安装设置初始化事件渠道。这些设置使用以下环境变量公开:

    • EVENTARC_CHANNEL:用户选择要将事件发布到的 Eventarc 渠道的完全限定名称。
    • EXT_SELECTED_EVENTS:用户选择发布的事件类型的逗号分隔列表。使用此值初始化渠道时,Admin SDK 会自动滤除用户未选择的事件。
    • EVENTARC_CLOUD_EVENT_SOURCE:Cloud 事件来源标识符。Admin SDK 会自动在已发布事件的 source 字段中传递此值。您通常不需要明确使用此变量。

    如果事件在安装时未启用,这些变量将处于未定义状态。只有在启用了事件的情况下,才能使用这些信息初始化事件渠道:

    import * as admin from "firebase-admin";
    import {getEventarc} from 'firebase-admin/eventarc';
    
    admin.initializeApp();
    
    // Set eventChannel to a newly-initialized channel, or `undefined` if events
    // aren't enabled.
    const eventChannel =
      process.env.EVENTARC_CHANNEL &&
      getEventarc().channel(process.env.EVENTARC_CHANNEL, {
        allowedEventTypes: process.env.EXT_SELECTED_EVENTS,
      });
    
  3. 在扩展程序中您希望向用户呈现事件的操作点,将事件发布到渠道。例如:

    // If events are enabled, publish a `complete` event to the configured
    // channel.
    eventChannel && eventChannel.publish({
        type: 'firebase.extensions.storage-resize-images.v1.complete',
        subject: filename,  // the name of the original file
        data: {
          // ...
        }
    });
    
  4. 在 PREINSTALL 或 POSTINSTALL 文件中,记录您发布的事件。

    对于每种事件,记录以下内容:

    • 事件的预期用途
    • 在扩展程序逻辑中,要运行事件的操作点
    • 事件包含的输出数据
    • 事件的执行条件

    此外,提醒用户不要在事件处理脚本中执行任何可能会触发同一扩展程序的操作,因为这可能会导致无限循环。

如果您通过扩展程序发布事件,用户可以部署事件处理脚本来使用自定义逻辑进行响应。

例如,以下示例会在图片的大小调整完毕后删除原始图片。请注意,该处理脚本示例使用了事件的 subject 属性,在这里即图片的原始文件名。

exports.onimageresized = onCustomEventPublished(
    "firebase.extensions.storage-resize-images.v1.complete",
    (event) => {
      logger.info("Received image resize completed event", event);
      // For example, delete the original.
      return admin.storage()
          .bucket("my-project.appspot.com")
          .file(event.subject)
          .delete();
    });

如需了解详情,请参阅自定义事件触发器

示例

官方的 Resize Images 扩展程序会在调整图片大小之后将事件发布到 Eventarc,提供了一个异步钩子。

同步钩子

如果您希望扩展程序函数在钩子成功完成之后运行,可以使用同步钩子。

同步钩子会调用用户定义的 HTTPS Callable Cloud Functions 函数,并等待其完成(可能会有返回值),之后才会进行后续操作。如果用户提供的函数中有错误,也会导致扩展程序函数出错。

如需公开同步钩子,请执行以下操作:

  1. 向您的扩展程序添加一个参数,让用户可以使用其自定义 Cloud Functions 函数的网址配置扩展程序。例如:

    - param: PREPROCESSING_FUNCTION
      label: Pre-processing function URL
      description: >
        An HTTPS callable function that will be called to transform the input data
        before it is processed by this function.
      type: string
      example: https://us-west1-my-project-id.cloudfunctions.net/preprocessData
      required: false
    
  2. 在扩展程序中要公开钩子的位置,使用函数的网址调用函数。例如:

    const functions = require('firebase-functions/v1');
    const fetch = require('node-fetch');
    
    const preprocessFunctionURL = process.env.PREPROCESSING_FUNCTION;
    
    exports.yourFunctionName = functions.firestore.document("collection/{doc_id}")
        .onWrite((change, context) => {
          // PREPROCESSING_FUNCTION hook begins here.
          // If a preprocessing function is defined, call it before continuing.
          if (preprocessFunctionURL) {
            try {
              await fetch(preprocessFunctionURL); // Could also be a POST request if you want to send data.
            } catch (e) {
              // Preprocessing failure causes the function to fail.
              functions.logger.error("Preprocessor error:", e);
              return;
            }
          }
          // End of PREPROCESSING_FUNCTION hook.
    
          // Main function logic follows.
          // ...
        });
    
  3. 在 PREINSTALL 或 POSTINSTALL 文件中记录您提供的所有钩子。

    对于每个钩子,记录以下内容:

    • 钩子的预期用途
    • 在扩展程序逻辑中,要运行钩子的操作点
    • 钩子的预期输入和输出
    • 钩子的执行条件(或选项)

    此外,提醒用户不要在钩子函数中执行任何可能会触发同一扩展程序的操作,因为这可能会导致无限循环。

示例

Algolia Search 扩展程序提供了一个同步钩子,在对 Algolia 执行写入操作之前调用一个用户提供的转换函数