ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

rrweb canvas-webrtc-record 插件变更日志解读:跨域画布流默认拦截策略与版本演进

rrweb canvas-webrtc-record 插件变更日志解读:跨域画布流默认拦截策略与版本演进 rrweb canvas-webrtc-record 插件变更日志解读跨域画布流默认拦截策略与版本演进【免费下载链接】rrwebrecord and replay the web项目地址: https://gitcode.com/gh_mirrors/rr/rrweb本文以rrweb/rrweb-plugin-canvas-webrtc-record的 CHANGELOG.md 为主线梳理该 rrweb 插件包从 2.0.0 独立成包、分发文件格式重构到 2.1.4 引入跨域画布 WebRTC 指令默认拦截的完整演进过程并结合插件源码、单元测试与 README 示例讲清recordCrossOriginIframes选项的语义边界、跨域 postMessage 信令协议与直播推流的完整接线方式。插件包的定位与当前基线rrweb/rrweb-plugin-canvas-webrtc-record是 rrweb 生态中负责通过 WebRTC 实时直播画布Canvas内容的录制侧插件与回放侧的rrweb/rrweb-plugin-canvas-webrtc-replay配套使用。它属于 rrweb 2.x 模块化拆分后的独立 npm 包而非rrweb主包的一部分。从 package.json 可以确认当前基线包版本为2.1.5与 CHANGELOG 顶部的## 2.1.5条目对应peerDependencies要求rrweb: ^2.1.1即该插件只在 rrweb 2.x 系列上工作构建体系为 Vite TypeScript脚本包括testvitest run、check-typestsc -noEmit和prepublish先类型检查再构建分发产物同时提供 ESM./dist/rrweb-plugin-canvas-webrtc-record.js、CommonJS.cjs与 UMD.umd.cjs同时被main/unpkg指向三种形态jsdelivr字段指向/umd/目录下的.js文件。这些字段本身就是 2.0.0 版本“重构分发文件”这条 Major 变更的落地结果下文会展开。2.0.0插件从 rrweb 主包拆分为独立包CHANGELOG 中 2.0.0 的 Major Changes 有两条核心记录提交2606a2a对应上游 PR #1497第一条插件整体拆包。rrweb/packer、rrweb/rrweb-plugin-canvas-webrtc-record、rrweb/rrweb-plugin-canvas-webrtc-replay、rrweb/rrweb-plugin-sequential-id-record、rrweb/rrweb-plugin-sequential-id-replay、rrweb/rrweb-plugin-console-record、rrweb/rrweb-plugin-console-replay全部从rrweb主包中拆出各自成为独立包。对本插件而言这意味着用户按需安装只用 WebRTC 直播画布的场景无需把全部插件代码打进 bundle版本节奏独立本插件可以单独发布 patch如 2.1.4而主包rrweb版本保持不变对使用者透明的兼容层保留仓库根目录下仍保留 rrweb/rrweb-record 等兼容入口import rrweb from rrweb的行为不受拆包影响CHANGELOG 原文强调你如何运行import rrweb from rrweb都不会注意到这次变更的差异。第二条分发文件的文件名、路径与扩展名全部变化。变更日志原文给出了一套完整规则对直接引用分发文件的用户尤其重要所有.js文件改为ES Modules可用于现代浏览器、Node.js 和支持 ESM 的打包器每个 npm 包额外提供.cjs面向旧版 Node.js 的 CommonJS 模块与.umd.cjs把全部文件打成一个文件、便于通过script标签引入的 CommonJS 模块如果此前通过script标签直接引入 rrweb 的文件路径需要更新为.umd.cjs如果此前直接引用rrweb/typings/...或rrdom/es这类子路径路径/文件名可能需要同步更新类型定义在package.json中定义得更规范特定类型例如PlayerMachineState、SpeedMachineState改由rrweb/replay导出需要查看package.json的main和exports字段确认可用文件。本插件当前 package.json 的exports字段正是这套规则的直接体现.入口下import条件指向./dist/rrweb-plugin-canvas-webrtc-record.jsESMrequire条件指向.cjs两者分别配套dist/index.d.ts与dist/index.d.cts类型声明files字段明确发布umd与dist两个目录。2.0.0 系列中的若干 Patch 变更拆包前后还有几条值得关注的记录类型迁移提交5a78938PR #1593NodeType枚举从rrweb-snapshot迁移到rrweb/types随迁的还有documentNode、documentTypeNode、legacyAttributes、textNode、cdataNode、commentNode、elementNode、serializedNode、serializedNodeWithId、serializedElementNodeWithId、serializedTextNodeWithId、IMirror、INode、mediaAttributes、attributes与DataURLOptions等类型。对写插件的开发者来说镜像Mirror相关类型统一从rrweb/types导入这一点在插件源码 src/index.ts 的 import 中可以直接看到版本同步提交db20184与仓库中其他包保持版本号同步UMD 输出目录提交33e01f5PR #1704在/dist/之外增加/umd/输出目录使 UMD 文件可以保留.js扩展名避免与package.json中dist 下所有.js都是模块的约定冲突——这正是jsdelivr字段指向./umd/rrweb-plugin-canvas-webrtc-record.js的原因。2.0.0 之后2.0.1 仅有一条记录跟随rrweb2.0.1的依赖升级提交5f52d63用于保持包间依赖版本一致。中间的2.0.0-alpha.15~alpha.20各条目基本都是依赖同步记录其中 alpha.15 提前落地了上述拆包与分发文件两条 Major 变更。2.1.0 至 2.1.3 区间在 CHANGELOG 中没有附带变更说明说明这几个版本没有值得单列的用户可见变更。2.1.4默认拒绝跨域画布 WebRTC 指令2.1.4 是本包 CHANGELOG 中最新一条实质变更提交34806cc它确立了当前版本最重要的安全默认值默认拒绝跨域的 canvas WebRTC 指令。跨域直播现在要求每个参与的录制插件实例单独设置recordCrossOriginIframes: true且该选项与 rrweb 的recordCrossOriginIframes录制选项相互独立。仅当页面所内嵌的来源可信时才应启用此选项。这条变更有三个关键语义需要逐一拆解默认值收紧不传该选项默认false时来自其他源的postMessage画布指令一律被丢弃两个同名选项、两层含义插件构造参数recordCrossOriginIframes控制的是插件是否接受跨域画布信令而 rrweb 核心record()的recordCrossOriginIframes选项定义见 types.ts默认false见 record/index.ts控制的是是否录制跨域 iframe 内的增量事件。两者必须分别开启缺一不可——CHANGELOG 与 README 的 Cross-origin recording 一节都特别强调了在record()里开启那个选项并不会启用本插件的跨域画布直播逐实例开启根页面与每一个参与直播的跨域 iframe 中的插件实例都要显式传recordCrossOriginIframes: true。源码层面的 origin 检查实现默认拦截逻辑落在 src/index.ts 的windowPostMessageHandler中。插件构造时L43-L59注册全局message监听并把recordCrossOriginIframes保存为只读字段注释即仅对可信的嵌入页面开启。消息处理入口先做结构校验isCrossOriginIframeMessageEventContent要求消息体形如{ type: rrweb-canvas-webrtc, data: ... }随后是 origin 策略// Opaque origins serialize to null but are not same-origin with each other. if ( !this.recordCrossOriginIframes (!event.origin || event.origin null || event.origin ! window.origin) ) return;这段代码L289-L321覆盖了三类边缘情况空 origin 与nullorigin无allow-same-origin的沙箱 frame 等不透明来源其 origin 序列化为字符串null但彼此并不同源因此不能简单比较字符串相等必须在未显式 opt-in 时一律拒绝同源消息event.origin window.origin时放行且无论插件是否 opt-in 都放行条件是!this.recordCrossOriginIframes时才拒绝同源消息满足event.origin window.origin不会进入拒绝分支跨域消息只有recordCrossOriginIframes: true才放行。放行后处理器按data.type分派三种指令消息类型载荷处理动作who-has-canvas{ id, rootId }调用setupStream(id, rootId)在本地或递归到子 iframe为该节点建立画布流signal{ signal }根帧中走signalReceiveFromCrossOriginIframe(signal, source)建立对跨域 iframe 的应答连接子帧中走signalReceive(signal)中继到根帧/回放端i-have-canvas{ rootId }将来源窗口写入canvasWindowMaprootId - WindowProxy登记哪个 iframe 拥有哪个画布测试用例对 origin 策略的完整覆盖test/post-message.test.ts 中的canvas postMessage origin policy测试组L63-L143把上述语义逐条固化为断言blocks every cross-origin command with opt-in %sundefined与false两例来自https://attacker.example的三类消息全部被丢弃setupStream、signalReceive、signalReceiveFromCrossOriginIframe均未被调用canvasWindowMap保持为空blocks an untrusted origin %s by default与null两例空 origin 与不透明nullorigin 在默认配置下被拦截preserves same-origin commands with opt-in %sfalse与true两例同源消息无论 opt-in 与否都正常处理setupStream以(1, 2)被调用canvasWindowMap正确登记来源窗口accepts cross-origin commands after explicit opt-in显式传recordCrossOriginIframes: true后来自https://trusted.example的三类消息全部生效does not equate opaque origins与uses the effective origin in a sandboxed document即使页面自身处于 origin 为null的沙箱文档中两个不同的不透明来源也不会被误判为同源且比较基准是文档的有效 origin测试通过 mockwindow.origingetter 验证。值得注意的是README 在说明该选项时给出了配套部署约束开启recordCrossOriginIframes意味着接受任意来源的指令插件不对嵌入页面做身份认证也不提供信令白名单因此开启该选项的页面必须用响应头Content-Security-Policy: frame-ancestors等手段把可嵌入来源限制在可信范围内若页面可能被不可信站点嵌入应保持该选项关闭。跨域消息流与 WebRTC 信令origin 检查背后的完整链路理解了 2.1.4 的拦截点之后再看插件如何组织跨域直播就能明白为什么跨域指令值得这么严格的默认策略。核心实现在 setupStream 与 setupPeersetupStream(id, rootId?)先从 rrweb 的节点镜像getMirror回调注入的nodeMirror取出对应HTMLCanvasElement确认元素具备captureStream能力后调用el.captureStream()建立MediaStream并以rootId为键存入streamMap随后惰性初始化 WebRTC 对端若本地镜像里查不到该节点则转入 setupStreamInCrossOriginIframe遍历页面内全部 iframe借助 rrweb 的crossOriginIframeMirror.getRemoteId(iframe, id)把根帧视角的节点 id 换算为 iframe 内部视角的 id再向对应contentWindow发出who-has-canvas消息——这正是 2.1.4 默认拦截的那类指令也解释了根页面与每个 iframe 都要 opt-in的原因消息链路上每一跳都要通过各自的 origin 检查setupPeer区分两种角色根帧创建initiator: true的 SimplePeer 连接面向回放端为跨域 iframe 创建initiator: false的应答连接通过windowPeerMap/peerWindowMap两个 WeakMap 一一映射。信令SDP offer/answer在根帧与回放端之间经signalSendCallback/signalReceive往返在根帧与跨域 iframe 之间经postMessage的signal消息中转媒体流通过 WebRTC data channel 传递{ nodeId, streamId }的 JSON 描述见 startStream 与WebRTCDataChannel类型连接建立connect时会把streamMap中已有的全部流重发给对端incomingStreams到达后再经flushStreams按streamNodeMap的 id 映射转发保证后加入的对端也能拿到全部画面。完整使用方式录制端、回放端与跨域配置以下示例完整继承自 插件 README是当前仓库推荐的接线方式。录制端// Record side import { record } from rrweb/record; import { RRWebPluginCanvasWebRTCRecord } from rrweb/rrweb-plugin-canvas-webrtc-record; const webRTCRecordPlugin new RRWebPluginCanvasWebRTCRecord({ signalSendCallback: (msg) { // provides webrtc sdp offer signal connect message // make sure you send this to the replayers webRTCReplayPlugin.signalReceive(signal) sendSignalToReplayer(msg); // example of function that sends the signal to the replayer }, }); record({ emit: (event) { // send these events to the replayer.addEvent(event), how you do that is up to you // you can send them to a server for example which can then send them to the replayer sendEventToReplayer(event); // example of function that sends the event to the replayer }, plugins: [ // add the plugin to the list of plugins, and initialize it via .initPlugin() webRTCRecordPlugin.initPlugin(), ], recordCanvas: false, // we dont want canvas recording turned on, were going to do that via the plugin });要点recordCanvas必须保持false走插件的 WebRTC 直播而非事件快照信令回调signalSendCallback需要自己实现到回放端的传输通道如 WebSocket回放端收到后调用webRTCReplayPlugin.signalReceive(signal)。跨域录制2.1.4 起的推荐配置const webRTCRecordPlugin new RRWebPluginCanvasWebRTCRecord({ signalSendCallback: sendSignalToReplayer, recordCrossOriginIframes: true, });在根页面与每个参与直播的 iframe 中都如此构造插件同时若要录制跨域 iframe 内的普通事件再在record()选项中开启 rrweb 自己的recordCrossOriginIframes。同源 iframe 不受影响、无需 opt-in。回放端// Replay side import { Replayer } from rrweb/replay; import { RRWebPluginCanvasWebRTCReplay } from rrweb/rrweb-plugin-canvas-webrtc-replay; const webRTCReplayPlugin new RRWebPluginCanvasWebRTCReplay({ canvasFoundCallback(canvas, context) { console.log(canvas, canvas); // send the canvas id to webRTCRecordPlugin.setupStream(id), how you do that is up to you sendCanvasIdToRecordScript(context.id); // example of function that sends the id to the record script }, signalSendCallback(signal) { // provides webrtc sdp offer signal connect message // make sure you send this to the record scripts webRTCRecordPlugin.signalReceive(signal) sendSignalToRecordScript(signal); // example of function that sends the signal to the record script }, }); const replayer new Replayer([], { UNSAFE_replayCanvas: true, // turn canvas replay on! liveMode: true, // live mode is needed to stream events to the replayer plugins: [webRTCReplayPlugin.initPlugin()], }); replayer.startLive(); // start the replayer in live mode replayer.addEvent(event); // call this whenever an event is received from the record script回放端通过canvasFoundCallback拿到重建出的 canvas 节点 id再回传给录制端触发setupStream(id)形成回放端发现画布 → 通知录制端开播的闭环。需要特别注意 README 中的风险提示开启画布回放会给回放 iframe 加上allow-scripts并退出 rrweb 的沙箱脚本执行保护UNSAFE_replayCanvas只应用于自己接受其风险的回放数据。该插件方案与事件快照方案的取舍可对照官方 Canvas recipe 阅读。在仓库中验证与复现单元测试插件包的测试脚本为vitest run跨域策略行为全部由 test/post-message.test.ts 中的 origin policy 用例守护端到端直播演示仓库提供了基于 Playwright 的直播脚本 packages/rrweb/scripts/stream.js其中_signal/_canvas暴露函数演示了完整的信令与开播流程window.plugin.signalReceive(signal)、window.plugin.setupStream(id)README 中提到的yarn live-stream示例即基于此脚本可作为理解本文信令链路的可运行参照构建与类型检查yarn turbo run prepublish会先执行tsc -noEmit再做 Vite 构建产物即dist/与umd/两套分发文件与上文 2.0.0 版本说明的文件布局一一对应。小结这份 CHANGELOG 记录了一条清晰的演进线2.0.0 通过插件拆包与 ESM/CJS/UMD 三格式分发让 canvas-webrtc-record 成为可按需引入、版本独立的rrweb/rrweb-plugin-canvas-webrtc-record2.1.4 则针对跨域 postMessage 信令把安全默认值从宽松收紧为默认拒绝用插件实例级的recordCrossOriginIframes选项与 rrweb 核心同名选项解耦并以 origin 策略测试套件把同源放行、不透明来源拒绝、显式 opt-in 三类行为固化为可回归验证的实现事实。对集成方而言升级到这个版本后的行动项很明确确认自身是否存在跨域画布直播需求若有则在根页面与全部参与 iframe 的插件实例上显式开启该选项并用Content-Security-Policy: frame-ancestors收敛可嵌入来源。【免费下载链接】rrwebrecord and replay the web项目地址: https://gitcode.com/gh_mirrors/rr/rrweb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进