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(() => { export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id }); 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 { .nav-list {
width: 100%; width: 100%;
justify-content: space-between;
align-items: center; align-items: center;
} }
+2 -2
View File
@@ -1,13 +1,13 @@
import { HashRouter as Router, Routes, Route, NavLink } from 'react-router-dom'; import { HashRouter as Router, Routes, Route, NavLink } from 'react-router-dom';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import TimestampPage from '../../pages/TimestampPage'; import TimestampPage from '../../pages/TimestampPage';
// import {RecordeReplayPage} from '../../pages/RecordeReplayPage'; import RecordeReplayPage from '../../pages/RecordeReplayPage';
import './App.css'; import './App.css';
const navItems = [ const navItems = [
{ path: '/', label: '时间戳', element: <TimestampPage /> }, { path: '/', label: '时间戳', element: <TimestampPage /> },
// {path: '/dzmy', label: '电子木鱼', element: <ElectronicWoodenFishPage/>}, // {path: '/dzmy', label: '电子木鱼', element: <ElectronicWoodenFishPage/>},
// { path: '/recorde-replay', label: '录制与回放', element: <RecordeReplayPage /> }, { path: '/recorde-replay', label: '录制与回放', element: <RecordeReplayPage /> },
]; ];
function App() { function App() {
+14 -9
View File
@@ -1,9 +1,7 @@
import {useRef, useState} from "react"; import { useRef, useState } from 'react';
import {record} from "rrweb"; import { record } from 'rrweb';
export const useRecorder = () => { export const useRecorder = () => {
// 存储录制事件
const eventRef = useRef([]);
// 存储停止录制函数 // 存储停止录制函数
const stopFnRef = useRef(); const stopFnRef = useRef();
const [isRecording, setIsRecording] = useState(false); const [isRecording, setIsRecording] = useState(false);
@@ -11,15 +9,19 @@ export const useRecorder = () => {
const startRecord = async () => { const startRecord = async () => {
if (isRecording) return; if (isRecording) return;
await chrome.runtime.sendMessage({ type: 'CREATE_OFFSCREEN' });
setIsRecording(true); setIsRecording(true);
// 启动录制 // 启动录制
stopFnRef.current = record({ stopFnRef.current = record({
emit(event) { emit(event) {
// 存储数据 // 存储数据
eventRef.current.push(event); // eventRef.current.push(event);
} chrome.runtime.sendMessage({ type: 'SAVE_EVENT', event });
},
}); });
console.log('[content] rrweb started');
}; };
const stopRecord = () => { const stopRecord = () => {
@@ -29,7 +31,10 @@ export const useRecorder = () => {
// 停止录制 // 停止录制
if (stopFnRef.current) { if (stopFnRef.current) {
stopFnRef.current(); chrome.runtime.sendMessage({ type: 'SAVE_EVENTS' });
stopFnRef.current?.();
console.log('[content] rrweb stopped');
} }
}; };
@@ -37,6 +42,6 @@ export const useRecorder = () => {
startRecord, startRecord,
stopRecord, stopRecord,
isRecording, 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 { useRecorder } from '../hooks/useRecorder';
import { downloadHtml } from '../utils/recordUtils'; import { downloadHtml } from '../utils/recordUtils';
@@ -32,7 +29,7 @@ const RecordeReplayPage = () => {
// setIsRecording(false); // setIsRecording(false);
// await storage.remove('isRecording'); // await storage.remove('isRecording');
stopRecord(); stopRecord();
downloadHtml(getEvents()); // downloadHtml(getEvents());
// console.log('events', getEvents()); // console.log('events', getEvents());
} catch (e) { } catch (e) {
console.error('Error stopping recording:', e); console.error('Error stopping recording:', e);
+1 -7
View File
@@ -7,13 +7,7 @@ export default defineConfig({
name: 'Testing Tools', name: 'Testing Tools',
version: '1.0', version: '1.0',
description: '测试工具', description: '测试工具',
permissions: [ permissions: ['storage', 'clipboardWrite', 'activeTab', 'scripting', 'tabs', 'offscreen'],
'storage',
'clipboardWrite',
'activeTab',
'scripting',
'tabs',
],
action: { action: {
default_title: 'Testing Tools', default_title: 'Testing Tools',
}, },