跨域 iframe
默认情况下,浏览器会让访问托管在不同域名下的 iframe 内容变得很困难。这是一项安全特性,用于防止恶意站点访问其他站点上的敏感信息。绕过这一安全特性是可行的,但除非你非常严格地只允许你信任的站点将你的网站嵌入 iframe 中,否则不建议这样做。 因为一旦允许录制跨域 iframe,任何恶意网站都可以嵌入你的网站,只要它们运行着 rrweb,就能录制你网站的全部内容。
如何录制跨域 iframe
在父页面中启用跨域 iframe 录制:
import { record } from '@rrweb/record';
record({
emit(event) {}, // all events will be emitted here, including events from cross origin iframes
recordCrossOriginIframes: true,
});在子页面中启用跨域 iframe 回放:
import { record } from '@rrweb/record';
record({
emit(event) {}, // this is required for rrweb, but the child page will not emit any events
recordCrossOriginIframes: true,
});注意事项
开启跨域 iframe 录制后,rrweb 会检查自身是否运行在顶级窗口中。 如果不是,它会通过 postMessage 将事件发送给父窗口。
如果顶级窗口中没有运行 rrweb,那么开启 recordCrossOriginIframes 后事件将会丢失。
如果顶级窗口是恶意网站,它可以监听这些事件,并将其发送到它选择的服务器。
或者,如果你的页面上运行着恶意脚本,它们也可以监听 postMessage;由于子窗口与父窗口之间的通信未加密,它们能够看到这些事件。
将 rrweb 注入跨域 iframe 的方式
1. 网站所有者:在 iframe 中添加 rrweb
如果你同时拥有包含 iframe 的网站和被嵌入 iframe 的网站,可以通过 script 标签在两个页面中都添加 rrweb。
2. 浏览器扩展
参见 https://developer.chrome.com/docs/extensions/mv3/content_scripts/#functionality
3. Puppeteer 脚本
import puppeteer from 'puppeteer';
async function injectRecording(frame) {
await frame.evaluate((rrwebCode) => {
if (window.__IS_RECORDING__) return;
window.__IS_RECORDING__ = true;
(async () => {
function loadScript(code) {
const s = document.createElement('script');
s.type = 'text/javascript';
s.innerHTML = code;
if (document.head) {
document.head.append(s);
} else {
requestAnimationFrame(() => {
document.head.append(s);
});
}
}
loadScript(rrwebCode);
window.rrweb.record({
emit: (event) => {
window._captureEvent(event);
},
recordCrossOriginIframes: true,
});
})();
}, code);
}
const browser = await puppeteer.launch();
const page = (await browser.pages())[0];
const events = []; // contains all events from all frames
await page.exposeFunction('_captureEvent', (event) => {
events.push(event);
});
page.on('framenavigated', async (frame) => {
await injectRecording(frame); // injects rrweb into the iframe
});
await page.goto('https://example.com');
// your events are in the events array4. Electron
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'rrweb-recording-script.js'),
// this turns on preload inside iframes, but disables node integration
nodeIntegrationInSubFrames: true,
nodeIntegration: false,
},
});参见 https://www.electronjs.org/docs/latest/tutorial/tutorial-preload 以及 https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts