cal.diy 集成 Tandem 虚拟办公室:视频会议应用接入指南与源码解析
2026/9/11 7:43:28 网站建设 项目流程

cal.diy 集成 Tandem 虚拟办公室:视频会议应用接入指南与源码解析

【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy

本指南以开源调度平台 cal.diy 中 Tandem 应用集成模块(packages/app-store/tandemvideo)为核心,系统讲解如何为会议类型配置 Tandem 视频会议地点、完成 OAuth 授权与 Credential 存储,并深入剖析其视频适配器与 Tandem REST API 的底层调用细节。读完本文,你将掌握从应用密钥配置到会议创建/更新/删除的完整接入链路,以及该模块在 cal.diy 应用商店体系中的注册方式。

Tandem 集成是什么:虚拟办公室中的即时协作

Tandem 是一个全新的虚拟办公空间,它让团队即使在线也能像身处实体办公室一样轻松连接。通过共办公房间(co-working rooms)、可用状态(available statuses)、实时视频通话(live real-time video call)与聊天(chat)等能力,你可以一眼看到谁在办公室,一键发起沟通与协作。它支持跨平台使用,同时提供桌面端与移动端版本(该描述同时被 DESCRIPTION.md 与应用元数据 _metadata.ts 引用)。

在 cal.diy 中,Tandem 被封装为conferencing(视频会议)类别的应用集成,其应用元数据声明如下(packages/app-store/tandemvideo/_metadata.ts):

  • type/appIdtandem_video,用于在 Credential 表中区分该集成;
  • slugtandem,用于应用商店路由与应用密钥读取;
  • variant/category/categoriesconferencing,表明它属于视频会议类应用;
  • isOAuth: true,走标准 OAuth 授权流程;
  • appData.location:注册integrations:tandem动态链接类型,供会议类型作为"地点"选项使用。

该模块在应用商店中的注册入口为 index.ts,同时它会被应用商店的代码生成产物自动汇总(见 apps.metadata.generated.ts、apps.server.generated.ts 与 video.adapters.generated.ts),无需手写任何注册代码。

前置条件:配置应用密钥(App Keys)

Tandem 集成运行前必须完成三项应用密钥配置,其校验逻辑在 lib/VideoApiAdapter.ts 与 api/add.ts 中均有体现:

配置键含义缺失时的表现
client_idTandem OAuth 客户端 ID返回 400:Tandem client_id missing.
client_secretTandem OAuth 客户端密钥返回 400:Tandem client_secret missing.
base_urlTandem API 基础地址返回 400:Tandem base_url missing.

配置键的 Schema 约束定义在 zod.ts:三个键均为非空字符串(z.string().min(1)),而appDataSchema为空对象,即该应用本身无需额外数据字段。密钥通过getAppKeysFromSlug("tandem")从应用商店统一机制加载,并以typeof appKeys.xxx === "string"做类型守卫后使用。

OAuth 授权流程:从发起安装到保存凭据

Tandem 集成包含两个 API 端点,均挂在api/index.ts导出下,路由前缀为/api/integrations/tandemvideo/

add:构造授权跳转地址

api/add.ts 处理 GET 请求,流程如下:

  1. 通过prisma.user.findFirstOrThrow校验当前会话用户存在;
  2. 读取client_idbase_url应用密钥,缺失则返回 400;
  3. WEBAPP_URL + /api/integrations/tandemvideo/callback作为redirect_uri并做 URL 编码;
  4. 使用node:querystringstringify构造client_idredirect_uri参数;
  5. 返回{ url: ${baseUrl}/oauth/approval?${query} },引导用户跳转到 Tandem 授权页。

callback:换取令牌并落库

api/callback.ts 处理授权回调:

  1. 校验code参数,缺失时返回 401;并用decodeOAuthState(req, "tandem")解析 OAuth state(Tandem 位于免 nonce 校验的应用白名单中,见 _utils/oauth/decodeOAuthState.ts);
  2. 携带codeclient_idclient_secret${baseUrl}/api/v1/oauth/v2/token发起 POST,换取访问令牌;
  3. 将响应中的expires_in转换为绝对过期时间戳expiry_date后删除原字段;
  4. 先清理该用户已有的tandem_video类型凭据(prisma.credential.deleteMany),再调用createOAuthAppCredential写入新凭据;
  5. 重定向回 state 中的returnTo地址,或应用商店的已安装 Conferencing 应用页面(getInstalledAppPath({ variant: "conferencing", slug: "tandem" }))。

