OpenReplay Spot 浏览器扩展开发与构建指南:从 `yarn dev` 本地调试到 Chrome MV3 打包发布
2026/9/23 1:38:47 网站建设 项目流程
  • 可观测性
  • 开发工具
  • 前端
  • 后端

【免费下载链接】openreplay

Session replay, cobrowsing and product analytics you can self-host. Best for reproducing issues and iterating on your product.

项目地址:https://gitcode.com/gh_mirrors/op/openreplay
点击查看免费下载

Spot 是 OpenReplay 项目内置的一款浏览器扩展:用户直接在浏览器里录制自己发现的 Bug 画面,即可自动生成包含网络请求、控制台日志、点击轨迹、Web Vitals 等完整信息的 Bug 报告,免去开发与测试之间的反复沟通。本文以 spot/README.md 为主线,结合spot/目录下的 WXT 配置、Service Worker、离屏录制模块与后端 Spot 路由实现,完整讲解该扩展的本地开发环境搭建、Ingest 接入点切换、源码结构,以及如何编译出可手动加载的 Chrome MV3 扩展包。

Spot 是什么:把"发现 Bug"变成"提交即完成的报告"

Spot 的核心定位在 spot/README.md 中有明确描述:直接在浏览器中录制你所看到的 Bug,即时生成包含工程师修复所需全部信息的综合 Bug 报告(comprehensive bug reports),不再需要来回沟通(No more back-and-forth)

从实现上看,这份"全部信息"不只是视频本身。在 spot/entrypoints/background.ts 中定义的SpotObj数据结构完整勾勒了报告的内容范围:

  • base64data:录制的视频数据(默认video/webm);
  • network:网络请求列表(SpotNetworkRequest[]);
  • logs:控制台日志({ level, msg, time });
  • clicks:页面点击事件({ time, label });
  • locations:URL 变化与navigation性能时间线(fcpTimevisuallyCompletetimeToInteractive);
  • vitals:Web Vitals 指标(CLSFCPFIDINPLCPTTFB);
  • crop:视频裁剪区间、startTs、浏览器版本、平台与屏幕分辨率等元数据。

也就是说,一段几十秒的录制会自动附带请求失败、JS 报错、性能指标等"工程师真正需要的上下文"。这正是 README 所述"无需来回沟通"的技术基础。

环境准备:Node 版本与包管理器

按照 spot/README.md 的 Contributing 一节,开始开发前需要先准备好 Node.js 环境:

  • 推荐先安装 nvm 或 n 来管理 Node 版本;
  • 也可以直接使用node >= v20.0.0的版本;
  • 包管理器使用 Yarn,spot/package.json中通过"packageManager": "yarn@4.5.3"固定了版本(Yarn 4.x,即 Berry)。

扩展本身基于WXT(版本wxt: 0.20.25)构建,UI 使用SolidJSsolid-js: ^1.9.15),样式采用Tailwind CSS 4与 daisyUI,另依赖@openreplay/network-proxy用于网络请求的代理捕获。postinstall钩子会执行wxt prepare生成类型与构建所需的辅助文件,因此首次安装依赖时请确保网络可以拉取这些包:

yarn install

本地开发:yarn dev与 Ingest 接入点切换

spot/package.json的 scripts 提供了两条开发命令:

{ "scripts": { "dev": "wxt", "dev:firefox": "wxt -b firefox", "build": "wxt build", "build:firefox": "wxt build -b firefox", "zip": "wxt zip", "zip:firefox": "wxt zip -b firefox", "compile": "tsc --noEmit", "postinstall": "wxt prepare" } }

启动开发实例

运行yarn dev会直接拉起一个全新的 Chrome 实例,Spot 扩展已预先安装好(WXT 的 dev 模式会自动 watch 代码变更并热更新)。对应的浏览器启动参数在 spot/wxt.config.ts 中定义:

webExt: { chromiumArgs: ['--user-data-dir=./.wxt/chrome-data'], },

也就是说,开发用的浏览器会使用spot/.wxt/chrome-data作为独立的用户数据目录,与你的日常浏览器配置隔离。

