资讯动态

在 Zoom 客户端内构建 In-Meeting App:基于 @zoom/appssdk 的完整开发实战指南

发布时间:2026/9/13 6:53:10 来源:尧图企业网站定制
在 Zoom 客户端内构建 In-Meeting App基于 zoom/appssdk 的完整开发实战指南【免费下载链接】knowledge-work-pluginsOpen source repository of plugins primarily intended for knowledge workers to use in Claude Cowork项目地址: https://gitcode.com/GitHub_Trending/kn/knowledge-work-pluginsIn-Meeting App 是运行在 Zoom 客户端嵌入式浏览器中的 Web 应用可在会议进行中为与会者提供投票、游戏、协作工具等交互能力。本文以 in-meeting-apps.md 为核心骨架结合本仓库 zoom-apps-sdk 技能包中的架构、配置与源码级实现细节系统讲解从 App 类型选型、前后端架构、SDK 初始化到投票、白板、计时器、Layers API 沉浸式视觉等常见用例的完整开发路径。读完本文你将掌握一套可直接落地复用的 In-Meeting App 开发方案并理解其背后 SDK 的配置规则、授权流程与发布约束。一、什么是 In-Meeting AppIn-Meeting App 是运行在 Zoom 会议客户端界面内部的 Zoom App与会者在会议过程中可以直接与之交互。典型形态包括投票、游戏、协作工具、议程管理等。这类应用与普通 Web 应用的区别在于它运行在 Zoom 客户端的嵌入式浏览器中通过zoom/appssdk提供的 JavaScript API 访问会议上下文、参与者信息与渲染能力。构建 In-Meeting App 需要组合以下技能zoom-apps-sdk主要提供运行在会议内所需的全部 SDK 能力oauth认证处理应用授权与令牌生命周期zoom-rest-api可选用于服务端侧的业务 API 调用。本仓库中对应的技能入口分别是 zoom-apps-sdk/SKILL.md、oauth/SKILL.md后者通过setup-zoom-oauth之后作为授权细节的参考。二、App 类型选型五种运行形态In-Meeting App 并不是单一形态根据交互方式可以划分为五种类型每种类型对应不同的核心 API类型描述关键 APISidebar app会议侧边栏面板getMeetingContext,shareAppImmersive app全屏 Layers API 渲染runRenderingContext,drawParticipantCamera mode虚拟摄像头叠加层runRenderingContext({ view: camera })Collaborate共享状态应用startCollaborate,connect,postMessageBackground app无可见 UI 运行Events、REST API 调用其中 Sidebar app 是最常见的形态inMeeting上下文Immersive 与 Camera 属于 Layers API 的高级渲染形态Collaborate 依赖实时共享状态Background app 则完全在后台运行仅依赖事件与 REST API。更完整的运行上下文对照可参考 concepts/running-contexts.md。三、总体架构嵌入式浏览器 后端服务In-Meeting App 的架构可以概括为一个“前端在 Zoom 内、后端在云上”的模式Frontend (Zoom embedded browser) Backend (Express/Node.js) ───────────────────────────────── ──────────────────────── zoom/appssdk OAuth token exchange zoomSdk.config() REST API calls zoomSdk.getMeetingContext() Token storage (Redis) fetch(/api/data) ───────────── Business logic前端是加载在 Zoom 嵌入式浏览器WebView中的 HTML/CSS/JS 应用zoom/appssdk是连接前端与 Zoom 客户端的桥梁后端推荐 Node.js Express负责 OAuth 令牌交换、REST API 调用与业务逻辑同时承担令牌存储如 Redis。前端通过fetch(/api/data)等常规 HTTP 请求与后端通信。从 concepts/architecture.md 的架构细节可以看到Zoom 在不同平台使用不同的浏览器引擎Windows 为 WebView2Chromium、macOS/iOS 为 WKWebViewWebKit、Android 为 WebView而 Camera Mode 等部分场景使用 CEFChromium Embedded Framework。这带来几个重要限制不支持浏览器扩展window.open支持有限应改用zoomSdk.openUrl()不同应用之间无法共享浏览器级存储CSP 必须允许frame-ancestors zoom.us *.zoom.usCookie 需要SameSiteNone; Secure。在数据访问层面In-Meeting App 拥有三条数据通路ContextualSDK API仅需config()即可获取会议/用户/参与者信息Server-sideREST API通过后端以 OAuth 令牌调用 Zoom 全量 APIHeaderX-Zoom-App-Context是 Zoom 加载前端时注入的加密请求头后端可用客户端密钥解密获得用户身份与会议上下文uid、mid、aud、iss、ts等字段解密实现可参考 concepts/architecture.md。四、开发前置条件与环境变量4.1 前置条件根据 zoom-apps-sdk/SKILL.md 与 examples/quick-start.md开始开发前需要准备在 Zoom Marketplace 中创建Zoom App类型的应用具备 OAuth 凭据Client ID Secret及 Zoom Apps 相关 scope一个 Web 应用推荐 Node.js 18 与 Express域名已加入 Marketplace 的 Domain Allowlist否则客户端显示空白面板且无任何报错本地开发使用 ngrok 或 HTTPS 隧道在 Marketplace 中为所需能力启用对应的 OAuth scope。4.2 环境变量变量说明补充信息来源ZOOM_APP_CLIENT_IDMarketplace App Credentials位于 Marketplace → 应用 → App CredentialsZOOM_APP_CLIENT_SECRETMarketplace App Credentials仅限服务端使用切勿写入前端或仓库ZOOM_APP_REDIRECT_URI你的服务器 URL /authOAuth 回调地址SESSION_SECRET用于 Cookie 签名的随机字符串应使用密钥管理器生成并管理ZOOM_APP_CLIENT_SECRET必须仅保留在服务端建议开发与生产使用两套独立的应用凭据。OAuth 流程中产生的ZOOM_ACCESS_TOKEN与ZOOM_REFRESH_TOKEN属于运行时值不应硬编码在仓库文件中。五、Quick Start最小可运行的 In-Meeting App5.1 SDK 初始化所有 Zoom App 的第一步都是调用zoomSdk.config()它声明应用将要使用的所有能力capabilities并返回运行上下文import zoomSdk from zoom/appssdk; await zoomSdk.config({ capabilities: [shareApp, getMeetingContext, getUserContext], version: 0.16 }); const context await zoomSdk.getMeetingContext(); console.log(Meeting ID:, context.meetingID); await zoomSdk.shareApp();config()的返回值包含runningContext当前运行上下文、clientVersion与unsupportedApis当前客户端版本不支持的能力列表。核心规则如下config()必须在任何其他 SDK 方法之前调用只有列在capabilities中的能力才可用调用未声明能力会抛错声明的能力必须与 Marketplace 中启用的 OAuth scope 匹配检查unsupportedApis以便优雅降级。5.2 两种 SDK 引入方式与全局变量陷阱方式 ANPM推荐用于框架项目npm install zoom/appssdkimport zoomSdk from zoom/appssdk;方式 BCDN原生 JSscript srchttps://appssdk.zoom.us/sdk.js/script关键陷阱CDN 方式会在全局定义window.zoomSdk。如果你的代码里再写let zoomSdk ...会在 Zoom 嵌入式浏览器中抛出SyntaxError: redeclaration of non-configurable global property。正确做法是换个变量名例如let sdk window.zoomSdk;。NPM 导入是模块作用域变量不存在此冲突。5.3 运行上下文与能力/scope 对应关系configResponse.runningContext决定你的应用当前运行在哪个界面常见取值包括inMeeting会议侧边栏最常用具备完整会议 API、inMainClient主客户端面板无会议上下文 API、inWebinar网络研讨会侧边栏、inImmersiveLayers API 全屏渲染、inCamera虚拟相机叠加、inCollaborate协作共享状态等。能力声明必须与 Marketplace 中启用的 OAuth scope 一一对应常见映射如下能力所需 ScopegetMeetingContextzoomapp:inmeetinggetUserContextzoomapp:inmeetingshareAppzoomapp:inmeetingopenUrlzoomapp:inmeetingsendAppInvitationzoomapp:inmeetingrunRenderingContextzoomapp:inmeetingauthorizezoomapp:inmeetinggetMeetingParticipantszoomapp:inmeeting缺失 scope 会导致能力静默失败或抛错新增 scope 后用户需要重新授权。配置路径Marketplace → 你的应用 →Scopes标签页。5.4 浏览器预览与 Demo 模式SDK 只在 Zoom 客户端内部生效。在普通浏览器中打开时sdk.config()会抛错因此必须实现 try/catch 回退 UI并加上约 3 秒的超时兜底防止 SDK 挂起这与 examples/quick-start.md 中的 Hello World 实现一致。5.5 完整后端骨架参考一个最小可运行的 In-Meeting App 后端Express需要Cookie 会话SameSiteNonesecure: true嵌入式浏览器必需、OWASP 安全响应头Marketplace 审核必需、/install安装入口跳转 Zoom OAuth、/auth回调交换授权码、获取 deeplink 重定向回客户端。完整可复制的代码位于 examples/quick-start.md包括package.json、.env、server.js与public/index.html四份文件。本地运行流程为npm install→ngrok http 3000→ 将 ngrok https 地址填入.env的ZOOM_APP_REDIRECT_URI→npm run dev并在 Marketplace 中配置 Home URL、Redirect URL 与 Domain Allow List。六、常见用例实战6.1 投票应用Poll App投票应用需要声明分享、会议上下文、参与者列表与邀请能力import zoomSdk from zoom/appssdk; // Initialize await zoomSdk.config({ capabilities: [ shareApp, getMeetingContext, getMeetingParticipants, sendAppInvitation ] }); // Poll state let currentPoll { question: , options: [], votes: {} }; // Create poll function createPoll(question, options) { currentPoll { question, options, votes: {} }; broadcastPollState(); } // Submit vote async function submitVote(optionIndex) { const context await zoomSdk.getMeetingContext(); currentPoll.votes[context.participantId] optionIndex; broadcastPollState(); } // Share results function getResults() { const counts currentPoll.options.map((_, i) Object.values(currentPoll.votes).filter(v v i).length ); return currentPoll.options.map((opt, i) ({ option: opt, count: counts[i], percentage: (counts[i] / Object.keys(currentPoll.votes).length * 100).toFixed(1) })); } // Invite others to participate async function inviteParticipants() { await zoomSdk.sendAppInvitation({ action: open, message: Join the poll! }); }投票结果同步到所有与会者属于典型的共享状态问题。本仓库的 examples/collaborate-mode.md 给出了三种状态同步模式服务端中继Socket.io——以getMeetingUUID()的返回值作为房间 ID服务端作为事实来源SDK 消息传递——无需服务端通过connect()postMessage()直接向所有连接实例广播状态CRDTY.js——适合文本/白板等协作编辑场景冲突自动消解可用 meeting UUID 作为文档名。6.2 协作白板Collaborative Whiteboard白板的核心是本地绘制 实时广播// Whiteboard with real-time sync const canvas document.getElementById(whiteboard); const ctx canvas.getContext(2d); // Drawing state let isDrawing false; let lastX 0; let lastY 0; canvas.addEventListener(mousedown, (e) { isDrawing true; [lastX, lastY] [e.offsetX, e.offsetY]; }); canvas.addEventListener(mousemove, (e) { if (!isDrawing) return; const stroke { from: { x: lastX, y: lastY }, to: { x: e.offsetX, y: e.offsetY }, color: currentColor, width: currentWidth }; drawStroke(stroke); broadcastStroke(stroke); // Sync with others [lastX, lastY] [e.offsetX, e.offsetY]; }); function drawStroke(stroke) { ctx.beginPath(); ctx.moveTo(stroke.from.x, stroke.from.y); ctx.lineTo(stroke.to.x, stroke.to.y); ctx.strokeStyle stroke.color; ctx.lineWidth stroke.width; ctx.lineCap round; ctx.stroke(); } // Receive strokes from others onRemoteStroke((stroke) { drawStroke(stroke); });6.3 会议计时器/议程Meeting Timer/Agenda通过 SDK 获取会议上下文在前端维护议程状态与倒计时import zoomSdk from zoom/appssdk; // Timer app class MeetingTimer { constructor() { this.agenda []; this.currentItem 0; this.startTime null; } async init() { await zoomSdk.config({ capabilities: [getMeetingContext, shareApp] }); } setAgenda(items) { // items: [{ title: Intro, duration: 5 }, ...] this.agenda items.map(item ({ ...item, elapsed: 0, status: pending })); this.broadcastState(); } start() { this.startTime Date.now(); this.agenda[this.currentItem].status active; this.tick(); } tick() { const item this.agenda[this.currentItem]; const elapsed Math.floor((Date.now() - this.startTime) / 1000 / 60); item.elapsed elapsed; if (elapsed item.duration) { this.alertTimeUp(); } this.broadcastState(); setTimeout(() this.tick(), 1000); } nextItem() { this.agenda[this.currentItem].status completed; this.currentItem; if (this.currentItem this.agenda.length) { this.startTime Date.now(); this.agenda[this.currentItem].status active; } } alertTimeUp() { // Visual/audio alert document.getElementById(timer).classList.add(warning); } }6.4 Layers API沉浸式视觉Immersive / CameraLayers APIv1.5提供两种渲染模式immersive全屏自定义视频布局与camera仅叠加到用户自己的摄像头画面需要 Zoom 客户端 v5.10.6。基础用法import zoomSdk from zoom/appssdk; // Layers API for immersive experiences await zoomSdk.config({ capabilities: [runRenderingContext, clearRenderingContext] }); // Start Layers mode await zoomSdk.runRenderingContext({ view: immersive }); // Draw on the video layer const canvas document.getElementById(layers-canvas); const ctx canvas.getContext(2d); // Example: Add participant name labels function drawNameLabel(participant, x, y) { ctx.fillStyle rgba(0, 0, 0, 0.7); ctx.fillRect(x, y - 25, 150, 25); ctx.fillStyle white; ctx.font 14px Arial; ctx.fillText(participant.name, x 5, y - 8); } // Example: Add virtual background effects function drawVirtualEffect() { // Draw confetti, borders, icons, etc. // These overlay on top of video } // Stop Layers mode async function exitLayers() { await zoomSdk.clearRenderingContext(); }references/layers-api.md 提供了更完整的实现约束值得注意的关键点包括运行模式runRenderingContext({ view: immersive, defaultCutout: person })对应 Team 模式AI 抠除背景的人像剪影v5.9.3defaultCutout: rectangle对应 Presentation 模式保留背景的全宽视频块view: camera为相机叠加。剪影形状还包括standard、circle、square、verticalRectanglev5.11.0。约束只有会议主持人可以将渲染上下文切换为 immersive同一时间只允许一个 immersive 上下文Camera Mode 可与 Presentation Mode 同时运行Camera Mode 使用 CEF 渲染初始化需要时间过早调用绘制方法会失败最佳做法是监听onRenderedAppOpened事件或对绘制调用实施指数退避重试。绘制方法drawParticipant需participantUUID旧的participantId已废弃Immersive 可绘制任意与会者Camera 只能绘制自己、drawImage接收的是标准ImageData对象而非 base64 字符串注意 HiDPI 需要按devicePixelRatio缩放并可能分块平铺、drawWebView将应用自身 OSR webview 嵌入画布每个渲染上下文只有一个 webview。坐标系与层级原点在左上角支持100px、50%、原始数字三种单位Immersive 使用 CSS 像素相对会议画布自动缩放Camera 使用相对renderTarget默认 1280×720的原始像素。z-index 建议背景图0、参与者视频1、webview/交互叠加2。移动已绘制元素时没有原位更新必须先 clear 再 draw。更完整的沉浸式与相机模式示例可参考 examples/layers-immersive.md 与 examples/layers-camera.md。七、授权In-Client OAuth with PKCEIn-Meeting App 的授权应优先使用In-Client OAuthzoomSdk.authorize()用户在 Zoom 客户端内弹出授权窗口无需跳转浏览器是回头客体验最好的方案Web 重定向方式仅在首次从 Marketplace 安装时使用。核心流程前端// 1. Get code challenge from your backend const { codeChallenge, state } await fetch(/api/auth/challenge).then(r r.json()); // 2. Trigger in-client authorization await zoomSdk.authorize({ codeChallenge, state }); // 3. Listen for authorization result zoomSdk.addEventListener(onAuthorized, async (event) { const { code, state } event; // 4. Send code to backend for token exchange await fetch(/api/auth/token, { method: POST, body: JSON.stringify({ code, state }) }); });后端侧需要实现三个端点GET /api/auth/challenge生成 32 字节随机code_verifier、state将code_challenge返回前端并仅存于服务端会话POST /api/auth/token必须先校验state防止 CSRF再用code code_verifier调用 Zoom 令牌端点交换 access_token/refresh_token交换后清理 PKCE 数据GET /api/auth/status检查令牌是否过期。同时提供基于refresh_token的自动刷新中间件在令牌距过期不足 5 分钟时提前刷新。完整可运行的前后端实现见 examples/in-client-oauth.md。promptAuthorize()可用于 Guest 模式下引导用户提升授权级别。八、发布清单与 Marketplace 配置应用上线前需要逐项核对 in-meeting-apps.md 中的发布清单所有响应带上 OWASP 安全头强制 HTTPS并持有有效 SSL 证书实现 PKCE OAuth对全部 SDK 调用做错误处理提供浏览器预览回退 UI配置域名白名单Domain Allowlist在多种屏幕尺寸下测试提交至 Zoom Marketplace其中 OWASP 安全头Strict-Transport-Security、X-Content-Type-Options、Content-Security-Policy中的frame-ancestors zoom.us *.zoom.us、Referrer-Policy是 Marketplace 审核的硬性要求标准配置可直接参考 examples/quick-start.md 与 concepts/security.md。关于域名白名单除应用自身域名外使用 CDN 时还需将appssdk.zoom.us及其他 CDN 域名加入白名单ngrok 免费版 URL 每次重启都会变化需要在 Marketplace 的 Home URL、Redirect URL、OAuth Allow List、Domain Allow List 四处同步更新。九、技能链路与深入路径In-Meeting App 开发的完整技能链路为zoom-apps-sdk -- oauth -- zoom-rest-api (optional)即先掌握 SDK 能力再实现授权最后按需接入 REST API。根据 zoom-apps-sdk/SKILL.md 的集成索引推荐的深入路径如下通读架构concepts/architecture.md嵌入式浏览器、深链、X-Zoom-App-Context 解密跑通 Hello Worldexamples/quick-start.md理解运行上下文concepts/running-contexts.md实现 In-Client OAuthexamples/in-client-oauth.md按需扩展能力references/apis.md100 SDK 方法分类参考排查问题troubleshooting/common-issues.md 与 troubleshooting/debugging.md。对于沉浸式体验可继续阅读 use-cases/immersive-experiences.md自定义视频布局实时共享状态应用可参考 use-cases/collaborative-apps.md。在动手前也可以先通过choose-zoom-approach技能确认 Zoom Apps 与 Meeting SDK 的适用边界见 concepts/meeting-sdk-vs-zoom-apps.md避免选错技术路线。十、高频问题速查应用显示空白面板→ 检查 Domain Allowlist在 Marketplace → 应用 → Feature → Zoom App → Add Allow List 中添加域名SyntaxError: redeclaration→ CDN 模式下不要用let zoomSdk改用let sdk window.zoomSdkconfig()抛错→ SDK 仅在 Zoom 客户端内生效普通浏览器请走 try/catch 预览 UISDK 调用静默失败→ 检查 Marketplace Scopes 中是否启用了对应 scope新增 scope 后仍失败→ 用户需要重新授权Camera Mode 绘制失败→ CEF 未就绪监听onRenderedAppOpened或指数退避重试。这些诊断要点均来自 troubleshooting/common-issues.md该文件覆盖了实际开发中 90% 的常见问题。【免费下载链接】knowledge-work-pluginsOpen source repository of plugins primarily intended for knowledge workers to use in Claude Cowork项目地址: https://gitcode.com/GitHub_Trending/kn/knowledge-work-plugins创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价