Refactor DebugAdapter to extend IOB11NetworkAdapter

Refactored DebugAdapter to inherit from IOB11NetworkAdapter, improving integration with the OneBot network manager. Enhanced WebSocket client management, error handling, and adapter lifecycle. Updated API and WebSocket handlers for better type safety and reliability. This change prepares the debug adapter for more robust and maintainable debugging sessions.
This commit is contained in:
手瓜一十雪 2026-01-22 16:22:18 +08:00
parent 0f9647bf64
commit 772f07c58b

View File

@ -1,11 +1,20 @@
import { Router, Request, Response } from 'express'; 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 { sendError, sendSuccess } from '@/napcat-webui-backend/src/utils/response';
import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data'; import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data';
import { IncomingMessage } from 'http'; import { IncomingMessage } from 'http';
import { OB11Response } from '@/napcat-onebot/action/OneBotAction'; import { OB11Response } from '@/napcat-onebot/action/OneBotAction';
import { ActionName } from '@/napcat-onebot/action/router'; import { ActionName } from '@/napcat-onebot/action/router';
import { OB11LifeCycleEvent, LifeCycleSubType } from '@/napcat-onebot/event/meta/OB11LifeCycleEvent'; 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 router = Router();
const DEFAULT_ADAPTER_NAME = 'debug-primary'; const DEFAULT_ADAPTER_NAME = 'debug-primary';
@ -14,218 +23,205 @@ const DEFAULT_ADAPTER_NAME = 'debug-primary';
* *
* OneBot NetworkManager WebSocket * OneBot NetworkManager WebSocket
*/ */
class DebugAdapter { class DebugAdapter extends IOB11NetworkAdapter<WebsocketServerConfig> {
name: string;
isEnable: boolean = true;
// 安全令牌
readonly token: string; readonly token: string;
wsClients: WebSocket[] = [];
// 添加 config 属性,模拟 PluginConfig 结构 wsClientWithEvent: WebSocket[] = [];
config: {
enable: boolean;
name: string;
messagePostFormat?: string;
reportSelfMessage?: boolean;
debug?: boolean;
token?: string;
heartInterval?: number;
};
wsClients: Set<WebSocket> = new Set();
lastActivityTime: number = Date.now(); lastActivityTime: number = Date.now();
inactivityTimer: NodeJS.Timeout | null = null; inactivityTimer: NodeJS.Timeout | null = null;
readonly INACTIVITY_TIMEOUT = 5 * 60 * 1000; // 5分钟不活跃 readonly INACTIVITY_TIMEOUT = 5 * 60 * 1000; // 5分钟不活跃
constructor (sessionId: string) { override get isActive (): boolean {
this.name = `debug-${sessionId}`; return this.isEnable && this.wsClientWithEvent.length > 0;
// 生成简单的随机 token }
this.token = Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2);
this.config = { constructor (sessionId: string, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap) {
const config: WebsocketServerConfig = {
enable: true, enable: true,
name: this.name, name: `debug-${sessionId}`,
host: '127.0.0.1',
port: 0,
messagePostFormat: 'array', messagePostFormat: 'array',
reportSelfMessage: true, reportSelfMessage: true,
token: '',
enableForcePushEvent: true,
debug: true, debug: true,
token: this.token, heartInterval: 0
heartInterval: 30000
}; };
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(); this.startInactivityCheck();
} }
// 实现 IOB11NetworkAdapter 接口所需的抽象方法 async open (): Promise<void> {
async open (): Promise<void> { } if (this.isEnable) {
async close (): Promise<void> { this.cleanup(); } this.logger.logError('[Debug] Cannot open an already opened adapter');
async reload (_config: any): Promise<any> { return 0; }
/**
* OneBot - WebSocket ()
*/
async onEvent (event: any) {
this.updateActivity();
const payload = JSON.stringify(event);
if (this.wsClients.size === 0) {
return; return;
} }
this.logger.log('[Debug] Adapter opened:', this.name);
this.wsClients.forEach((client) => { this.isEnable = true;
if (client.readyState === WebSocket.OPEN) {
try {
client.send(payload);
} catch (error) {
console.error('[Debug] 发送事件到 WebSocket 失败:', error);
}
}
});
} }
/** async close (): Promise<void> {
* OneBot API (HTTP 使) if (!this.isEnable) {
*/
async callApi (actionName: string, params: any): Promise<any> {
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));
return; 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) { if (this.inactivityTimer) {
clearInterval(this.inactivityTimer); clearInterval(this.inactivityTimer);
this.inactivityTimer = null; this.inactivityTimer = null;
} }
// 关闭所有 WebSocket 连接 // 关闭所有 WebSocket 连接并移除事件监听器
this.wsClients.forEach((client) => { this.wsClients.forEach((client) => {
try { try {
client.removeAllListeners();
client.close(); client.close();
} catch (error) { } catch (error) {
// ignore this.logger.logError('[Debug] 关闭 WebSocket 失败:', error);
} }
}); });
this.wsClients.clear(); this.wsClients = [];
this.wsClientWithEvent = [];
// 从 OneBot NetworkManager 移除 }
const oneBotContext = WebUiDataRuntime.getOneBotContext();
if (oneBotContext) { async reload (_config: unknown): Promise<OB11NetworkReloadType> {
oneBotContext.networkManager.adapters.delete(this.name); return OB11NetworkReloadType.NetWorkReload;
} }
// 从管理器中移除 async onEvent<T extends OB11EmitEventContent> (event: T): Promise<void> {
debugAdapterManager.removeAdapter(this.name); 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<string, unknown>): Promise<unknown> {
this.updateActivity();
const action = this.actions.get(actionName as Parameters<typeof this.actions.get>[0]);
if (!action) {
throw new Error(`不支持的 API: ${actionName}`);
}
type ActionHandler = { handle: (params: unknown, ...args: unknown[]) => Promise<unknown>; };
return await (action as ActionHandler).handle(params, this.name, this.config);
}
private async handleMessage (wsClient: WebSocket, message: RawData): Promise<void> {
this.updateActivity();
let receiveData: { action: ActionNameType, params?: Record<string, unknown>, 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<typeof this.actions.get>[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<unknown>; };
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<void> {
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 { validateToken (inputToken: string): boolean {
return this.token === inputToken; return this.token === inputToken;
} }
@ -244,17 +240,20 @@ class DebugAdapterManager {
return this.currentAdapter; 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; this.currentAdapter = adapter;
// 注册到 OneBot NetworkManager // 使用 NetworkManager 标准流程注册并打开适配器
const oneBotContext = WebUiDataRuntime.getOneBotContext(); oneBotContext.networkManager.registerAdapterAndOpen(adapter).catch((e: unknown) => {
if (oneBotContext) { console.error('[Debug] 注册适配器失败:', e);
oneBotContext.networkManager.adapters.set(adapter.name, adapter as any); });
} else {
console.warn('[Debug] OneBot 未初始化,无法注册适配器');
}
return adapter; return adapter;
} }
@ -286,8 +285,9 @@ router.post('/create', async (_req: Request, res: Response) => {
token: adapter.token, token: adapter.token,
message: '调试适配器已就绪', message: '调试适配器已就绪',
}); });
} catch (error: any) { } catch (error: unknown) {
sendError(res, error.message); 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 { action, params } = req.body;
const result = await adapter.callApi(action, params || {}); const result = await adapter.callApi(action as ActionNameType, params || {});
sendSuccess(res, result); sendSuccess(res, result);
} catch (error: any) { } catch (error: unknown) {
sendError(res, error.message); const err = error as Error;
sendError(res, err.message);
} }
}; };
@ -329,10 +330,25 @@ router.post('/close/:adapterName', async (req: Request, res: Response) => {
if (!adapterName) { if (!adapterName) {
return sendError(res, '缺少 adapterName 参数'); return sendError(res, '缺少 adapterName 参数');
} }
const adapter = debugAdapterManager.getAdapter(adapterName);
if (!adapter) {
return sendError(res, '调试适配器不存在');
}
// 先从管理器移除,避免重复销毁
debugAdapterManager.removeAdapter(adapterName); debugAdapterManager.removeAdapter(adapterName);
// 使用 NetworkManager 标准流程关闭适配器
const oneBotContext = WebUiDataRuntime.getOneBotContext();
if (oneBotContext) {
await oneBotContext.networkManager.closeSomeAdapters([adapter]);
}
sendSuccess(res, { message: '调试适配器已关闭' }); sendSuccess(res, { message: '调试适配器已关闭' });
} catch (error: any) { } catch (error: unknown) {
sendError(res, error.message); const err = error as Error;
sendError(res, err.message);
} }
}); });
@ -340,7 +356,7 @@ router.post('/close/:adapterName', async (req: Request, res: Response) => {
* WebSocket * WebSocket
* : /api/Debug/ws?adapterName=xxx&token=xxx * : /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}`); const url = new URL(request.url || '', `http://${request.headers.host}`);
let adapterName = url.searchParams.get('adapterName'); let adapterName = url.searchParams.get('adapterName');
const token = url.searchParams.get('token') || url.searchParams.get('access_token'); 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 // Debug session should provide token
if (!token) { if (!token) {
console.log('[Debug] WebSocket 连接被拒绝: 缺少 Token'); console.log('[Debug] WebSocket 连接被拒绝: 缺少 Token');
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); (socket as { write: (data: string) => void; destroy: () => void; }).write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy(); (socket as { destroy: () => void; }).destroy();
return; return;
} }
@ -362,43 +378,37 @@ export function handleDebugWebSocket (request: IncomingMessage, socket: any, hea
// 如果是默认 adapter 且不存在,尝试创建 // 如果是默认 adapter 且不存在,尝试创建
if (!adapter && adapterName === DEFAULT_ADAPTER_NAME) { 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) { if (!adapter) {
console.log('[Debug] WebSocket 连接被拒绝: 适配器不存在'); console.log('[Debug] WebSocket 连接被拒绝: 适配器不存在');
socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); (socket as { write: (data: string) => void; destroy: () => void; }).write('HTTP/1.1 404 Not Found\r\n\r\n');
socket.destroy(); (socket as { destroy: () => void; }).destroy();
return; return;
} }
if (!adapter.validateToken(token)) { if (!adapter.validateToken(token)) {
console.log('[Debug] WebSocket 连接被拒绝: Token 无效'); console.log('[Debug] WebSocket 连接被拒绝: Token 无效');
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n'); (socket as { write: (data: string) => void; destroy: () => void; }).write('HTTP/1.1 403 Forbidden\r\n\r\n');
socket.destroy(); (socket as { destroy: () => void; }).destroy();
return; return;
} }
// 创建 WebSocket 服务器 // 创建 WebSocket 服务器
const wsServer = new WebSocketServer({ noServer: true }); const wsServer = new WebSocketServer({ noServer: true });
wsServer.handleUpgrade(request, socket, head, (ws) => { wsServer.handleUpgrade(request, socket as never, head as Buffer, (ws) => {
adapter.addWsClient(ws); adapter.addWsClient(ws).catch((e: unknown) => {
console.error('[Debug] 添加 WebSocket 客户端失败:', e);
ws.on('message', async (data) => { ws.close();
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);
}); });
}); });
} }