向扩展程序添加用户钩子
使用集合让一切井井有条
根据您的偏好保存内容并对其进行分类。
您可以允许安装您的扩展程序的用户在扩展程序执行过程中插入自己的自定义逻辑。可以通过两种方法实现此目的:
Eventarc 事件:若要让用户能够异步响应事件,您可以将事件发布到 Eventarc。例如,用户可以部署事件处理脚本函数,在长时间运行的任务完成后发送通知;此外,用户还可以定义自己的后处理函数。
同步钩子:若要让用户能够向您的扩展程序添加屏蔽逻辑,您可以在扩展程序的预定义操作点添加同步钩子。在这些操作点,您需要运行用户提供的函数,并且必须在函数完成运行后才能进行后续操作。预处理任务通常属于此类别。
扩展程序可以使用任一方法,也可以结合使用这两种方法。
Eventarc 事件
如需从扩展程序发布事件,请执行以下操作:
在 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.
用户可以在安装扩展程序时选择要订阅哪些事件。
在扩展程序函数中,从 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,
});
在扩展程序中您希望向用户呈现事件的操作点,将事件发布到渠道。请参阅以下示例:
// 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: {
// ...
}
});
在 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.firebasestorage.app")
.file(event.subject)
.delete();
});
如需了解详情,请参阅自定义事件触发器。
示例
官方的 Resize Images 扩展程序会在调整图片大小之后将事件发布到 Eventarc,提供了一个异步钩子。
同步钩子
如果您希望扩展程序函数在钩子成功完成之后运行,可以使用同步钩子。
同步钩子会调用用户定义的 HTTPS Callable Cloud Functions 函数,并等待其完成(可能会有返回值),之后才会进行后续操作。如果用户提供的函数中有错误,也会导致扩展程序函数出错。
如需公开同步钩子,请执行以下操作:
向您的扩展程序添加一个参数,让用户可以使用其自定义 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
在扩展程序中要公开钩子的位置,使用函数的网址调用函数。请参阅以下示例:
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.
// ...
});
在 PREINSTALL 或 POSTINSTALL 文件中记录您提供的所有钩子。
对于每个钩子,记录以下内容:
- 钩子的预期用途
- 在扩展程序逻辑中,要运行钩子的操作点
- 钩子的预期输入和输出
- 钩子的执行条件(或选项)
此外,提醒用户不要在钩子函数中执行任何可能会触发同一扩展程序的操作,因为这可能会导致无限循环。
示例
Algolia Search 扩展程序提供了一个同步钩子,在对 Algolia 执行写入操作之前调用一个用户提供的转换函数。
如未另行说明,那么本页面中的内容已根据知识共享署名 4.0 许可获得了许可,并且代码示例已根据 Apache 2.0 许可获得了许可。有关详情,请参阅 Google 开发者网站政策。Java 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2025-08-12。
[null,null,["最后更新时间 (UTC):2025-08-12。"],[],[],null,["\u003cbr /\u003e\n\nYou can provide users who install your extension the ability to insert their own\ncustom logic into the execution of your extension. There are two ways to\naccomplish this:\n\n- **Eventarc events**: to give users a way to asynchronously react to\n events, you can publish to Eventarc. Users can deploy event handler\n functions that, for example, send notifications after long-running\n tasks complete, or they can define their own post-processing functions.\n\n- **Synchronous hooks**: to give users a way to add blocking logic to your\n extension, you can add synchronous hooks at predefined points in the\n extension's operation. At these points, you run a user-provider function\n and proceed only after it completes. Pre-processing tasks often fall under\n this category.\n\nAn extension can use either or both methods.\n\nEventarc events\n\nTo publish events from an extension:\n\n1. Declare the event types you will publish in the `extension.yaml` file:\n\n events:\n - type: publisher-id.extension-name.version.event-name\n description: event-description\n - type: publisher-id.extension-name.version.another-event-name\n description: another-event-description\n\n The `type` identifier is made of several dot-delimited fields. The\n [publisher ID](/docs/extensions/publishers/register), extension name, and event name fields are\n required. The version field is recommended. Choose a unique and descriptive\n event name for each event type you publish.\n\n For example, the [`storage-resize-images` extension](https://github.com/firebase/extensions/blob/next/storage-resize-images/extension.yaml)\n declares a single event type: \n\n events:\n - type: firebase.extensions.storage-resize-images.v1.complete\n description: |\n Occurs when image resizing completes. The event will contain further\n details about specific formats and sizes.\n\n Users will be able to choose which events to subscribe to when they\n install the extension.\n2. In your extension functions, import the Eventarc API from the Admin SDK\n and initialize an event channel using the user's installation settings.\n These settings are exposed using the following environment variables:\n\n - `EVENTARC_CHANNEL`: the fully-qualified name of the Eventarc channel to which the user chose to publish events.\n - `EXT_SELECTED_EVENTS`: a comma-separated list of event types the user chose to publish. When you initialize a channel with this value, the Admin SDK automatically filters out events user did not select.\n - `EVENTARC_CLOUD_EVENT_SOURCE`: the Cloud Event source identifier. The Admin SDK automatically passes this value in the `source` field of published events. You typically don't need to explicitly use this variable.\n\n If events weren't enabled at installation, these variables will be\n undefined. You can use this fact to initialize an event channel only when\n events are enabled: \n\n import * as admin from \"firebase-admin\";\n import {getEventarc} from 'firebase-admin/eventarc';\n\n admin.initializeApp();\n\n // Set eventChannel to a newly-initialized channel, or `undefined` if events\n // aren't enabled.\n const eventChannel =\n process.env.EVENTARC_CHANNEL &&\n getEventarc().channel(process.env.EVENTARC_CHANNEL, {\n allowedEventTypes: process.env.EXT_SELECTED_EVENTS,\n });\n\n3. Publish events to the channel at the points in your extension you want to\n expose to users. For example:\n\n // If events are enabled, publish a `complete` event to the configured\n // channel.\n eventChannel && eventChannel.publish({\n type: 'firebase.extensions.storage-resize-images.v1.complete',\n subject: filename, // the name of the original file\n data: {\n // ...\n }\n });\n\n4. Document the events you publish, in either the PREINSTALL or POSTINSTALL\n file.\n\n For each event, document the following:\n - Its intended purpose\n - The point in your extension's logic it runs\n - The output data it includes\n - The conditions for its execution\n\n Additionally, warn users not to perform any actions in their event\n handlers that might trigger the same extension, resulting in an infinite\n loop.\n\nWhen you publish events from an extension, users can deploy event handlers\nto respond with custom logic.\n\nFor example, the following example deletes the original image after it has been\nresized. Note that this example handler makes use of the `subject` property of\nthe event, which in this case is the image's original filename. \n\n exports.onimageresized = onCustomEventPublished(\n \"firebase.extensions.storage-resize-images.v1.complete\",\n (event) =\u003e {\n logger.info(\"Received image resize completed event\", event);\n // For example, delete the original.\n return admin.storage()\n .bucket(\"my-project.firebasestorage.app\")\n .file(event.subject)\n .delete();\n });\n\nSee [Custom event triggers](/docs/functions/custom-events#handle-events) for more\ninformation.\n\nExample\n\nThe official [Resize Images extension](https://github.com/firebase/extensions/tree/next/storage-resize-images)\nprovides an asynchronous hook by [publishing to Eventarc](https://github.com/firebase/extensions/blob/c29781c7e67c004e2491e4ce3c43b25b05bd3de6/storage-resize-images/functions/src/index.ts#L109-L117)\nafter resizing an image.\n\nSynchronous hooks\n\nWhen you want to provide users with a hook that must complete successfully\nfor one of your extension functions to operate, use *synchronous hooks*.\n\nA synchronous hook calls a user-defined [HTTPS callable Cloud\nFunction](/docs/functions/http-events) and awaits completion (possibly with a\nreturned value) before continuing. An error in the user-provided function\nresults in an error in the extension function.\n\nTo expose a synchronous hook:\n\n1. Add a parameter to your extension that allows users to configure the\n extension with the URL to their custom Cloud Function. For example:\n\n - param: PREPROCESSING_FUNCTION\n label: Pre-processing function URL\n description: \u003e\n An HTTPS callable function that will be called to transform the input data\n before it is processed by this function.\n type: string\n example: https://us-west1-my-project-id.cloudfunctions.net/preprocessData\n required: false\n\n2. At the point in your extension where you want to expose the hook, call the\n function using its URL. For example:\n\n const functions = require('firebase-functions/v1');\n const fetch = require('node-fetch');\n\n const preprocessFunctionURL = process.env.PREPROCESSING_FUNCTION;\n\n exports.yourFunctionName = functions.firestore.document(\"collection/{doc_id}\")\n .onWrite((change, context) =\u003e {\n // PREPROCESSING_FUNCTION hook begins here.\n // If a preprocessing function is defined, call it before continuing.\n if (preprocessFunctionURL) {\n try {\n await fetch(preprocessFunctionURL); // Could also be a POST request if you want to send data.\n } catch (e) {\n // Preprocessing failure causes the function to fail.\n functions.logger.error(\"Preprocessor error:\", e);\n return;\n }\n }\n // End of PREPROCESSING_FUNCTION hook.\n\n // Main function logic follows.\n // ...\n });\n\n3. Document any hooks you make available in either the PREINSTALL or\n POSTINSTALL file.\n\n For each hook, document the following:\n - Its intended purpose\n - The point in your extension's logic it runs\n - Its expected inputs and outputs\n - The conditions (or options) for its execution\n\n Additionally, warn users not to perform any actions in the hook\n function that might trigger the same extension, resulting in an infinite\n loop.\n\nExample\n\nThe [Algolia Search extension](https://github.com/algolia/firestore-algolia-search/)\nprovides a synchronous hook to [call a user-supplied transform function](https://github.com/algolia/firestore-algolia-search/blob/34592d513eac22691d76917874a6466032976f67/functions/src/transform.ts)\nprior to writing to Algolia."]]