feat: 添加离屏文档创建和下载功能,优化录制逻辑

This commit is contained in:
雨霖铃
2026-01-29 19:35:55 +08:00
parent ed5e90f0fc
commit 0ff33cbeb3
8 changed files with 87 additions and 33 deletions
+24
View File
@@ -1,3 +1,27 @@
export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id });
chrome.runtime.onMessage.addListener(async (msg, sender, sendResponse) => {
if (msg.type === 'CREATE_OFFSCREEN') {
const exists = await chrome.offscreen.hasDocument();
if (!exists) {
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['DISPLAY_MEDIA', 'USER_MEDIA', 'BLOBS', 'AUDIO_PLAYBACK'],
justification: 'rrweb record & replay',
});
console.log('[bg] offscreen created');
}
sendResponse({ ok: true });
}
if (msg.type === 'DOWNLOAD') {
console.log('[bg] DOWNLOAD received');
chrome.downloads.download({
url: msg.dataUrl,
filename: `rrweb-${Date.now()}.json`,
saveAs: true,
});
}
});
});
+7
View File
@@ -0,0 +1,7 @@
<!doctype html>
<html>
<head>
<script src="main.tsx" type="module"></script>
</head>
<body></body>
</html>
+28
View File
@@ -0,0 +1,28 @@
import { useRef } from 'react';
console.log('[offscreen] loaded');
const events = [];
chrome.runtime.onMessage.addListener((msg) => {
if (msg.type === 'SAVE_EVENT') {
console.log('[offscreen] rrweb event', msg.event);
events.push(msg.event);
// 这里你可以存 IndexedDB / memory / file
}
if (msg.type === 'SAVE_EVENTS') {
console.log('[offscreen] SAVE_EVENTS received');
console.log('[offscreen] total events:', events.length);
console.log('[offscreen] events:', JSON.stringify(events));
// const blob = new Blob([JSON.stringify(events)], {
// type: 'application/json',
// });
// reader.onload = () => {
// chrome.runtime.sendMessage({
// type: 'DOWNLOAD',
// dataUrl: reader.result,
// });
// };
// reader.readAsDataURL(blob);
}
});
-1
View File
@@ -311,7 +311,6 @@ body {
.nav-list {
width: 100%;
justify-content: space-between;
align-items: center;
}
+3 -3
View File
@@ -1,13 +1,13 @@
import { HashRouter as Router, Routes, Route, NavLink } from 'react-router-dom';
import { useState, useEffect } from 'react';
import TimestampPage from '../../pages/TimestampPage';
// import {RecordeReplayPage} from '../../pages/RecordeReplayPage';
import RecordeReplayPage from '../../pages/RecordeReplayPage';
import './App.css';
const navItems = [
{ path: '/', label: '时间戳', element: <TimestampPage /> },
// {path: '/dzmy', label: '电子木鱼', element: <ElectronicWoodenFishPage/>},
// { path: '/recorde-replay', label: '录制与回放', element: <RecordeReplayPage /> },
{ path: '/recorde-replay', label: '录制与回放', element: <RecordeReplayPage /> },
];
function App() {
@@ -43,7 +43,7 @@ function App() {
const collapsedNavItems = navItems.slice(visibleItems);
return (
<Router future={{v7_startTransition: true, v7_relativeSplatPath: true}}>
<Router future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<div className="app">
<nav className="nav">
<div className="nav-content">
+22 -17
View File
@@ -1,42 +1,47 @@
import {useRef, useState} from "react";
import {record} from "rrweb";
import { useRef, useState } from 'react';
import { record } from 'rrweb';
export const useRecorder = () => {
// 存储录制事件
const eventRef = useRef([]);
// 存储停止录制函数
const stopFnRef = useRef();
const [isRecording, setIsRecording] = useState(false);
const startRecord = async () => {
if (isRecording) return;
await chrome.runtime.sendMessage({ type: 'CREATE_OFFSCREEN' });
setIsRecording(true);
// 启动录制
stopFnRef.current = record({
emit(event) {
// 存储数据
eventRef.current.push(event);
}
// eventRef.current.push(event);
chrome.runtime.sendMessage({ type: 'SAVE_EVENT', event });
},
});
console.log('[content] rrweb started');
};
const stopRecord = () => {
if (!isRecording) return;
setIsRecording(false);
// 停止录制
if (stopFnRef.current) {
stopFnRef.current();
chrome.runtime.sendMessage({ type: 'SAVE_EVENTS' });
stopFnRef.current?.();
console.log('[content] rrweb stopped');
}
};
return {
startRecord,
stopRecord,
isRecording,
getEvents: () => eventRef.current
}
};
getEvents: () => eventRef.current,
};
};
+1 -4
View File
@@ -1,6 +1,3 @@
import React from 'react';
// import {sendRecordCommand} from '../services/bridge'
// import {storage} from "../services/storage";
import { useRecorder } from '../hooks/useRecorder';
import { downloadHtml } from '../utils/recordUtils';
@@ -32,7 +29,7 @@ const RecordeReplayPage = () => {
// setIsRecording(false);
// await storage.remove('isRecording');
stopRecord();
downloadHtml(getEvents());
// downloadHtml(getEvents());
// console.log('events', getEvents());
} catch (e) {
console.error('Error stopping recording:', e);
+2 -8
View File
@@ -7,13 +7,7 @@ export default defineConfig({
name: 'Testing Tools',
version: '1.0',
description: '测试工具',
permissions: [
'storage',
'clipboardWrite',
'activeTab',
'scripting',
'tabs',
],
permissions: ['storage', 'clipboardWrite', 'activeTab', 'scripting', 'tabs', 'offscreen'],
action: {
default_title: 'Testing Tools',
},
@@ -28,4 +22,4 @@ export default defineConfig({
128: 'favicon.ico',
},
},
});
});