- 后端
- 前端
- 企业应用
【免费下载链接】cal.diy
Scheduling infrastructure for absolutely everyone.
Signal 集成是 Cal.com(本仓库 cal.diy)应用商店中的一个轻量级 messaging / conferencing 应用,其唯一能力由一行描述概括:Schedule a chat with your guests or have a Signal Video call.(与受邀者安排聊天,或发起 Signal 视频通话)。本文以该集成应用的描述文档为骨架,深入仓库源码,完整讲解其配置参数、安装流程、静态链接地点的运行时校验机制,以及在事件类型(Event Type)与 Platform API v2 中的使用方式。读完本文,你将掌握 Cal.com 应用商店中"静态链接型会议地点"这一类应用的完整工作原理,并具备自行扩展同类集成的能力。
Signal 集成:一句话描述背后的完整功能
DESCRIPTION.md 全篇只有一段描述文字,但它同时以 YAML frontmatter 的形式承载了应用商店的元信息:
--- description: Schedule a chat with your guests or have a Signal Video call. items: - 1.jpg - 2.jpg --- Schedule a chat with your guests or have a Signal Video call.description:应用商店列表页中展示的一句话简介,会被 Cal.com 的应用市场直接使用;items:应用详情页中的展示截图列表,对应 static/1.jpg 与 static/2.jpg 两张随包发布的截图;- 正文部分与 frontmatter 的
description相同,用于渲染应用详情页的简介段落。
从功能实现看,Signal 集成本质上是把「一个可预约的 Signal 链接」作为事件类型(Event Type)的会议地点。它不需要 OAuth 授权、不需要同步日历,安装后组织者在创建事件类型时填写自己的signal.me个人链接,受邀者即可看到该链接并据此发起 Signal 通话。
应用目录结构:一个最小化 App 的完整骨架
Signal 集成位于 packages/app-store/signal/,目录结构非常精简,是整个应用商店中"最小可运行 App"的典型代表:
signal/ ├── api/ │ ├── add.ts # 安装入口(声明式 handler) │ └── index.ts # 导出 add handler ├── static/ │ ├── 1.jpg # 应用商店展示截图(DESCRIPTION.md 中声明) │ ├── 2.jpg │ └── icon.svg # 应用图标(蓝色圆角方形 + 白色 Signal 标志) ├── DESCRIPTION.md # 应用描述与展示图声明 ├── config.json # 应用核心配置(名称、类型、地点元信息) ├── index.ts # 包入口,重新导出 api └── package.json # 包名为 @calcom/signal- index.ts 仅一行
export * as api from "./api",将安装 API 暴露给应用注册表; - api/index.ts 将
addhandler 作为默认导出转发; - package.json 声明包名为
@calcom/signal,主入口为./index.ts,依赖@calcom/lib工作区包; - static/icon.svg 是官方风格图标:以
#3A76F0蓝色圆角方形为底,中央为白色 Signal 标志图形。
config.json:Signal 如何定义"会议地点"
config.json 是整个集成的配置中枢,所有应用商店展示信息与地点行为都由它驱动:
{ "name": "Signal", "slug": "signal", "type": "signal_video", "logo": "icon.svg", "url": "https://cal.com/", "variant": "messaging", "categories": ["messaging", "conferencing"], "publisher": "Cal.com, Inc.", "email": "support@cal.com", "description": "Schedule a chat with your guests or have a Signal Video call.", "appData": { "location": { "type": "integrations:signal_video", "label": "Signal", "linkType": "static", "organizerInputPlaceholder": "https://signal.me/#p/+11234567890", "urlRegExp": "^http(s)?:\\/\\/(www\\.)?signal.me\\/[a-zA-Z0-9]*" } }, "isOAuth": false }核心字段解析:
| 字段 | 值 | 含义 |
|---|---|---|
name/slug | Signal/signal | 应用名称与唯一标识(slug 禁止随意修改) |
type | signal_video | 应用类型,同时作为 Credential 记录的type字段值 |
variant | messaging | 应用形态分类,影响应用商店的展示逻辑 |
categories | ["messaging", "conferencing"] | 同时归属消息与会议两个分类 |
isOAuth | false | 非 OAuth 应用,安装无需外部授权跳转 |
appData.location.type | integrations:signal_video | 事件类型中实际存储的"地点类型"标识,integrations:前缀表示来自应用商店 |
appData.location.linkType | static | 静态链接:地点是一个由组织者预先填写的固定 URL |
appData.location.organizerInputPlaceholder | https://signal.me/#p/+11234567890 | 组织者填写链接时的输入框占位示例 |
appData.location.urlRegExp | ^http(s)?:\/\/(www\.)?signal.me\/[a-zA-Z0-9]* | 组织者输入的链接必须匹配的正则校验规则 |
其中urlRegExp限定了合法链接必须位于signal.me域名下(可带http/https与www.前缀),从源头保证事件类型中存储的确实是有效的 Signal 链接。
安装流程:声明式 Handler 与默认安装
Signal 的安装逻辑位于 api/add.ts,它采用 Cal.com 的声明式 App Handler 模式:
import type { AppDeclarativeHandler } from "@calcom/types/AppHandler"; import { createDefaultInstallation } from "../../_utils/installation"; import appConfig from "../config.json"; const handler: AppDeclarativeHandler = { appType: appConfig.type, variant: appConfig.variant, slug: appConfig.slug, supportsMultipleInstalls: false, handlerType: "add", createCredential: ({ appType, user, slug, teamId }) => createDefaultInstallation({ appType, user: user, slug, key: {}, teamId }), }; export default handler;要点:
AppDeclarativeHandler的类型定义见 packages/types/AppHandler.d.ts,要求提供appType、slug、variant、supportsMultipleInstalls、handlerType: "add"与createCredential回调;supportsMultipleInstalls: false表示每个用户(或团队)只能安装一次;createCredential直接调用 _utils/installation.ts 中的createDefaultInstallation,传入空key: {}——因为 Signal 不需要保存任何第三方凭据。
createDefaultInstallation的实际行为是在数据库中创建一条Credential记录:
const installation = await prisma.credential.create({ data: { type: appType, key, ...(teamId ? { teamId } : { userId: user.id }), appId: slug, subscriptionId, paymentStatus, billingCycleStart, }, });可以看到,安装的本质只是记录「该用户/团队已启用 Signal 应用」,不涉及任何外部 API 交互。这也解释了isOAuth: false的含义:从点击安装到安装完成是瞬时且无跳转的。
注册表:生成的集成元数据
安装入口与元数据会被构建期脚本汇总进生成文件,Signal 在其中均有登记:
- apps.server.generated.ts 第 65 行:
signal: import("./signal/api"),使服务器端能够调用其安装 handler; - apps.metadata.generated.ts 第 200 行:
signal: signal_config_json,将config.json注册为应用元数据; - bookerApps.metadata.generated.ts 第 82 行:同样注册 Signal 元数据,供预订(booking)页面在运行时读取其地点信息。
静态链接地点的运行时行为
Cal.com 将所有应用商店提供的地点统一汇总于 packages/app-store/locations.ts。该文件遍历appStoreMetadata(由各应用config.json生成),把appData.location转换为标准的地点类型对象,Signal 正是其中之一。
模板变量替换与字段补全
for (const [key, value] of Object.entries(location)) { if (typeof value === "string") { location[key] = value.replace(/{SLUG}/g, meta.slug).replace(/{TITLE}/g, meta.name); } } const newLocation = { ...location, messageForOrganizer: location.messageForOrganizer || `Set ${location.label} link`, iconUrl: meta.logo, variable: location.variable || "locationLink", defaultValueVariable: location.defaultValueVariable || "link", };- 地点字符串中的
{SLUG}、{TITLE}占位符会被替换为实际的应用 slug 与名称(详见模板章节); - 若未显式提供
messageForOrganizer,默认文案为Set Signal link; - 默认使用
locationLink作为组织者输入字段、link作为存储变量。
静态链接强制要求组织者输入
if (newLocation.linkType === "static") { newLocation.organizerInputType = location.organizerInputType || "text"; if (newLocation.organizerInputPlaceholder?.match(/https?:\/\//)) { // HACK: Translation ends up removing https? if it's in the beginning :( newLocation.organizerInputPlaceholder = ` ${newLocation.organizerInputPlaceholder}`; } }linkType === "static"的地点(Signal 属于此类)会在事件类型配置界面强制显示组织者输入框,并将以https://开头的占位符前加一个前导空格,避免 i18n 翻译处理误删协议头。
链接合法性校验
组织者填写的链接会在保存事件类型时通过 locations.ts 中的locationsResolver(zod schema)校验:
if ( eventLocationType && !eventLocationType.default && eventLocationType.linkType === "static" && eventLocationType.urlRegExp ) { const valid = z.string().regex(new RegExp(eventLocationType.urlRegExp)).safeParse(link).success; if (!valid) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: [eventLocationType?.defaultValueVariable ?? "link"], message: t("invalid_url_error_message", { label: eventLocationType.label, sampleUrl: sampleUrl ?? "https://cal.com", }), }); } }即:静态链接类型的地点,其链接必须以urlRegExp正则(Signal 为signal.me域名规则)做匹配校验,不合法则给出带示例 URL 的报错信息。
地点类型与存储
AppStoreLocationType[appName] = newLocation.type将应用名映射到地点类型,随后LocationType枚举会包含integrations:signal_video;getAppSlugFromLocationType支持从地点类型反查应用 slug;- 预订时
getLocationValueForDB会把事件类型中的 Signal 链接值写入预订记录,作为本次预约的会议地点; getHumanReadableLocationValue会在成功页/邮件中把integrations:signal_video渲染为可读的Signal标签或直接展示链接。
在事件类型与 Platform API v2 中使用 Signal
事件类型配置
组织者安装 Signal 后,在创建或编辑事件类型时,地点选项列表会动态加入Signal(数据来自bookerApps.metadata.generated.ts中的元数据)。选择后输入自己的signal.me链接(格式如https://signal.me/#p/+11234567890),受邀者在预订页即可看到该链接,确认预订后链接会出现在确认页与通知邮件中。
Platform API v2 的地点映射
如果你使用 Platform API 创建事件类型,Signal 地点对应一个公开的集成标识signal-video。相关转换逻辑位于 apps/api/v2/src/platform/event-types/event-types_2024_06_14/transformers/:
- api-to-internal/locations.ts 第 27 行:
"signal-video": "integrations:signal_video",将 API 入参转换为内部地点类型; - internal-to-api/locations.ts 第 29 行:
"integrations:signal_video": "signal-video",反向转换; - internal/locations.ts 第 21 行:zod 校验中允许
"signal-video"作为合法的集成地点标识。
因此,通过 API 创建带 Signal 地点的事件类型时,请求体中可声明type: "integration", integration: "signal-video"并附带 Signal 链接,服务端会将其规范化为integrations:signal_video存储。
复用模板开发同类集成
Signal 集成并非特例,Cal.com 为其提供了官方模板 packages/app-store/templates/event-type-location-video-static/,模板的 config.json 使用占位符定义同样的地点结构:
"appData": { "location": { "type": "integrations:{SLUG}_video", "label": "{TITLE}", "linkType": "static", "organizerInputPlaceholder": "https://video.app/mylink", "urlRegExp": "^http(s)?:\\/\\/(www\\.)?video.app\\/[a-zA-Z0-9]*" } }模板的 api/add.ts 与 Signal 的 api/add.ts 逐行一致。对照可知,开发一个「静态链接型」集成只需四步:
- 复制模板目录,修改
config.json中的name、slug、type、appData.location(特别是organizerInputPlaceholder与urlRegExp); - 编写
DESCRIPTION.md(描述 +items截图列表); - 替换
static/下的图标与展示截图; - 通过 app-store CLI 生成注册表条目,即可出现在应用商店并作为事件类型地点使用。
小结
Signal 集成是理解 Cal.com 应用商店架构的最佳最小案例:DESCRIPTION.md负责展示信息,config.json定义地点行为(静态链接 + 正则校验),声明式 handler 完成无 OAuth 的即装即用安装,运行时由 locations.ts 统一驱动地点类型解析、校验与存储,并通过 API v2 转换器对 Platform 用户开放。掌握了这条链路,你就掌握了 Cal.com 中所有linkType: "static"类会议地点应用(如 Skype、Telegram、Facetime 等同类集成)的完整工作机制。
- 后端
- 前端
- 企业应用
【免费下载链接】cal.diy
Scheduling infrastructure for absolutely everyone.
相关推荐
Cal.com Telegram 集成解析:从声明式 App 配置到静态链接会议地点的完整实现
Cal.com Telegram 集成解析:从声明式 App 配置到静态链接会议地点的完整实现 Telegram 是 Cal.com 应用商店中一个典型的“消息
后端前端企业应用Cal.diy 怎么配置 Daily.co API Key 启用视频通话
Cal.diy 怎么配置 Daily.co API Key 启用视频通话 如果你的 Cal.diy 自托管实例需要自带的视频通话能力,可以通过配置 Daily.
后端前端企业应用Bats中的实时通信测试:验证聊天与视频通话
Bats中的实时通信测试:验证聊天与视频通话 你是否还在为聊天应用的消息丢失、视频通话的连接中断而烦恼?作为开发者,如何确保用户在使用实时通信功能时获得流畅体验
测试
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考