From 0b70c0c0c1306ee0238b10330370f2b8d5a2c600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Sun, 1 Feb 2026 00:21:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=201=E3=80=81=E7=A7=BB=E5=8A=A8popup?= =?UTF-8?q?=E7=9A=84page=E3=80=81components=202=E3=80=81=E6=9B=B4=E6=96=B0?= =?UTF-8?q?wxt=E7=9A=84host=E6=9D=83=E9=99=90=203=E3=80=81=E9=AA=8C?= =?UTF-8?q?=E8=AF=81message=E4=BC=A0=E9=80=92=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entrypoints/background.ts | 62 +++++++++---------- entrypoints/content.ts | 20 ++---- entrypoints/popup/App.tsx | 4 +- .../popup/components}/CopyButton.tsx | 0 .../popup/components}/DatetimeToTimestamp.tsx | 2 +- .../popup/components}/TimestampExecution.tsx | 0 .../popup/components}/TimestampToDatetime.tsx | 2 +- .../popup/pages}/RecordeReplayPage.tsx | 57 +++++++---------- .../popup/pages}/TimestampPage.tsx | 0 entrypoints/popup/types.tsx | 4 ++ utils/messages.tsx | 16 ++++- wxt.config.ts | 1 + 12 files changed, 83 insertions(+), 85 deletions(-) rename {components => entrypoints/popup/components}/CopyButton.tsx (100%) rename {components => entrypoints/popup/components}/DatetimeToTimestamp.tsx (98%) rename {components => entrypoints/popup/components}/TimestampExecution.tsx (100%) rename {components => entrypoints/popup/components}/TimestampToDatetime.tsx (98%) rename {pages => entrypoints/popup/pages}/RecordeReplayPage.tsx (53%) rename {pages => entrypoints/popup/pages}/TimestampPage.tsx (100%) create mode 100644 entrypoints/popup/types.tsx diff --git a/entrypoints/background.ts b/entrypoints/background.ts index f6f2aac..ba4f3f5 100644 --- a/entrypoints/background.ts +++ b/entrypoints/background.ts @@ -2,8 +2,7 @@ import '../.wxt/types/imports.d.ts'; import { browser } from 'wxt/browser'; export default defineBackground(() => { - console.log('Hello background!', { id: browser.runtime.id }); - + // 监听扩展安装或更新事件 browser.runtime.onInstalled.addListener(async ({ reason }) => { if (reason === 'install') { // 第一次安装扩展时触发 @@ -28,41 +27,42 @@ export default defineBackground(() => { } }); - 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 }); + // 监听来自 popup script 的消息 + chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => { + if (msg.type === messages.popup.checkStatus) { + console.log('[bg] checkStatus received'); + // void chrome.runtime.sendMessage({ type: messages.content.checkStatus }); + sendToActiveTab({ type: messages.content.checkStatus }) + .then(() => { + sendResponse({ ok: true }); + }) + .catch(() => { + sendResponse({ ok: false }); + }); + return true; } - if (msg.type === 'DOWNLOAD') { - console.log('[bg] DOWNLOAD received'); + if (msg.type === messages.offscreen.to.startRecording) { + console.log('[bg] startRecording received'); sendResponse({ ok: true }); + // void browser.runtime.sendMessage({ type: messages.popup.to.started }); } return true; }); - // async function _sendToActiveTab(message: { - // type: string; - // data?: unknown; - // activeTabId?: number; - // [key: string]: unknown; - // }) { - // const activeTabs = await browser.tabs.query({ - // active: true, - // currentWindow: true, - // }); - // const activeTab = activeTabs[0]; - // const sendTo = message.activeTabId || activeTab.id; - // await browser.tabs.sendMessage(sendTo!, message); - // } + async function sendToActiveTab(message: { + type: string; + data?: unknown; + activeTabId?: number; + [key: string]: unknown; + }) { + const activeTabs = await chrome.tabs.query({ + active: true, + currentWindow: true, + }); + const activeTab = activeTabs[0]; + const sendTo = message.activeTabId || activeTab.id; + await chrome.tabs.sendMessage(sendTo!, message); + } }); diff --git a/entrypoints/content.ts b/entrypoints/content.ts index bb9d2a9..be7cb99 100644 --- a/entrypoints/content.ts +++ b/entrypoints/content.ts @@ -1,25 +1,17 @@ import '../.wxt/types/imports.d.ts'; -import { browser } from 'wxt/browser'; export default defineContentScript({ // matches: ['*://*.google.com/*'], matches: [''], runAt: 'document_start', main() { - 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' }); + // 监听来自 background script 的消息 + chrome.runtime.onMessage.addListener((msg: { type: string }, _sender, sendResponse) => { + if (msg.type === messages.content.checkStatus) { + console.log('[content] checkStatus received'); + sendResponse({ ok: true }); } else { - console.log('Unknown command received in content script'); - sendResponse({ status: 'Unknown command' }); + console.log('[content] unknown message type:', msg.type); } return true; diff --git a/entrypoints/popup/App.tsx b/entrypoints/popup/App.tsx index 6c20c07..d0bacb6 100644 --- a/entrypoints/popup/App.tsx +++ b/entrypoints/popup/App.tsx @@ -1,7 +1,7 @@ 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 TimestampPage from './pages/TimestampPage'; +import RecordeReplayPage from './pages/RecordeReplayPage'; import './App.css'; const navItems = [ diff --git a/components/CopyButton.tsx b/entrypoints/popup/components/CopyButton.tsx similarity index 100% rename from components/CopyButton.tsx rename to entrypoints/popup/components/CopyButton.tsx diff --git a/components/DatetimeToTimestamp.tsx b/entrypoints/popup/components/DatetimeToTimestamp.tsx similarity index 98% rename from components/DatetimeToTimestamp.tsx rename to entrypoints/popup/components/DatetimeToTimestamp.tsx index 8c6fc0b..885e4ec 100644 --- a/components/DatetimeToTimestamp.tsx +++ b/entrypoints/popup/components/DatetimeToTimestamp.tsx @@ -1,4 +1,4 @@ -import { formatWithDate, formatWithZone } from '../utils/timeUtils'; +import { formatWithDate, formatWithZone } from '../../../utils/timeUtils'; import { useState, useCallback } from 'react'; /** diff --git a/components/TimestampExecution.tsx b/entrypoints/popup/components/TimestampExecution.tsx similarity index 100% rename from components/TimestampExecution.tsx rename to entrypoints/popup/components/TimestampExecution.tsx diff --git a/components/TimestampToDatetime.tsx b/entrypoints/popup/components/TimestampToDatetime.tsx similarity index 98% rename from components/TimestampToDatetime.tsx rename to entrypoints/popup/components/TimestampToDatetime.tsx index ccd7991..662a39f 100644 --- a/components/TimestampToDatetime.tsx +++ b/entrypoints/popup/components/TimestampToDatetime.tsx @@ -1,5 +1,5 @@ import { useState, useCallback } from 'react'; -import { formatWithZone } from '../utils/timeUtils'; +import { formatWithZone } from '../../../utils/timeUtils'; /** * 时间戳转日期时间组件 diff --git a/pages/RecordeReplayPage.tsx b/entrypoints/popup/pages/RecordeReplayPage.tsx similarity index 53% rename from pages/RecordeReplayPage.tsx rename to entrypoints/popup/pages/RecordeReplayPage.tsx index 3b24d56..e1a817b 100644 --- a/pages/RecordeReplayPage.tsx +++ b/entrypoints/popup/pages/RecordeReplayPage.tsx @@ -1,6 +1,9 @@ import { useEffect, useState } from 'react'; +import { AppState } from '../types'; +import { messages } from '../../../utils/messages'; const RecordeReplayPage = () => { + const [status, setStatus] = useState(AppState.READ); const [isRecording, setIsRecording] = useState(false); const getActiveTab = async () => { @@ -8,14 +11,29 @@ const RecordeReplayPage = () => { return tab; }; + useEffect(() => { + const handleMessage = (msg: { type: string }) => { + if (msg.type === messages.popup.ready) { + setStatus(AppState.READ); + } else if (msg.type === messages.popup.to.started) { + setStatus(AppState.RECORDING); + } else if (msg.type === messages.popup.to.stoped) { + setStatus(AppState.READ); + } + }; + + browser.runtime.onMessage.addListener(handleMessage); + + browser.runtime.sendMessage({ type: messages.popup.checkStatus }); + + return () => browser.runtime.onMessage.removeListener(handleMessage); + }, []); + 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) { @@ -24,6 +42,8 @@ const RecordeReplayPage = () => { } if (response?.isRecording) { setIsRecording(true); + setStatus(AppState.RECORDING); + console.log(status); } }); } catch (e) { @@ -32,37 +52,11 @@ const RecordeReplayPage = () => { } }; checkStatus(); - }, []); + }, [status]); 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 ( @@ -71,9 +65,6 @@ const RecordeReplayPage = () => { - ); diff --git a/pages/TimestampPage.tsx b/entrypoints/popup/pages/TimestampPage.tsx similarity index 100% rename from pages/TimestampPage.tsx rename to entrypoints/popup/pages/TimestampPage.tsx diff --git a/entrypoints/popup/types.tsx b/entrypoints/popup/types.tsx new file mode 100644 index 0000000..6cb8e9c --- /dev/null +++ b/entrypoints/popup/types.tsx @@ -0,0 +1,4 @@ +export enum AppState { + READ = 'ready', + RECORDING = 'recording', +} diff --git a/utils/messages.tsx b/utils/messages.tsx index ba858f1..21d92e5 100644 --- a/utils/messages.tsx +++ b/utils/messages.tsx @@ -1,12 +1,22 @@ export const messages = { - content: { + popup: { from: { - + start: 'popup:start', }, + to: { + stoped: 'popup:stoped', + started: 'popup:started', + }, + checkStatus: 'popup:check-status', + ready: 'popup:ready', + }, + content: { + from: {}, to: { startRecording: 'content:start-recording', stopRecording: 'content:stop-recording', - } + }, + checkStatus: 'content:check-status', }, offscreen: { to: { diff --git a/wxt.config.ts b/wxt.config.ts index efee20b..7a1b6b9 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ 'tabs', 'offscreen', ], + host_permissions: [''], action: { default_title: 'Testing Tools', },