aqualink 2.7.0 → 2.7.1
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.
- package/build/structures/Aqua.js +68 -63
- package/build/structures/Node.js +78 -107
- package/build/structures/Player.js +1 -1
- package/package.json +1 -2
package/build/structures/Aqua.js
CHANGED
|
@@ -4,7 +4,7 @@ const Player = require("./Player");
|
|
|
4
4
|
const Track = require("./Track");
|
|
5
5
|
const { version: pkgVersion } = require("../../package.json");
|
|
6
6
|
const { EventEmitter } = require('tseep');
|
|
7
|
-
const fs = require('fs
|
|
7
|
+
const fs = require('fs/promises');
|
|
8
8
|
|
|
9
9
|
const URL_REGEX = /^https?:\/\//;
|
|
10
10
|
const DEFAULT_OPTIONS = Object.freeze({
|
|
@@ -57,11 +57,11 @@ class Aqua extends EventEmitter {
|
|
|
57
57
|
this.send = this.options.send || this.defaultSendFunction.bind(this);
|
|
58
58
|
|
|
59
59
|
this._leastUsedCache = { nodes: [], timestamp: 0 };
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
this._nodeStates = new Map();
|
|
62
62
|
this._failoverQueue = new Map();
|
|
63
63
|
this._lastFailoverAttempt = new Map();
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
this._boundCleanupPlayer = this.cleanupPlayer.bind(this);
|
|
66
66
|
this._boundHandlePlayerDestroy = this._handlePlayerDestroy.bind(this);
|
|
67
67
|
}
|
|
@@ -100,16 +100,16 @@ class Aqua extends EventEmitter {
|
|
|
100
100
|
console.error(`Failed to create node ${node.name || node.host}:`, err);
|
|
101
101
|
return null;
|
|
102
102
|
}));
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
const results = await Promise.allSettled(nodePromises);
|
|
105
105
|
const successfulNodes = results.filter(r => r.status === 'fulfilled' && r.value).length;
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
if (successfulNodes === 0) {
|
|
108
108
|
throw new Error("No nodes could be connected");
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
await Promise.all(this.plugins.map(plugin =>
|
|
112
|
-
Promise.resolve(plugin.load(this)).catch(err =>
|
|
111
|
+
await Promise.all(this.plugins.map(plugin =>
|
|
112
|
+
Promise.resolve(plugin.load(this)).catch(err =>
|
|
113
113
|
console.error("Plugin load error:", err)
|
|
114
114
|
)
|
|
115
115
|
));
|
|
@@ -166,10 +166,10 @@ class Aqua extends EventEmitter {
|
|
|
166
166
|
|
|
167
167
|
async handleNodeFailover(failedNode) {
|
|
168
168
|
if (!this.failoverOptions.enabled) return;
|
|
169
|
-
|
|
169
|
+
|
|
170
170
|
const nodeId = failedNode.name || failedNode.host;
|
|
171
171
|
const now = Date.now();
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
const nodeState = this._nodeStates.get(nodeId);
|
|
174
174
|
if (nodeState?.failoverInProgress) return;
|
|
175
175
|
|
|
@@ -182,10 +182,10 @@ class Aqua extends EventEmitter {
|
|
|
182
182
|
this._nodeStates.set(nodeId, { connected: false, failoverInProgress: true });
|
|
183
183
|
this._lastFailoverAttempt.set(nodeId, now);
|
|
184
184
|
this._failoverQueue.set(nodeId, currentAttempts + 1);
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
try {
|
|
187
187
|
this.emit("nodeFailover", failedNode);
|
|
188
|
-
|
|
188
|
+
|
|
189
189
|
const affectedPlayers = this._getPlayersForNode(failedNode);
|
|
190
190
|
if (affectedPlayers.length === 0) {
|
|
191
191
|
this._nodeStates.set(nodeId, { connected: false, failoverInProgress: false });
|
|
@@ -200,14 +200,14 @@ class Aqua extends EventEmitter {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
const failoverResults = await this._migratePlayersWithRetry(affectedPlayers, availableNodes);
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
const successful = failoverResults.filter(r => r.success).length;
|
|
205
205
|
const failed = failoverResults.length - successful;
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
if (successful > 0) {
|
|
208
208
|
this.emit("nodeFailoverComplete", failedNode, successful, failed);
|
|
209
209
|
}
|
|
210
|
-
|
|
210
|
+
|
|
211
211
|
} catch (error) {
|
|
212
212
|
this.emit("error", null, new Error(`Failover failed for node ${nodeId}: ${error.message}`));
|
|
213
213
|
} finally {
|
|
@@ -226,14 +226,14 @@ class Aqua extends EventEmitter {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
_getAvailableNodesForFailover(failedNode) {
|
|
229
|
-
return this.leastUsedNodes.filter(node =>
|
|
229
|
+
return this.leastUsedNodes.filter(node =>
|
|
230
230
|
node !== failedNode && node.name !== failedNode.name
|
|
231
231
|
);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
async _migratePlayersWithRetry(players, availableNodes) {
|
|
235
235
|
const results = [];
|
|
236
|
-
|
|
236
|
+
|
|
237
237
|
const concurrency = 3;
|
|
238
238
|
for (let i = 0; i < players.length; i += concurrency) {
|
|
239
239
|
const batch = players.slice(i, i + concurrency);
|
|
@@ -246,11 +246,11 @@ class Aqua extends EventEmitter {
|
|
|
246
246
|
return { player, success: false, error };
|
|
247
247
|
}
|
|
248
248
|
});
|
|
249
|
-
|
|
249
|
+
|
|
250
250
|
const batchResults = await Promise.allSettled(batchPromises);
|
|
251
251
|
results.push(...batchResults.map(r => r.value || r.reason));
|
|
252
252
|
}
|
|
253
|
-
|
|
253
|
+
|
|
254
254
|
return results;
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -261,7 +261,7 @@ class Aqua extends EventEmitter {
|
|
|
261
261
|
|
|
262
262
|
const guildId = player.guildId;
|
|
263
263
|
let retryCount = 0;
|
|
264
|
-
|
|
264
|
+
|
|
265
265
|
while (retryCount < this.failoverOptions.maxRetries) {
|
|
266
266
|
try {
|
|
267
267
|
const targetNode = this._selectBestNode(availableNodes, player);
|
|
@@ -274,15 +274,15 @@ class Aqua extends EventEmitter {
|
|
|
274
274
|
if (!newPlayer) throw new Error("Failed to create player on target node");
|
|
275
275
|
|
|
276
276
|
await this._restorePlayerState(newPlayer, playerState);
|
|
277
|
-
|
|
277
|
+
|
|
278
278
|
newPlayer.destroy();
|
|
279
279
|
if (playerState.current) {
|
|
280
280
|
newPlayer.queue.add(playerState.current);
|
|
281
281
|
}
|
|
282
|
-
|
|
282
|
+
|
|
283
283
|
this.emit("playerMigrated", player, newPlayer, targetNode);
|
|
284
284
|
return newPlayer;
|
|
285
|
-
|
|
285
|
+
|
|
286
286
|
} catch (error) {
|
|
287
287
|
retryCount++;
|
|
288
288
|
if (retryCount < this.failoverOptions.maxRetries) {
|
|
@@ -296,12 +296,12 @@ class Aqua extends EventEmitter {
|
|
|
296
296
|
|
|
297
297
|
_selectBestNode(availableNodes, player) {
|
|
298
298
|
if (player.region) {
|
|
299
|
-
const regionNode = availableNodes.find(node =>
|
|
299
|
+
const regionNode = availableNodes.find(node =>
|
|
300
300
|
node.regions?.includes(player.region.toLowerCase())
|
|
301
301
|
);
|
|
302
302
|
if (regionNode) return regionNode;
|
|
303
303
|
}
|
|
304
|
-
|
|
304
|
+
|
|
305
305
|
return availableNodes[0];
|
|
306
306
|
}
|
|
307
307
|
|
|
@@ -349,7 +349,7 @@ class Aqua extends EventEmitter {
|
|
|
349
349
|
try {
|
|
350
350
|
// Batch operations where possible
|
|
351
351
|
const operations = [];
|
|
352
|
-
|
|
352
|
+
|
|
353
353
|
if (playerState.volume !== undefined) {
|
|
354
354
|
operations.push(newPlayer.setVolume(playerState.volume));
|
|
355
355
|
}
|
|
@@ -365,15 +365,15 @@ class Aqua extends EventEmitter {
|
|
|
365
365
|
// Handle current track restoration
|
|
366
366
|
if (playerState.current && this.failoverOptions.preservePosition) {
|
|
367
367
|
newPlayer.queue.unshift(playerState.current);
|
|
368
|
-
|
|
368
|
+
|
|
369
369
|
if (this.failoverOptions.resumePlayback) {
|
|
370
370
|
await newPlayer.play();
|
|
371
|
-
|
|
371
|
+
|
|
372
372
|
if (playerState.position > 0) {
|
|
373
373
|
await this._delay(300); // Reduced delay
|
|
374
374
|
await newPlayer.seek(playerState.position);
|
|
375
375
|
}
|
|
376
|
-
|
|
376
|
+
|
|
377
377
|
if (playerState.paused) {
|
|
378
378
|
await newPlayer.pause();
|
|
379
379
|
}
|
|
@@ -557,7 +557,7 @@ class Aqua extends EventEmitter {
|
|
|
557
557
|
baseResponse.tracks.push(new Track(response.data, requester, requestNode));
|
|
558
558
|
}
|
|
559
559
|
break;
|
|
560
|
-
|
|
560
|
+
|
|
561
561
|
case "playlist": {
|
|
562
562
|
const info = response.data?.info;
|
|
563
563
|
if (info) {
|
|
@@ -604,42 +604,50 @@ class Aqua extends EventEmitter {
|
|
|
604
604
|
}
|
|
605
605
|
}
|
|
606
606
|
|
|
607
|
-
// Optimized save/load methods
|
|
608
607
|
async savePlayer(filePath = "./AquaPlayers.json") {
|
|
609
|
-
const data = Array.from(this.players.values()
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
608
|
+
const data = Array.from(this.players.values()).map(player => {
|
|
609
|
+
const requester = player.requester || player.current?.requester;
|
|
610
|
+
|
|
611
|
+
return {
|
|
612
|
+
g: player.guildId,
|
|
613
|
+
t: player.textChannel,
|
|
614
|
+
v: player.voiceChannel,
|
|
615
|
+
u: player.current?.uri || null,
|
|
616
|
+
p: player.position || 0,
|
|
617
|
+
ts: player.timestamp || 0,
|
|
618
|
+
q: player.queue?.tracks?.map(tr => tr.uri).slice(0, 5) || [],
|
|
619
|
+
r: requester ? {
|
|
620
|
+
id: requester.id,
|
|
621
|
+
username: requester.username,
|
|
622
|
+
globalName: requester.globalName,
|
|
623
|
+
discriminator: requester.discriminator,
|
|
624
|
+
avatar: requester.avatar
|
|
625
|
+
} : null,
|
|
626
|
+
vol: player.volume,
|
|
627
|
+
pa: player.paused,
|
|
628
|
+
isPlaying: !!player.current && !player.paused
|
|
629
|
+
};
|
|
630
|
+
});
|
|
631
|
+
|
|
623
632
|
await fs.writeFile(filePath, JSON.stringify(data), "utf8");
|
|
633
|
+
this.emit("debug", "Aqua", `Saved ${data.length} players to ${filePath}`);
|
|
624
634
|
}
|
|
625
635
|
|
|
626
636
|
async loadPlayers(filePath = "./AquaPlayers.json") {
|
|
627
637
|
try {
|
|
628
638
|
await fs.access(filePath);
|
|
629
639
|
await this._waitForFirstNode();
|
|
630
|
-
|
|
640
|
+
|
|
631
641
|
const data = JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
632
|
-
|
|
633
|
-
// Process in batches to avoid overwhelming
|
|
642
|
+
|
|
634
643
|
const batchSize = 5;
|
|
635
644
|
for (let i = 0; i < data.length; i += batchSize) {
|
|
636
645
|
const batch = data.slice(i, i + batchSize);
|
|
637
646
|
await Promise.all(batch.map(p => this._restorePlayer(p)));
|
|
638
647
|
}
|
|
639
|
-
|
|
648
|
+
|
|
640
649
|
await fs.writeFile(filePath, "[]", "utf8");
|
|
641
650
|
} catch (error) {
|
|
642
|
-
// Silent fail if file doesn't exist
|
|
643
651
|
}
|
|
644
652
|
}
|
|
645
653
|
|
|
@@ -647,11 +655,11 @@ class Aqua extends EventEmitter {
|
|
|
647
655
|
try {
|
|
648
656
|
let player = this.players.get(p.g);
|
|
649
657
|
if (!player) {
|
|
650
|
-
const targetNode = (p.n && this.nodeMap.get(p.n)?.connected) ?
|
|
658
|
+
const targetNode = (p.n && this.nodeMap.get(p.n)?.connected) ?
|
|
651
659
|
this.nodeMap.get(p.n) : this.leastUsedNodes[0];
|
|
652
|
-
|
|
660
|
+
|
|
653
661
|
if (!targetNode) return;
|
|
654
|
-
|
|
662
|
+
|
|
655
663
|
player = await this.createConnection({
|
|
656
664
|
guildId: p.g,
|
|
657
665
|
textChannel: p.t,
|
|
@@ -661,7 +669,6 @@ class Aqua extends EventEmitter {
|
|
|
661
669
|
});
|
|
662
670
|
}
|
|
663
671
|
|
|
664
|
-
// Restore current track
|
|
665
672
|
if (p.u && player) {
|
|
666
673
|
const resolved = await this.resolve({ query: p.u, requester: p.r });
|
|
667
674
|
if (resolved.tracks?.[0]) {
|
|
@@ -670,13 +677,12 @@ class Aqua extends EventEmitter {
|
|
|
670
677
|
if (typeof p.ts === "number") player.timestamp = p.ts;
|
|
671
678
|
}
|
|
672
679
|
}
|
|
673
|
-
|
|
674
|
-
// Restore queue
|
|
680
|
+
|
|
675
681
|
if (p.q?.length && player) {
|
|
676
682
|
const queuePromises = p.q
|
|
677
683
|
.filter(uri => uri !== p.u)
|
|
678
684
|
.map(uri => this.resolve({ query: uri, requester: p.r }));
|
|
679
|
-
|
|
685
|
+
|
|
680
686
|
const queueResults = await Promise.allSettled(queuePromises);
|
|
681
687
|
queueResults.forEach(result => {
|
|
682
688
|
if (result.status === 'fulfilled' && result.value.tracks?.[0]) {
|
|
@@ -684,21 +690,20 @@ class Aqua extends EventEmitter {
|
|
|
684
690
|
}
|
|
685
691
|
});
|
|
686
692
|
}
|
|
687
|
-
|
|
693
|
+
|
|
688
694
|
if (player) {
|
|
689
695
|
player.paused = !!p.pa;
|
|
690
|
-
if (
|
|
696
|
+
if ((p.isPlaying || (p.pa && p.u)) && player.queue.size > 0) {
|
|
691
697
|
player.play();
|
|
692
698
|
}
|
|
693
699
|
}
|
|
694
700
|
} catch (error) {
|
|
695
|
-
// Silent fail for individual player restoration
|
|
696
701
|
}
|
|
697
702
|
}
|
|
698
703
|
|
|
699
704
|
async _waitForFirstNode() {
|
|
700
705
|
if (this.leastUsedNodes.length > 0) return;
|
|
701
|
-
|
|
706
|
+
|
|
702
707
|
return new Promise(resolve => {
|
|
703
708
|
const checkInterval = setInterval(() => {
|
|
704
709
|
if (this.leastUsedNodes.length > 0) {
|
|
@@ -747,15 +752,15 @@ class Aqua extends EventEmitter {
|
|
|
747
752
|
}
|
|
748
753
|
return stats;
|
|
749
754
|
}
|
|
750
|
-
|
|
755
|
+
|
|
751
756
|
async forceFailover(nodeIdentifier) {
|
|
752
757
|
const node = this.nodeMap.get(nodeIdentifier);
|
|
753
758
|
if (!node) return;
|
|
754
|
-
|
|
759
|
+
|
|
755
760
|
if (node.connected) {
|
|
756
761
|
await node.destroy();
|
|
757
762
|
}
|
|
758
|
-
|
|
763
|
+
|
|
759
764
|
this._cleanupNode(nodeIdentifier);
|
|
760
765
|
}
|
|
761
766
|
}
|
package/build/structures/Node.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
|
|
2
3
|
const WebSocket = require('ws');
|
|
3
4
|
const Rest = require("./Rest");
|
|
4
5
|
|
|
@@ -29,9 +30,6 @@ class Node {
|
|
|
29
30
|
this.sessionId = sessionId;
|
|
30
31
|
this.regions = regions;
|
|
31
32
|
|
|
32
|
-
this.wsUrl = `ws${this.secure ? "s" : ""}://${this.host}:${this.port}/v4/websocket`;
|
|
33
|
-
this.rest = new Rest(aqua, this);
|
|
34
|
-
|
|
35
33
|
const {
|
|
36
34
|
resumeTimeout = 60,
|
|
37
35
|
autoResume = false,
|
|
@@ -46,15 +44,24 @@ class Node {
|
|
|
46
44
|
this.reconnectTries = reconnectTries;
|
|
47
45
|
this.infiniteReconnects = infiniteReconnects;
|
|
48
46
|
|
|
47
|
+
this.wsUrl = `ws${this.secure ? "s" : ""}://${this.host}:${this.port}/v4/websocket`;
|
|
48
|
+
this.rest = new Rest(aqua, this);
|
|
49
|
+
this._headers = this._constructHeaders();
|
|
50
|
+
|
|
49
51
|
this.connected = false;
|
|
52
|
+
this.isDestroyed = false;
|
|
50
53
|
this.info = null;
|
|
51
54
|
this.ws = null;
|
|
52
55
|
this.reconnectAttempted = 0;
|
|
53
56
|
this.reconnectTimeoutId = null;
|
|
54
|
-
this.isDestroyed = false;
|
|
55
57
|
this.lastHealthCheck = Date.now();
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
// Pre-bind event handlers for efficiency
|
|
60
|
+
this._onOpen = this._onOpen.bind(this);
|
|
61
|
+
this._onError = this._onError.bind(this);
|
|
62
|
+
this._onMessage = this._onMessage.bind(this);
|
|
63
|
+
this._onClose = this._onClose.bind(this);
|
|
64
|
+
|
|
58
65
|
this.initializeStats();
|
|
59
66
|
}
|
|
60
67
|
|
|
@@ -89,6 +96,9 @@ class Node {
|
|
|
89
96
|
this.reconnectAttempted = 0;
|
|
90
97
|
this.lastHealthCheck = Date.now();
|
|
91
98
|
this.aqua.emit("debug", this.name, "WebSocket connection established");
|
|
99
|
+
|
|
100
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
101
|
+
this.reconnectTimeoutId = null;
|
|
92
102
|
|
|
93
103
|
if (this.aqua.bypassChecks?.nodeFetchInfo) return;
|
|
94
104
|
|
|
@@ -97,7 +107,7 @@ class Node {
|
|
|
97
107
|
this.aqua.emit("nodeConnected", this);
|
|
98
108
|
|
|
99
109
|
if (this.autoResume && this.sessionId) {
|
|
100
|
-
await this.
|
|
110
|
+
await this.resumePlayers();
|
|
101
111
|
}
|
|
102
112
|
} catch (err) {
|
|
103
113
|
this.info = null;
|
|
@@ -118,30 +128,27 @@ class Node {
|
|
|
118
128
|
return;
|
|
119
129
|
}
|
|
120
130
|
|
|
121
|
-
const op = payload
|
|
131
|
+
const { op, guildId } = payload;
|
|
122
132
|
if (!op) return;
|
|
123
133
|
|
|
124
134
|
this.lastHealthCheck = Date.now();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (payload.guildId) {
|
|
143
|
-
const player = this.aqua.players.get(payload.guildId);
|
|
144
|
-
if (player) player.emit(op, payload);
|
|
135
|
+
|
|
136
|
+
switch (op) {
|
|
137
|
+
case "stats":
|
|
138
|
+
this._updateStats(payload);
|
|
139
|
+
break;
|
|
140
|
+
case "ready":
|
|
141
|
+
this._handleReadyOp(payload);
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
if (op.startsWith("Lyrics")) {
|
|
145
|
+
const player = guildId ? this.aqua.players.get(guildId) : null;
|
|
146
|
+
this.aqua.emit(op, player, payload.track || null, payload);
|
|
147
|
+
} else if (guildId) {
|
|
148
|
+
const player = this.aqua.players.get(guildId);
|
|
149
|
+
player?.emit(op, payload);
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
145
152
|
}
|
|
146
153
|
}
|
|
147
154
|
|
|
@@ -150,22 +157,20 @@ class Node {
|
|
|
150
157
|
const reasonStr = reason?.toString() || "No reason provided";
|
|
151
158
|
|
|
152
159
|
this.aqua.emit("nodeDisconnect", this, { code, reason: reasonStr });
|
|
153
|
-
|
|
154
160
|
this.aqua.handleNodeFailover(this);
|
|
155
|
-
|
|
156
161
|
this.scheduleReconnect(code);
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
scheduleReconnect(code) {
|
|
160
|
-
this.clearReconnectTimeout();
|
|
161
|
-
|
|
162
165
|
if (code === Node.WS_CLOSE_NORMAL || this.isDestroyed) {
|
|
163
|
-
this.aqua.emit("debug", this.name, "WebSocket closed normally, not reconnecting");
|
|
166
|
+
this.aqua.emit("debug", this.name, "WebSocket closed normally, not reconnecting.");
|
|
164
167
|
return;
|
|
165
168
|
}
|
|
166
169
|
|
|
170
|
+
if (this.reconnectTimeoutId) return;
|
|
171
|
+
|
|
167
172
|
if (this.infiniteReconnects) {
|
|
168
|
-
this.aqua.emit("nodeReconnect", this, "Infinite reconnects enabled, trying again in 10 seconds");
|
|
173
|
+
this.aqua.emit("nodeReconnect", this, "Infinite reconnects enabled, trying again in 10 seconds.");
|
|
169
174
|
this.reconnectTimeoutId = setTimeout(() => this.connect(), 10000);
|
|
170
175
|
return;
|
|
171
176
|
}
|
|
@@ -188,26 +193,19 @@ class Node {
|
|
|
188
193
|
}
|
|
189
194
|
|
|
190
195
|
calculateBackoff() {
|
|
191
|
-
const baseBackoff = this.reconnectTimeout * Math.pow(Node.BACKOFF_MULTIPLIER, this.reconnectAttempted);
|
|
196
|
+
const baseBackoff = this.reconnectTimeout * Math.pow(Node.BACKOFF_MULTIPLIER, this.reconnectAttempted - 1);
|
|
192
197
|
const jitter = Math.random() * Math.min(2000, baseBackoff * 0.2);
|
|
193
198
|
return Math.min(baseBackoff + jitter, Node.MAX_BACKOFF);
|
|
194
199
|
}
|
|
195
200
|
|
|
196
|
-
|
|
197
|
-
if (this.reconnectTimeoutId) {
|
|
198
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
199
|
-
this.reconnectTimeoutId = null;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async connect() {
|
|
201
|
+
connect() {
|
|
204
202
|
if (this.isDestroyed) return;
|
|
205
|
-
|
|
203
|
+
|
|
206
204
|
if (this.ws && this.ws.readyState === Node.WS_OPEN) {
|
|
207
205
|
this.aqua.emit("debug", this.name, "WebSocket already connected");
|
|
208
206
|
return;
|
|
209
207
|
}
|
|
210
|
-
|
|
208
|
+
|
|
211
209
|
this.cleanupExistingConnection();
|
|
212
210
|
|
|
213
211
|
this.ws = new WebSocket(this.wsUrl, {
|
|
@@ -215,31 +213,35 @@ class Node {
|
|
|
215
213
|
perMessageDeflate: false
|
|
216
214
|
});
|
|
217
215
|
|
|
218
|
-
this.ws.once("open", this._onOpen
|
|
219
|
-
this.ws.once("error", this._onError
|
|
220
|
-
this.ws.on("message", this._onMessage
|
|
221
|
-
this.ws.once("close", this._onClose
|
|
216
|
+
this.ws.once("open", this._onOpen);
|
|
217
|
+
this.ws.once("error", this._onError);
|
|
218
|
+
this.ws.on("message", this._onMessage);
|
|
219
|
+
this.ws.once("close", this._onClose);
|
|
222
220
|
}
|
|
223
221
|
|
|
224
222
|
cleanupExistingConnection() {
|
|
225
223
|
if (!this.ws) return;
|
|
226
224
|
|
|
227
225
|
this.ws.removeAllListeners();
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.ws.close();
|
|
232
|
-
} catch (err) {
|
|
233
|
-
this.emitError(`Failed to close WebSocket: ${err.message}`);
|
|
226
|
+
try {
|
|
227
|
+
if (this.ws.readyState === Node.WS_OPEN) {
|
|
228
|
+
this.ws.close(Node.WS_CLOSE_NORMAL, "Manual closure");
|
|
234
229
|
}
|
|
230
|
+
} catch (err) {
|
|
231
|
+
this.emitError(`Failed to close WebSocket: ${err.message}`);
|
|
235
232
|
}
|
|
236
|
-
|
|
237
233
|
this.ws = null;
|
|
238
234
|
}
|
|
239
235
|
|
|
240
236
|
destroy(clean = false) {
|
|
237
|
+
if (this.isDestroyed) return;
|
|
241
238
|
this.isDestroyed = true;
|
|
242
|
-
|
|
239
|
+
|
|
240
|
+
if (this.reconnectTimeoutId) {
|
|
241
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
242
|
+
this.reconnectTimeoutId = null;
|
|
243
|
+
}
|
|
244
|
+
|
|
243
245
|
this.cleanupExistingConnection();
|
|
244
246
|
|
|
245
247
|
if (!clean) {
|
|
@@ -251,61 +253,29 @@ class Node {
|
|
|
251
253
|
this.aqua.emit("nodeDestroy", this);
|
|
252
254
|
this.info = null;
|
|
253
255
|
}
|
|
254
|
-
|
|
255
|
-
async getStats() {
|
|
256
|
-
if (this.connected && this.stats) {
|
|
257
|
-
return this.stats;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
try {
|
|
261
|
-
const newStats = await this.rest.getStats();
|
|
262
|
-
if (newStats && this.stats) {
|
|
263
|
-
this.stats.players = newStats.players ?? this.stats.players;
|
|
264
|
-
this.stats.playingPlayers = newStats.playingPlayers ?? this.stats.playingPlayers;
|
|
265
|
-
this.stats.uptime = newStats.uptime ?? this.stats.uptime;
|
|
266
|
-
this.stats.ping = newStats.ping ?? this.stats.ping;
|
|
267
|
-
|
|
268
|
-
if (newStats.memory) {
|
|
269
|
-
Object.assign(this.stats.memory, newStats.memory);
|
|
270
|
-
this._calculateMemoryPercentages();
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (newStats.cpu) {
|
|
274
|
-
Object.assign(this.stats.cpu, newStats.cpu);
|
|
275
|
-
this._calculateCpuPercentages();
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (newStats.frameStats) {
|
|
279
|
-
Object.assign(this.stats.frameStats, newStats.frameStats);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return this.stats;
|
|
283
|
-
} catch (err) {
|
|
284
|
-
this.emitError(`Failed to fetch node stats: ${err.message}`);
|
|
285
|
-
return this.stats;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
256
|
+
|
|
289
257
|
_updateStats(payload) {
|
|
290
258
|
if (!payload) return;
|
|
259
|
+
|
|
260
|
+
const { players, playingPlayers, uptime, memory, cpu, frameStats, ping } = payload;
|
|
261
|
+
|
|
262
|
+
this.stats.players = players;
|
|
263
|
+
this.stats.playingPlayers = playingPlayers;
|
|
264
|
+
this.stats.uptime = uptime;
|
|
265
|
+
this.stats.ping = ping ?? this.stats.ping;
|
|
291
266
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
this.stats.uptime = payload.uptime;
|
|
295
|
-
this.stats.ping = payload.ping;
|
|
296
|
-
|
|
297
|
-
if (payload.memory) {
|
|
298
|
-
Object.assign(this.stats.memory, payload.memory);
|
|
267
|
+
if (memory) {
|
|
268
|
+
Object.assign(this.stats.memory, memory);
|
|
299
269
|
this._calculateMemoryPercentages();
|
|
300
270
|
}
|
|
301
271
|
|
|
302
|
-
if (
|
|
303
|
-
Object.assign(this.stats.cpu,
|
|
272
|
+
if (cpu) {
|
|
273
|
+
Object.assign(this.stats.cpu, cpu);
|
|
304
274
|
this._calculateCpuPercentages();
|
|
305
275
|
}
|
|
306
276
|
|
|
307
|
-
if (
|
|
308
|
-
Object.assign(this.stats.frameStats,
|
|
277
|
+
if (frameStats) {
|
|
278
|
+
Object.assign(this.stats.frameStats, frameStats);
|
|
309
279
|
}
|
|
310
280
|
}
|
|
311
281
|
|
|
@@ -324,16 +294,16 @@ class Node {
|
|
|
324
294
|
}
|
|
325
295
|
}
|
|
326
296
|
|
|
327
|
-
_handleReadyOp(
|
|
328
|
-
if (!
|
|
297
|
+
_handleReadyOp({ sessionId }) {
|
|
298
|
+
if (!sessionId) {
|
|
329
299
|
this.emitError("Ready payload missing sessionId");
|
|
330
300
|
return;
|
|
331
301
|
}
|
|
332
302
|
|
|
333
|
-
this.sessionId =
|
|
334
|
-
this.rest.setSessionId(
|
|
303
|
+
this.sessionId = sessionId;
|
|
304
|
+
this.rest.setSessionId(sessionId);
|
|
335
305
|
this._headers = this._constructHeaders();
|
|
336
|
-
this.aqua.emit("
|
|
306
|
+
this.aqua.emit("nodeReady", this);
|
|
337
307
|
}
|
|
338
308
|
|
|
339
309
|
async resumePlayers() {
|
|
@@ -344,6 +314,7 @@ class Node {
|
|
|
344
314
|
this.emitError(`Failed to resume session: ${err.message}`);
|
|
345
315
|
}
|
|
346
316
|
}
|
|
317
|
+
|
|
347
318
|
emitError(error) {
|
|
348
319
|
const errorObj = error instanceof Error ? error : new Error(error);
|
|
349
320
|
console.error(`[Aqua] [${this.name}] Error:`, errorObj);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aqualink",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "An Lavalink client, focused in pure performance and features",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -42,7 +42,6 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"ws": "^8.18.3",
|
|
45
|
-
"fs-extra": "^11.3.0",
|
|
46
45
|
"tseep": "^1.3.1"
|
|
47
46
|
},
|
|
48
47
|
"maintainers": [
|