Files
liquid a91afda0f4 docs: 更新 postmessage-duplex 规则 (v1.1.0)
- 更新通信模式:broadcast/onBroadcast 用于单向通知
- 更新初始化:enableGlobalRouting 自动管理 channel
- 新增客户端配置:autoReconnect 处理 SW 更新
- 新增任务队列双服务同步规则
2026-01-31 14:09:20 +08:00

362 lines
9.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* SW Debug Panel - Duplex Client
* 基于 postmessage-duplex 的双工通信客户端
*/
import { ServiceWorkerChannel } from './postmessage-duplex.js';
// RPC 方法名常量(与 SW 端保持一致)
const RPC_METHODS = {
// Debug methods
DEBUG_GET_STATUS: 'debug:getStatus',
DEBUG_GET_LOGS: 'debug:getLogs',
DEBUG_CLEAR_LOGS: 'debug:clearLogs',
DEBUG_GET_CONSOLE_LOGS: 'debug:getConsoleLogs',
DEBUG_CLEAR_CONSOLE_LOGS: 'debug:clearConsoleLogs',
DEBUG_GET_POSTMESSAGE_LOGS: 'debug:getPostMessageLogs',
DEBUG_CLEAR_POSTMESSAGE_LOGS: 'debug:clearPostMessageLogs',
DEBUG_GET_LLM_API_LOGS: 'debug:getLLMApiLogs',
DEBUG_GET_LLM_API_LOG_BY_ID: 'debug:getLLMApiLogById',
DEBUG_CLEAR_LLM_API_LOGS: 'debug:clearLLMApiLogs',
DEBUG_DELETE_LLM_API_LOGS: 'debug:deleteLLMApiLogs',
DEBUG_GET_CRASH_SNAPSHOTS: 'debug:getCrashSnapshots',
DEBUG_CLEAR_CRASH_SNAPSHOTS: 'debug:clearCrashSnapshots',
DEBUG_GET_CACHE_STATS: 'debug:getCacheStats',
};
// 事件类型常量(必须与 SW 端 channel-manager.ts 中的 SW_EVENTS 保持一致)
const SW_EVENTS = {
DEBUG_STATUS_CHANGED: 'debug:statusChanged',
DEBUG_LOG: 'debug:log', // SW 的 fetch 调试日志
CONSOLE_LOG: 'console:log', // SW 的控制台日志
POSTMESSAGE_LOG: 'postmessage:log', // PostMessage 日志
DEBUG_LLM_LOG: 'debug:llmLog', // LLM API 日志
POSTMESSAGE_LOG_BATCH: 'postmessage:logBatch', // PostMessage 批量日志
};
/** @type {ServiceWorkerChannel|null} */
let channel = null;
/** @type {boolean} */
let initialized = false;
/** @type {Map<string, Function[]>} */
const eventListeners = new Map();
/**
* 重置 duplex 客户端(在 SW 更新后调用)
*/
export function resetDuplexClient() {
console.log('[DuplexClient] Resetting client for SW update');
channel = null;
initialized = false;
}
/**
* 初始化 duplex 客户端
* postmessage-duplex 1.1.0 配合 SW 的 enableGlobalRouting,自动创建 channel
* @returns {Promise<boolean>}
*/
export async function initDuplexClient() {
if (initialized && channel) {
return true;
}
try {
// 等待 SW 准备好
const registration = await navigator.serviceWorker.ready;
if (!registration.active) {
console.warn('[DuplexClient] No active SW');
return false;
}
// 创建 duplex 通道
// SW 端的 enableGlobalRouting 会自动为新客户端创建 channel
// 不再需要显式的 SW_CHANNEL_CONNECT 握手
channel = await ServiceWorkerChannel.createFromPage({
log: { log: () => {}, warn: () => {}, error: () => {} }
});
// 注册事件订阅
setupEventSubscriptions();
initialized = true;
console.log('[DuplexClient] Initialized successfully');
return true;
} catch (error) {
console.error('[DuplexClient] Failed to initialize:', error);
return false;
}
}
/**
* 设置事件订阅
*/
function setupEventSubscriptions() {
if (!channel) return;
// 使用 onBroadcast 接收 SW 的广播消息(单向,不需要响应)
// SW 使用 channel.broadcast() 发送事件
Object.values(SW_EVENTS).forEach(eventName => {
channel.onBroadcast(eventName, ({ data }) => {
if (data !== undefined) {
emitEvent(eventName, data);
}
});
});
}
/**
* 触发事件
* @param {string} eventName
* @param {*} data
*/
function emitEvent(eventName, data) {
const listeners = eventListeners.get(eventName) || [];
listeners.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`[DuplexClient] Event handler error for ${eventName}:`, error);
}
});
}
/**
* 添加事件监听器
* @param {string} eventName
* @param {Function} callback
*/
export function addEventListener(eventName, callback) {
if (!eventListeners.has(eventName)) {
eventListeners.set(eventName, []);
}
eventListeners.get(eventName).push(callback);
}
/**
* 移除事件监听器
* @param {string} eventName
* @param {Function} callback
*/
export function removeEventListener(eventName, callback) {
const listeners = eventListeners.get(eventName);
if (listeners) {
const index = listeners.indexOf(callback);
if (index !== -1) {
listeners.splice(index, 1);
}
}
}
/**
* 调用 RPC 方法(带自动重试)
* @param {string} method
* @param {object} params
* @param {number} timeout
* @param {number} retries - 重试次数
* @returns {Promise<*>}
*/
async function callRPC(method, params = {}, timeout = 10000, retries = 2) {
if (!channel) {
throw new Error('DuplexClient not initialized');
}
for (let attempt = 0; attempt <= retries; attempt++) {
try {
// 注意:publish 用于发送请求并等待响应(RPC 模式)
// 响应格式: { ret: 0, data: {...} },需要解包 data 字段
const response = await channel.publish(method, params, { timeout });
// 检查响应格式并解包 data
if (response && typeof response === 'object') {
// ret === undefined || ret === 0 表示成功(publish 模式下 ret 可能是 undefined)
if ((response.ret === undefined || response.ret === 0) && response.data !== undefined) {
return response.data;
}
// 如果有错误信息(ret 存在且不为 0)
if (response.ret !== undefined && response.ret !== 0) {
throw new Error(response.msg || `RPC failed with code ${response.ret}`);
}
}
return response;
} catch (error) {
if (attempt < retries) {
// 等待一段时间后重试(给 SW 更新完成的时间)
await new Promise(resolve => setTimeout(resolve, 500));
// 重新初始化 channel
if (!initialized) {
await initDuplexClient();
}
} else {
throw error;
}
}
}
}
// ============================================================================
// Debug API
// ============================================================================
/**
* 获取调试状态
* @returns {Promise<{debugModeEnabled: boolean, swVersion: string}>}
*/
export async function getDebugStatus() {
return callRPC(RPC_METHODS.DEBUG_GET_STATUS);
}
/**
* 获取 Fetch 日志
* @returns {Promise<{logs: Array}>}
*/
export async function getFetchLogs() {
return callRPC(RPC_METHODS.DEBUG_GET_LOGS);
}
/**
* 清除 Fetch 日志
* @returns {Promise<void>}
*/
export async function clearFetchLogs() {
return callRPC(RPC_METHODS.DEBUG_CLEAR_LOGS);
}
/**
* 获取 Console 日志
* @param {number} limit
* @returns {Promise<{logs: Array}>}
*/
export async function getConsoleLogs(limit = 1000) {
return callRPC(RPC_METHODS.DEBUG_GET_CONSOLE_LOGS, { limit });
}
/**
* 清除 Console 日志
* @returns {Promise<void>}
*/
export async function clearConsoleLogs() {
return callRPC(RPC_METHODS.DEBUG_CLEAR_CONSOLE_LOGS);
}
/**
* 获取 PostMessage 日志
* @param {number} limit
* @returns {Promise<{logs: Array}>}
*/
export async function getPostMessageLogs(limit = 500) {
return callRPC(RPC_METHODS.DEBUG_GET_POSTMESSAGE_LOGS, { limit });
}
/**
* 清除 PostMessage 日志
* @returns {Promise<void>}
*/
export async function clearPostMessageLogs() {
return callRPC(RPC_METHODS.DEBUG_CLEAR_POSTMESSAGE_LOGS);
}
/**
* 获取 LLM API 日志(分页)
* @param {number} page - 页码,默认 1
* @param {number} pageSize - 每页条数,默认 20
* @param {object} filter - 过滤条件 { taskType?, status? }
* @returns {Promise<{logs: Array, total: number, page: number, pageSize: number, totalPages: number}>}
*/
export async function getLLMApiLogs(page = 1, pageSize = 20, filter = {}) {
return callRPC(RPC_METHODS.DEBUG_GET_LLM_API_LOGS, { page, pageSize, ...filter });
}
/**
* 清除 LLM API 日志
* @returns {Promise<void>}
*/
export async function clearLLMApiLogs() {
return callRPC(RPC_METHODS.DEBUG_CLEAR_LLM_API_LOGS);
}
/**
* 删除指定的 LLM API 日志
* @param {string[]} logIds - 要删除的日志 ID 列表
* @returns {Promise<{success: boolean, deletedCount: number}>}
*/
export async function deleteLLMApiLogs(logIds) {
return callRPC(RPC_METHODS.DEBUG_DELETE_LLM_API_LOGS, { logIds });
}
/**
* 获取单条 LLM API 日志的完整数据
* @param {string} logId - 日志 ID
* @returns {Promise<object|null>}
*/
export async function getLLMApiLogById(logId) {
return callRPC(RPC_METHODS.DEBUG_GET_LLM_API_LOG_BY_ID, { logId });
}
/**
* 获取 Crash 快照
* @returns {Promise<{snapshots: Array}>}
*/
export async function getCrashSnapshots() {
return callRPC(RPC_METHODS.DEBUG_GET_CRASH_SNAPSHOTS);
}
/**
* 清除 Crash 快照
* @returns {Promise<void>}
*/
export async function clearCrashSnapshots() {
return callRPC(RPC_METHODS.DEBUG_CLEAR_CRASH_SNAPSHOTS);
}
/**
* 获取缓存统计
* @returns {Promise<{stats: object}>}
*/
export async function getCacheStats() {
return callRPC(RPC_METHODS.DEBUG_GET_CACHE_STATS);
}
// ============================================================================
// 原生 postMessage 支持(用于启用/禁用调试模式等简单操作)
// ============================================================================
/**
* 发送原生 postMessage(用于不需要响应的简单操作)
* @param {string} type
* @param {object} data
*/
export function sendNativeMessage(type, data = {}) {
const sw = navigator.serviceWorker.controller;
if (sw) {
sw.postMessage({ type, ...data });
}
}
/**
* 启用调试模式
*/
export function enableDebugMode() {
sendNativeMessage('SW_DEBUG_ENABLE');
}
/**
* 禁用调试模式
*/
export function disableDebugMode() {
sendNativeMessage('SW_DEBUG_DISABLE');
}
/**
* 发送心跳
*/
export function sendHeartbeat() {
sendNativeMessage('SW_DEBUG_HEARTBEAT');
}
// 导出事件常量供外部使用
export { SW_EVENTS, RPC_METHODS };