视频适配器:Tandem REST API 的完整封装

核心实现位于 lib/VideoApiAdapter.ts,它实现 cal.diy 的VideoApiAdapter接口(packages/types/VideoApiAdapter.d.ts),并通过 video.adapters.generated.ts 被视频会议系统按 slug 动态加载。

令牌管理与自动刷新

tandemAuth封装令牌生命周期(lib/VideoApiAdapter.ts):

  • 有效性判定:isTokenValid检查access_token存在且expiry_date < Date.now()——注意这里的命名语义是"已过期";
  • 过期时携带refresh_token(即 credential 中存储的 code)向${baseUrl}/api/v1/oauth/v2/token重新换发;
  • 新令牌按Date.now() + expires_in * 1000计算expiry_date,并立即通过prisma.credential.update持久化,避免重启后令牌失效;
  • getToken()在令牌未过期时直接返回现有 token,否则先刷新再返回。

事件翻译与会议操作

_translateEvent将 cal.diy 的CalendarEvent翻译为 Tandem 接口所需的 JSON 载荷(lib/VideoApiAdapter.ts):

{ "meeting": { "title": "事件标题", "starts_at": "开始时间(Unix 秒)", "ends_at": "结束时间(Unix 秒)", "description": "事件描述", "conference_solution": "tandem", "type": 3 } }

其中时间通过Date.parse(date) / 1000转为 Unix 秒级时间戳。适配器暴露三类核心操作:

  • createMeeting:POST${baseUrl}/api/v1/meetings,以 Bearer Token 认证,返回{ id, event_link }并翻译为{ type: "tandem_video", id, url: event_link, password: "" }供会议参与者使用(lib/VideoApiAdapter.ts);
  • updateMeeting:PUT${baseUrl}/api/v1/meetings/${bookingRef.meetingId},用updates键携带更新后的载荷,用于改期/编辑场景(lib/VideoApiAdapter.ts);
  • deleteMeeting:DELETE${baseUrl}/api/v1/meetings/${uid},用于取消会议时释放资源(lib/VideoApiAdapter.ts)。

特别地,getAvailability直接返回空数组(lib/VideoApiAdapter.ts),注释明确说明"Tandem 不需要返回忙碌时段",即 Tandem 会议不参与空闲度计算。

在会议类型中使用 Tandem 作为地点

安装并授权 Tandem 后,即可在事件类型(Event Type)中将会议地点设置为Tandem Video。这得益于 _metadata.ts 中注册的动态链接类型:

appData: { location: { linkType: "dynamic", type: "integrations:tandem", label: "Tandem Video", }, },

linkType: "dynamic"表示会议链接由系统在预约创建时动态生成(而非用户手动粘贴固定链接)。一旦会议地点选择了该类型,Booking 创建时会经由视频适配器的createMeeting创建 Tandem 会议并回填event_link;会议改期/取消时则对应触发updateMeeting/deleteMeeting,从而保证预约与虚拟办公室会议的全生命周期同步。

依赖与模块边界

Tandem 模块保持极简依赖(package.json):

  • 运行时依赖仅@calcom/lib@calcom/prisma(workspace 内部包);
  • 开发依赖@calcom/types,用于引用VideoApiAdapterCredentialPayloadCalendarEvent等类型;
  • 静态资源方面,static/目录包含图标icon.svg与 6 张产品示意图(tandem1–tandem6.jpg,由 DESCRIPTION.md 的 frontmatter 引用)。

该模块不依赖任何第三方运行时库,网络请求全部基于 Node 原生fetch,令牌刷新、会议 CRUD 与配置校验均复用@calcom/libhandleErrorsJsonhandleErrorsRawHttpErrorgetAppKeysFromSlug等通用工具,保持了与其他视频会议应用一致的结构化开发方式。如需为 cal.diy 接入其他虚拟办公室/视频会议服务,可直接以本模块为模板,替换元数据、OAuth 端点与VideoApiAdapter中的 API 调用即可。

【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询