@@ -34,6 +34,10 @@ const DEFAULT_NAPCAT_OPTIONS: Required<OptionalProps<NapcatOptions>> = {
3434 port : 3001 ,
3535 logger : CONSOLE_LOGGER ,
3636 token : '' ,
37+ reconnect : true ,
38+ reconnectInterval : 1000 ,
39+ maxReconnectAttempts : Infinity ,
40+ maxReconnectInterval : 30000 ,
3741}
3842
3943export class NapCat {
@@ -74,6 +78,14 @@ export class NapCat {
7478 legacyCookie : string
7579 }
7680 > ( )
81+ /** 当前重连尝试次数 */
82+ #reconnectAttempts: number = 0
83+ /** 重连定时器 */
84+ #reconnectTimer: NodeJS . Timeout | null = null
85+ /** 是否正在重连 */
86+ #reconnecting: boolean = false
87+ /** 是否手动关闭 */
88+ #manualClose: boolean = false
7789
7890 public static ABSTRACT_LOGGER : Logger = ABSTRACT_LOGGER
7991 public static CONSOLE_LOGGER : Logger = CONSOLE_LOGGER
@@ -93,6 +105,10 @@ export class NapCat {
93105 port : this . options . port || DEFAULT_NAPCAT_OPTIONS . port ,
94106 logger : this . options . logger || DEFAULT_NAPCAT_OPTIONS . logger ,
95107 token : this . options . token || DEFAULT_NAPCAT_OPTIONS . token ,
108+ reconnect : this . options . reconnect ?? DEFAULT_NAPCAT_OPTIONS . reconnect ,
109+ reconnectInterval : this . options . reconnectInterval || DEFAULT_NAPCAT_OPTIONS . reconnectInterval ,
110+ maxReconnectAttempts : this . options . maxReconnectAttempts ?? DEFAULT_NAPCAT_OPTIONS . maxReconnectAttempts ,
111+ maxReconnectInterval : this . options . maxReconnectInterval || DEFAULT_NAPCAT_OPTIONS . maxReconnectInterval ,
96112 }
97113 }
98114
@@ -216,23 +232,38 @@ export class NapCat {
216232 }
217233
218234 /** 等待服务器响应操作 */
219- #waitForAction< T extends any > ( echoId : string ) {
235+ #waitForAction< T extends any > ( echoId : string , timeout : number = 30_000 ) {
220236 const eventName = `echo#${ echoId } `
221237
222238 return new Promise < T > ( ( resolve , reject ) => {
239+ let timeoutId : NodeJS . Timeout | null = null
240+
241+ const cleanup = ( ) => {
242+ if ( timeoutId ) {
243+ clearTimeout ( timeoutId )
244+ timeoutId = null
245+ }
246+ this . #echoEvent. off ( eventName , handle )
247+ }
248+
223249 const handle = ( data : any ) => {
224250 if ( ! data || data . echo !== echoId ) return
225251
226- this . #echoEvent . off ( eventName , handle )
252+ cleanup ( )
227253
228254 if ( data . retcode === 0 ) {
229255 resolve ( data . data as T )
230256 } else {
231- reject ( `API 错误: ${ data . message } ` )
257+ reject ( new Error ( `API 错误: ${ data . message } ` ) )
232258 }
233259 }
234260
235261 this . #echoEvent. on ( eventName , handle )
262+
263+ timeoutId = setTimeout ( ( ) => {
264+ cleanup ( )
265+ reject ( new Error ( `API 请求超时: ${ echoId } ` ) )
266+ } , timeout )
236267 } )
237268 }
238269
@@ -683,8 +714,14 @@ export class NapCat {
683714 this . #ensureWsConnection( this . #ws)
684715 this . logger . debug ( `调用 API: ${ action } ,参数: ${ JSON . stringify ( params ) } ` )
685716 const echo = this . #echoId( )
686- this . #ws. send ( JSON . stringify ( { echo, action, params } ) )
687- return this . #waitForAction< T > ( echo )
717+
718+ try {
719+ this . #ws. send ( JSON . stringify ( { echo, action, params } ) )
720+ return this . #waitForAction< T > ( echo )
721+ } catch ( error : any ) {
722+ this . logger . error ( `发送 API 请求失败: ${ error ?. message || error } ` )
723+ return Promise . reject ( new Error ( `发送 API 请求失败: ${ error ?. message || error } ` ) )
724+ }
688725 }
689726
690727 /**
@@ -961,12 +998,60 @@ export class NapCat {
961998 return bkn
962999 }
9631000
964- /** 启动 NapCat SDK 实例,建立 WebSocket 连接 */
965- async run ( ) : Promise < void > {
966- const { logger : _ , token : __ , ...config } = this . #config
1001+ /** 计算重连延迟(指数退避) */
1002+ #getReconnectDelay( ) : number {
1003+ const { reconnectInterval, maxReconnectInterval } = this . #config
1004+ const delay = reconnectInterval * Math . pow ( 2 , this . #reconnectAttempts)
1005+ return Math . min ( delay , maxReconnectInterval )
1006+ }
9671007
968- this . logger . debug ( `启动配置: ${ JSON . stringify ( config ) } ` )
1008+ /** 清除重连定时器 */
1009+ #clearReconnectTimer( ) : void {
1010+ if ( this . #reconnectTimer) {
1011+ clearTimeout ( this . #reconnectTimer)
1012+ this . #reconnectTimer = null
1013+ }
1014+ }
1015+
1016+ /** 调度重连 */
1017+ #scheduleReconnect( ) : void {
1018+ const { reconnect, maxReconnectAttempts } = this . #config
1019+
1020+ if ( ! reconnect || this . #manualClose) {
1021+ return
1022+ }
1023+
1024+ if ( this . #reconnectAttempts >= maxReconnectAttempts ) {
1025+ this . logger . error ( `重连失败,已达到最大重连次数 ${ maxReconnectAttempts } ` )
1026+ this . #event. emit ( 'napcat.reconnect_failed' , {
1027+ attempt : this . #reconnectAttempts,
1028+ maxAttempts : maxReconnectAttempts ,
1029+ } )
1030+ return
1031+ }
9691032
1033+ this . #reconnecting = true
1034+ this . #reconnectAttempts++
1035+
1036+ const delay = this . #getReconnectDelay( )
1037+
1038+ this . logger . info ( `将在 ${ delay } ms 后尝试第 ${ this . #reconnectAttempts} 次重连` )
1039+ this . #event. emit ( 'napcat.reconnecting' , {
1040+ attempt : this . #reconnectAttempts,
1041+ maxAttempts : maxReconnectAttempts ,
1042+ delay,
1043+ } )
1044+
1045+ this . #clearReconnectTimer( )
1046+ this . #reconnectTimer = setTimeout ( ( ) => {
1047+ this . #connect( ) . catch ( ( err ) => {
1048+ this . logger . error ( `重连失败: ${ err ?. message || err } ` )
1049+ } )
1050+ } , delay )
1051+ }
1052+
1053+ /** 建立 WebSocket 连接 */
1054+ #connect( ) : Promise < void > {
9701055 return new Promise < void > ( ( resolve , reject ) => {
9711056 const ws = new WebSocket ( this . #buildWsUrl( ) )
9721057
@@ -992,28 +1077,61 @@ export class NapCat {
9921077 this . #online = false
9931078 this . logger . debug ( 'WebSocket 已断开连接' )
9941079 this . #event. emit ( 'ws.close' )
1080+
1081+ // 如果不是手动关闭,尝试重连
1082+ if ( ! this . #manualClose) {
1083+ this . #scheduleReconnect( )
1084+ }
9951085 }
9961086
9971087 ws . onerror = ( event ) => {
9981088 this . #online = false
9991089 const msg = `WebSocket 发生错误,请确认 NapCat 服务已启动,且端口、访问令牌正确`
10001090 this . logger . debug ( msg )
10011091 this . #event. emit ( 'ws.error' , event )
1002- reject ( new Error ( msg ) )
1092+
1093+ // 如果是首次连接,则 reject
1094+ if ( ! this . #reconnecting) {
1095+ reject ( new Error ( msg ) )
1096+ }
10031097 }
10041098
10051099 ws . onopen = ( ) => {
10061100 this . logger . info ( `WebSocket 已连接,NapCat SDK 实例已启动` )
10071101 this . #event. emit ( 'ws.open' )
1102+
1103+ // 重连成功
1104+ if ( this . #reconnecting) {
1105+ this . logger . info ( `第 ${ this . #reconnectAttempts} 次重连成功` )
1106+ this . #event. emit ( 'napcat.reconnected' , {
1107+ attempt : this . #reconnectAttempts,
1108+ } )
1109+ this . #reconnecting = false
1110+ this . #reconnectAttempts = 0
1111+ }
1112+
10081113 resolve ( )
10091114 }
10101115
10111116 this . #ws = ws
10121117 } )
10131118 }
10141119
1120+ /** 启动 NapCat SDK 实例,建立 WebSocket 连接 */
1121+ async run ( ) : Promise < void > {
1122+ const { logger : _ , token : __ , ...config } = this . #config
1123+
1124+ this . logger . debug ( `启动配置: ${ JSON . stringify ( config ) } ` )
1125+
1126+ this . #manualClose = false
1127+ return this . #connect( )
1128+ }
1129+
10151130 /** 销毁 NapCat SDK 实例,关闭 WebSocket 连接 */
10161131 close ( ) : void {
1132+ this . #manualClose = true
1133+ this . #clearReconnectTimer( )
1134+
10171135 if ( this . #ws) {
10181136 this . logger . info ( '正在销毁 NapCat SDK 实例...' )
10191137 this . #ws. close ( )
@@ -1022,6 +1140,10 @@ export class NapCat {
10221140 } else {
10231141 this . logger . warn ( 'NapCat SDK 实例未初始化' )
10241142 }
1143+
1144+ // 重置重连状态
1145+ this . #reconnecting = false
1146+ this . #reconnectAttempts = 0
10251147 }
10261148}
10271149
0 commit comments