关键一步:切换 Ingest 接入点

README 明确强调了一个常见陷阱:

Runningyarn devwill start new chrome instance with spot extension installed already, but you need to change ingest point to your local dev env if you don't have account on app.openreplay.com.

即在app.openreplay.com上没有账号时,必须把扩展的Ingest Point(数据接入点)改为你的本地开发环境,否则扩展不知道把录制的 Spot 数据提交到哪里。

Ingest 的默认值定义在两处,均为https://app.openreplay.com

  • spot/entrypoints/background.ts 中的defaultSettings.ingestPoint
  • spot/entrypoints/popup/Settings.tsx 中的defaultIngest

修改方式有两种:

  1. 扩展弹窗设置:点击扩展图标 → 打开 Settings → 打开 "Ingest Point" 开关 → 编辑 URL 并 Save。设置页的代码逻辑在 spot/entrypoints/popup/Settings.tsx:它用new URL(url)校验合法性(isValidUrl),通过chrome.runtime.sendMessage({ type: "ort:settings", settings: { ingestPoint: val } })写入chrome.storage.local
  2. 直接在代码中改默认值:把defaultSettings.ingestPoint改成你的本地地址(如http://localhost:3000),适用于本地联调。

需要注意:background.ts中监听messages.popup.from.updateSettings(即"ort:settings")消息时,一旦检测到ingestPoint被修改,会立即调用setJWTToken("")使当前登录态失效——因为接入点变了,旧的登录凭据已不再适用于新后端,需要重新登录。

Ingest 点在后端对接了什么

把 Ingest 指向本地环境后,扩展会通过以下 API 与后端通信(实现见 ee/api/routers/subs/spot.py):

  • GET {ingest}/spot/v1/ping:心跳探活,Service Worker 每隔 30 秒调用一次(PING_INT = 30 * 1000),并携带Ext-Version请求头;
  • GET {ingest}/api/spot/refresh:JWT 刷新接口,每 60 秒(CHECK_INT = 60 * 1000)检查并刷新 token,返回新的jwtspotRefreshToken
  • GET {ingest}/spot/integrations/slack/channels:拉取已配置的 Slack 频道列表;
  • POST {ingest}/spot/v1/spots:创建 Spot 记录,返回{ id, mobURL, videoURL }
  • PUT mobURL/PUT videoURL:分别上传事件 JSON 与视频二进制;
  • POST {ingest}/spot/v1/spots/{id}/uploaded:标记上传完成。

服务端refresh路由会通过Set-Cookie写入spotRefreshTokenhttponlysecure),cookie 路径在本地开发时为/spot/refresh,生产环境为/api/spot/refresh(见 ee/api/routers/subs/spot.py 的LOCAL_DEV判断)。登录态由authenticate一并返回的spotJwtspotRefreshTokenspotRefreshTokenMaxAge驱动(见 ee/api/chalicelib/core/users.py)。

扩展源码结构一览

spot/目录采用 WXT 约定的entrypoints/布局,各模块职责清晰:

目录/文件职责
spot/entrypoints/background.tsService Worker(后台脚本):管理 JWT、录制状态机、汇总数据、与后端 API 通信
spot/entrypoints/content/Content Script:注入录制控制 UI(倒计时、录制/暂停/麦克风/结束控制条、保存界面)
spot/entrypoints/offscreen/离屏文档:实际执行tabCapture/getDisplayMedia录制与MediaRecorder编码
spot/entrypoints/injected.ts页面主世界(MAIN world)脚本:patchconsole、代理网络请求
spot/entrypoints/popup/扩展弹窗:登录、开始/停止录制、音频设备选择、设置
spot/utils/工具库:JWT 过期判断、网络/控制台跟踪、消息常量、base64 转换等

消息路由:四个角色如何协作

