Skip to content

Commit 832ca48

Browse files
committed
feat: 支持 ws 短线自动重连(默认开启)
1 parent 90aa49b commit 832ca48

File tree

2 files changed

+158
-10
lines changed

2 files changed

+158
-10
lines changed

packages/napcat-sdk/src/napcat.ts

Lines changed: 132 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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

3943
export 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

packages/napcat-sdk/src/types.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ export interface NapcatOptions {
2323
port?: number
2424
/** 日志记录器,默认为控制台日志记录器 */
2525
logger?: Logger
26+
/** 是否启用自动重连,默认为 true */
27+
reconnect?: boolean
28+
/** 初始重连间隔(毫秒),默认为 1000 */
29+
reconnectInterval?: number
30+
/** 最大重连次数,默认为 Infinity(无限重连) */
31+
maxReconnectAttempts?: number
32+
/** 最大重连间隔(毫秒),用于退避策略,默认为 30000 */
33+
maxReconnectInterval?: number
2634
}
2735

2836
export type OptionalKeys<T> = {
@@ -52,6 +60,24 @@ export interface EventMap extends OneBotEventMap {
5260
protocol_version: string
5361
timestamp: number
5462
}
63+
64+
/** 开始尝试重连 */
65+
'napcat.reconnecting': {
66+
attempt: number
67+
maxAttempts: number
68+
delay: number
69+
}
70+
71+
/** 重连成功 */
72+
'napcat.reconnected': {
73+
attempt: number
74+
}
75+
76+
/** 重连失败(达到最大重连次数) */
77+
'napcat.reconnect_failed': {
78+
attempt: number
79+
maxAttempts: number
80+
}
5581
}
5682

5783
/**

0 commit comments

Comments
 (0)