From 5e7bde1879a5d5d754c750903696f32159f0b5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Tue, 10 Feb 2026 09:52:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(M1.4):=20=E6=A8=A1=E5=9D=97=E9=97=B4?= =?UTF-8?q?=E9=80=9A=E4=BF=A1=E5=B7=A5=E5=85=B7=20(v0.0.4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 src/shared/messaging.js 通信工具类 - 实现 MessageClient.sendToBackground() 方法 - 实现 BackgroundHandler.sendToContent() 方法 - 提供 ping/pong 测试验证通信链路 - 修复异步响应处理机制 --- README.md | 2 +- docs/QUICK_REF.md | 2 +- docs/README.md | 6 +- docs/VERSION.md | 2 +- manifest.json | 2 +- package.json | 2 +- src/background/index.js | 21 ++-- src/content/index.js | 36 +++++-- src/shared/messaging.js | 219 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 267 insertions(+), 25 deletions(-) create mode 100644 src/shared/messaging.js diff --git a/README.md b/README.md index 1ad06cd..f177939 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ### ai对话常用语句 -继续任务M1.3,必须严格遵守docs/README.md开发流程规范,等我验收通过后才能提交代码。如果你的上下文不够了,记得提醒我新开对话。 +继续任务M1.3,必须严格遵守docs/README.md开发流程规范,等我验收通过后才能提交代码。如果你的上下文不够了,需要提醒我新开对话。 M1.2验收通过,提交代码。 diff --git a/docs/QUICK_REF.md b/docs/QUICK_REF.md index 2085b2b..8d88439 100644 --- a/docs/QUICK_REF.md +++ b/docs/QUICK_REF.md @@ -7,7 +7,7 @@ ## 版本速查 ### 当前版本 -`0.0.3` → 下一目标 `0.0.4` ([M1.4](./M1.md)) +`0.0.4` → 下一目标 `0.0.5` ([M1.5](./M1.md)) ### 模块版本范围 diff --git a/docs/README.md b/docs/README.md index 41a4b86..7504e1e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -65,9 +65,9 @@ M11.10完成 → 1.0.0 (正式发布) ## 当前状态 -**当前版本**: `0.0.3` -**当前进度**: 3/97 (3%) -**下一任务**: [M1.4 模块间通信工具](./M1.md#m14-模块间通信工具--目标版本-0004) +**当前版本**: `0.0.4` +**当前进度**: 4/97 (4%) +**下一任务**: [M1.5 开发环境验证](./M1.md#m15-开发环境验证--目标版本-0005) --- diff --git a/docs/VERSION.md b/docs/VERSION.md index d0e4fc0..3236cd9 100644 --- a/docs/VERSION.md +++ b/docs/VERSION.md @@ -20,7 +20,7 @@ | M1.1 | 0.0.1 | 项目初始化 | ✅ | 2026-02-09 | | M1.2 | 0.0.2 | Manifest V3 配置 | ✅ | 2026-02-09 | | M1.3 | 0.0.3 | 构建工具配置 | ✅ | 2026-02-09 | -| M1.4 | 0.0.4 | 模块间通信工具 | ⬜ | - | +| M1.4 | 0.0.4 | 模块间通信工具 | ✅ | 2026-02-09 | | M1.5 | 0.0.5 | 开发环境验证 | ⬜ | - | --- diff --git a/manifest.json b/manifest.json index b05f430..25bbe26 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "沙拉查词", - "version": "0.0.3", + "version": "0.0.4", "description": "聚合词典划词翻译", "permissions": [ "storage", diff --git a/package.json b/package.json index 6c09d9e..7a7c6a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salad-dict", - "version": "0.0.3", + "version": "0.0.4", "description": "聚合词典划词翻译", "private": true, "type": "module", diff --git a/src/background/index.js b/src/background/index.js index 3a6f9d6..4055966 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,14 +1,17 @@ // Background Service Worker -console.log('[SaladDict] Background service worker started') +import { backgroundHandler } from '../shared/messaging.js'; + +console.log('[SaladDict] Background service worker started'); // 监听安装事件 chrome.runtime.onInstalled.addListener((details) => { - console.log('[SaladDict] Extension installed:', details.reason) -}) + console.log('[SaladDict] Extension installed:', details.reason); +}); -// 监听消息 -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - console.log('[SaladDict] Message received:', message) - sendResponse({ pong: true }) - return true -}) +// BackgroundHandler 自动初始化消息监听 +console.log('[SaladDict] Message handler initialized'); + +// 注册自定义消息处理器示例 +backgroundHandler.register('ECHO', async (payload) => { + return { echo: payload }; +}); diff --git a/src/content/index.js b/src/content/index.js index c62b161..2273416 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -1,11 +1,31 @@ // Content Script -console.log('[SaladDict] Content script loaded') +import { messaging, ping } from '../shared/messaging.js'; -// 简单的 ping 测试 -chrome.runtime.sendMessage({ type: 'PING' }, (response) => { - if (chrome.runtime.lastError) { - console.error('[SaladDict] Ping failed:', chrome.runtime.lastError) - } else { - console.log('[SaladDict] Ping success:', response) +console.log('[SaladDict] Content script loaded'); + +// Ping 测试 - 验证通信链路 +async function testCommunication() { + console.log('[SaladDict] Testing communication...'); + + // 测试 1: 简单 Ping + const pingResult = await ping(); + console.log('[SaladDict] Ping test:', pingResult ? '✅ OK' : '❌ Failed'); + + // 测试 2: Echo 消息 + try { + const echoResult = await messaging.sendToBackground('ECHO', { message: 'Hello from content' }); + console.log('[SaladDict] Echo test:', echoResult); + } catch (error) { + console.error('[SaladDict] Echo test failed:', error.message); } -}) +} + +// 延迟执行测试,确保 background 已就绪 +setTimeout(testCommunication, 1000); + +// 监听来自 Background 的消息 +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + console.log('[SaladDict] Content received message:', message); + sendResponse({ received: true }); + return true; +}); diff --git a/src/shared/messaging.js b/src/shared/messaging.js new file mode 100644 index 0000000..6c427bd --- /dev/null +++ b/src/shared/messaging.js @@ -0,0 +1,219 @@ +/** + * @file 模块间通信工具 + * @description 封装 Content Script、Background、Popup 之间的通信 + */ + +/** + * 生成唯一请求ID + * @returns {string} UUID + */ +function generateUUID() { + return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; +} + +/** + * 消息客户端 - 用于 Content Script 和 Popup + */ +class MessageClient { + /** + * 向 Background 发送消息并等待响应 + * @param {string} type - 消息类型 + * @param {Object} payload - 消息数据 + * @param {number} timeout - 超时时间(ms) + * @returns {Promise} 响应数据 + */ + async sendToBackground(type, payload = {}, timeout = 5000) { + const requestId = generateUUID(); + const message = { + type, + payload, + meta: { + timestamp: Date.now(), + requestId + } + }; + + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error(`Request timeout: ${type}`)); + }, timeout); + + chrome.runtime.sendMessage(message, (response) => { + clearTimeout(timer); + + if (chrome.runtime.lastError) { + reject(new Error(chrome.runtime.lastError.message)); + return; + } + + if (response?.error) { + reject(new Error(response.error.message)); + } else { + resolve(response?.payload); + } + }); + }); + } + + /** + * Ping 测试 + * @returns {Promise} + */ + async ping() { + try { + const result = await this.sendToBackground('PING'); + return result?.pong === true; + } catch (error) { + console.error('[Messaging] Ping failed:', error); + return false; + } + } +} + +/** + * Background 消息处理器 - 用于 Service Worker + */ +class BackgroundHandler { + constructor() { + this.handlers = new Map(); + this.init(); + } + + /** + * 注册消息处理器 + * @param {string} type - 消息类型 + * @param {Function} handler - 处理函数 (payload, sender) => Promise + */ + register(type, handler) { + this.handlers.set(type, handler); + } + + /** + * 初始化监听 + */ + init() { + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + this.handleMessage(message, sender, sendResponse); + return true; // 保持消息通道开放 + }); + } + + /** + * 处理消息 + */ + handleMessage(message, sender, sendResponse) { + const { type, payload, meta } = message; + const requestId = meta?.requestId; + + console.log('[Background] Received:', type, payload); + + // 内置 PING 处理 + if (type === 'PING') { + sendResponse({ + type: 'PONG', + payload: { pong: true }, + meta: { requestId } + }); + return; + } + + const handler = this.handlers.get(type); + if (!handler) { + sendResponse({ + type: `${type}_RESPONSE`, + error: { message: `No handler for type: ${type}` }, + meta: { requestId } + }); + return; + } + + // 异步处理 + handler(payload, sender) + .then(result => { + sendResponse({ + type: `${type}_RESPONSE`, + payload: result, + meta: { requestId } + }); + }) + .catch(error => { + console.error('[Background] Handler error:', error); + sendResponse({ + type: `${type}_RESPONSE`, + error: { message: error.message }, + meta: { requestId } + }); + }); + } + + /** + * 向指定 Tab 的 Content Script 发送消息 + * @param {number} tabId - Tab ID + * @param {string} type - 消息类型 + * @param {Object} payload - 消息数据 + */ + async sendToContent(tabId, type, payload = {}) { + const requestId = generateUUID(); + const message = { + type, + payload, + meta: { + timestamp: Date.now(), + requestId + } + }; + + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error(`Send to content timeout: ${type}`)); + }, 5000); + + chrome.tabs.sendMessage(tabId, message, (response) => { + clearTimeout(timer); + if (chrome.runtime.lastError) { + reject(new Error(chrome.runtime.lastError.message)); + } else if (response?.error) { + reject(new Error(response.error.message)); + } else { + resolve(response?.payload); + } + }); + }); + } + + /** + * 向所有 Tab 广播消息 + * @param {string} type - 消息类型 + * @param {Object} payload - 消息数据 + */ + async broadcastToAllContent(type, payload = {}) { + const tabs = await chrome.tabs.query({}); + const results = []; + + for (const tab of tabs) { + if (tab.id) { + try { + const result = await this.sendToContent(tab.id, type, payload); + results.push({ tabId: tab.id, success: true, result }); + } catch (error) { + results.push({ tabId: tab.id, success: false, error: error.message }); + } + } + } + + return results; + } +} + +// 导出单例 +export const messaging = new MessageClient(); +export const backgroundHandler = new BackgroundHandler(); + +// 兼容性导出 +export const sendToBackground = (type, payload, timeout) => + messaging.sendToBackground(type, payload, timeout); + +export const sendToContent = (tabId, type, payload) => + backgroundHandler.sendToContent(tabId, type, payload); + +export const ping = () => messaging.ping();