各模块通过browser.runtime.sendMessage/tabs.sendMessage通信,消息类型集中在 spot/utils/messages.ts,按popupcontentinjectedoffscreen分组。一条典型的录制流程如下:

  1. 用户在 popup 点击开始录制 →popup:start(携带area: "tab" | "desktop"micaudioIdpermissions)发给 background;
  2. background 向当前活动标签页发送content:mount,content script 挂载倒计时 UI(Countdown);
  3. 倒计时结束(ort:countend)后,background 通过chrome.tabCapture.getMediaStreamId获取标签页流,或让 offscreen 用getDisplayMedia抓取桌面(displaySurface: 'monitor');
  4. offscreen 的ScreenRecorder开始录制,每秒产出一个 chunk;background 把 base64 分片(offscr:video-data-chunk)转交给 content script 拼装;
  5. 同时 content script 启动点击/位置/Web Vitals 跟踪(spot/entrypoints/content/eventTrackers.ts),injected script 采集控制台日志与网络请求(ort:bump-logsort:bump-network);
  6. 用户点结束 → background 汇总数据 →POST /spot/v1/spots创建记录 → 并行PUT上传事件与视频 → 在新标签页打开${link}/view-spot/{id}查看结果。

值得注意的一个细节:录制控制条 UI 在"桌面录制"模式下会跟随当前活动标签页迁移(browser.tabs.onActivated监听),并会在页面导航完成(webNavigation.onCompleted)后自动重建,保证录制期间切换标签页不会丢控制条,实现代码在background.tsstartRecording函数中。

离屏录制:编码质量与容错

spot/entrypoints/offscreen/main.js 中的getRecordingSettings定义了 6 档质量预设,对应不同的分辨率与码率:

档位音频码率 (bps)视频码率 (bps)分辨率
4k192000400000004096×2160
1080p19200080000001920×1080
720p(默认)12800025000001280×720
480p960002500000854×480
360p960001000000640×360
240p64000500000426×240

默认初始化使用720precorder.init(getRecordingSettings('720p'))。编码上按vp9+opusvp8+opus→ 纯video/webm的顺序探测MediaRecorder.isTypeSupported,若初始化阶段出现EncodingError(编码器初始化失败),还会自动降级重试 vp8/vp9,提升兼容性。帧率约束为{ min: 20, max: 30 },录制上限3 分钟,分片间隔 1 秒(mRecorder.start(1000))。

视频数据通过消息通道回传时受浏览器消息大小限制,因此代码以24MBhardLimit = 24 * 1024 * 1024)为基准把 Blob 切成 base64 分片(convertBlobToBase64Chunks),再按序号发送,由 content script 按index/total重组。

音频方面,ScreenRecorder._getStream会把标签页音频与麦克风(可选,echoCancellation: false)通过AudioContext.createMediaStreamDestination()混流;麦克风不可用时创建静音占位轨道(createPlaceholderAudioTrack),确保 MediaRecorder 始终有音频轨道可编码。

网络与控制台采集的两种实现

spot/entrypoints/popup/Settings.tsx中有一个 "Use Debugger" 开关,对应defaultSettings.useDebugger(默认false)。这对应两套网络采集方案:

  • 默认方案:通过注入页面主世界的脚本(spot/entrypoints/injected.ts + spot/utils/proxyNetworkTracking.ts)以及 content script 的注入机制(injectScript向页面插入injected.js)采集 console 与网络事件;
  • Debugger 方案:开启useDebugger后,使用 Chrome DevTools Protocol 的debugger权限做更精确的请求跟踪(spot/utils/networkDebuggerTracking.ts),这也是wxt.config.ts中声明"debugger""webRequest""webNavigation"权限的原因。

控制台日志通过 patchconsole对象实现(spot/utils/consoleTracking.ts),并提供__or_revokeSpotPatch撤销钩子,停止录制后可还原原始 console。

打包构建:手动加载 Chrome 扩展

spot/README.md 的 Building 一节给出了编译自定义版本的完整步骤:

yarn build

构建产物输出到spot/.output/chrome-mv3目录(WXT 默认按浏览器类型组织输出目录)。随后:

  1. 打开 Chrome,地址栏输入chrome://extensions/
  2. 打开右上角Developer mode(开发者模式)开关;
  3. 点击Load unpacked(加载已解压的扩展程序)
  4. 选择spot/.output下的chrome-mv3文件夹。

