diff --git a/entrypoints/background.ts b/entrypoints/background.ts index eda83e4..a3e1f97 100644 --- a/entrypoints/background.ts +++ b/entrypoints/background.ts @@ -1,27 +1,65 @@ 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'); + browser.runtime.onInstalled.addListener(async ({ reason }) => { + if (reason === 'install') { + // 第一次安装扩展时触发 + console.log('Extension installed for the first time'); + } else if (reason === 'update') { + // 扩展更新时触发 + console.log('Extension updated to a new version'); + } + + // 给所有已打开的标签页注入 content script + for (const tab of await browser.tabs.query({})) { + if (tab.url?.match(/(chrome|chrome-extension):\/\//gi) || !tab.id) { + continue; } + + // 注入 content script + const res = browser.scripting.executeScript({ + target: { tabId: tab.id }, + files: ['/content-scripts/content.js'], + }); + console.log('Content script injected on installed/updated:', res); + } + }); + + chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + // 处理来自其他部分的消息 + console.log('[bg] message received:', msg, sender.tab); + + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + const currentTab = tabs[0]; + console.log('[bg] 真正活跃的页面标题是:', currentTab?.title); + sendResponse({ ok: true, title: currentTab?.title }); + }); + + if (msg.type === 'CREATE_OFFSCREEN') { + 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, - }); + sendResponse({ ok: true }); } + + return true; }); + + async function sendToActiveTab(message: { + type: string; + data?: any; + activeTabId?: number; + [key: string]: any; + }) { + let activeTabs = await browser.tabs.query({ + active: true, + currentWindow: true, + }); + let activeTab = activeTabs[0]; + const sendTo = message.activeTabId || activeTab.id; + await browser.tabs.sendMessage(sendTo!, message); + } }); diff --git a/entrypoints/content.ts b/entrypoints/content.ts index 264a528..728b276 100644 --- a/entrypoints/content.ts +++ b/entrypoints/content.ts @@ -1,6 +1,27 @@ +// import { useRecorder } from '../hooks/useRecorder'; + export default defineContentScript({ - matches: ['*://*.google.com/*'], + // matches: ['*://*.google.com/*'], + matches: [''], + runAt: 'document_start', main() { - console.log('Hello content.'); + console.log('Content script loaded successfully', { id: browser.runtime.id }); + + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'START_RECORD') { + // startRecord(); + console.log('Start recording command received in content script'); + sendResponse({ status: 'Recording started' }); + } else if (message.type === 'STOP_RECORD') { + // stopRecord(); + console.log('Stop recording command received in content script'); + sendResponse({ status: 'Recording stopped' }); + } else { + console.log('Unknown command received in content script'); + sendResponse({ status: 'Unknown command' }); + } + + return true; + }); }, }); diff --git a/entrypoints/offscreen/main.tsx b/entrypoints/offscreen/main.tsx index f55d820..36094fa 100644 --- a/entrypoints/offscreen/main.tsx +++ b/entrypoints/offscreen/main.tsx @@ -1,28 +1,28 @@ -import { useRef } from 'react'; +// import { useRef } from 'react'; console.log('[offscreen] loaded'); -const events = []; +// 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 - } +// 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); - } -}); +// 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); +// } +// }); diff --git a/hooks/useRecorder.tsx b/hooks/useRecorder.tsx index f572dc1..38f9134 100644 --- a/hooks/useRecorder.tsx +++ b/hooks/useRecorder.tsx @@ -1,28 +1,21 @@ -import { useRef, useState } from 'react'; +import { useRef, useState, useCallback } from 'react'; import { record } from 'rrweb'; export const useRecorder = () => { // 存储停止录制函数 - const stopFnRef = useRef(); + const stopFnRef = useRef<(() => void) | null>(null); const [isRecording, setIsRecording] = useState(false); - const startRecord = async () => { + const startRecord = useCallback(async () => { if (isRecording) return; - await chrome.runtime.sendMessage({ type: 'CREATE_OFFSCREEN' }); - setIsRecording(true); + try { + // await chrome.runtime.sendMessage({ type: 'CREATE_OFFSCREEN' }); + setIsRecording(true); - // 启动录制 - stopFnRef.current = record({ - emit(event) { - // 存储数据 - // eventRef.current.push(event); - chrome.runtime.sendMessage({ type: 'SAVE_EVENT', event }); - }, - }); - - console.log('[content] rrweb started'); - }; + console.log('[content] rrweb started'); + } catch (error) {} + }, [isRecording]); const stopRecord = () => { if (!isRecording) return; @@ -42,6 +35,5 @@ export const useRecorder = () => { startRecord, stopRecord, isRecording, - getEvents: () => eventRef.current, }; }; diff --git a/pages/RecordeReplayPage.tsx b/pages/RecordeReplayPage.tsx index 325a1a3..830c260 100644 --- a/pages/RecordeReplayPage.tsx +++ b/pages/RecordeReplayPage.tsx @@ -1,57 +1,77 @@ -import { useRecorder } from '../hooks/useRecorder'; -import { downloadHtml } from '../utils/recordUtils'; - const RecordeReplayPage = () => { - // const [isRecording, setIsRecording] = useState(false); + const [isRecording, setIsRecording] = useState(false); - const { startRecord, stopRecord, getEvents, isRecording } = useRecorder(); - - const handleStart = async () => { - try { - // await sendRecordCommand("START_RECORD"); - if (!isRecording) { - await startRecord(); - } else { - stopRecord(); - } - // await storage.set({isRecording: true, recordingStartTime: Date.now()}); - // console.log('events', events); - } catch (e) { - console.error('Error starting recording:', e); - } + const getActiveTab = async () => { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + return tab; }; - const handleStop = async () => { - try { - // await sendRecordCommand("STOP_RECORD"); - // const events =useRecorder().getEvents(); - // console.log('events', events); - // setIsRecording(false); - // await storage.remove('isRecording'); - stopRecord(); - // downloadHtml(getEvents()); - // console.log('events', getEvents()); - } catch (e) { - console.error('Error stopping recording:', e); - } + useEffect(() => { + const checkStatus = async () => { + const tab = await getActiveTab(); + if (tab?.id) { + try { + // 发送一个检查状态的命令(需要在 content script 增加对应的处理) + // 或者使用 storage 方案(推荐) + // 这里假设你用 sendMessage 检查 + chrome.tabs.sendMessage(tab.id, { command: 'CHECK_STATUS' }, (response) => { + // 如果 content script 没加载,这里会报错,需要 catch + if (chrome.runtime.lastError) { + console.log('Content script not ready'); + return; + } + if (response?.isRecording) { + setIsRecording(true); + } + }); + } catch (e) { + console.error(e); + } + } + }; + checkStatus(); + }, []); + + const toggleRecording = async () => { + const tab = await getActiveTab(); + if (!tab?.id) return; + + // 2. 无论开始还是停止,都重新获取当前的 Tab ID + // 不要依赖 state 中的 tabId,因为 popup 关闭后 state 会丢 + const nextState = !isRecording; + const command = nextState ? 'START_RECORD' : 'STOP_RECORD'; + + chrome.tabs.sendMessage(tab.id, { type: command }, (response) => { + // 处理 runtime.lastError 防止报错红字 + if (chrome.runtime.lastError) { + console.error('通信失败:', chrome.runtime.lastError.message); + alert('请刷新当前网页后再试(Content Script 未注入)'); + return; + } + + console.log('Content回复:', response); + if (response?.status) { + // 只有收到确认回复后,才改变 UI 状态 + setIsRecording(nextState); + } + }); + }; + + const test = async () => { + await chrome.runtime.sendMessage({ type: 'test' }).then((response) => { + console.log('[popup] Response from background:', response); + }); }; return ( -
-

RRWeb Recorder

- - {/* 3. 选择标签页逻辑:默认为当前页,如果需要跨页,需先列出 chrome.tabs.query */} - +
- {!isRecording ? ( - - ) : ( - - )} + +
); diff --git a/utils/messages.tsx b/utils/messages.tsx new file mode 100644 index 0000000..ba858f1 --- /dev/null +++ b/utils/messages.tsx @@ -0,0 +1,17 @@ +export const messages = { + content: { + from: { + + }, + to: { + startRecording: 'content:start-recording', + stopRecording: 'content:stop-recording', + } + }, + offscreen: { + to: { + startRecording: 'offscreen:start-recording', + stopRecording: 'offscreen:stop-recording', + }, + }, +}; diff --git a/wxt.config.ts b/wxt.config.ts index ea38ca3..efee20b 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -7,7 +7,15 @@ export default defineConfig({ name: 'Testing Tools', version: '1.0', description: '测试工具', - permissions: ['storage', 'clipboardWrite', 'activeTab', 'scripting', 'tabs', 'offscreen'], + permissions: [ + 'storage', + 'unlimitedStorage', + 'clipboardWrite', + 'activeTab', + 'scripting', + 'tabs', + 'offscreen', + ], action: { default_title: 'Testing Tools', },