feat(M1.4): 模块间通信工具 (v0.0.4)
- 创建 src/shared/messaging.js 通信工具类 - 实现 MessageClient.sendToBackground() 方法 - 实现 BackgroundHandler.sendToContent() 方法 - 提供 ping/pong 测试验证通信链路 - 修复异步响应处理机制
This commit is contained in:
parent
1f1b0a440f
commit
5e7bde1879
@ -1,7 +1,7 @@
|
||||
|
||||
### ai对话常用语句
|
||||
|
||||
继续任务M1.3,必须严格遵守docs/README.md开发流程规范,等我验收通过后才能提交代码。如果你的上下文不够了,记得提醒我新开对话。
|
||||
继续任务M1.3,必须严格遵守docs/README.md开发流程规范,等我验收通过后才能提交代码。如果你的上下文不够了,需要提醒我新开对话。
|
||||
|
||||
M1.2验收通过,提交代码。
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
## 版本速查
|
||||
|
||||
### 当前版本
|
||||
`0.0.3` → 下一目标 `0.0.4` ([M1.4](./M1.md))
|
||||
`0.0.4` → 下一目标 `0.0.5` ([M1.5](./M1.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)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -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 | 开发环境验证 | ⬜ | - |
|
||||
|
||||
---
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "沙拉查词",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "聚合词典划词翻译",
|
||||
"permissions": [
|
||||
"storage",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "salad-dict",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "聚合词典划词翻译",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@ -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 };
|
||||
});
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
219
src/shared/messaging.js
Normal file
219
src/shared/messaging.js
Normal file
@ -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<any>} 响应数据
|
||||
*/
|
||||
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<boolean>}
|
||||
*/
|
||||
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<any>
|
||||
*/
|
||||
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();
|
||||
Loading…
x
Reference in New Issue
Block a user