aqualink 2.20.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,575 @@
1
+ const { AqualinkEvents } = require('./AqualinkEvents')
2
+ const { reportSuppressedError } = require('./Reporting')
3
+
4
+ class PlayerLifecycle {
5
+ constructor(player, deps) {
6
+ this.player = player
7
+ this._functions = deps._functions
8
+ this.PLAYER_STATE = deps.PLAYER_STATE
9
+ this.VOICE_TRACE_INTERVAL = deps.VOICE_TRACE_INTERVAL
10
+ this.PLAYER_UPDATE_SILENCE_THRESHOLD = deps.PLAYER_UPDATE_SILENCE_THRESHOLD
11
+ this.VOICE_DOWN_THRESHOLD = deps.VOICE_DOWN_THRESHOLD
12
+ this.VOICE_ABANDON_MULTIPLIER = deps.VOICE_ABANDON_MULTIPLIER
13
+ this.VOICE_FORCE_DESTROY_MS = deps.VOICE_FORCE_DESTROY_MS
14
+ this.RECONNECT_MAX = deps.RECONNECT_MAX
15
+ this.MUTE_TOGGLE_DELAY = deps.MUTE_TOGGLE_DELAY
16
+ this.SEEK_DELAY = deps.SEEK_DELAY
17
+ this.PAUSE_DELAY = deps.PAUSE_DELAY
18
+ this.RETRY_BACKOFF_BASE = deps.RETRY_BACKOFF_BASE
19
+ this.RETRY_BACKOFF_MAX = deps.RETRY_BACKOFF_MAX
20
+ }
21
+
22
+ handlePlayerUpdate(packet) {
23
+ const player = this.player
24
+ if (player.destroyed || !packet?.state) return
25
+ const s = packet.state
26
+ player._lastPlayerUpdateAt = Date.now()
27
+ const wasConnected = player.connected
28
+ player.position = this._functions.isNum(s.position) ? s.position : 0
29
+ player.connected = !!s.connected
30
+ player.ping = this._functions.isNum(s.ping) ? s.ping : 0
31
+ player.timestamp = this._functions.isNum(s.time) ? s.time : Date.now()
32
+
33
+ if (!player.connected) {
34
+ if (wasConnected || !player._voiceDownSince) {
35
+ if (player.aqua?.debugTrace) {
36
+ player.aqua._trace('player.voice.down', {
37
+ guildId: player.guildId,
38
+ reconnecting: !!player._reconnecting,
39
+ recovering: !!player._voiceRecovering
40
+ })
41
+ }
42
+ }
43
+ if (
44
+ !player._voiceDownSince &&
45
+ !player._reconnecting &&
46
+ !player._voiceRecovering
47
+ ) {
48
+ player._voiceDownSince = Date.now()
49
+ const recoveryToken = player._claimVoiceRecovery('player_update_resume')
50
+ player._createTimer(() => {
51
+ if (
52
+ !player._isVoiceRecoveryActive(recoveryToken) ||
53
+ player.connected ||
54
+ player.destroyed ||
55
+ player._reconnecting ||
56
+ player._voiceRecovering ||
57
+ player.nodes?.info?.isNodelink ||
58
+ !player.voiceChannel
59
+ )
60
+ return
61
+ player.connection.attemptResume()
62
+ }, 1000)
63
+ }
64
+ } else {
65
+ player._voiceDownSince = 0
66
+ player.state = this.PLAYER_STATE.READY
67
+ player._clearVoiceRecovery(undefined, 'connected')
68
+ player._voiceRecovering = false
69
+
70
+ if (player._reconnecting && !player._isActivelyReconnecting) {
71
+ player._reconnecting = false
72
+ }
73
+ if (player._resuming) {
74
+ player._resuming = false
75
+ }
76
+
77
+ const now = Date.now()
78
+ if (
79
+ !wasConnected ||
80
+ now - player._lastVoiceUpTraceAt >= this.VOICE_TRACE_INTERVAL
81
+ ) {
82
+ player._lastVoiceUpTraceAt = now
83
+ if (player.aqua?.debugTrace) {
84
+ player.aqua._trace('player.voice.up', {
85
+ guildId: player.guildId,
86
+ ping: player.ping
87
+ })
88
+ }
89
+ }
90
+ this.flushDeferredPlay()
91
+ }
92
+
93
+ player.aqua.emit(AqualinkEvents.PlayerUpdate, player, packet)
94
+ }
95
+
96
+ async voiceWatchdog() {
97
+ const player = this.player
98
+ if (player.destroyed || !player.connection) return
99
+
100
+ const now = Date.now()
101
+ const silentPlayer =
102
+ player.playing &&
103
+ !player.paused &&
104
+ !!player.voiceChannel &&
105
+ !player._reconnecting &&
106
+ !player._voiceRecovering &&
107
+ now - (player._lastPlayerUpdateAt || 0) >=
108
+ this.PLAYER_UPDATE_SILENCE_THRESHOLD
109
+
110
+ if (silentPlayer) {
111
+ const silenceMs = now - (player._lastPlayerUpdateAt || now)
112
+ if (!player._voiceDownSince)
113
+ player._voiceDownSince = now - this.VOICE_DOWN_THRESHOLD - 1
114
+ player._lastPlayerUpdateAt = now
115
+ player.connected = false
116
+ if (player.aqua?.debugTrace) {
117
+ player.aqua._trace('player.voice.silence', {
118
+ guildId: player.guildId,
119
+ silenceMs,
120
+ playing: !!player.playing,
121
+ paused: !!player.paused
122
+ })
123
+ }
124
+ }
125
+
126
+ if (player._voiceDownSince && !player.connected) {
127
+ const downFor = Date.now() - player._voiceDownSince
128
+ if (
129
+ downFor > this.VOICE_FORCE_DESTROY_MS &&
130
+ player.reconnectionRetries >= this.RECONNECT_MAX
131
+ ) {
132
+ if (player.aqua?.debugTrace) {
133
+ player.aqua._trace('player.forceDestroy', {
134
+ guildId: player.guildId
135
+ })
136
+ }
137
+ player.destroy()
138
+ return
139
+ }
140
+ }
141
+
142
+ if (!player._shouldAttemptVoiceRecovery()) return
143
+
144
+ const hasVoiceData =
145
+ player.connection?.sessionId &&
146
+ player.connection?.endpoint &&
147
+ player.connection?.token
148
+ if (!hasVoiceData) {
149
+ const downFor = Date.now() - player._voiceDownSince
150
+ if (downFor > this.VOICE_DOWN_THRESHOLD * this.VOICE_ABANDON_MULTIPLIER) {
151
+ const recoveryToken = player._claimVoiceRecovery('watchdog_voice_refresh')
152
+ if (player._isVoiceRecoveryActive(recoveryToken))
153
+ player.connection?._requestVoiceState?.()
154
+ if (player._isVoiceRecoveryActive(recoveryToken))
155
+ player.connection?.resendVoiceUpdate(true)
156
+ player.reconnectionRetries = Math.min(
157
+ player.reconnectionRetries + 1,
158
+ 30
159
+ )
160
+ if (
161
+ downFor > this.VOICE_FORCE_DESTROY_MS &&
162
+ player.reconnectionRetries >= this.RECONNECT_MAX * 2
163
+ ) {
164
+ player.destroy()
165
+ }
166
+ }
167
+ return
168
+ }
169
+
170
+ const recoveryToken = player._claimVoiceRecovery('watchdog_resume')
171
+ player._voiceRecovering = true
172
+ try {
173
+ if (!player._isVoiceRecoveryActive(recoveryToken)) return
174
+ if (await player.connection.attemptResume()) {
175
+ player.reconnectionRetries = player._voiceDownSince = 0
176
+ player._clearVoiceRecovery(recoveryToken, 'resumed')
177
+ return
178
+ }
179
+ if (!player._isVoiceRecoveryActive(recoveryToken)) return
180
+ const originalMute = player.mute
181
+ player.send({
182
+ guild_id: player.guildId,
183
+ channel_id: player.voiceChannel,
184
+ self_deaf: player.deaf,
185
+ self_mute: !originalMute
186
+ })
187
+ await player._delay(this.MUTE_TOGGLE_DELAY)
188
+ if (!player.destroyed && player._isVoiceRecoveryActive(recoveryToken)) {
189
+ player.send({
190
+ guild_id: player.guildId,
191
+ channel_id: player.voiceChannel,
192
+ self_deaf: player.deaf,
193
+ self_mute: originalMute
194
+ })
195
+ }
196
+ if (player._isVoiceRecoveryActive(recoveryToken))
197
+ player.connection.resendVoiceUpdate()
198
+ player.reconnectionRetries++
199
+ } catch (error) {
200
+ player.reconnectionRetries++
201
+ reportSuppressedError(player, 'player.voiceWatchdog', error, {
202
+ guildId: player.guildId
203
+ })
204
+ if (player.reconnectionRetries >= this.RECONNECT_MAX) {
205
+ if (player._isVoiceRecoveryActive(recoveryToken))
206
+ player.connection?._requestVoiceState?.()
207
+ if (player._isVoiceRecoveryActive(recoveryToken))
208
+ player.connection?.resendVoiceUpdate(true)
209
+ player.reconnectionRetries = this.RECONNECT_MAX - 2
210
+ }
211
+ } finally {
212
+ if (player._isVoiceRecoveryActive(recoveryToken)) {
213
+ player._voiceRecovering = false
214
+ }
215
+ }
216
+ }
217
+
218
+ async attemptVoiceResume() {
219
+ const player = this.player
220
+ if (!player.connection?.sessionId) throw new Error('No session')
221
+ if (!(await player.connection.attemptResume()))
222
+ throw new Error('Resume failed')
223
+ }
224
+
225
+ async socketClosed(_player, _track, payload) {
226
+ const player = this.player
227
+ if (player.destroyed || player._reconnecting) return
228
+ if (player.aqua?.debugTrace) {
229
+ player.aqua._trace('player.socketClosed', {
230
+ guildId: player.guildId,
231
+ code: payload?.code
232
+ })
233
+ }
234
+
235
+ const code = payload?.code
236
+ if (code === 4006 && player._resuming) {
237
+ if (player.aqua?.debugTrace) {
238
+ player.aqua._trace('player.socketClosed.ignored', {
239
+ guildId: player.guildId,
240
+ code,
241
+ reason: 'transient_while_resuming'
242
+ })
243
+ }
244
+ return
245
+ }
246
+
247
+ let isRecoverable = [4015, 4009, 4006, 4014, 4022].includes(code)
248
+ if (code === 4014 && player.connection?.isWaitingForDisconnect)
249
+ isRecoverable = false
250
+
251
+ if (code === 4015 && !player.nodes?.info?.isNodelink) {
252
+ const recoveryToken = player._claimVoiceRecovery('socket_closed_resume')
253
+ player._reconnecting = true
254
+ player._isActivelyReconnecting = true
255
+ try {
256
+ if (!player._isVoiceRecoveryActive(recoveryToken)) return
257
+ await this.attemptVoiceResume()
258
+ player._clearVoiceRecovery(recoveryToken, 'socket_closed_resumed')
259
+ player._reconnecting = false
260
+ player._isActivelyReconnecting = false
261
+ return
262
+ } catch (error) {
263
+ player._reconnecting = false
264
+ reportSuppressedError(player, 'player.socketClosed.resume', error, {
265
+ guildId: player.guildId,
266
+ code
267
+ })
268
+ }
269
+ }
270
+
271
+ if (!isRecoverable) {
272
+ player.aqua.emit(AqualinkEvents.SocketClosed, player, payload)
273
+ player.destroy()
274
+ return
275
+ }
276
+
277
+ if (code === 4014 || code === 4022) {
278
+ player.connected = false
279
+ if (!player._voiceDownSince) player._voiceDownSince = Date.now()
280
+ player._suppressResumeUntil = Date.now() + (code === 4022 ? 3000 : 2000)
281
+ }
282
+
283
+ const aqua = player.aqua
284
+ const vcId = this._functions.toId(player.voiceChannel)
285
+ const tcId = this._functions.toId(player.textChannel)
286
+ const { guildId, deaf, mute } = player
287
+
288
+ if (!vcId) {
289
+ aqua?.emit?.(AqualinkEvents.SocketClosed, player, payload)
290
+ return
291
+ }
292
+
293
+ if (code === 4014 || code === 4022) {
294
+ const recoveryToken = player._claimVoiceRecovery('socket_closed_soft')
295
+ const now = Date.now()
296
+ if (
297
+ now - (player._voiceRequestAt || 0) >= 1200 &&
298
+ player._isVoiceRecoveryActive(recoveryToken)
299
+ ) {
300
+ player._voiceRequestAt = now
301
+ if (player._isVoiceRecoveryActive(recoveryToken))
302
+ player.connection?._requestVoiceState?.()
303
+ if (player._isVoiceRecoveryActive(recoveryToken))
304
+ player.connection?.resendVoiceUpdate?.(true)
305
+ if (player._isVoiceRecoveryActive(recoveryToken))
306
+ this._functions.safeCall(() =>
307
+ player.connect({
308
+ guildId,
309
+ voiceChannel: vcId,
310
+ deaf,
311
+ mute
312
+ })
313
+ )
314
+ }
315
+
316
+ if (player.aqua?.debugTrace) {
317
+ player.aqua._trace('player.socketClosed.softRecover', {
318
+ guildId: player.guildId,
319
+ code,
320
+ strategy: code === 4022 ? 'resume_after_voice_refresh' : '4014_retry'
321
+ })
322
+ }
323
+ const waitMs = Math.max(0, player._suppressResumeUntil - Date.now())
324
+ if (waitMs > 0) await player._delay(waitMs)
325
+
326
+ let resumed = false
327
+ if (
328
+ player._isVoiceRecoveryActive(recoveryToken) &&
329
+ !player.destroyed &&
330
+ !player.connected
331
+ ) {
332
+ resumed = await player.connection?.attemptResume?.().catch((error) => {
333
+ reportSuppressedError(
334
+ player,
335
+ 'player.socketClosed.softRecover',
336
+ error,
337
+ {
338
+ guildId: player.guildId,
339
+ code
340
+ }
341
+ )
342
+ return false
343
+ })
344
+ }
345
+ if (resumed) player._clearVoiceRecovery(recoveryToken, 'socket_soft_resumed')
346
+ if (
347
+ resumed ||
348
+ player.connected ||
349
+ player.destroyed ||
350
+ player._reconnecting
351
+ ) {
352
+ if (player.aqua?.debugTrace) {
353
+ player.aqua._trace('player.socketClosed.softRecover.ok', {
354
+ guildId: player.guildId,
355
+ code,
356
+ resumed: !!resumed
357
+ })
358
+ }
359
+ return
360
+ }
361
+ if (player.aqua?.debugTrace) {
362
+ player.aqua._trace('player.socketClosed.softRecover.failed', {
363
+ guildId: player.guildId,
364
+ code
365
+ })
366
+ }
367
+ }
368
+
369
+ const state = {
370
+ volume: player.volume,
371
+ position: player.position,
372
+ paused: player.paused,
373
+ loop: player.loop,
374
+ isAutoplayEnabled: player.isAutoplayEnabled,
375
+ currentTrack: player.current,
376
+ queue: player.queue?.toArray() || [],
377
+ previousIdentifiers: Array.from(player.previousIdentifiers),
378
+ autoplaySeed: player.autoplaySeed,
379
+ nowPlayingMessage: player.nowPlayingMessage,
380
+ voiceState: player.connection
381
+ ? {
382
+ sessionId: player.connection.sessionId || null,
383
+ endpoint: player.connection.endpoint || null,
384
+ token: player.connection.token || null,
385
+ region: player.connection.region || null,
386
+ channelId: player.connection.channelId || null
387
+ }
388
+ : null
389
+ }
390
+
391
+ player._reconnecting = true
392
+ player._isActivelyReconnecting = true
393
+ player.destroy({
394
+ preserveClient: true,
395
+ skipRemote: true,
396
+ preserveMessage: true,
397
+ preserveReconnecting: true,
398
+ preserveTracks: true
399
+ })
400
+
401
+ const reconnectNonce = player._reconnectNonce
402
+ player._reconnectTimers = new Set()
403
+ const reconnectTimers = player._reconnectTimers
404
+ const tryReconnect = async (attempt) => {
405
+ if (aqua?.destroyed || player._reconnectNonce !== reconnectNonce) {
406
+ this._functions.clearTimers(reconnectTimers)
407
+ player._reconnectTimers = null
408
+ player._reconnecting = false
409
+ player._isActivelyReconnecting = false
410
+ return
411
+ }
412
+ const activePlayer = aqua?.players?.get?.(String(guildId))
413
+ if (activePlayer && activePlayer !== player && !activePlayer.destroyed) {
414
+ this._functions.clearTimers(reconnectTimers)
415
+ player._reconnectTimers = null
416
+ player._reconnecting = false
417
+ player._isActivelyReconnecting = false
418
+ return
419
+ }
420
+ try {
421
+ const np = await aqua.createConnection({
422
+ guildId,
423
+ voiceChannel: vcId,
424
+ textChannel: tcId,
425
+ deaf,
426
+ mute,
427
+ defaultVolume: state.volume,
428
+ preserveMessage: true,
429
+ resuming: true
430
+ })
431
+ if (!np) throw new Error('Failed to create player')
432
+ if (player._reconnectNonce !== reconnectNonce || aqua?.destroyed) {
433
+ try {
434
+ np.destroy?.()
435
+ } catch {}
436
+ this._functions.clearTimers(reconnectTimers)
437
+ player._reconnectTimers = null
438
+ player._reconnecting = false
439
+ player._isActivelyReconnecting = false
440
+ return
441
+ }
442
+ const latestActivePlayer = aqua?.players?.get?.(String(guildId))
443
+ if (
444
+ latestActivePlayer &&
445
+ latestActivePlayer !== player &&
446
+ !latestActivePlayer.destroyed
447
+ ) {
448
+ try {
449
+ np.destroy?.()
450
+ } catch {}
451
+ this._functions.clearTimers(reconnectTimers)
452
+ player._reconnectTimers = null
453
+ player._reconnecting = false
454
+ player._isActivelyReconnecting = false
455
+ return
456
+ }
457
+
458
+ np.reconnectionRetries = 0
459
+ np.loop = state.loop
460
+ np.isAutoplayEnabled = state.isAutoplayEnabled
461
+ np.autoplaySeed = state.autoplaySeed
462
+ np.previousIdentifiers = new Set(state.previousIdentifiers)
463
+ np.nowPlayingMessage = state.nowPlayingMessage
464
+ if (state.voiceState && np.connection) {
465
+ np.connection.sessionId =
466
+ state.voiceState.sessionId || np.connection.sessionId
467
+ np.connection.endpoint =
468
+ state.voiceState.endpoint || np.connection.endpoint
469
+ np.connection.token = state.voiceState.token || np.connection.token
470
+ np.connection.region = state.voiceState.region || np.connection.region
471
+ np.connection.channelId =
472
+ state.voiceState.channelId || np.connection.channelId
473
+ np.connection._lastEndpoint =
474
+ state.voiceState.endpoint || np.connection._lastEndpoint
475
+ if (
476
+ np.connection.sessionId &&
477
+ np.connection.endpoint &&
478
+ np.connection.token
479
+ ) {
480
+ np.connection._lastVoiceDataUpdate = Date.now()
481
+ np.connection.resendVoiceUpdate(true)
482
+ }
483
+ }
484
+
485
+ const ct = state.currentTrack
486
+ if (ct) np.queue.add(ct)
487
+ for (const q of state.queue) if (q !== ct) np.queue.add(q)
488
+
489
+ if (ct) {
490
+ await np.play()
491
+ if (state.position > 5000)
492
+ np._createTimer(
493
+ () => !np.destroyed && np.seek(state.position),
494
+ this.SEEK_DELAY
495
+ )
496
+ if (state.paused)
497
+ np._createTimer(
498
+ () => !np.destroyed && np.pause(true),
499
+ this.PAUSE_DELAY
500
+ )
501
+ }
502
+
503
+ this._functions.clearTimers(reconnectTimers)
504
+ player._reconnectTimers = null
505
+ player._reconnecting = false
506
+ player._isActivelyReconnecting = false
507
+ aqua.emit(AqualinkEvents.PlayerReconnected, np, {
508
+ oldPlayer: player,
509
+ restoredState: state
510
+ })
511
+ } catch (error) {
512
+ if (player._reconnectNonce !== reconnectNonce || aqua?.destroyed) {
513
+ this._functions.clearTimers(reconnectTimers)
514
+ player._reconnectTimers = null
515
+ player._reconnecting = false
516
+ player._isActivelyReconnecting = false
517
+ return
518
+ }
519
+ const retriesLeft = this.RECONNECT_MAX - attempt
520
+ aqua.emit(AqualinkEvents.ReconnectionFailed, player, {
521
+ error,
522
+ code,
523
+ payload,
524
+ retriesLeft
525
+ })
526
+
527
+ if (retriesLeft > 0) {
528
+ this._functions.createTimer(
529
+ () => tryReconnect(attempt + 1),
530
+ Math.min(this.RETRY_BACKOFF_BASE * attempt, this.RETRY_BACKOFF_MAX),
531
+ reconnectTimers
532
+ )
533
+ } else {
534
+ this._functions.clearTimers(reconnectTimers)
535
+ player._reconnectTimers = null
536
+ player._reconnecting = false
537
+ player._isActivelyReconnecting = false
538
+ aqua.emit(AqualinkEvents.SocketClosed, player, payload)
539
+ }
540
+ }
541
+ }
542
+
543
+ tryReconnect(1)
544
+ }
545
+
546
+ flushDeferredPlay() {
547
+ const player = this.player
548
+ if (
549
+ !player._deferredStart ||
550
+ player.destroyed ||
551
+ !player.current?.track ||
552
+ !player._updateBatcher
553
+ )
554
+ return
555
+ player._deferredStart = false
556
+ const updateData = {
557
+ track: { encoded: player.current.track },
558
+ paused: player.paused
559
+ }
560
+ if (player.position > 0) updateData.position = player.position
561
+ if (player.aqua?.debugTrace) {
562
+ player.aqua._trace('player.play.deferred.flush', {
563
+ guildId: player.guildId,
564
+ hasEndpoint: !!player.connection?.endpoint
565
+ })
566
+ }
567
+ player.batchUpdatePlayer(updateData, true).catch((error) =>
568
+ reportSuppressedError(player, 'player.deferredPlay.flush', error, {
569
+ guildId: player.guildId
570
+ })
571
+ )
572
+ }
573
+ }
574
+
575
+ module.exports = PlayerLifecycle
@@ -0,0 +1,42 @@
1
+ function defineLifecycleAccessor(player, prop, key) {
2
+ Object.defineProperty(player, prop, {
3
+ configurable: true,
4
+ enumerable: false,
5
+ get() {
6
+ return this._lifecycle[key]
7
+ },
8
+ set(value) {
9
+ this._lifecycle[key] = !!value
10
+ }
11
+ })
12
+ }
13
+
14
+ function attachPlayerLifecycleState(player, options = {}) {
15
+ Object.defineProperty(player, '_lifecycle', {
16
+ configurable: true,
17
+ enumerable: false,
18
+ writable: false,
19
+ value: {
20
+ voiceRecovering: false,
21
+ reconnecting: false,
22
+ activelyReconnecting: false,
23
+ resuming: !!options.resuming,
24
+ deferredStart: false
25
+ }
26
+ })
27
+
28
+ defineLifecycleAccessor(player, '_voiceRecovering', 'voiceRecovering')
29
+ defineLifecycleAccessor(player, '_reconnecting', 'reconnecting')
30
+ defineLifecycleAccessor(
31
+ player,
32
+ '_isActivelyReconnecting',
33
+ 'activelyReconnecting'
34
+ )
35
+ defineLifecycleAccessor(player, '_resuming', 'resuming')
36
+ defineLifecycleAccessor(player, '_deferredStart', 'deferredStart')
37
+ return player._lifecycle
38
+ }
39
+
40
+ module.exports = {
41
+ attachPlayerLifecycleState
42
+ }
@@ -0,0 +1,32 @@
1
+ const { AqualinkEvents } = require('./AqualinkEvents')
2
+
3
+ function normalizeError(error, fallback = 'Unknown error') {
4
+ if (error instanceof Error) return error
5
+ if (typeof error === 'string' && error) return new Error(error)
6
+ if (error && typeof error.message === 'string' && error.message) {
7
+ return new Error(error.message)
8
+ }
9
+ return new Error(fallback)
10
+ }
11
+
12
+ function getAqua(target) {
13
+ return target?.aqua || target || null
14
+ }
15
+
16
+ function reportSuppressedError(target, scope, error, data = null) {
17
+ const aqua = getAqua(target)
18
+ const err = normalizeError(error, `Suppressed error in ${scope}`)
19
+ if (aqua?.debugTrace) {
20
+ aqua._trace(`${scope}.suppressed`, {
21
+ ...(data || {}),
22
+ error: err.message
23
+ })
24
+ }
25
+ aqua?.emit?.(AqualinkEvents.Debug, err)
26
+ return err
27
+ }
28
+
29
+ module.exports = {
30
+ normalizeError,
31
+ reportSuppressedError
32
+ }
@@ -172,6 +172,7 @@ class Rest {
172
172
 
173
173
  this._headerPool = []
174
174
  this._tlsOptions = null
175
+ this._autoplayAgent = null
175
176
  this._setupAgent(node)
176
177
  this.useHttp2 = !!aqua?.options?.useHttp2
177
178
  this._h2 = null
@@ -207,8 +208,21 @@ class Rest {
207
208
  this.agent = new (node.ssl ? HttpsAgent : HttpAgent)(opts)
208
209
  this.request = node.ssl ? httpsRequest : httpRequest
209
210
 
210
- if (node.ssl && autoplayModule?.setSharedAgent) {
211
- autoplayModule.setSharedAgent(this.agent)
211
+ if (autoplayModule?.setSharedAgent) {
212
+ if (node.ssl) {
213
+ this._autoplayAgent = this.agent
214
+ } else {
215
+ this._autoplayAgent = new HttpsAgent({
216
+ keepAlive: true,
217
+ maxSockets: node.maxSockets || 128,
218
+ maxFreeSockets: node.maxFreeSockets || 64,
219
+ freeSocketTimeout: node.freeSocketTimeout || 15000,
220
+ keepAliveMsecs: node.keepAliveMsecs || 500,
221
+ scheduling: 'lifo',
222
+ timeout: this.timeout
223
+ })
224
+ }
225
+ autoplayModule.setSharedAgent(this._autoplayAgent)
212
226
  }
213
227
 
214
228
  const origCreate = this.agent.createConnection.bind(this.agent)
@@ -845,10 +859,18 @@ class Rest {
845
859
  }
846
860
 
847
861
  destroy() {
862
+ const autoplayAgent = this._autoplayAgent
863
+ const primaryAgent = this.agent
848
864
  if (this.agent) {
849
865
  this.agent.destroy()
850
866
  this.agent = null
851
867
  }
868
+ if (autoplayAgent && autoplayAgent !== primaryAgent) {
869
+ autoplayAgent.destroy?.()
870
+ }
871
+ if (autoplayModule?.setSharedAgent && autoplayAgent) {
872
+ autoplayModule.setSharedAgent(null)
873
+ }
852
874
  this._closeH2()
853
875
  if (this._headerPool) {
854
876
  this._headerPool.length = 0
@@ -859,6 +881,7 @@ class Rest {
859
881
  this.request =
860
882
  this.defaultHeaders =
861
883
  this._endpoints =
884
+ this._autoplayAgent =
862
885
  null
863
886
  this.calls = 0
864
887
  }