refactor: 统一扩展消息机制并优化组件结构
1、统一消息系统:将 background、content 和 popup 的通信方式从原生的 chrome.runtime 迁移至 @webext-core/messaging,修复了消息回调返回 undefined 的问题。 2、优化组件结构:将通用组件从 entrypoints/popup/components 迁移至根目录 components,并使用 MUI 重构了 Navbar。 3、同步更新:调整了录制工具 (useRecorder) 和各页面组件(RecordeReplayPage, TestPage等),以适配新的消息协议和组件路径。
This commit is contained in:
@@ -0,0 +1,117 @@
|
|||||||
|
import { NavLink, useLocation } from 'react-router-dom';
|
||||||
|
import { useState, ReactNode, MouseEvent } from 'react';
|
||||||
|
import {
|
||||||
|
AppBar,
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
Toolbar,
|
||||||
|
useMediaQuery,
|
||||||
|
} from '@mui/material';
|
||||||
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
|
|
||||||
|
interface RouteItem {
|
||||||
|
path: string;
|
||||||
|
label: string;
|
||||||
|
element: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NavbarProps {
|
||||||
|
items?: RouteItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function Navbar({ items = [] }: NavbarProps) {
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
const isMenuOpen = Boolean(anchorEl);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
// Replicating the screen size logic from original component
|
||||||
|
const isLargeScreen = useMediaQuery('(min-width:768px)');
|
||||||
|
const isMediumScreen = useMediaQuery('(min-width:480px)');
|
||||||
|
|
||||||
|
let visibleItemsCount: number;
|
||||||
|
if (isLargeScreen) {
|
||||||
|
visibleItemsCount = items.length;
|
||||||
|
} else if (isMediumScreen) {
|
||||||
|
visibleItemsCount = Math.min(3, items.length);
|
||||||
|
} else {
|
||||||
|
// Small screen
|
||||||
|
visibleItemsCount = Math.min(2, items.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleNavItems = items.slice(0, visibleItemsCount);
|
||||||
|
const collapsedNavItems = items.slice(visibleItemsCount);
|
||||||
|
|
||||||
|
const handleMenuOpen = (event: MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the current active tab index for the Tabs value
|
||||||
|
// Using startsWith to handle nested routes correctly.
|
||||||
|
const activeTabIndex = visibleNavItems.findIndex((item) =>
|
||||||
|
location.pathname.startsWith(item.path),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppBar
|
||||||
|
position="static"
|
||||||
|
color="default"
|
||||||
|
elevation={0}
|
||||||
|
sx={{ backgroundColor: 'transparent', borderBottom: 1, borderColor: 'divider' }}
|
||||||
|
>
|
||||||
|
<Toolbar sx={{ justifyContent: 'center', position: 'relative' }}>
|
||||||
|
<Tabs
|
||||||
|
value={activeTabIndex === -1 ? false : activeTabIndex}
|
||||||
|
variant="scrollable"
|
||||||
|
scrollButtons="auto"
|
||||||
|
allowScrollButtonsMobile
|
||||||
|
aria-label="navigation tabs"
|
||||||
|
>
|
||||||
|
{visibleNavItems.map((item) => (
|
||||||
|
<Tab key={item.path} label={item.label} component={NavLink} to={item.path} />
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
{collapsedNavItems.length > 0 && (
|
||||||
|
<Box sx={{ position: 'absolute', right: 8 }}>
|
||||||
|
<IconButton color="inherit" aria-label="open menu" edge="end" onClick={handleMenuOpen}>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={isMenuOpen}
|
||||||
|
onClose={handleMenuClose}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
maxHeight: 48 * 4.5,
|
||||||
|
width: '20ch',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{collapsedNavItems.map((item) => (
|
||||||
|
<MenuItem
|
||||||
|
key={item.path}
|
||||||
|
component={NavLink}
|
||||||
|
to={item.path}
|
||||||
|
onClick={handleMenuClose}
|
||||||
|
selected={location.pathname.startsWith(item.path)}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Navbar;
|
||||||
+54
-81
@@ -1,6 +1,7 @@
|
|||||||
import '../.wxt/types/imports.d.ts';
|
import '../.wxt/types/imports.d.ts';
|
||||||
import { browser } from 'wxt/browser';
|
import { browser } from 'wxt/browser';
|
||||||
import { downloadHtmlInBackground } from '@/utils/recordUtils.tsx';
|
import { downloadHtmlInBackground } from '@/utils/recordUtils.tsx';
|
||||||
|
import { sendMessage, onMessage } from '@/utils/messages';
|
||||||
|
|
||||||
const events: unknown[] = [];
|
const events: unknown[] = [];
|
||||||
|
|
||||||
@@ -51,88 +52,65 @@ export default defineBackground(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听来自 popup script 的消息
|
onMessage('popup:check-status', async (message) => {
|
||||||
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
console.log(`[background] popup:check-status: ${message}`);
|
||||||
if (msg.type === messages.popup.checkStatus) {
|
const obj = {
|
||||||
console.log('[bg] checkStatus received');
|
active: false,
|
||||||
sendToActiveTab({ type: messages.content.checkStatus })
|
startTime: -1,
|
||||||
.then((res) => {
|
};
|
||||||
sendResponse(res);
|
|
||||||
})
|
|
||||||
.catch((error: Error) => {
|
|
||||||
if (error.message.includes('restricted URL')) {
|
|
||||||
console.warn('Could not check status: on a restricted page.', error.message);
|
|
||||||
} else if (error.message.includes('No active tab found')) {
|
|
||||||
console.warn('Could not check status: no active tab found.');
|
|
||||||
} else {
|
|
||||||
console.error('Failed to check status in content script', error);
|
|
||||||
}
|
|
||||||
sendResponse({ ok: false });
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.type === messages.popup.from.start) {
|
try {
|
||||||
console.log('[bg] startRecording received');
|
const tabId = await getActiveTabId();
|
||||||
sendToActiveTab({ type: messages.content.to.startRecording })
|
const result = await sendMessage('content:check-status', undefined, tabId);
|
||||||
.then(async () => {
|
console.log(`[background]111${result}`);
|
||||||
await chrome.runtime.sendMessage({ type: messages.popup.to.started });
|
obj.active = result;
|
||||||
sendResponse({ ok: true });
|
obj.startTime = new Date().getTime();
|
||||||
})
|
} catch (err) {
|
||||||
.catch((error: Error) => {
|
console.error(`[background-err]${err}`);
|
||||||
if (error.message.includes('restricted URL')) {
|
|
||||||
console.warn('Could not start recording: on a restricted page.', error.message);
|
|
||||||
} else if (error.message.includes('No active tab found')) {
|
|
||||||
console.warn('Could not start recording: no active tab found.');
|
|
||||||
} else {
|
|
||||||
console.error('Failed to start recording in content script', error);
|
|
||||||
}
|
|
||||||
sendResponse({ ok: false });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
if (msg.type === messages.popup.from.stop) {
|
onMessage('popup:start', async () => {
|
||||||
console.log('[bg] stopRecording received');
|
console.log('[bg] startRecording received');
|
||||||
sendToActiveTab({ type: messages.content.to.stopRecording })
|
try {
|
||||||
.then(async () => {
|
const tabId = await getActiveTabId();
|
||||||
await chrome.runtime.sendMessage({ type: messages.popup.to.stopped });
|
const response = await sendMessage('content:start-recording', undefined, tabId);
|
||||||
downloadHtmlInBackground(events);
|
if (response.ok) {
|
||||||
sendResponse({ ok: true });
|
await sendMessage('popup:started', undefined);
|
||||||
})
|
return { ok: true };
|
||||||
.catch((error: Error) => {
|
}
|
||||||
if (error.message.includes('restricted URL')) {
|
return { ok: false };
|
||||||
console.warn('Could not stop recording: on a restricted page.', error.message);
|
} catch (error) {
|
||||||
} else if (error.message.includes('No active tab found')) {
|
console.error('Failed to start recording in content script', error);
|
||||||
console.warn('Could not stop recording: no active tab found.');
|
return { ok: false };
|
||||||
} else {
|
|
||||||
console.error('Failed to stop recording in content script', error);
|
|
||||||
}
|
|
||||||
sendResponse({ ok: false });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMessage('popup:stop', async () => {
|
||||||
|
console.log('[bg] stopRecording received');
|
||||||
|
try {
|
||||||
|
const tabId = await getActiveTabId();
|
||||||
|
const response = await sendMessage('content:stop-recording', undefined, tabId);
|
||||||
|
if (response.ok) {
|
||||||
|
await sendMessage('popup:stopped', undefined);
|
||||||
|
downloadHtmlInBackground(events);
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
return { ok: false };
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error('Failed to stop recording in content script', error);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMessage('content:save-tracke-events', (eventsList) => {
|
||||||
|
console.log('[bg] saveTrackeEvents received:', eventsList);
|
||||||
|
events.push(eventsList);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听来自 content script 的消息
|
async function getActiveTabId() {
|
||||||
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
||||||
if (msg.type === messages.content.from.saveTrackeEvents) {
|
|
||||||
console.log('[bg] saveTrackeEvents received:', msg.payload);
|
|
||||||
events.push(msg.payload);
|
|
||||||
sendResponse({ ok: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMessage('getStringLength', (message) => {
|
|
||||||
return message.data.length;
|
|
||||||
});
|
|
||||||
|
|
||||||
async function sendToActiveTab(message: {
|
|
||||||
type: string;
|
|
||||||
data?: unknown;
|
|
||||||
activeTabId?: number;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}) {
|
|
||||||
const activeTabs = await chrome.tabs.query({
|
const activeTabs = await chrome.tabs.query({
|
||||||
active: true,
|
active: true,
|
||||||
currentWindow: true,
|
currentWindow: true,
|
||||||
@@ -159,12 +137,7 @@ export default defineBackground(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendTo = message.activeTabId || activeTab.id;
|
const sendTo = activeTab.id;
|
||||||
try {
|
return sendTo!;
|
||||||
return await chrome.tabs.sendMessage(sendTo!, message);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending message to active tab:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+29
-54
@@ -1,11 +1,6 @@
|
|||||||
import '../.wxt/types/imports.d.ts';
|
import '../.wxt/types/imports.d.ts';
|
||||||
import { createRecorder } from '@/utils/useRecorder';
|
import { createRecorder } from '@/utils/useRecorder';
|
||||||
|
import { onMessage } from '@/utils/messages.tsx';
|
||||||
interface IResponse {
|
|
||||||
ok: boolean;
|
|
||||||
error?: string;
|
|
||||||
data?: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineContentScript({
|
export default defineContentScript({
|
||||||
// matches: ['*://*.google.com/*'],
|
// matches: ['*://*.google.com/*'],
|
||||||
@@ -15,56 +10,36 @@ export default defineContentScript({
|
|||||||
const recorder = createRecorder();
|
const recorder = createRecorder();
|
||||||
let isRecording = false;
|
let isRecording = false;
|
||||||
|
|
||||||
onMessage('getStringLength', (message) => {
|
onMessage('content:check-status', () => {
|
||||||
console.log(`[content] getStringLength received: ${message.data}`);
|
console.log(`[content]${isRecording}`);
|
||||||
|
return isRecording;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听来自 background script 的消息
|
onMessage('content:start-recording', async () => {
|
||||||
chrome.runtime.onMessage.addListener(
|
try {
|
||||||
(msg: { type: string }, _sender, sendResponse: (response: IResponse) => void) => {
|
const started = await recorder.startRecord();
|
||||||
const handleAsyncMessage = async () => {
|
if (started) {
|
||||||
try {
|
isRecording = true;
|
||||||
switch (msg.type) {
|
return { ok: true };
|
||||||
// 在这里处理异步消息类型
|
} else {
|
||||||
case messages.content.checkStatus:
|
return { ok: false, error: 'Failed to start recorder' };
|
||||||
return { ok: true, data: { isRecording } };
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling start recording:', error);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
case messages.content.to.startRecording: {
|
onMessage('content:stop-recording', () => {
|
||||||
const started = await recorder.startRecord(messages.content.from.saveTrackeEvents);
|
try {
|
||||||
|
console.log('[content] stopRecording received');
|
||||||
if (started) {
|
recorder.stopRecord();
|
||||||
isRecording = true;
|
isRecording = false;
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
} else {
|
} catch (error) {
|
||||||
// 如果启动失败,返回错误信息
|
console.error('Error handling stop recording:', error);
|
||||||
return { ok: false, error: 'Failed to start recorder' };
|
return { ok: false };
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
case messages.content.to.stopRecording:
|
|
||||||
// 停止录制的处理逻辑
|
|
||||||
console.log('[content] stopRecording received');
|
|
||||||
recorder.stopRecord();
|
|
||||||
isRecording = false;
|
|
||||||
return { ok: true };
|
|
||||||
|
|
||||||
default:
|
|
||||||
return { ok: false, error: 'Unknown message type' };
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error handling message:', error);
|
|
||||||
return { ok: false };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAsyncMessage().then((response) => {
|
|
||||||
if (sendResponse) {
|
|
||||||
sendResponse(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { HashRouter as Router, Routes, Route } from 'react-router-dom';
|
|||||||
import TimestampPage from './pages/TimestampPage';
|
import TimestampPage from './pages/TimestampPage';
|
||||||
import RecordeReplayPage from './pages/RecordeReplayPage';
|
import RecordeReplayPage from './pages/RecordeReplayPage';
|
||||||
import TestPage from './pages/TestPage';
|
import TestPage from './pages/TestPage';
|
||||||
import Navbar from './components/Navbar';
|
import Navbar from '../../components/Navbar';
|
||||||
import RoutePersistence from './components/RoutePersistence';
|
import RoutePersistence from '../../components/RoutePersistence';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
// 路由配置数据
|
// 路由配置数据
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
import { useState, useEffect, ReactNode } from 'react';
|
|
||||||
import { IconButton } from '@mui/material';
|
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
|
||||||
|
|
||||||
interface RouteItem {
|
|
||||||
path: string;
|
|
||||||
label: string;
|
|
||||||
element: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NavbarProps {
|
|
||||||
items?: RouteItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function Navbar({ items = [] }: NavbarProps) {
|
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
|
||||||
const [visibleItems, setVisibleItems] = useState(items.length);
|
|
||||||
|
|
||||||
// 检测屏幕尺寸变化
|
|
||||||
useEffect(() => {
|
|
||||||
const handleResize = () => {
|
|
||||||
const width = window.innerWidth;
|
|
||||||
setIsMobile(width < 768);
|
|
||||||
|
|
||||||
// 根据屏幕宽度决定显示多少个导航项
|
|
||||||
if (width >= 768) {
|
|
||||||
setVisibleItems(items.length); // 大屏幕显示所有
|
|
||||||
} else if (width >= 480) {
|
|
||||||
// 中等屏幕:如果导航项超过3个,显示3个,否则显示全部
|
|
||||||
setVisibleItems(Math.min(3, items.length));
|
|
||||||
} else {
|
|
||||||
// 小屏幕:如果导航项超过2个,显示2个,否则显示全部
|
|
||||||
setVisibleItems(Math.min(2, items.length));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleResize(); // 初始调用
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
|
||||||
}, [items.length]); // 添加依赖,如果 items 长度变化也需要重新计算
|
|
||||||
|
|
||||||
// 计算哪些导航项应该显示,哪些应该折叠
|
|
||||||
const visibleNavItems = items.slice(0, visibleItems);
|
|
||||||
const collapsedNavItems = items.slice(visibleItems);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav className="nav">
|
|
||||||
<div className="nav-content">
|
|
||||||
<ul className={`nav-list ${isMenuOpen ? 'open' : ''}`}>
|
|
||||||
{/* 显示的导航项 */}
|
|
||||||
{visibleNavItems.map((item) => (
|
|
||||||
<li key={item?.path}>
|
|
||||||
<NavLink
|
|
||||||
to={item?.path}
|
|
||||||
className={({ isActive }) => (isActive ? 'nav-link active' : 'nav-link')}
|
|
||||||
onClick={() => isMobile && setIsMenuOpen(false)}
|
|
||||||
>
|
|
||||||
{item?.label}
|
|
||||||
</NavLink>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* 折叠区域逻辑 */}
|
|
||||||
{collapsedNavItems.length > 0 && (
|
|
||||||
<li className="nav-collapse-item">
|
|
||||||
<div className={`nav-collapse-content ${isMenuOpen ? 'show' : ''}`}>
|
|
||||||
{isMenuOpen &&
|
|
||||||
collapsedNavItems.map((item) => (
|
|
||||||
<NavLink
|
|
||||||
key={item?.path}
|
|
||||||
to={item?.path}
|
|
||||||
className={({ isActive }) => (isActive ? 'nav-link active' : 'nav-link')}
|
|
||||||
onClick={() => setIsMenuOpen(false)}
|
|
||||||
>
|
|
||||||
{item?.label}
|
|
||||||
</NavLink>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
|
||||||
aria-label={isMenuOpen ? '收起菜单' : '展开菜单'}
|
|
||||||
>
|
|
||||||
{isMenuOpen ? <CloseIcon /> : <MenuIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Navbar;
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState, useMemo } from 'react';
|
import { useEffect, useState, useMemo } from 'react';
|
||||||
import { AppState } from '../types';
|
import { AppState } from '../types';
|
||||||
import { messages } from '@/utils/messages';
|
import { sendMessage, onMessage } from '@/utils/messages';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
|
|
||||||
const RecordeReplayPage = () => {
|
const RecordeReplayPage = () => {
|
||||||
@@ -8,27 +8,22 @@ const RecordeReplayPage = () => {
|
|||||||
const isRecording = useMemo(() => status === AppState.RECORDING, [status]);
|
const isRecording = useMemo(() => status === AppState.RECORDING, [status]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleMessage = (msg: { type: string }) => {
|
const unlistenStarted = onMessage('popup:started', () => {
|
||||||
if (msg.type === messages.popup.ready) {
|
console.log('[popup] Received started message');
|
||||||
setStatus(AppState.READ);
|
setStatus(AppState.RECORDING);
|
||||||
}
|
});
|
||||||
|
|
||||||
if (msg.type === messages.popup.to.started) {
|
const unlistenStopped = onMessage('popup:stopped', () => {
|
||||||
console.log('[popup] Received started message');
|
setStatus(AppState.READ);
|
||||||
setStatus(AppState.RECORDING);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.type === messages.popup.to.stopped) {
|
const unlistenReady = onMessage('popup:ready', () => {
|
||||||
setStatus(AppState.READ);
|
setStatus(AppState.READ);
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener(handleMessage);
|
sendMessage('popup:check-status', undefined)
|
||||||
|
|
||||||
browser.runtime
|
|
||||||
.sendMessage({ type: messages.popup.checkStatus })
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res?.data?.isRecording) {
|
if (res?.active) {
|
||||||
setStatus(AppState.RECORDING);
|
setStatus(AppState.RECORDING);
|
||||||
} else {
|
} else {
|
||||||
setStatus(AppState.READ);
|
setStatus(AppState.READ);
|
||||||
@@ -38,13 +33,20 @@ const RecordeReplayPage = () => {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => browser.runtime.onMessage.removeListener(handleMessage);
|
return () => {
|
||||||
|
unlistenStarted();
|
||||||
|
unlistenStopped();
|
||||||
|
unlistenReady();
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleRecording = async () => {
|
const toggleRecording = async () => {
|
||||||
try {
|
try {
|
||||||
const actionType = isRecording ? messages.popup.from.stop : messages.popup.from.start;
|
if (isRecording) {
|
||||||
await browser.runtime.sendMessage({ type: actionType });
|
await sendMessage('popup:stop', undefined);
|
||||||
|
} else {
|
||||||
|
await sendMessage('popup:start', undefined);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error toggling recording:', error);
|
console.error('Error toggling recording:', error);
|
||||||
setStatus(AppState.READ);
|
setStatus(AppState.READ);
|
||||||
|
|||||||
@@ -3,12 +3,8 @@ import { Button } from '@mui/material';
|
|||||||
|
|
||||||
const TestPage = () => {
|
const TestPage = () => {
|
||||||
const handleSeedMessage = async () => {
|
const handleSeedMessage = async () => {
|
||||||
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
const status = await sendMessage('popup:check-status');
|
||||||
tabs.forEach(async (tab) => {
|
console.log(`[popup]status: ${status}`);
|
||||||
console.log(tab.id);
|
|
||||||
const length = await sendMessage('getStringLength', 'hello world', tab.id);
|
|
||||||
console.log('字符串长度:', length);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TimestampToDatetime } from '../components/TimestampToDatetime';
|
import { TimestampToDatetime } from '@/components/TimestampToDatetime';
|
||||||
import { DatetimeToTimestamp } from '../components/DatetimeToTimestamp';
|
import { DatetimeToTimestamp } from '@/components/DatetimeToTimestamp';
|
||||||
import { TimestampExecution } from '../components/TimestampExecution';
|
import { TimestampExecution } from '@/components/TimestampExecution';
|
||||||
|
|
||||||
const TimestampPage = () => {
|
const TimestampPage = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
+2
-1
@@ -48,7 +48,8 @@
|
|||||||
"assets/**/*",
|
"assets/**/*",
|
||||||
"hooks/**/*",
|
"hooks/**/*",
|
||||||
".wxt/types/**/*.ts",
|
".wxt/types/**/*.ts",
|
||||||
".wxt/types/*.d.ts"
|
".wxt/types/*.d.ts",
|
||||||
|
"components"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules", ".wxt", "eslint.config.ts"]
|
"exclude": ["node_modules", ".wxt", "eslint.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-1
@@ -32,7 +32,23 @@ export const messages = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface ProtocolMap {
|
interface ProtocolMap {
|
||||||
getStringLength(data: string): number;
|
// --- Popup 相关 ---
|
||||||
|
'popup:start': () => { ok: boolean };
|
||||||
|
'popup:stop': () => { ok: boolean };
|
||||||
|
'popup:started': () => void;
|
||||||
|
'popup:stopped': () => void;
|
||||||
|
'popup:check-status': () => { active: boolean; startTime?: number };
|
||||||
|
'popup:ready': () => void;
|
||||||
|
|
||||||
|
// --- Content 相关 ---
|
||||||
|
'content:save-tracke-events': (event) => boolean;
|
||||||
|
'content:start-recording': () => { ok: boolean; error?: string };
|
||||||
|
'content:stop-recording': () => { ok: boolean };
|
||||||
|
'content:check-status': () => boolean;
|
||||||
|
|
||||||
|
// --- Offscreen 相关 ---
|
||||||
|
'offscreen:start-recording': (streamId: string) => void;
|
||||||
|
'offscreen:stop-recording': () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { sendMessage, onMessage } = defineExtensionMessaging<ProtocolMap>();
|
export const { sendMessage, onMessage } = defineExtensionMessaging<ProtocolMap>();
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import * as rrweb from 'rrweb';
|
import * as rrweb from 'rrweb';
|
||||||
import { listenerHandler } from '@rrweb/types';
|
import { listenerHandler } from '@rrweb/types';
|
||||||
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
|
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
|
||||||
|
import { sendMessage } from '@/utils/messages';
|
||||||
|
|
||||||
export const createRecorder = () => {
|
export const createRecorder = () => {
|
||||||
let stopFn: listenerHandler | null = null;
|
let stopFn: listenerHandler | null = null;
|
||||||
|
|
||||||
const startRecord = async (msg: string) => {
|
const startRecord = async () => {
|
||||||
try {
|
try {
|
||||||
const handler = rrweb.record({
|
const handler = rrweb.record({
|
||||||
emit(event) {
|
emit(event) {
|
||||||
chrome.runtime.sendMessage({
|
sendMessage('content:save-tracke-events', event);
|
||||||
type: msg,
|
|
||||||
payload: event,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
plugins: [getRecordConsolePlugin()],
|
plugins: [getRecordConsolePlugin()],
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user