From a2450b72beea3b72a54634e141f21b14853c0f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Wed, 14 Jan 2026 13:11:17 +0800 Subject: [PATCH] Refactor network adapter activation and message handling Introduces isActive property to network adapters for more accurate activation checks, refactors message dispatch logic to use only active adapters, and improves heartbeat management for WebSocket adapters. Also sets default enableWebsocket to false in config and frontend forms, and adds a security dialog for missing tokens in the web UI. --- packages/napcat-onebot/config/config.ts | 4 +- packages/napcat-onebot/index.ts | 86 ++++----- packages/napcat-onebot/network/adapter.ts | 6 +- .../napcat-onebot/network/http-server-sse.ts | 7 +- packages/napcat-onebot/network/http-server.ts | 179 +++++++++++++++++- packages/napcat-onebot/network/index.ts | 16 +- .../napcat-onebot/network/websocket-client.ts | 4 + .../napcat-onebot/network/websocket-server.ts | 37 ++-- .../components/network_edit/http_server.tsx | 8 +- .../src/components/network_edit/http_sse.tsx | 8 +- .../src/components/network_edit/modal.tsx | 57 ++++-- 11 files changed, 316 insertions(+), 96 deletions(-) diff --git a/packages/napcat-onebot/config/config.ts b/packages/napcat-onebot/config/config.ts index b43c2c49..189d61ad 100644 --- a/packages/napcat-onebot/config/config.ts +++ b/packages/napcat-onebot/config/config.ts @@ -6,7 +6,7 @@ const HttpServerConfigSchema = Type.Object({ port: Type.Number({ default: 3000 }), host: Type.String({ default: '127.0.0.1' }), enableCors: Type.Boolean({ default: true }), - enableWebsocket: Type.Boolean({ default: true }), + enableWebsocket: Type.Boolean({ default: false }), messagePostFormat: Type.String({ default: 'array' }), token: Type.String({ default: '' }), debug: Type.Boolean({ default: false }), @@ -18,7 +18,7 @@ const HttpSseServerConfigSchema = Type.Object({ port: Type.Number({ default: 3000 }), host: Type.String({ default: '127.0.0.1' }), enableCors: Type.Boolean({ default: true }), - enableWebsocket: Type.Boolean({ default: true }), + enableWebsocket: Type.Boolean({ default: false }), messagePostFormat: Type.String({ default: 'array' }), token: Type.String({ default: '' }), debug: Type.Boolean({ default: false }), diff --git a/packages/napcat-onebot/index.ts b/packages/napcat-onebot/index.ts index 4fd656d0..5a9e288e 100644 --- a/packages/napcat-onebot/index.ts +++ b/packages/napcat-onebot/index.ts @@ -246,7 +246,7 @@ export class NapCatOneBot11Adapter { await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11WebSocketClientAdapter); } - private async handleConfigChange( + private async handleConfigChange ( prevConfig: NetworkAdapterConfig[], nowConfig: NetworkAdapterConfig[], adapterClass: new ( @@ -305,6 +305,9 @@ export class NapCatOneBot11Adapter { }; msgListener.onRecvMsg = async (msg) => { + if (!this.networkManager.hasActiveAdapters()) { + return; + } for (const m of msg) { if (this.bootTime > parseInt(m.msgTime)) { this.context.logger.logDebug(`消息时间${m.msgTime}早于启动时间${this.bootTime},忽略上报`); @@ -517,15 +520,14 @@ export class NapCatOneBot11Adapter { } private async emitMsg (message: RawMessage) { - const network = await this.networkManager.getAllConfig(); this.context.logger.logDebug('收到新消息 RawMessage', message); await Promise.allSettled([ - this.handleMsg(message, network), + this.handleMsg(message), message.chatType === ChatType.KCHATTYPEGROUP ? this.handleGroupEvent(message) : this.handlePrivateMsgEvent(message), ]); } - private async handleMsg (message: RawMessage, network: Array) { + private async handleMsg (message: RawMessage) { // 过滤无效消息 if (message.msgType === NTMsgType.KMSGTYPENULL) { return; @@ -535,10 +537,36 @@ export class NapCatOneBot11Adapter { if (ob11Msg) { const isSelfMsg = this.isSelfMessage(ob11Msg); this.context.logger.logDebug('转化为 OB11Message', ob11Msg); - const msgMap = this.createMsgMap(network, ob11Msg, isSelfMsg, message); - this.handleDebugNetwork(network, msgMap, message); - this.handleNotReportSelfNetwork(network, msgMap, isSelfMsg); - this.networkManager.emitEventByNames(msgMap); + if (isSelfMsg || message.chatType !== ChatType.KCHATTYPEGROUP) { + const targetId = parseInt(message.peerUin); + ob11Msg.stringMsg.target_id = targetId; + ob11Msg.arrayMsg.target_id = targetId; + } + + const msgMap = new Map(); + + for (const adapter of this.networkManager.adapters.values()) { + if (!adapter.isActive) continue; + const config = adapter.config; + if (isSelfMsg) { + if (!('reportSelfMessage' in config) || !config.reportSelfMessage) { + continue; + } + } + const msgData = config.messagePostFormat === 'string' ? ob11Msg.stringMsg : ob11Msg.arrayMsg; + if (config.debug) { + const clone = structuredClone(msgData); + clone.raw = message; + msgMap.set(adapter.name, clone); + } else { + msgMap.set(adapter.name, msgData); + } + } + if (msgMap.size > 0) { + this.networkManager.emitEventByNames(msgMap); + } else if (this.networkManager.hasActiveAdapters()) { + this.context.logger.logDebug('没有可用的网络适配器发送消息,消息内容:', message); + } } } catch (e) { this.context.logger.logError('constructMessage error: ', e); @@ -553,48 +581,6 @@ export class NapCatOneBot11Adapter { ob11Msg.arrayMsg.user_id.toString() === this.core.selfInfo.uin; } - private createMsgMap (network: Array, ob11Msg: { - stringMsg: OB11Message; - arrayMsg: OB11Message; - }, isSelfMsg: boolean, message: RawMessage): Map { - const msgMap: Map = new Map(); - network.filter(e => e.enable).forEach(e => { - if (isSelfMsg || message.chatType !== ChatType.KCHATTYPEGROUP) { - ob11Msg.stringMsg.target_id = parseInt(message.peerUin); - ob11Msg.arrayMsg.target_id = parseInt(message.peerUin); - } - if ('messagePostFormat' in e && e.messagePostFormat === 'string') { - msgMap.set(e.name, structuredClone(ob11Msg.stringMsg)); - } else { - msgMap.set(e.name, structuredClone(ob11Msg.arrayMsg)); - } - }); - return msgMap; - } - - private handleDebugNetwork (network: Array, msgMap: Map, message: RawMessage) { - const debugNetwork = network.filter(e => e.enable && e.debug); - if (debugNetwork.length > 0) { - debugNetwork.forEach(adapter => { - const msg = msgMap.get(adapter.name); - if (msg) { - msg.raw = message; - } - }); - } else if (msgMap.size === 0) { - this.context.logger.logDebug('没有可用的网络适配器发送消息,消息内容:', message); - } - } - - private handleNotReportSelfNetwork (network: Array, msgMap: Map, isSelfMsg: boolean) { - if (isSelfMsg) { - const notReportSelfNetwork = network.filter(e => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e))); - notReportSelfNetwork.forEach(adapter => { - msgMap.delete(adapter.name); - }); - } - } - private async handleGroupEvent (message: RawMessage) { try { // 群名片修改事件解析 任何都该判断 diff --git a/packages/napcat-onebot/network/adapter.ts b/packages/napcat-onebot/network/adapter.ts index 63b90e9f..a4f1c1f8 100644 --- a/packages/napcat-onebot/network/adapter.ts +++ b/packages/napcat-onebot/network/adapter.ts @@ -23,11 +23,15 @@ export abstract class IOB11NetworkAdapter { this.logger = core.context.logger; } - abstract onEvent(event: T): Promise; + abstract onEvent (event: T): Promise; abstract open (): void | Promise; abstract close (): void | Promise; abstract reload (config: unknown): OB11NetworkReloadType | Promise; + + get isActive (): boolean { + return this.isEnable; + } } diff --git a/packages/napcat-onebot/network/http-server-sse.ts b/packages/napcat-onebot/network/http-server-sse.ts index 9ae3ec8c..db5d5aa1 100644 --- a/packages/napcat-onebot/network/http-server-sse.ts +++ b/packages/napcat-onebot/network/http-server-sse.ts @@ -5,6 +5,10 @@ import { OB11HttpServerAdapter } from './http-server'; export class OB11HttpSSEServerAdapter extends OB11HttpServerAdapter { private sseClients: Response[] = []; + override get isActive (): boolean { + return this.isEnable && (this.sseClients.length > 0 || super.isActive); + } + override async handleRequest (req: Request, res: Response) { if (req.path === '/_events') { this.createSseSupport(req, res); @@ -25,7 +29,8 @@ export class OB11HttpSSEServerAdapter extends OB11HttpServerAdapter { }); } - override async onEvent(event: T) { + override async onEvent (event: T) { + super.onEvent(event); const promises: Promise[] = []; this.sseClients.forEach((res) => { promises.push(new Promise((resolve, reject) => { diff --git a/packages/napcat-onebot/network/http-server.ts b/packages/napcat-onebot/network/http-server.ts index 9a06e924..a2b19260 100644 --- a/packages/napcat-onebot/network/http-server.ts +++ b/packages/napcat-onebot/network/http-server.ts @@ -1,6 +1,6 @@ import { OB11EmitEventContent, OB11NetworkReloadType } from './index'; import express, { Express, NextFunction, Request, Response } from 'express'; -import http from 'http'; +import http, { IncomingMessage } from 'http'; import { OB11Response } from '@/napcat-onebot/action/OneBotAction'; import cors from 'cors'; import { HttpServerConfig } from '@/napcat-onebot/config/config'; @@ -8,13 +8,41 @@ import { IOB11NetworkAdapter } from '@/napcat-onebot/network/adapter'; import json5 from 'json5'; import { isFinished } from 'on-finished'; import typeis from 'type-is'; +import { WebSocket, WebSocketServer, RawData } from 'ws'; +import { URL } from 'url'; +import { ActionName } from '@/napcat-onebot/action/router'; +import { OB11HeartbeatEvent } from '@/napcat-onebot/event/meta/OB11HeartbeatEvent'; +import { OB11LifeCycleEvent, LifeCycleSubType } from '@/napcat-onebot/event/meta/OB11LifeCycleEvent'; +import { Mutex } from 'async-mutex'; export class OB11HttpServerAdapter extends IOB11NetworkAdapter { private app: Express | undefined; private server: http.Server | undefined; + private wsServer?: WebSocketServer; + private wsClients: WebSocket[] = []; + private wsClientsMutex = new Mutex(); + private heartbeatIntervalId: NodeJS.Timeout | null = null; + private wsClientWithEvent: WebSocket[] = []; - override async onEvent (_event: T) { + override get isActive (): boolean { + return this.isEnable && this.wsClientWithEvent.length > 0; + } + + override async onEvent (event: T) { // http server is passive, no need to emit event + this.wsClientsMutex.runExclusive(async () => { + const promises = this.wsClientWithEvent.map((wsClient) => { + return new Promise((resolve, reject) => { + if (wsClient.readyState === WebSocket.OPEN) { + wsClient.send(JSON.stringify(event)); + resolve(); + } else { + reject(new Error('WebSocket is not open')); + } + }); + }); + await Promise.allSettled(promises); + }); } open () { @@ -36,11 +64,24 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter this.isEnable = false; this.server?.close(); this.app = undefined; + this.stopHeartbeat(); + await this.wsClientsMutex.runExclusive(async () => { + this.wsClients.forEach((wsClient) => { + wsClient.close(); + }); + this.wsClients = []; + this.wsClientWithEvent = []; + }); + this.wsServer?.close(); } private initializeServer () { this.app = express(); this.server = http.createServer(this.app); + if (this.config.enableWebsocket) { + this.wsServer = new WebSocketServer({ server: this.server }); + this.createWSServer(this.wsServer); + } this.app.use(cors()); this.app.use(express.urlencoded({ extended: true, limit: '5000mb' })); @@ -93,6 +134,137 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter } } + createWSServer (newServer: WebSocketServer) { + newServer.on('connection', async (wsClient, wsReq) => { + if (!this.isEnable) { + wsClient.close(); + return; + } + if (!this.authorizeWS(this.config.token, wsClient, wsReq)) { + return; + } + const paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url; + const isApiConnect = paramUrl === '/api' || paramUrl === '/api/'; + if (!isApiConnect) { + this.connectEvent(this.core, wsClient); + } + + wsClient.on('error', (err) => this.logger.log('[OneBot] [HTTP WebSocket] Client Error:', err.message)); + wsClient.on('message', (message) => { + this.handleWSMessage(wsClient, message).then().catch(e => this.logger.logError(e)); + }); + wsClient.on('ping', () => { + wsClient.pong(); + }); + wsClient.on('pong', () => { + // this.logger.logDebug('[OneBot] [HTTP WebSocket] Pong received'); + }); + wsClient.once('close', () => { + this.wsClientsMutex.runExclusive(async () => { + const NormolIndex = this.wsClients.indexOf(wsClient); + if (NormolIndex !== -1) { + this.wsClients.splice(NormolIndex, 1); + } + const EventIndex = this.wsClientWithEvent.indexOf(wsClient); + if (EventIndex !== -1) { + this.wsClientWithEvent.splice(EventIndex, 1); + } + if (this.wsClientWithEvent.length === 0) { + this.stopHeartbeat(); + } + }); + }); + await this.wsClientsMutex.runExclusive(async () => { + if (!isApiConnect) { + this.wsClientWithEvent.push(wsClient); + } + this.wsClients.push(wsClient); + if (this.wsClientWithEvent.length > 0) { + this.startHeartbeat(); + } + }); + }).on('error', (err) => this.logger.log('[OneBot] [HTTP WebSocket] Server Error:', err.message)); + } + + connectEvent (core: any, wsClient: WebSocket) { + try { + this.checkStateAndReply(new OB11LifeCycleEvent(core, LifeCycleSubType.CONNECT), wsClient).catch(e => this.logger.logError('[OneBot] [HTTP WebSocket] 发送生命周期失败', e)); + } catch (e) { + this.logger.logError('[OneBot] [HTTP WebSocket] 发送生命周期失败', e); + } + } + + private startHeartbeat () { + if (this.heartbeatIntervalId) return; + this.heartbeatIntervalId = setInterval(() => { + this.wsClientsMutex.runExclusive(async () => { + this.wsClientWithEvent.forEach((wsClient) => { + if (wsClient.readyState === WebSocket.OPEN) { + wsClient.send(JSON.stringify(new OB11HeartbeatEvent(this.core, 30000, this.core.selfInfo.online ?? true, true))); + } + }); + }); + }, 30000); + } + + private stopHeartbeat () { + if (this.heartbeatIntervalId) { + clearInterval(this.heartbeatIntervalId); + this.heartbeatIntervalId = null; + } + } + + private authorizeWS (token: string | undefined, wsClient: WebSocket, wsReq: IncomingMessage) { + if (!token || token.length === 0) return true; + const url = new URL(wsReq?.url || '', `http://${wsReq.headers.host}`); + const QueryClientToken = url.searchParams.get('access_token'); + const HeaderClientToken = wsReq.headers.authorization?.split('Bearer ').pop() || ''; + const ClientToken = typeof (QueryClientToken) === 'string' && QueryClientToken !== '' ? QueryClientToken : HeaderClientToken; + if (ClientToken === token) { + return true; + } + wsClient.send(JSON.stringify(OB11Response.res(null, 'failed', 1403, 'token验证失败'))); + wsClient.close(); + return false; + } + + private async checkStateAndReply (data: T, wsClient: WebSocket) { + return await new Promise((resolve, reject) => { + if (wsClient.readyState === WebSocket.OPEN) { + wsClient.send(JSON.stringify(data)); + resolve(); + } else { + reject(new Error('WebSocket is not open')); + } + }); + } + + private async handleWSMessage (wsClient: WebSocket, message: RawData) { + let receiveData: { action: typeof ActionName[keyof typeof ActionName], params?: any, echo?: any; } = { action: ActionName.Unknown, params: {} }; + let echo; + try { + receiveData = json5.parse(message.toString()); + echo = receiveData.echo; + } catch { + await this.checkStateAndReply(OB11Response.error('json解析失败,请检查数据格式', 1400, echo), wsClient); + return; + } + receiveData.params = (receiveData?.params) ? receiveData.params : {}; + + const action = this.actions.get(receiveData.action as any); + if (!action) { + this.logger.logError('[OneBot] [HTTP WebSocket] 发生错误', '不支持的API ' + receiveData.action); + await this.checkStateAndReply(OB11Response.error('不支持的API ' + receiveData.action, 1404, echo), wsClient); + return; + } + const retdata = await action.websocketHandle(receiveData.params, echo ?? '', this.name, this.config, { + send: async (data: object) => { + await this.checkStateAndReply({ ...OB11Response.ok(data, echo ?? '', true) }, wsClient); + }, + }); + await this.checkStateAndReply({ ...retdata }, wsClient); + } + async httpApiRequest (req: Request, res: Response, request_sse: boolean = false) { let payload = req.body; if (req.method === 'get') { @@ -152,6 +324,7 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter async reload (newConfig: HttpServerConfig) { const wasEnabled = this.isEnable; const oldPort = this.config.port; + const oldEnableWebsocket = this.config.enableWebsocket; this.config = newConfig; if (newConfig.enable && !wasEnabled) { @@ -162,7 +335,7 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter return OB11NetworkReloadType.NetWorkClose; } - if (oldPort !== newConfig.port) { + if (oldPort !== newConfig.port || oldEnableWebsocket !== newConfig.enableWebsocket) { this.close(); if (newConfig.enable) { this.open(); diff --git a/packages/napcat-onebot/network/index.ts b/packages/napcat-onebot/network/index.ts index 1eda2dbb..94d51d74 100644 --- a/packages/napcat-onebot/network/index.ts +++ b/packages/napcat-onebot/network/index.ts @@ -49,23 +49,23 @@ export class OB11NetworkManager { })); } - registerAdapter(adapter: IOB11NetworkAdapter) { + registerAdapter (adapter: IOB11NetworkAdapter) { this.adapters.set(adapter.name, adapter); } - async registerAdapterAndOpen(adapter: IOB11NetworkAdapter) { + async registerAdapterAndOpen (adapter: IOB11NetworkAdapter) { this.registerAdapter(adapter); await adapter.open(); } - async closeSomeAdapters(adaptersToClose: IOB11NetworkAdapter[]) { + async closeSomeAdapters (adaptersToClose: IOB11NetworkAdapter[]) { for (const adapter of adaptersToClose) { this.adapters.delete(adapter.name); await adapter.close(); } } - async closeSomeAdaterWhenOpen(adaptersToClose: IOB11NetworkAdapter[]) { + async closeSomeAdaterWhenOpen (adaptersToClose: IOB11NetworkAdapter[]) { for (const adapter of adaptersToClose) { this.adapters.delete(adapter.name); if (adapter.isEnable) { @@ -88,17 +88,21 @@ export class OB11NetworkManager { this.adapters.clear(); } - async readloadAdapter(name: string, config: T) { + async readloadAdapter (name: string, config: T) { const adapter = this.adapters.get(name); if (adapter) { await adapter.reload(config); } } - async readloadSomeAdapters(configMap: Map) { + async readloadSomeAdapters (configMap: Map) { await Promise.all(Array.from(configMap.entries()).map(([name, config]) => this.readloadAdapter(name, config))); } + hasActiveAdapters (): boolean { + return Array.from(this.adapters.values()).some(adapter => adapter.isActive); + } + async getAllConfig () { return Array.from(this.adapters.values()).map(adapter => adapter.config); } diff --git a/packages/napcat-onebot/network/websocket-client.ts b/packages/napcat-onebot/network/websocket-client.ts index 67a014aa..b40e668f 100644 --- a/packages/napcat-onebot/network/websocket-client.ts +++ b/packages/napcat-onebot/network/websocket-client.ts @@ -13,6 +13,10 @@ export class OB11WebSocketClientAdapter extends IOB11NetworkAdapter (event: T) { if (this.connection && this.connection.readyState === WebSocket.OPEN) { this.connection.send(JSON.stringify(event)); diff --git a/packages/napcat-onebot/network/websocket-server.ts b/packages/napcat-onebot/network/websocket-server.ts index 18f9cc49..d5a9affe 100644 --- a/packages/napcat-onebot/network/websocket-server.ts +++ b/packages/napcat-onebot/network/websocket-server.ts @@ -21,6 +21,10 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter 0; + } + constructor ( name: string, config: WebsocketServerConfig, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap ) { @@ -70,6 +74,9 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter { @@ -77,6 +84,9 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter 0) { + this.startHeartbeat(); + } }); }).on('error', (err) => this.logger.log('[OneBot] [WebSocket Server] Server Error:', err.message)); } @@ -114,9 +124,6 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter 0) { - this.registerHeartBeat(); - } } async close () { @@ -128,10 +135,7 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter { this.wsClients.forEach((wsClient) => { wsClient.close(); @@ -141,7 +145,8 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter { this.wsClientsMutex.runExclusive(async () => { this.wsClientWithEvent.forEach((wsClient) => { @@ -153,6 +158,13 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter 0 && this.isEnable) { - this.registerHeartBeat(); + this.stopHeartbeat(); + if (newConfig.heartInterval > 0 && this.isEnable && this.wsClientWithEvent.length > 0) { + this.startHeartbeat(); } return OB11NetworkReloadType.NetWorkReload; } diff --git a/packages/napcat-webui-frontend/src/components/network_edit/http_server.tsx b/packages/napcat-webui-frontend/src/components/network_edit/http_server.tsx index 7fb74883..3df93844 100644 --- a/packages/napcat-webui-frontend/src/components/network_edit/http_server.tsx +++ b/packages/napcat-webui-frontend/src/components/network_edit/http_server.tsx @@ -2,9 +2,9 @@ import GenericForm, { random_token } from './generic_form'; import type { Field } from './generic_form'; export interface HTTPServerFormProps { - data?: OneBotConfig['network']['httpServers'][0] - onClose: () => void - onSubmit: (data: OneBotConfig['network']['httpServers'][0]) => Promise + data?: OneBotConfig['network']['httpServers'][0]; + onClose: () => void; + onSubmit: (data: OneBotConfig['network']['httpServers'][0]) => Promise; } type HTTPServerFormType = OneBotConfig['network']['httpServers']; @@ -20,7 +20,7 @@ const HTTPServerForm: React.FC = ({ host: '127.0.0.1', port: 3000, enableCors: true, - enableWebsocket: true, + enableWebsocket: false, messagePostFormat: 'array', token: random_token(16), debug: false, diff --git a/packages/napcat-webui-frontend/src/components/network_edit/http_sse.tsx b/packages/napcat-webui-frontend/src/components/network_edit/http_sse.tsx index 06dbbf09..18f23cc3 100644 --- a/packages/napcat-webui-frontend/src/components/network_edit/http_sse.tsx +++ b/packages/napcat-webui-frontend/src/components/network_edit/http_sse.tsx @@ -2,11 +2,11 @@ import GenericForm, { random_token } from './generic_form'; import type { Field } from './generic_form'; export interface HTTPServerSSEFormProps { - data?: OneBotConfig['network']['httpSseServers'][0] - onClose: () => void + data?: OneBotConfig['network']['httpSseServers'][0]; + onClose: () => void; onSubmit: ( data: OneBotConfig['network']['httpSseServers'][0] - ) => Promise + ) => Promise; } type HTTPServerSSEFormType = OneBotConfig['network']['httpSseServers']; @@ -22,7 +22,7 @@ const HTTPServerSSEForm: React.FC = ({ host: '127.0.0.1', port: 3000, enableCors: true, - enableWebsocket: true, + enableWebsocket: false, messagePostFormat: 'array', token: random_token(16), debug: false, diff --git a/packages/napcat-webui-frontend/src/components/network_edit/modal.tsx b/packages/napcat-webui-frontend/src/components/network_edit/modal.tsx index dab5b78e..409c89f5 100644 --- a/packages/napcat-webui-frontend/src/components/network_edit/modal.tsx +++ b/packages/napcat-webui-frontend/src/components/network_edit/modal.tsx @@ -2,6 +2,7 @@ import { Modal, ModalContent, ModalHeader } from '@heroui/modal'; import toast from 'react-hot-toast'; import useConfig from '@/hooks/use-config'; +import useDialog from '@/hooks/use-dialog'; import HTTPClientForm from './http_client'; import HTTPServerForm from './http_server'; @@ -31,23 +32,57 @@ const NetworkFormModal = ( ) => { const { isOpen, onOpenChange, field, data } = props; const { createNetworkConfig, updateNetworkConfig } = useConfig(); + const dialog = useDialog(); const isCreate = !data; const onSubmit = async (data: OneBotConfig['network'][typeof field][0]) => { - try { - if (isCreate) { - await createNetworkConfig(field, data); - } else { - await updateNetworkConfig(field, data); + const saveData = async (dataToSave: OneBotConfig['network'][typeof field][0]) => { + try { + if (isCreate) { + await createNetworkConfig(field, dataToSave); + } else { + await updateNetworkConfig(field, dataToSave); + } + toast.success('保存配置成功'); + } catch (error) { + const msg = (error as Error).message; + + toast.error(`保存配置失败: ${msg}`); + + throw error; } - toast.success('保存配置成功'); - } catch (error) { - const msg = (error as Error).message; + }; - toast.error(`保存配置失败: ${msg}`); - - throw error; + if (['httpServers', 'httpSseServers', 'websocketServers'].includes(field)) { + const serverData = data as any; + if (!serverData.token) { + await new Promise((resolve, reject) => { + dialog.confirm({ + title: '安全警告', + content: ( +
+

检测到未配置Token,这可能导致安全风险。确认要继续吗?

+

(未配置Token时,Host将被强制限制为 127.0.0.1)

+
+ ), + onConfirm: async () => { + serverData.host = '127.0.0.1'; + try { + await saveData(serverData); + resolve(); + } catch (e) { + reject(e); + } + }, + onCancel: () => { + reject(new Error('Cancelled')); + }, + }); + }); + return; + } } + await saveData(data); }; const renderFormComponent = (onClose: () => void) => {