feat: 添加离屏文档创建和下载功能,优化录制逻辑
This commit is contained in:
@@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="main.tsx" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -311,7 +311,6 @@ body {
|
|||||||
|
|
||||||
.nav-list {
|
.nav-list {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
@@ -43,7 +43,7 @@ function App() {
|
|||||||
const collapsedNavItems = navItems.slice(visibleItems);
|
const collapsedNavItems = navItems.slice(visibleItems);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router future={{v7_startTransition: true, v7_relativeSplatPath: true}}>
|
<Router future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<nav className="nav">
|
<nav className="nav">
|
||||||
<div className="nav-content">
|
<div className="nav-content">
|
||||||
|
|||||||
+14
-9
@@ -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,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
@@ -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',
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user