Web Tape — Guide
A lightweight, rrweb-based bug-scene recording SDK. Capture user actions, network requests and console logs, then replay them with a scrubbing timeline, on-frame annotations, cURL reproduction and AI summaries — so "cannot reproduce" is a thing of the past.
⚠️ Data masking is on the roadmap. For now, inject only in test / staging. The SDK starts background recording on load — use caution in production user environments.
Web Tape 使用指南
基于 rrweb 的轻量级 Bug 现场录制 SDK。采集用户操作、网络请求与控制台日志,配合时间轴回放、画面批注、cURL 复现与 AI 摘要——彻底告别「无法复现」。
⚠️ 数据脱敏仍在规划中,当前建议仅在测试 / 预发环境注入。SDK 加载即开始后台录制,生产用户环境请谨慎。
How it works
Web Tape has two cooperating parts. Understanding this first makes the rest of the guide obvious:
- Capture SDK (
@webtapejs/toolbox) — a companion ("parasitic") library that you inject into the front-end app you want to record, via npm or a <script> tag. It runs inside that page, records events and uploads them. After install it offers two UI modes:- Built-in FAB — call
mountFab()and a floating record button appears (zero UI work); - Custom UI — skip the FAB and drive recording via the Core API from your own buttons/flows.
- Built-in FAB — call
- Replay service — stores recordings and serves the replay UI. You self-host it (one
curlcommand) and point the SDK'sserverUrlat it. There is no public hosted service.
Order to set it up: ① deploy the replay service (get its URL) → ② install the SDK in your app and set serverUrl to that URL → ③ choose a UI mode. Flow at runtime: SDK records a sliding window (or active recording) → uploads to /api/replayer → you get a shareable replay link.
工作原理
Web Tape 由两部分协作,先理解这层关系,后面的步骤就顺理成章:
- 采集 SDK(
@webtapejs/toolbox)——一个寄生(伴随)库,通过 npm 或一行 <script> 注入到你要录制的前端应用里,随该页面运行、录制事件并上传。安装后提供两种 UI 模式:- 内置悬浮按钮(FAB)——调用
mountFab()即出现悬浮录制按钮(零 UI 工作量); - 自渲染 UI——不挂 FAB,用 Core API 从你自己的按钮/流程里控制录制。
- 内置悬浮按钮(FAB)——调用
- 回放服务——存储录制、提供回放界面。由你自建(一行
curl),并把 SDK 的serverUrl指向它。没有公共托管服务。
上手顺序:① 部署回放服务(拿到它的地址)→ ② 在你的应用里装 SDK,把 serverUrl 设为该地址 → ③ 选一种 UI 模式。运行时流程:SDK 录制滑动窗口(或主动录制)→ 上传到 /api/replayer → 得到可分享的回放链接。
Install
npm install @webtapejs/toolbox
# or: pnpm add @webtapejs/toolbox / yarn add @webtapejs/toolbox
The npm entry is side-effect free — nothing runs until you call it. Two ways to use it: the built-in floating button (fastest), or the Core API (full control).
安装
npm install @webtapejs/toolbox
# 或:pnpm add @webtapejs/toolbox / yarn add @webtapejs/toolbox
npm 入口无副作用——不调用就什么都不会跑。两种用法:内置悬浮按钮(最快),或 Core API(完全掌控)。
Mode 1 · Built-in FAB
recommended The fastest path. Call mountFab() and a floating record button appears bottom-right with the full record UI — start/stop, a confirm popover, a duration guard, and a "report the last N seconds" shortcut.
import { configure, mountFab } from '@webtapejs/toolbox'
// Load only in test/staging, and only once (e.g. your app entry).
configure({
serverUrl: 'https://your-replayer.example.com/api/replayer', // required
errorPrompt: true, // sentinel mode
locale: 'en', // 'en' (default) | 'zh'
})
mountFab()
Background sliding-window recording starts automatically regardless of the FAB.
模式一 · 内置 FAB
推荐 最快接入。调用 mountFab(),页面右下角出现悬浮录制按钮,内置完整录制 UI——开始/停止、二次确认、时长保护,以及「上报最近 N 秒」快捷入口。
import { configure, mountFab } from '@webtapejs/toolbox'
// 仅测试/预发加载,且只加载一次(如放在应用入口)。
configure({
serverUrl: 'https://your-replayer.example.com/api/replayer', // 必填
errorPrompt: true, // 哨兵模式
locale: 'zh', // 'en'(默认)| 'zh'
})
mountFab()
无论是否挂 FAB,后台滑动窗口录制都会自动启动。
Mode 2 · Core API
Skip mountFab() and drive recording yourself — wire it into your own buttons, feedback entry, or automated flows.
import {
configure, startRecord, stopRecord, discardRecord,
reportRecent, getState, onStateChange,
} from '@webtapejs/toolbox'
configure({ serverUrl: 'https://your-replayer.example.com/api/replayer' })
模式二 · Core API
跳过 mountFab(),自己掌控录制时机——接到你自己的按钮、反馈入口或自动化流程里。
import {
configure, startRecord, stopRecord, discardRecord,
reportRecent, getState, onStateChange,
} from '@webtapejs/toolbox'
configure({ serverUrl: 'https://your-replayer.example.com/api/replayer' })
configure(config)
Inject runtime configuration. Can be called repeatedly — values are merged and take effect immediately (no need to reload rrweb).
configure({
serverUrl: 'https://your-replayer.example.com/api/replayer',
autoBackgroundRecord: true,
backgroundWindowMs: 60_000,
errorPrompt: true,
errorPromptIgnore: { statusCodes: [404, 499], urls: ['/health', /\/ping/] },
locale: 'en',
})
| Field | Type | Default | Description |
|---|---|---|---|
serverUrl | string | — | Upload endpoint of your replay service. Required in production. |
autoBackgroundRecord | boolean | true | Auto-start the background sliding-window recording. |
backgroundWindowMs | number | 30000 | Sliding window length (ms). Larger = more memory; keep ≤ 60000. |
errorPrompt | boolean | false | Sentinel mode: prompt a one-click report on HTTP ≥ 400. |
errorPromptIgnore.statusCodes | number[] | — | Status codes to ignore (no toast). |
errorPromptIgnore.urls | (string|RegExp)[] | — | URLs to ignore (substring or RegExp). |
locale | 'en'|'zh' | 'en' | Language of the injected UI. |
Performance: the SDK only records DOM events (no screenshots / video encoding), so overhead is far smaller than canvas recording. If you only need manual recording and no rewind, set autoBackgroundRecord: false.
configure() 配置
注入运行时配置。可重复调用——配置合并且立即生效(无需重启 rrweb)。
configure({
serverUrl: 'https://your-replayer.example.com/api/replayer',
autoBackgroundRecord: true,
backgroundWindowMs: 60_000,
errorPrompt: true,
errorPromptIgnore: { statusCodes: [404, 499], urls: ['/health', /\/ping/] },
locale: 'zh',
})
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
serverUrl | string | — | 回放服务上传地址。生产必填。 |
autoBackgroundRecord | boolean | true | 是否自动启动后台滑动窗口录制。 |
backgroundWindowMs | number | 30000 | 滑动窗口长度(毫秒)。越大占用越多,建议 ≤ 60000。 |
errorPrompt | boolean | false | 哨兵模式:HTTP ≥ 400 时弹一键上报提示。 |
errorPromptIgnore.statusCodes | number[] | — | 忽略的状态码(不弹提示)。 |
errorPromptIgnore.urls | (string|RegExp)[] | — | 忽略的 URL(子串或正则)。 |
locale | 'en'|'zh' | 'en' | 注入 UI 的语言。 |
性能:SDK 只录制 DOM 事件(不截图/不编码视频),开销远小于 canvas 录制。若只需手动录制、不需要回溯,设 autoBackgroundRecord: false。
startRecord / stopRecord / discardRecord
await startRecord() // only callable in the `finished` state
const result = await stopRecord() // stop & upload → RecordingResult | false
discardRecord() // discard without uploading; back to `finished`
| Method | Returns | Notes |
|---|---|---|
startRecord() | Promise<void> | Start active recording; state → recording. |
stopRecord() | Promise<RecordingResult|false> | Stop & upload; result on success, false otherwise. |
discardRecord() | void | Drop the current recording without uploading. |
interface RecordingResult {
sourceId: string // 32-hex recording id
url: string // full, shareable replay link
}
startRecord / stopRecord / discardRecord
await startRecord() // 仅 `finished` 态可调
const result = await stopRecord() // 停止并上传 → RecordingResult | false
discardRecord() // 丢弃不上传,回到 `finished`
| 方法 | 返回 | 说明 |
|---|---|---|
startRecord() | Promise<void> | 开始主动录制;状态 → recording。 |
stopRecord() | Promise<RecordingResult|false> | 停止并上传;成功返回结果,否则 false。 |
discardRecord() | void | 丢弃当前录制,不上传。 |
interface RecordingResult {
sourceId: string // 32 位 hex 录制标识
url: string // 完整可分享回放链接
}
reportRecent()
Upload the last backgroundWindowMs from the background sliding window — no need to start recording ahead of time. Ideal for auto-reporting on form-submit failures or request timeouts.
async function onSubmitError() {
const result = await reportRecent() // RecordingResult | false
if (result) submitBugReport({ replayUrl: result.url })
}
Only callable in thefinishedstate; returnsfalsewhilerecording/uploading.
reportRecent() 回溯
上传后台滑动窗口最近 backgroundWindowMs 的内容——无需提前开录。适合表单提交失败、接口超时等场景的自动上报。
async function onSubmitError() {
const result = await reportRecent() // RecordingResult | false
if (result) submitBugReport({ replayUrl: result.url })
}
仅finished态可调;recording/uploading态返回false。
getState() / onStateChange()
Read the current state or subscribe to changes, to drive your own UI.
const state = getState() // 'finished' | 'recording' | 'uploading'
const unsubscribe = onStateChange((state) => {
// fires once immediately with the current state
})
unsubscribe() // on unmount
State machine: finished → startRecord → recording → stopRecord → uploading → finished (discardRecord returns to finished directly).
getState() / onStateChange() 状态与订阅
读取当前状态或订阅变化,驱动你自己的 UI。
const state = getState() // 'finished' | 'recording' | 'uploading'
const unsubscribe = onStateChange((state) => {
// 订阅时立即回调一次当前状态
})
unsubscribe() // 组件卸载时取消
状态机:finished → startRecord → recording → stopRecord → uploading → finished(discardRecord 直接回到 finished)。
Background sliding window — internals
On load the SDK records continuously, but memory only ever holds the most recent slice of events. This is what powers reportRecent() / the FAB's "upload last N seconds" — you can capture a bug after it happened, without recording ahead.
Double buffer + checkout signal
Two buffers are kept:
prev— events from the previous checkout cycle;curr— the current cycle, whose first event is always a FullSnapshot.
rrweb is started with checkoutEveryNms = backgroundWindowMs. Periodically it emits a fresh FullSnapshot flagged isCheckout=true; on that signal the buffers rotate:
on emit(event, isCheckout):
if isCheckout && event is FullSnapshot:
prev = curr // archive the finished cycle
curr = [] // start a new cycle at the fresh FS
curr.push(event)
A report returns prev.concat(curr), so it always begins with a FullSnapshot (needed to rebuild the DOM) and spans [windowMs, 2×windowMs).
Why two buffers?
Trimming a single buffer purely by time could cut off the FullSnapshot at the start, leaving events that can't be replayed (blank screen). Keeping the whole previous cycle guarantees a valid FullSnapshot anchor while bounding memory.
Long-idle fallback
checkoutEveryNms only fires when increments arrive. If the user is idle for a long time, no checkout happens and prev's span can far exceed the window. On report, if the total span exceeds 2×windowMs, prev is discarded and only curr (a fresh FullSnapshot) is used — so the worst case is roughly one static snapshot, not cross-day data.
Memory & config
- Only the last
[windowMs, 2×windowMs)of events is retained; older events are dropped on rotation. backgroundWindowMssets the window (default 30s; keep ≤ 60s). Larger = more memory.autoBackgroundRecord: falsedisables the background window entirely (manual recording only).
后台滑动窗口 — 实现细节
SDK 加载即持续录制,但内存里始终只保留最近一段事件。这正是 reportRecent() / FAB「上报最近 N 秒」的底层——出问题后再回溯,无需提前开录。
双 buffer + checkout 信号
维护两个 buffer:
prev—— 上一个 checkout 周期的事件;curr—— 当前周期,其首个事件必为 FullSnapshot(全量快照)。
rrweb 以 checkoutEveryNms = backgroundWindowMs 启动,会周期性 emit 一个带 isCheckout=true 的全量快照;收到该信号即轮换:
emit(event, isCheckout) 时:
若 isCheckout 且 event 为 FullSnapshot:
prev = curr // 归档已完成的周期
curr = [] // 从新的全量快照开始新周期
curr.push(event)
上报返回 prev.concat(curr),因此必以 FullSnapshot 开头(重建 DOM 所需),时间跨度稳定在 [windowMs, 2×windowMs)。
为什么用两个 buffer?
若只用单 buffer 按时间裁剪,可能把开头的全量快照裁掉,剩下的事件无法回放(白屏)。保留完整的上一周期,既能保证有效的 FullSnapshot 起点,又能约束内存占用。
长静默兜底
checkoutEveryNms 仅在收到增量事件时才检查触发。用户长时间不操作时不会产生 checkout,prev 的跨度可能远超窗口。上报时若总跨度超过 2×windowMs,则丢弃 prev、只用 curr(起点是 fresh 全量快照)——最坏情况约等于一帧静态截图,而不是跨天数据。
内存与配置
- 只保留最近
[windowMs, 2×windowMs)的事件,轮换时丢弃更早的。 backgroundWindowMs设定窗口(默认 30s,建议 ≤ 60s)。越大占用越多。autoBackgroundRecord: false可完全关闭后台窗口(仅手动录制)。
Sentinel mode
With errorPrompt: true, the SDK watches every HTTP request. On status ≥ 400 it pops a toast (bottom-right) guiding the user to report the scene in one click.
- Only one toast at a time; consecutive errors are de-duplicated.
- Auto-dismisses after ~6s.
- Never interrupts while
recording/uploading. - Use
errorPromptIgnoreto exclude health checks and other harmless endpoints.
哨兵模式
开启 errorPrompt: true 后,SDK 监听每个请求;状态码 ≥ 400 时在右下角弹 toast,引导用户一键上报现场。
- 同时只显示一个 toast,连续错误自动去重。
- 约 6 秒后自动消失。
recording/uploading期间不打扰。- 用
errorPromptIgnore排除健康检查等无害接口。
AI analysis
On the replay page, the AI Analysis panel sends the recording's network requests + console logs + DOM stats to an AI workflow and returns a root-cause summary and suggestions. It is opt-in and requires configuration on the replay service — if unset, the feature degrades gracefully (a message says it's not configured) while replay / network / annotations keep working.
Built with a Dify workflow
The analysis is powered by a Dify AI workflow (any compatible Workflow API works). Build a workflow that:
- accepts these input variables:
network_requests_info,console_logs_info,dom_nodes_info; - runs in blocking mode and exposes the final answer as an output variable named
result.
Configure the replay service
Set these in the replayer's .env (server-side only):
# Dify workflow run endpoint, e.g. https://api.dify.ai/v1/workflows/run
AI_WORKFLOW_API_URL=...
# Dify workflow API key (kept on the server, never shipped to the browser)
AI_WORKFLOW_API_KEY=...
Restart the replayer; the AI Analysis button in the replay UI now works.
Security: the browser never sees the key. The replayer exposes a server-side proxyPOST /api/ai/analyzethat attaches the key and forwards to the workflow — soAI_WORKFLOW_API_KEYstays out of the client bundle.
AI 分析
在回放页,AI 分析面板会把本次录制的网络请求 + 控制台日志 + DOM 统计发给 AI 工作流(workflow),返回根因摘要与改进建议。该功能默认关闭、需在回放服务端配置——未配置时会优雅降级(提示未配置),回放 / 网络 / 批注等其它能力不受影响。
结合 Dify 工作流构建
分析能力由 Dify AI 工作流驱动(兼容的 Workflow API 也可)。你需要搭一个工作流:
- 接收输入变量:
network_requests_info、console_logs_info、dom_nodes_info; - 以 blocking 模式运行,并把最终结果输出为名为
result的输出变量。
配置回放服务
在 replayer 的 .env 里设置(仅服务端):
# Dify 工作流运行端点,例如 https://api.dify.ai/v1/workflows/run
AI_WORKFLOW_API_URL=...
# Dify 工作流 API Key(只存在于服务端,绝不下发到浏览器)
AI_WORKFLOW_API_KEY=...
重启 replayer,回放界面里的 AI 分析 按钮即可使用。
安全:浏览器永远拿不到 Key。replayer 提供服务端代理POST /api/ai/analyze,在服务端附上 Key 再转发给工作流——因此AI_WORKFLOW_API_KEY不会进前端 bundle。
CDN usage
For QA / staging pages without a build step, load the IIFE bundle. The same API is exposed on window._webTape.
<script src="https://unpkg.com/@webtapejs/toolbox/dist/web-tape.iife.js"></script>
<script>
window._webTape.configure({ serverUrl: 'https://your-replayer.example.com/api/replayer' })
</script>
Add data-builtin-fab="false" to stay silent (no FAB), or data-locale="zh" for Chinese. Prefer the npm package for real projects — it's tree-shakeable, typed and side-effect free.
CDN 引入
无构建步骤的 QA / 预发页面,可直接引入 IIFE 产物;同一套 API 挂在 window._webTape 上。
<script src="https://unpkg.com/@webtapejs/toolbox/dist/web-tape.iife.js"></script>
<script>
window._webTape.configure({ serverUrl: 'https://your-replayer.example.com/api/replayer' })
</script>
加 data-builtin-fab="false" 可静默不挂 FAB;加 data-locale="zh" 切中文。工程化项目建议用 npm 包——可摇树、带类型、无副作用。
Self-host the replay service
Recordings upload to a replay service you host. With Docker installed, one command brings up MySQL + the replayer (generates a random password, no manual .env):
curl -fsSL https://raw.githubusercontent.com/chernbo/webTape/main/deploy/install.sh | bash
Then use http://localhost:3100/api/replayer as your serverUrl. The upload endpoint is unauthenticated by design (Web Tape rides inside a host app rather than being a standalone service) — add auth at your gateway if exposing it publicly. See the GitHub repo for details.
自建回放服务
录制数据上传到你自建的回放服务。装了 Docker 后一行命令拉起 MySQL + 回放服务(自动生成随机密码,无需手写 .env):
curl -fsSL https://raw.githubusercontent.com/chernbo/webTape/main/deploy/install.sh | bash
然后把 http://localhost:3100/api/replayer 作为 serverUrl。上传接口默认不鉴权(Web Tape 是寄生在宿主应用内的伴随工具,而非独立服务)——公网暴露请在网关层自行加鉴权。详见 GitHub 仓库。
Roadmap
- 🤖 Deeper AI — structure recordings (action paths + network + console) into an agent-consumable format so an AI agent can auto-reproduce and locate root causes.
- 📱 Multi-platform — explore recording for H5 / mini-programs, unifying the bug-report experience across web and mobile.
- 🛡️ Data masking — configurable field/selector masking so it can be used beyond test/staging.
后期计划
- 🤖 AI 深度探索——把录制(操作路径 + 网络 + 控制台)结构化为 Agent 可消费格式,让 AI Agent 自动复现并定位根因。
- 📱 多端支持——探索 H5 / 小程序录制,统一 Web 与移动端的 Bug 上报体验。
- 🛡️ 数据脱敏——可配置的字段/选择器脱敏,让它能走出测试/预发环境。