加载完成后,扩展图标会出现在工具栏,登录 OpenReplay 账号(或本地 Ingest 对应的后端账号)即可开始录制。

其他构建命令

package.json还提供了面向 Firefox 的构建与打包命令:

yarn build:firefox # 构建 Firefox 版(输出 .output/firefox-mv2) yarn zip # 生成 Chrome 版 zip 包 yarn zip:firefox # 生成 Firefox 版 zip 包 yarn compile # 仅做 TypeScript 类型检查(tsc --noEmit)

扩展的 manifest 由 spot/wxt.config.ts 生成,关键权限包括:

manifest: { name: "__MSG_extName__", description: "__MSG_extDescription__", default_locale: "en", host_permissions: ["<all_urls>"], permissions: [ "storage", "tabCapture", "offscreen", "unlimitedStorage", "webNavigation", "webRequest", "debugger", ], web_accessible_resources: [{ resources: ["injected.js", "notifications.js", "/content-scripts/content.css"], matches: ["<all_urls>"], }], }

其中tabCapture用于抓取标签页媒体流、offscreen用于创建离屏录制文档、debugger用于精确网络跟踪、unlimitedStorage用于存放较大的录制数据。多语言文案位于 spot/public/_locales/en/messages.json,通过__MSG_extName__这类占位符引用。

面向自托管用户的配置要点

如果你部署的是自托管(self-host)OpenReplay 实例,需要注意以下几点:

  1. Ingest Point 必须指向你的实例(见上文"切换 Ingest 接入点"一节),否则扩展会把数据提交到公共的app.openreplay.com
  2. 登录凭据使用独立的 Spot JWT:服务端登录接口会额外返回spotJwt/spotRefreshToken,扩展通过ort:login-token消息写入chrome.storage.local,后台再通过setJWTToken维护内存态与定时刷新;
  3. 查看录制的 URL 规则:当 Ingest 主机名为api.openreplay.com时,报告页链接指向https://app.openreplay.com/view-spot/{id},否则直接使用你配置的 Ingest 地址下的/view-spot/{id}(见background.tssaveSpotData分支)。

调试与常见问题

  • yarn dev启动后扩展没有出现:确认使用的是新拉起的 Chrome 实例(--user-data-dir=./.wxt/chrome-data),而非日常浏览器;WXT 的 dev server 需要保持运行。
  • 录制后一直提示 "couldn't get active login":说明 Ingest 点与你登录的账号后端不一致,或在ingestPoint变更后 token 被主动失效(setJWTToken("")),需要重新登录。
  • 录制结束但视频为空:offscreen 模块在stop-recording时会对空 Blob 做诊断输出(console.error('No data recorded', diag)),包含 recorder 状态、mimeType 支持矩阵、chunk 数量与轨道信息,可据此定位是编码器不支持还是媒体流未就绪。
  • 消息过大报错:视频数据以 24MB 为上限分片传输,若仍超限代码会进一步二分切片(convertBlobToBase64Chunks内的 safety 分支)。

小结

从 spot/README.md 出发可以看到,OpenReplay Spot 是一个典型的 WXT + SolidJS 浏览器扩展工程:开发阶段一条yarn dev即可拉起带插件的 Chrome 调试实例,重点是按自托管场景切换 Ingest 接入点;发布阶段yarn build后通过chrome://extensions的 Load unpacked 加载spot/.output/chrome-mv3即可。其内部则通过 background Service Worker、content script、offscreen 离屏录制与注入脚本四个角色的协作,把"屏幕录制 + 网络/控制台/点击/Vitals 上下文 + 一键上传"打包成工程师可直接上手修复的完整 Bug 报告。

  • 可观测性
  • 开发工具
  • 前端
  • 后端

【免费下载链接】openreplay

Session replay, cobrowsing and product analytics you can self-host. Best for reproducing issues and iterating on your product.

项目地址:https://gitcode.com/gh_mirrors/op/openreplay
点击查看免费下载

相关推荐

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

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

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

立即咨询