diff --git a/packages/napcat-webui-backend/src/api/Debug.ts b/packages/napcat-webui-backend/src/api/Debug.ts index 366ec822..6ce26c54 100644 --- a/packages/napcat-webui-backend/src/api/Debug.ts +++ b/packages/napcat-webui-backend/src/api/Debug.ts @@ -1,11 +1,20 @@ import { Router, Request, Response } from 'express'; -import { WebSocket, WebSocketServer } from 'ws'; +import { WebSocket, WebSocketServer, RawData } from 'ws'; import { sendError, sendSuccess } from '@/napcat-webui-backend/src/utils/response'; import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data'; import { IncomingMessage } from 'http'; import { OB11Response } from '@/napcat-onebot/action/OneBotAction'; import { ActionName } from '@/napcat-onebot/action/router'; import { OB11LifeCycleEvent, LifeCycleSubType } from '@/napcat-onebot/event/meta/OB11LifeCycleEvent'; +import { IOB11NetworkAdapter } from '@/napcat-onebot/network/adapter'; +import { WebsocketServerConfig } from '@/napcat-onebot/config/config'; +import { ActionMap } from '@/napcat-onebot/action'; +import { NapCatCore } from '@/napcat-core/index'; +import { NapCatOneBot11Adapter } from '@/napcat-onebot/index'; +import { OB11EmitEventContent, OB11NetworkReloadType } from '@/napcat-onebot/network/index'; +import json5 from 'json5'; + +type ActionNameType = typeof ActionName[keyof typeof ActionName]; const router = Router(); const DEFAULT_ADAPTER_NAME = 'debug-primary'; @@ -14,218 +23,205 @@ const DEFAULT_ADAPTER_NAME = 'debug-primary'; * 统一的调试适配器 * 用于注入到 OneBot NetworkManager,接收所有事件并转发给 WebSocket 客户端 */ -class DebugAdapter { - name: string; - isEnable: boolean = true; - // 安全令牌 +class DebugAdapter extends IOB11NetworkAdapter { readonly token: string; - - // 添加 config 属性,模拟 PluginConfig 结构 - config: { - enable: boolean; - name: string; - messagePostFormat?: string; - reportSelfMessage?: boolean; - debug?: boolean; - token?: string; - heartInterval?: number; - }; - wsClients: Set = new Set(); + wsClients: WebSocket[] = []; + wsClientWithEvent: WebSocket[] = []; lastActivityTime: number = Date.now(); inactivityTimer: NodeJS.Timeout | null = null; readonly INACTIVITY_TIMEOUT = 5 * 60 * 1000; // 5分钟不活跃 - constructor (sessionId: string) { - this.name = `debug-${sessionId}`; - // 生成简单的随机 token - this.token = Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2); + override get isActive (): boolean { + return this.isEnable && this.wsClientWithEvent.length > 0; + } - this.config = { + constructor (sessionId: string, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap) { + const config: WebsocketServerConfig = { enable: true, - name: this.name, + name: `debug-${sessionId}`, + host: '127.0.0.1', + port: 0, messagePostFormat: 'array', reportSelfMessage: true, + token: '', + enableForcePushEvent: true, debug: true, - token: this.token, - heartInterval: 30000 + heartInterval: 0 }; + + super(`debug-${sessionId}`, config, core, obContext, actions); + this.token = Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2); + this.isEnable = false; this.startInactivityCheck(); } - // 实现 IOB11NetworkAdapter 接口所需的抽象方法 - async open (): Promise { } - async close (): Promise { this.cleanup(); } - async reload (_config: any): Promise { return 0; } - - /** - * OneBot 事件回调 - 转发给所有 WebSocket 客户端 (原始流) - */ - async onEvent (event: any) { - this.updateActivity(); - - const payload = JSON.stringify(event); - - if (this.wsClients.size === 0) { + async open (): Promise { + if (this.isEnable) { + this.logger.logError('[Debug] Cannot open an already opened adapter'); return; } - - this.wsClients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - try { - client.send(payload); - } catch (error) { - console.error('[Debug] 发送事件到 WebSocket 失败:', error); - } - } - }); + this.logger.log('[Debug] Adapter opened:', this.name); + this.isEnable = true; } - /** - * 调用 OneBot API (HTTP 接口使用) - */ - async callApi (actionName: string, params: any): Promise { - this.updateActivity(); - - const oneBotContext = WebUiDataRuntime.getOneBotContext(); - if (!oneBotContext) { - throw new Error('OneBot 未初始化'); - } - - const action = oneBotContext.actions.get(actionName); - if (!action) { - throw new Error(`不支持的 API: ${actionName}`); - } - - return await action.handle(params, this.name, { - name: this.name, - enable: true, - messagePostFormat: 'array', - reportSelfMessage: true, - debug: true, - }); - } - - /** - * 处理 WebSocket 消息 (OneBot 标准) - */ - async handleWsMessage (ws: WebSocket, message: string | Buffer) { - this.updateActivity(); - let receiveData: { action: typeof ActionName[keyof typeof ActionName], params?: any, echo?: any; } = { action: ActionName.Unknown, params: {} }; - let echo; - - try { - receiveData = JSON.parse(message.toString()); - echo = receiveData.echo; - } catch { - this.sendWsResponse(ws, OB11Response.error('json解析失败,请检查数据格式', 1400, echo)); + async close (): Promise { + if (!this.isEnable) { return; } + this.logger.log('[Debug] Adapter closing:', this.name); + this.isEnable = false; - receiveData.params = (receiveData?.params) ? receiveData.params : {}; - - // 兼容 WebUI 之前可能的一些非标准格式 (如果用户是旧前端) - // 但既然用户说要"原始流",我们优先支持标准格式 - - const oneBotContext = WebUiDataRuntime.getOneBotContext(); - if (!oneBotContext) { - this.sendWsResponse(ws, OB11Response.error('OneBot 未初始化', 1404, echo)); - return; - } - - const action = oneBotContext.actions.get(receiveData.action as any); - if (!action) { - this.sendWsResponse(ws, OB11Response.error('不支持的API ' + receiveData.action, 1404, echo)); - return; - } - - try { - const retdata = await action.websocketHandle(receiveData.params, echo ?? '', this.name, this.config, { - send: async (data: object) => { - this.sendWsResponse(ws, OB11Response.ok(data, echo ?? '', true)); - }, - }); - this.sendWsResponse(ws, retdata); - } catch (e: any) { - this.sendWsResponse(ws, OB11Response.error(e.message || '内部错误', 1200, echo)); - } - } - - sendWsResponse (ws: WebSocket, data: any) { - if (ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify(data)); - } - } - - /** - * 添加 WebSocket 客户端 - */ - addWsClient (ws: WebSocket) { - this.wsClients.add(ws); - this.updateActivity(); - - // 发送生命周期事件 (Connect) - const oneBotContext = WebUiDataRuntime.getOneBotContext(); - if (oneBotContext && oneBotContext.core) { - try { - const event = new OB11LifeCycleEvent(oneBotContext.core, LifeCycleSubType.CONNECT); - ws.send(JSON.stringify(event)); - } catch (e) { - console.error('[Debug] 发送生命周期事件失败', e); - } - } - } - - /** - * 移除 WebSocket 客户端 - */ - removeWsClient (ws: WebSocket) { - this.wsClients.delete(ws); - } - - updateActivity () { - this.lastActivityTime = Date.now(); - } - - startInactivityCheck () { - this.inactivityTimer = setInterval(() => { - const inactive = Date.now() - this.lastActivityTime; - // 如果没有 WebSocket 连接且超时,则自动清理 - if (inactive > this.INACTIVITY_TIMEOUT && this.wsClients.size === 0) { - console.log(`[Debug] Adapter ${this.name} 不活跃,自动关闭`); - this.cleanup(); - } - }, 30000); - } - - cleanup () { + // 停止不活跃检查定时器 if (this.inactivityTimer) { clearInterval(this.inactivityTimer); this.inactivityTimer = null; } - // 关闭所有 WebSocket 连接 + // 关闭所有 WebSocket 连接并移除事件监听器 this.wsClients.forEach((client) => { try { + client.removeAllListeners(); client.close(); } catch (error) { - // ignore + this.logger.logError('[Debug] 关闭 WebSocket 失败:', error); } }); - this.wsClients.clear(); - - // 从 OneBot NetworkManager 移除 - const oneBotContext = WebUiDataRuntime.getOneBotContext(); - if (oneBotContext) { - oneBotContext.networkManager.adapters.delete(this.name); - } - - // 从管理器中移除 - debugAdapterManager.removeAdapter(this.name); + this.wsClients = []; + this.wsClientWithEvent = []; + } + + async reload (_config: unknown): Promise { + return OB11NetworkReloadType.NetWorkReload; + } + + async onEvent (event: T): Promise { + this.updateActivity(); + + const payload = JSON.stringify(event); + this.wsClientWithEvent.forEach((wsClient) => { + if (wsClient.readyState === WebSocket.OPEN) { + try { + wsClient.send(payload); + } catch (error) { + this.logger.logError('[Debug] 发送事件失败:', error); + } + } + }); + } + + async callApi (actionName: ActionNameType, params: Record): Promise { + this.updateActivity(); + + const action = this.actions.get(actionName as Parameters[0]); + if (!action) { + throw new Error(`不支持的 API: ${actionName}`); + } + + type ActionHandler = { handle: (params: unknown, ...args: unknown[]) => Promise; }; + return await (action as ActionHandler).handle(params, this.name, this.config); + } + + private async handleMessage (wsClient: WebSocket, message: RawData): Promise { + this.updateActivity(); + let receiveData: { action: ActionNameType, params?: Record, echo?: unknown; } = { + action: ActionName.Unknown, + params: {} + }; + let echo: unknown = undefined; + + try { + receiveData = json5.parse(message.toString()); + echo = receiveData.echo; + } catch { + this.sendToClient(wsClient, OB11Response.error('json解析失败,请检查数据格式', 1400, echo)); + return; + } + + receiveData.params = receiveData?.params || {}; + + const action = this.actions.get(receiveData.action as Parameters[0]); + if (!action) { + this.logger.logError('[Debug] 不支持的API:', receiveData.action); + this.sendToClient(wsClient, OB11Response.error('不支持的API ' + receiveData.action, 1404, echo)); + return; + } + + try { + type ActionHandler = { websocketHandle: (params: unknown, ...args: unknown[]) => Promise; }; + const retdata = await (action as ActionHandler).websocketHandle(receiveData.params, echo ?? '', this.name, this.config, { + send: async (data: object) => { + this.sendToClient(wsClient, OB11Response.ok(data, echo ?? '', true)); + }, + }); + this.sendToClient(wsClient, retdata); + } catch (e: unknown) { + const error = e as Error; + this.logger.logError('[Debug] 处理消息失败:', error); + this.sendToClient(wsClient, OB11Response.error(error.message || '内部错误', 1200, echo)); + } + } + + private sendToClient (wsClient: WebSocket, data: unknown): void { + if (wsClient.readyState === WebSocket.OPEN) { + try { + wsClient.send(JSON.stringify(data)); + } catch (error) { + this.logger.logError('[Debug] 发送消息失败:', error); + } + } + } + + async addWsClient (ws: WebSocket): Promise { + this.wsClientWithEvent.push(ws); + this.wsClients.push(ws); + this.updateActivity(); + + // 发送连接事件 + this.sendToClient(ws, new OB11LifeCycleEvent(this.core, LifeCycleSubType.CONNECT)); + + ws.on('error', (err) => this.logger.log('[Debug] WebSocket Error:', err.message)); + ws.on('message', (message) => { + this.handleMessage(ws, message).catch((e: unknown) => { + this.logger.logError('[Debug] handleMessage error:', e); + }); + }); + ws.on('ping', () => ws.pong()); + ws.once('close', () => this.removeWsClient(ws)); + } + + private removeWsClient (ws: WebSocket): void { + const normalIndex = this.wsClients.indexOf(ws); + if (normalIndex !== -1) { + this.wsClients.splice(normalIndex, 1); + } + const eventIndex = this.wsClientWithEvent.indexOf(ws); + if (eventIndex !== -1) { + this.wsClientWithEvent.splice(eventIndex, 1); + } + } + + updateActivity (): void { + this.lastActivityTime = Date.now(); + } + + startInactivityCheck (): void { + this.inactivityTimer = setInterval(() => { + const inactive = Date.now() - this.lastActivityTime; + if (inactive > this.INACTIVITY_TIMEOUT && this.wsClients.length === 0) { + this.logger.log(`[Debug] Adapter ${this.name} 不活跃,自动关闭`); + const oneBotContext = WebUiDataRuntime.getOneBotContext(); + if (oneBotContext) { + // 先从管理器移除,避免重复销毁 + debugAdapterManager.removeAdapter(this.name); + // 使用 NetworkManager 标准流程关闭 + oneBotContext.networkManager.closeSomeAdapters([this]).catch((e: unknown) => { + this.logger.logError('[Debug] 自动关闭适配器失败:', e); + }); + } + } + }, 30000); } - /** - * 验证 Token - */ validateToken (inputToken: string): boolean { return this.token === inputToken; } @@ -244,17 +240,20 @@ class DebugAdapterManager { return this.currentAdapter; } + // 获取 OneBot 上下文 + const oneBotContext = WebUiDataRuntime.getOneBotContext(); + if (!oneBotContext) { + throw new Error('OneBot 未初始化,无法创建调试适配器'); + } + // 创建新实例 - const adapter = new DebugAdapter('primary'); + const adapter = new DebugAdapter('primary', oneBotContext.core, oneBotContext, oneBotContext.actions); this.currentAdapter = adapter; - // 注册到 OneBot NetworkManager - const oneBotContext = WebUiDataRuntime.getOneBotContext(); - if (oneBotContext) { - oneBotContext.networkManager.adapters.set(adapter.name, adapter as any); - } else { - console.warn('[Debug] OneBot 未初始化,无法注册适配器'); - } + // 使用 NetworkManager 标准流程注册并打开适配器 + oneBotContext.networkManager.registerAdapterAndOpen(adapter).catch((e: unknown) => { + console.error('[Debug] 注册适配器失败:', e); + }); return adapter; } @@ -286,8 +285,9 @@ router.post('/create', async (_req: Request, res: Response) => { token: adapter.token, message: '调试适配器已就绪', }); - } catch (error: any) { - sendError(res, error.message); + } catch (error: unknown) { + const err = error as Error; + sendError(res, err.message); } }); @@ -310,10 +310,11 @@ const handleCallApi = async (req: Request, res: Response) => { } const { action, params } = req.body; - const result = await adapter.callApi(action, params || {}); + const result = await adapter.callApi(action as ActionNameType, params || {}); sendSuccess(res, result); - } catch (error: any) { - sendError(res, error.message); + } catch (error: unknown) { + const err = error as Error; + sendError(res, err.message); } }; @@ -329,10 +330,25 @@ router.post('/close/:adapterName', async (req: Request, res: Response) => { if (!adapterName) { return sendError(res, '缺少 adapterName 参数'); } + + const adapter = debugAdapterManager.getAdapter(adapterName); + if (!adapter) { + return sendError(res, '调试适配器不存在'); + } + + // 先从管理器移除,避免重复销毁 debugAdapterManager.removeAdapter(adapterName); + + // 使用 NetworkManager 标准流程关闭适配器 + const oneBotContext = WebUiDataRuntime.getOneBotContext(); + if (oneBotContext) { + await oneBotContext.networkManager.closeSomeAdapters([adapter]); + } + sendSuccess(res, { message: '调试适配器已关闭' }); - } catch (error: any) { - sendError(res, error.message); + } catch (error: unknown) { + const err = error as Error; + sendError(res, err.message); } }); @@ -340,7 +356,7 @@ router.post('/close/:adapterName', async (req: Request, res: Response) => { * WebSocket 连接处理 * 路径: /api/Debug/ws?adapterName=xxx&token=xxx */ -export function handleDebugWebSocket (request: IncomingMessage, socket: any, head: any) { +export function handleDebugWebSocket (request: IncomingMessage, socket: unknown, head: unknown) { const url = new URL(request.url || '', `http://${request.headers.host}`); let adapterName = url.searchParams.get('adapterName'); const token = url.searchParams.get('token') || url.searchParams.get('access_token'); @@ -353,8 +369,8 @@ export function handleDebugWebSocket (request: IncomingMessage, socket: any, hea // Debug session should provide token if (!token) { console.log('[Debug] WebSocket 连接被拒绝: 缺少 Token'); - socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); - socket.destroy(); + (socket as { write: (data: string) => void; destroy: () => void; }).write('HTTP/1.1 401 Unauthorized\r\n\r\n'); + (socket as { destroy: () => void; }).destroy(); return; } @@ -362,43 +378,37 @@ export function handleDebugWebSocket (request: IncomingMessage, socket: any, hea // 如果是默认 adapter 且不存在,尝试创建 if (!adapter && adapterName === DEFAULT_ADAPTER_NAME) { - adapter = debugAdapterManager.getOrCreateAdapter(); + try { + adapter = debugAdapterManager.getOrCreateAdapter(); + } catch (error) { + console.log('[Debug] WebSocket 连接被拒绝: 无法创建适配器', error); + (socket as { write: (data: string) => void; destroy: () => void; }).write('HTTP/1.1 500 Internal Server Error\r\n\r\n'); + (socket as { destroy: () => void; }).destroy(); + return; + } } if (!adapter) { console.log('[Debug] WebSocket 连接被拒绝: 适配器不存在'); - socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); - socket.destroy(); + (socket as { write: (data: string) => void; destroy: () => void; }).write('HTTP/1.1 404 Not Found\r\n\r\n'); + (socket as { destroy: () => void; }).destroy(); return; } if (!adapter.validateToken(token)) { console.log('[Debug] WebSocket 连接被拒绝: Token 无效'); - socket.write('HTTP/1.1 403 Forbidden\r\n\r\n'); - socket.destroy(); + (socket as { write: (data: string) => void; destroy: () => void; }).write('HTTP/1.1 403 Forbidden\r\n\r\n'); + (socket as { destroy: () => void; }).destroy(); return; } // 创建 WebSocket 服务器 const wsServer = new WebSocketServer({ noServer: true }); - wsServer.handleUpgrade(request, socket, head, (ws) => { - adapter.addWsClient(ws); - - ws.on('message', async (data) => { - try { - await adapter.handleWsMessage(ws, data as any); - } catch (error: any) { - console.error('[Debug] handleWsMessage error', error); - } - }); - - ws.on('close', () => { - adapter.removeWsClient(ws); - }); - - ws.on('error', () => { - adapter.removeWsClient(ws); + wsServer.handleUpgrade(request, socket as never, head as Buffer, (ws) => { + adapter.addWsClient(ws).catch((e: unknown) => { + console.error('[Debug] 添加 WebSocket 客户端失败:', e); + ws.close(); }); }); }