第08章 消息传递机制
12/3/25About 13 min
第8章:消息传递机制
学习目标
- 理解Chrome扩展的消息传递架构
- 掌握不同组件间的通信方式
- 学会处理异步消息和错误
- 实现高效的数据传输策略
8.1 消息传递概述
Chrome扩展的消息传递机制是连接各个组件的桥梁,实现background script、content script、popup和options页面之间的通信。
8.1.1 消息传递架构
通信特点
- 基于JSON的消息格式
- 异步通信模式
- 支持请求-响应模式
- 可以传递复杂数据结构
- 具备错误处理机制
8.1.2 消息类型分类
// 消息类型定义
const MessageTypes = {
// 一次性消息
SIMPLE_REQUEST: 'simple_request',
// 长连接消息
PORT_CONNECT: 'port_connect',
// 扩展内部通信
INTERNAL_COMMAND: 'internal_command',
// 外部通信
EXTERNAL_REQUEST: 'external_request'
};
// 消息结构标准
const MessageStructure = {
action: 'string', // 操作类型
data: 'any', // 数据载荷
timestamp: 'number', // 时间戳
requestId: 'string', // 请求ID(可选)
source: 'string' // 来源标识
};8.2 一次性消息传递
8.2.1 基本消息发送
// background.js - 消息监听
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('收到消息:', message, '来自:', sender);
switch (message.action) {
case 'getUserData':
handleGetUserData(message, sendResponse);
return true; // 保持消息通道开启
case 'updateSettings':
handleUpdateSettings(message.data, sendResponse);
return true;
case 'performAction':
handlePerformAction(message, sender, sendResponse);
return true;
default:
sendResponse({
success: false,
error: '未知操作类型'
});
}
});
// 处理获取用户数据
async function handleGetUserData(message, sendResponse) {
try {
const userData = await chrome.storage.sync.get(['userProfile', 'preferences']);
sendResponse({
success: true,
data: {
profile: userData.userProfile,
preferences: userData.preferences
}
});
} catch (error) {
sendResponse({
success: false,
error: error.message
});
}
}
// 处理设置更新
async function handleUpdateSettings(settings, sendResponse) {
try {
await chrome.storage.sync.set(settings);
// 通知其他组件设置已更新
chrome.tabs.query({}, (tabs) => {
tabs.forEach(tab => {
chrome.tabs.sendMessage(tab.id, {
action: 'settingsUpdated',
data: settings
}).catch(() => {
// 忽略无法发送消息的标签页
});
});
});
sendResponse({ success: true });
} catch (error) {
sendResponse({
success: false,
error: error.message
});
}
}8.2.2 从Content Script发送消息
// content-script.js
class ContentScriptMessenger {
constructor() {
this.requestCounter = 0;
this.pendingRequests = new Map();
}
// 生成请求ID
generateRequestId() {
return `req_${++this.requestCounter}_${Date.now()}`;
}
// 发送消息并返回Promise
sendMessage(action, data = null) {
return new Promise((resolve, reject) => {
const requestId = this.generateRequestId();
const message = {
action,
data,
requestId,
timestamp: Date.now(),
source: 'content-script'
};
// 设置超时
const timeout = setTimeout(() => {
this.pendingRequests.delete(requestId);
reject(new Error('消息发送超时'));
}, 10000); // 10秒超时
this.pendingRequests.set(requestId, { resolve, reject, timeout });
chrome.runtime.sendMessage(message, (response) => {
const request = this.pendingRequests.get(requestId);
if (request) {
clearTimeout(request.timeout);
this.pendingRequests.delete(requestId);
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (response && response.success === false) {
reject(new Error(response.error || '操作失败'));
} else {
resolve(response);
}
}
});
});
}
// 批量发送消息
async sendBatchMessages(messages) {
const promises = messages.map(msg =>
this.sendMessage(msg.action, msg.data)
);
try {
const results = await Promise.allSettled(promises);
return results.map((result, index) => ({
index,
success: result.status === 'fulfilled',
data: result.status === 'fulfilled' ? result.value : null,
error: result.status === 'rejected' ? result.reason.message : null
}));
} catch (error) {
throw new Error(`批量消息发送失败: ${error.message}`);
}
}
// 监听来自background的消息
setupMessageListener() {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('Content script收到消息:', message);
switch (message.action) {
case 'settingsUpdated':
this.handleSettingsUpdate(message.data);
sendResponse({ received: true });
break;
case 'pageAction':
this.handlePageAction(message.data, sendResponse);
return true;
case 'extractData':
this.handleDataExtraction(message.data, sendResponse);
return true;
default:
sendResponse({ error: '未处理的消息类型' });
}
});
}
handleSettingsUpdate(settings) {
// 应用新设置到页面
console.log('应用新设置:', settings);
if (settings.theme) {
this.applyTheme(settings.theme);
}
if (settings.fontSize) {
this.adjustFontSize(settings.fontSize);
}
}
async handlePageAction(actionData, sendResponse) {
try {
let result;
switch (actionData.type) {
case 'highlight':
result = this.highlightText(actionData.text);
break;
case 'extract':
result = this.extractPageData(actionData.selector);
break;
case 'modify':
result = this.modifyContent(actionData.changes);
break;
default:
throw new Error('未知的页面操作类型');
}
sendResponse({ success: true, result });
} catch (error) {
sendResponse({ success: false, error: error.message });
}
}
extractPageData(selector) {
const elements = document.querySelectorAll(selector || '*');
const data = [];
elements.forEach((element, index) => {
if (index < 100) { // 限制数量避免数据过大
data.push({
tag: element.tagName.toLowerCase(),
text: element.textContent.substring(0, 200),
attributes: this.getElementAttributes(element)
});
}
});
return {
count: elements.length,
data: data,
url: window.location.href,
title: document.title
};
}
getElementAttributes(element) {
const attrs = {};
for (let attr of element.attributes) {
attrs[attr.name] = attr.value;
}
return attrs;
}
}
// 初始化消息处理器
const messenger = new ContentScriptMessenger();
messenger.setupMessageListener();
// 使用示例
async function exampleUsage() {
try {
// 获取用户数据
const userData = await messenger.sendMessage('getUserData');
console.log('用户数据:', userData);
// 更新设置
await messenger.sendMessage('updateSettings', {
theme: 'dark',
autoSave: true
});
// 批量发送消息
const batchResults = await messenger.sendBatchMessages([
{ action: 'getStats', data: null },
{ action: 'checkUpdates', data: null },
{ action: 'syncData', data: { force: true } }
]);
console.log('批量操作结果:', batchResults);
} catch (error) {
console.error('消息发送失败:', error);
}
}8.2.3 Python模拟消息传递
# message_system.py - 模拟Chrome扩展消息传递系统
import asyncio
import json
import time
import uuid
from typing import Dict, Any, Callable, Optional, List
from dataclasses import dataclass
from enum import Enum
class MessageSource(Enum):
BACKGROUND = "background"
CONTENT_SCRIPT = "content_script"
POPUP = "popup"
OPTIONS = "options"
@dataclass
class Message:
action: str
data: Any = None
source: MessageSource = MessageSource.BACKGROUND
request_id: str = None
timestamp: float = None
def __post_init__(self):
if self.request_id is None:
self.request_id = str(uuid.uuid4())
if self.timestamp is None:
self.timestamp = time.time()
class MessageBus:
"""模拟Chrome扩展消息总线"""
def __init__(self):
self.listeners: Dict[MessageSource, List[Callable]] = {
source: [] for source in MessageSource
}
self.pending_responses: Dict[str, asyncio.Future] = {}
self.response_timeout = 10 # 10秒超时
def add_listener(self, source: MessageSource, callback: Callable):
"""添加消息监听器"""
self.listeners[source].append(callback)
print(f"为 {source.value} 添加了消息监听器")
async def send_message(self, message: Message, target: MessageSource) -> Dict[str, Any]:
"""发送消息"""
print(f"发送消息: {message.action} 从 {message.source.value} 到 {target.value}")
# 创建响应Future
response_future = asyncio.Future()
self.pending_responses[message.request_id] = response_future
# 查找目标监听器
target_listeners = self.listeners.get(target, [])
if not target_listeners:
response_future.set_result({
'success': False,
'error': f'没有找到 {target.value} 的监听器'
})
else:
# 调用第一个监听器(模拟Chrome的行为)
listener = target_listeners[0]
try:
asyncio.create_task(self._handle_message(message, listener))
except Exception as e:
response_future.set_result({
'success': False,
'error': str(e)
})
# 等待响应(带超时)
try:
response = await asyncio.wait_for(
response_future,
timeout=self.response_timeout
)
return response
except asyncio.TimeoutError:
if message.request_id in self.pending_responses:
del self.pending_responses[message.request_id]
raise Exception("消息发送超时")
async def _handle_message(self, message: Message, listener: Callable):
"""处理消息"""
try:
# 创建发送响应的函数
def send_response(response_data):
if message.request_id in self.pending_responses:
future = self.pending_responses[message.request_id]
if not future.done():
future.set_result(response_data)
del self.pending_responses[message.request_id]
# 调用监听器
if asyncio.iscoroutinefunction(listener):
await listener(message, send_response)
else:
listener(message, send_response)
except Exception as e:
self.send_response({
'success': False,
'error': str(e)
})
class BackgroundScript:
"""模拟Background Script"""
def __init__(self, message_bus: MessageBus):
self.message_bus = message_bus
self.storage = {}
message_bus.add_listener(MessageSource.BACKGROUND, self.handle_message)
async def handle_message(self, message: Message, send_response: Callable):
"""处理接收到的消息"""
print(f"Background处理消息: {message.action}")
try:
if message.action == 'getUserData':
response_data = {
'success': True,
'data': {
'profile': self.storage.get('userProfile', {}),
'preferences': self.storage.get('preferences', {})
}
}
send_response(response_data)
elif message.action == 'updateSettings':
self.storage.update(message.data)
# 通知其他组件设置已更新
await self.broadcast_settings_update(message.data)
send_response({'success': True})
elif message.action == 'getStats':
await asyncio.sleep(0.1) # 模拟异步操作
stats = {
'clickCount': 42,
'activeTime': 3600,
'lastUpdate': time.time()
}
send_response({'success': True, 'data': stats})
else:
send_response({
'success': False,
'error': f'未知操作: {message.action}'
})
except Exception as e:
send_response({
'success': False,
'error': str(e)
})
async def broadcast_settings_update(self, settings):
"""广播设置更新"""
update_message = Message(
action='settingsUpdated',
data=settings,
source=MessageSource.BACKGROUND
)
# 发送到content script
try:
await self.message_bus.send_message(
update_message,
MessageSource.CONTENT_SCRIPT
)
except Exception as e:
print(f"广播设置更新失败: {e}")
class ContentScript:
"""模拟Content Script"""
def __init__(self, message_bus: MessageBus):
self.message_bus = message_bus
self.page_data = {
'url': 'https://example.com',
'title': '示例页面',
'elements': ['div', 'p', 'span', 'a']
}
message_bus.add_listener(MessageSource.CONTENT_SCRIPT, self.handle_message)
async def handle_message(self, message: Message, send_response: Callable):
"""处理接收到的消息"""
print(f"Content Script处理消息: {message.action}")
try:
if message.action == 'extractData':
# 模拟数据提取
extracted_data = {
'url': self.page_data['url'],
'title': self.page_data['title'],
'elements': len(self.page_data['elements']),
'timestamp': time.time()
}
send_response({
'success': True,
'data': extracted_data
})
elif message.action == 'settingsUpdated':
print(f"应用新设置: {message.data}")
send_response({'received': True})
elif message.action == 'pageAction':
result = await self.perform_page_action(message.data)
send_response({
'success': True,
'result': result
})
else:
send_response({
'success': False,
'error': f'未处理的消息类型: {message.action}'
})
except Exception as e:
send_response({
'success': False,
'error': str(e)
})
async def perform_page_action(self, action_data: Dict[str, Any]):
"""执行页面操作"""
action_type = action_data.get('type')
if action_type == 'highlight':
return {'highlighted': True, 'text': action_data.get('text')}
elif action_type == 'extract':
return {'extracted': self.page_data}
elif action_type == 'modify':
return {'modified': True, 'changes': action_data.get('changes')}
else:
raise ValueError(f'未知操作类型: {action_type}')
async def send_to_background(self, action: str, data: Any = None):
"""向background发送消息"""
message = Message(
action=action,
data=data,
source=MessageSource.CONTENT_SCRIPT
)
return await self.message_bus.send_message(
message,
MessageSource.BACKGROUND
)
class PopupScript:
"""模拟Popup Script"""
def __init__(self, message_bus: MessageBus):
self.message_bus = message_bus
message_bus.add_listener(MessageSource.POPUP, self.handle_message)
async def handle_message(self, message: Message, send_response: Callable):
"""处理接收到的消息"""
print(f"Popup处理消息: {message.action}")
send_response({'received': True})
async def get_user_data(self):
"""获取用户数据"""
message = Message(
action='getUserData',
source=MessageSource.POPUP
)
return await self.message_bus.send_message(
message,
MessageSource.BACKGROUND
)
async def update_settings(self, settings: Dict[str, Any]):
"""更新设置"""
message = Message(
action='updateSettings',
data=settings,
source=MessageSource.POPUP
)
return await self.message_bus.send_message(
message,
MessageSource.BACKGROUND
)
# 演示函数
async def demo_message_system():
"""演示消息传递系统"""
print("=== Chrome扩展消息传递系统演示 ===\n")
# 创建消息总线和组件
message_bus = MessageBus()
background = BackgroundScript(message_bus)
content_script = ContentScript(message_bus)
popup = PopupScript(message_bus)
print("1. Popup获取用户数据:")
user_data = await popup.get_user_data()
print(f" 结果: {user_data}\n")
print("2. Popup更新设置:")
update_result = await popup.update_settings({
'theme': 'dark',
'autoSave': True
})
print(f" 结果: {update_result}\n")
print("3. Content Script发送数据到Background:")
stats_result = await content_script.send_to_background('getStats')
print(f" 结果: {stats_result}\n")
print("4. 向Content Script发送页面操作指令:")
page_action_message = Message(
action='pageAction',
data={
'type': 'extract',
'selector': 'div'
},
source=MessageSource.BACKGROUND
)
action_result = await message_bus.send_message(
page_action_message,
MessageSource.CONTENT_SCRIPT
)
print(f" 结果: {action_result}\n")
print("=== 演示完成 ===")
# 运行演示(注释掉实际执行)
# asyncio.run(demo_message_system())8.3 长连接通信(Port)
8.3.1 建立长连接
// background.js - 长连接处理
class ConnectionManager {
constructor() {
this.activeConnections = new Map();
this.setupConnectionListener();
}
setupConnectionListener() {
chrome.runtime.onConnect.addListener((port) => {
console.log('新连接建立:', port.name);
const connectionId = this.generateConnectionId();
const connection = {
id: connectionId,
port: port,
type: port.name,
sender: port.sender,
createdAt: Date.now(),
lastActivity: Date.now()
};
this.activeConnections.set(connectionId, connection);
// 设置消息监听
port.onMessage.addListener((message) => {
this.handlePortMessage(connectionId, message);
});
// 设置断开连接监听
port.onDisconnect.addListener(() => {
this.handlePortDisconnect(connectionId);
});
// 发送连接确认
port.postMessage({
type: 'connectionEstablished',
connectionId: connectionId,
timestamp: Date.now()
});
});
}
generateConnectionId() {
return `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
handlePortMessage(connectionId, message) {
const connection = this.activeConnections.get(connectionId);
if (!connection) return;
// 更新活动时间
connection.lastActivity = Date.now();
console.log(`连接 ${connectionId} 收到消息:`, message);
switch (message.type) {
case 'subscribe':
this.handleSubscription(connectionId, message.data);
break;
case 'unsubscribe':
this.handleUnsubscription(connectionId, message.data);
break;
case 'dataRequest':
this.handleDataRequest(connectionId, message.data);
break;
case 'liveUpdate':
this.handleLiveUpdate(connectionId, message.data);
break;
default:
this.sendToConnection(connectionId, {
type: 'error',
message: '未知消息类型'
});
}
}
handlePortDisconnect(connectionId) {
const connection = this.activeConnections.get(connectionId);
if (connection) {
console.log(`连接 ${connectionId} 已断开`);
this.activeConnections.delete(connectionId);
}
}
sendToConnection(connectionId, message) {
const connection = this.activeConnections.get(connectionId);
if (connection && connection.port) {
try {
connection.port.postMessage({
...message,
timestamp: Date.now(),
connectionId: connectionId
});
} catch (error) {
console.error(`发送消息到连接 ${connectionId} 失败:`, error);
this.activeConnections.delete(connectionId);
}
}
}
// 广播消息到所有连接
broadcast(message, filter = null) {
for (const [connectionId, connection] of this.activeConnections) {
if (!filter || filter(connection)) {
this.sendToConnection(connectionId, message);
}
}
}
// 广播到特定类型的连接
broadcastToType(connectionType, message) {
this.broadcast(message, (conn) => conn.type === connectionType);
}
handleSubscription(connectionId, subscriptionData) {
const connection = this.activeConnections.get(connectionId);
if (!connection) return;
if (!connection.subscriptions) {
connection.subscriptions = new Set();
}
connection.subscriptions.add(subscriptionData.topic);
this.sendToConnection(connectionId, {
type: 'subscribed',
topic: subscriptionData.topic,
message: `已订阅 ${subscriptionData.topic}`
});
// 发送初始数据
this.sendTopicData(connectionId, subscriptionData.topic);
}
sendTopicData(connectionId, topic) {
let data;
switch (topic) {
case 'stats':
data = {
clickCount: 42,
activeTime: 3600,
lastUpdate: Date.now()
};
break;
case 'notifications':
data = {
unreadCount: 5,
latest: 'New update available'
};
break;
default:
data = { message: `没有 ${topic} 的数据` };
}
this.sendToConnection(connectionId, {
type: 'topicData',
topic: topic,
data: data
});
}
}
// 实例化连接管理器
const connectionManager = new ConnectionManager();
// 定期向订阅了stats的连接发送更新
setInterval(() => {
const statsUpdate = {
type: 'topicData',
topic: 'stats',
data: {
clickCount: Math.floor(Math.random() * 100),
activeTime: Math.floor(Math.random() * 10000),
lastUpdate: Date.now()
}
};
connectionManager.broadcast(statsUpdate, (conn) => {
return conn.subscriptions && conn.subscriptions.has('stats');
});
}, 5000); // 每5秒更新一次8.3.2 客户端长连接
// popup.js 或 content-script.js - 长连接客户端
class LongConnectionClient {
constructor(connectionName) {
this.connectionName = connectionName;
this.port = null;
this.connectionId = null;
this.isConnected = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000; // 1秒
this.subscriptions = new Set();
this.messageHandlers = new Map();
this.connect();
}
connect() {
try {
console.log(`建立连接: ${this.connectionName}`);
this.port = chrome.runtime.connect({ name: this.connectionName });
this.port.onMessage.addListener((message) => {
this.handleMessage(message);
});
this.port.onDisconnect.addListener(() => {
this.handleDisconnect();
});
} catch (error) {
console.error('建立连接失败:', error);
this.scheduleReconnect();
}
}
handleMessage(message) {
console.log('收到端口消息:', message);
switch (message.type) {
case 'connectionEstablished':
this.onConnectionEstablished(message);
break;
case 'subscribed':
console.log(`订阅成功: ${message.topic}`);
break;
case 'topicData':
this.handleTopicData(message);
break;
case 'error':
console.error('服务器错误:', message.message);
break;
default:
// 调用自定义消息处理器
const handler = this.messageHandlers.get(message.type);
if (handler) {
handler(message);
}
}
}
onConnectionEstablished(message) {
this.connectionId = message.connectionId;
this.isConnected = true;
this.reconnectAttempts = 0;
console.log(`连接已建立: ${this.connectionId}`);
// 恢复订阅
this.subscriptions.forEach(topic => {
this.subscribe(topic);
});
// 触发连接成功事件
this.onConnected();
}
handleDisconnect() {
console.log('连接已断开');
this.isConnected = false;
this.connectionId = null;
this.onDisconnected();
this.scheduleReconnect();
}
scheduleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.reconnectDelay * this.reconnectAttempts;
console.log(`${delay}ms后尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
setTimeout(() => {
this.connect();
}, delay);
} else {
console.error('重连次数已达上限');
}
}
// 发送消息
sendMessage(message) {
if (this.isConnected && this.port) {
try {
this.port.postMessage(message);
return true;
} catch (error) {
console.error('发送消息失败:', error);
return false;
}
} else {
console.warn('连接未建立,无法发送消息');
return false;
}
}
// 订阅主题
subscribe(topic) {
this.subscriptions.add(topic);
if (this.isConnected) {
this.sendMessage({
type: 'subscribe',
data: { topic: topic }
});
}
}
// 取消订阅
unsubscribe(topic) {
this.subscriptions.delete(topic);
if (this.isConnected) {
this.sendMessage({
type: 'unsubscribe',
data: { topic: topic }
});
}
}
// 注册消息处理器
onMessage(messageType, handler) {
this.messageHandlers.set(messageType, handler);
}
// 处理主题数据
handleTopicData(message) {
const topic = message.topic;
const data = message.data;
console.log(`收到主题数据 ${topic}:`, data);
// 触发主题特定的处理
const event = new CustomEvent(`topic:${topic}`, {
detail: data
});
document.dispatchEvent(event);
}
// 连接成功回调(可重写)
onConnected() {
console.log('连接成功回调');
}
// 连接断开回调(可重写)
onDisconnected() {
console.log('连接断开回调');
}
// 断开连接
disconnect() {
if (this.port) {
this.port.disconnect();
}
}
}
// 实际使用示例
class StatsDisplayClient extends LongConnectionClient {
constructor() {
super('statsDisplay');
this.setupEventListeners();
}
onConnected() {
super.onConnected();
// 连接成功后订阅统计数据
this.subscribe('stats');
this.subscribe('notifications');
}
setupEventListeners() {
// 监听统计数据更新
document.addEventListener('topic:stats', (event) => {
this.updateStatsDisplay(event.detail);
});
// 监听通知更新
document.addEventListener('topic:notifications', (event) => {
this.updateNotifications(event.detail);
});
}
updateStatsDisplay(statsData) {
const clickCountElement = document.getElementById('clickCount');
const activeTimeElement = document.getElementById('activeTime');
if (clickCountElement) {
clickCountElement.textContent = statsData.clickCount;
}
if (activeTimeElement) {
activeTimeElement.textContent = this.formatTime(statsData.activeTime);
}
}
updateNotifications(notificationData) {
const notificationBadge = document.getElementById('notificationBadge');
if (notificationBadge) {
notificationBadge.textContent = notificationData.unreadCount;
notificationBadge.style.display = notificationData.unreadCount > 0 ? 'block' : 'none';
}
}
formatTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${hours}h ${minutes}m`;
}
}
// 在popup或content script中使用
// const statsClient = new StatsDisplayClient();8.4 跨扩展通信
8.4.1 扩展间消息传递
// 发送方扩展
class ExternalMessenger {
constructor() {
this.trustedExtensions = [
'abcdefghijklmnopqrstuvwxyz123456', // 信任的扩展ID
'zyxwvutsrqponmlkjihgfedcba654321'
];
}
// 发送消息到其他扩展
async sendToExtension(extensionId, message) {
if (!this.trustedExtensions.includes(extensionId)) {
throw new Error('不信任的扩展ID');
}
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(extensionId, {
...message,
timestamp: Date.now(),
source: chrome.runtime.id
}, (response) => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else {
resolve(response);
}
});
});
}
// 广播消息到所有信任的扩展
async broadcastToTrustedExtensions(message) {
const promises = this.trustedExtensions.map(extensionId =>
this.sendToExtension(extensionId, message).catch(error => ({
extensionId,
error: error.message
}))
);
const results = await Promise.allSettled(promises);
return results.map((result, index) => ({
extensionId: this.trustedExtensions[index],
success: result.status === 'fulfilled' && !result.value.error,
data: result.status === 'fulfilled' ? result.value : null,
error: result.status === 'rejected' ? result.reason.message :
(result.value && result.value.error) || null
}));
}
}
// 接收方扩展
chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
console.log('收到外部消息:', message, '来自:', sender.id);
// 验证发送者
const trustedSenders = [
'abcdefghijklmnopqrstuvwxyz123456',
'fedcba9876543210abcdef1234567890'
];
if (!trustedSenders.includes(sender.id)) {
sendResponse({
success: false,
error: '未授权的扩展'
});
return;
}
// 处理消息
switch (message.action) {
case 'getData':
handleExternalDataRequest(message, sendResponse);
return true;
case 'syncSettings':
handleSettingsSync(message, sendResponse);
return true;
case 'notify':
handleExternalNotification(message, sendResponse);
return true;
default:
sendResponse({
success: false,
error: '未知操作'
});
}
});
async function handleExternalDataRequest(message, sendResponse) {
try {
const requestedData = await chrome.storage.sync.get(message.keys);
sendResponse({
success: true,
data: requestedData
});
} catch (error) {
sendResponse({
success: false,
error: error.message
});
}
}8.5 错误处理和调试
8.5.1 消息错误处理
// 消息错误处理器
class MessageErrorHandler {
constructor() {
this.errorLog = [];
this.retryCount = new Map();
this.maxRetries = 3;
}
// 带重试的消息发送
async sendMessageWithRetry(message, maxRetries = this.maxRetries) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`发送消息尝试 ${attempt}/${maxRetries}:`, message.action);
const response = await this.sendMessage(message);
if (response && response.success !== false) {
// 重置重试计数
this.retryCount.delete(message.action);
return response;
} else {
throw new Error(response?.error || '操作失败');
}
} catch (error) {
lastError = error;
console.warn(`消息发送失败 (尝试 ${attempt}/${maxRetries}):`, error.message);
this.logError(message, error, attempt);
if (attempt < maxRetries) {
await this.delay(1000 * attempt); // 递增延迟
}
}
}
// 所有尝试都失败
this.recordFailedMessage(message, lastError);
throw new Error(`消息发送最终失败: ${lastError.message}`);
}
async sendMessage(message) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('消息发送超时'));
}, 10000);
chrome.runtime.sendMessage(message, (response) => {
clearTimeout(timeout);
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else {
resolve(response);
}
});
});
}
logError(message, error, attempt) {
const errorEntry = {
timestamp: Date.now(),
message: message,
error: error.message,
attempt: attempt
};
this.errorLog.push(errorEntry);
// 限制日志大小
if (this.errorLog.length > 100) {
this.errorLog = this.errorLog.slice(-50);
}
}
recordFailedMessage(message, error) {
const current = this.retryCount.get(message.action) || 0;
this.retryCount.set(message.action, current + 1);
console.error(`消息 ${message.action} 失败:`, error.message);
}
getErrorLog() {
return [...this.errorLog];
}
getFailureStats() {
const stats = {};
for (const [action, count] of this.retryCount) {
stats[action] = count;
}
return stats;
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 消息调试器
class MessageDebugger {
constructor() {
this.messageLog = [];
this.enabled = false;
}
enable() {
this.enabled = true;
this.setupMessageInterception();
}
setupMessageInterception() {
// 拦截发送的消息
const originalSendMessage = chrome.runtime.sendMessage;
chrome.runtime.sendMessage = (message, responseCallback) => {
if (this.enabled) {
this.logOutgoingMessage(message);
}
return originalSendMessage.call(chrome.runtime, message, (response) => {
if (this.enabled) {
this.logMessageResponse(message, response);
}
if (responseCallback) {
responseCallback(response);
}
});
};
}
logOutgoingMessage(message) {
this.messageLog.push({
type: 'outgoing',
timestamp: Date.now(),
message: JSON.parse(JSON.stringify(message))
});
console.log('📤 发送消息:', message);
}
logMessageResponse(originalMessage, response) {
this.messageLog.push({
type: 'response',
timestamp: Date.now(),
originalMessage: JSON.parse(JSON.stringify(originalMessage)),
response: JSON.parse(JSON.stringify(response))
});
console.log('📥 消息响应:', response);
}
getMessageLog() {
return [...this.messageLog];
}
clearLog() {
this.messageLog = [];
}
exportLog() {
const logData = {
timestamp: new Date().toISOString(),
messages: this.messageLog
};
const blob = new Blob([JSON.stringify(logData, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `message-log-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
}
// 全局实例
const messageErrorHandler = new MessageErrorHandler();
const messageDebugger = new MessageDebugger();
// 开发模式下启用调试
if (process.env.NODE_ENV === 'development') {
messageDebugger.enable();
}注意事项
- 消息大小限制: Chrome对消息大小有限制,避免发送过大的数据
- 异步处理: 总是使用
return true保持异步消息通道开启 - 错误处理: 检查
chrome.runtime.lastError和响应的错误状态 - 内存泄漏: 及时清理事件监听器和定时器
- 安全验证: 验证消息来源和内容的合法性
最佳实践
- 定义统一的消息格式和错误处理策略
- 实现消息重试和超时机制
- 使用TypeScript增强类型安全
- 添加消息日志和调试工具
- 考虑使用消息队列处理大量消息
总结
本章深入介绍了Chrome扩展的消息传递机制,包括一次性消息、长连接通信、跨扩展通信等各种场景的实现方法。掌握这些通信技术是构建复杂Chrome扩展的关键。下一章我们将学习存储和数据管理,实现数据的持久化和同步。
