aqualink 2.7.1 → 2.7.2
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 +171 -31
- package/build/structures/Node.js +84 -55
- package/package.json +1 -1
package/build/structures/Aqua.js
CHANGED
|
@@ -62,8 +62,16 @@ class Aqua extends EventEmitter {
|
|
|
62
62
|
this._failoverQueue = new Map();
|
|
63
63
|
this._lastFailoverAttempt = new Map();
|
|
64
64
|
|
|
65
|
+
this._brokenPlayers = new Map();
|
|
66
|
+
this._nodeReconnectHandlers = new Map();
|
|
67
|
+
|
|
65
68
|
this._boundCleanupPlayer = this.cleanupPlayer.bind(this);
|
|
66
69
|
this._boundHandlePlayerDestroy = this._handlePlayerDestroy.bind(this);
|
|
70
|
+
this._boundHandleNodeReconnect = this._handleNodeReconnect.bind(this);
|
|
71
|
+
this._boundHandleSocketClosed = this._handleSocketClosed.bind(this);
|
|
72
|
+
|
|
73
|
+
this.on('nodeConnect', (node) => this._handleNodeReconnect(node));
|
|
74
|
+
this.on('nodeDisconnect', (node) => this._handleSocketClosed(node));
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
defaultSendFunction(packet) {
|
|
@@ -128,6 +136,7 @@ class Aqua extends EventEmitter {
|
|
|
128
136
|
this.destroyNode(nodeId);
|
|
129
137
|
|
|
130
138
|
const node = new Node(this, options, this.options);
|
|
139
|
+
node.players = new Set();
|
|
131
140
|
this.nodeMap.set(nodeId, node);
|
|
132
141
|
this._invalidateCache();
|
|
133
142
|
|
|
@@ -144,6 +153,116 @@ class Aqua extends EventEmitter {
|
|
|
144
153
|
}
|
|
145
154
|
}
|
|
146
155
|
|
|
156
|
+
async _handleNodeReconnect(node) {
|
|
157
|
+
if (!this.autoResume) return;
|
|
158
|
+
|
|
159
|
+
const nodeId = node.name || node.host;
|
|
160
|
+
this.emit("debug", "Aqua", `Node ${nodeId} reconnected, attempting to rebuild broken players`);
|
|
161
|
+
|
|
162
|
+
this._nodeStates.set(nodeId, { connected: true, failoverInProgress: false });
|
|
163
|
+
|
|
164
|
+
await this._rebuildBrokenPlayers(node);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async _handleSocketClosed(node) {
|
|
168
|
+
if (!this.autoResume) return;
|
|
169
|
+
|
|
170
|
+
const nodeId = node.name || node.host;
|
|
171
|
+
this.emit("debug", "Aqua", `Socket closed for node ${nodeId}, storing broken players`);
|
|
172
|
+
|
|
173
|
+
this._nodeStates.set(nodeId, { connected: false, failoverInProgress: false });
|
|
174
|
+
|
|
175
|
+
await this._storeBrokenPlayersForNode(node);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async _storeBrokenPlayersForNode(node) {
|
|
179
|
+
const nodeId = node.name || node.host;
|
|
180
|
+
const affectedPlayers = await this._getPlayersForNode(node);
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
for (const player of affectedPlayers) {
|
|
184
|
+
try {
|
|
185
|
+
const playerState = this._capturePlayerState(player);
|
|
186
|
+
if (playerState) {
|
|
187
|
+
playerState.originalNodeId = nodeId;
|
|
188
|
+
playerState.brokenAt = Date.now();
|
|
189
|
+
this._brokenPlayers.set(player.guildId, playerState);
|
|
190
|
+
|
|
191
|
+
this.emit("debug", "Aqua", `Stored broken player state for guild ${player.guildId}`);
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
this.emit("error", null, new Error(`Failed to store broken player state: ${error.message}`));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async _getPlayersForNode(node) {
|
|
200
|
+
const affectedPlayers = [];
|
|
201
|
+
for (const player of this.players.values()) {
|
|
202
|
+
if (player.nodes === node || player.nodes?.name === node.name) {
|
|
203
|
+
affectedPlayers.push(player);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return affectedPlayers;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async _rebuildBrokenPlayers(node) {
|
|
210
|
+
const nodeId = node.name || node.host;
|
|
211
|
+
const rebuiltCount = 0;
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
for (const [guildId, brokenState] of this._brokenPlayers.entries()) {
|
|
216
|
+
if (brokenState.originalNodeId !== nodeId) continue;
|
|
217
|
+
try {
|
|
218
|
+
await this._rebuildPlayer(brokenState, node);
|
|
219
|
+
this._brokenPlayers.delete(guildId);
|
|
220
|
+
rebuiltCount++;
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
this.emit("debug", "Aqua", `Successfully rebuilt player for guild ${guildId}`);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
this.emit("error", null, new Error(`Failed to rebuild player for guild ${guildId}: ${error.message}`));
|
|
226
|
+
|
|
227
|
+
if (Date.now() - brokenState.brokenAt > 300000) {
|
|
228
|
+
this._brokenPlayers.delete(guildId);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (rebuiltCount > 0) {
|
|
234
|
+
this.emit("playersRebuilt", node, rebuiltCount);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async _rebuildPlayer(brokenState, targetNode) {
|
|
239
|
+
const { guildId, textChannel, voiceChannel, current, volume = 65, deaf = true } = brokenState;
|
|
240
|
+
|
|
241
|
+
const connectionOptions = { guildId, textChannel, voiceChannel, defaultVolume: volume, deaf };
|
|
242
|
+
const existingPlayer = this.players.get(guildId);
|
|
243
|
+
|
|
244
|
+
if (existingPlayer) {
|
|
245
|
+
await existingPlayer.destroy();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
setTimeout(async () => {
|
|
249
|
+
const newestPlayer = await this.createConnection(connectionOptions);
|
|
250
|
+
this.players.set(guildId, newestPlayer);
|
|
251
|
+
|
|
252
|
+
if (current) {
|
|
253
|
+
try {
|
|
254
|
+
await newestPlayer.queue.add(current);
|
|
255
|
+
await newestPlayer.play();
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error("Error while playing track:", error);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.emit("trackStart", newestPlayer, brokenState.current);
|
|
262
|
+
return newestPlayer;
|
|
263
|
+
}, 2000);
|
|
264
|
+
}
|
|
265
|
+
|
|
147
266
|
destroyNode(identifier) {
|
|
148
267
|
const node = this.nodeMap.get(identifier);
|
|
149
268
|
if (!node) return;
|
|
@@ -153,6 +272,11 @@ class Aqua extends EventEmitter {
|
|
|
153
272
|
}
|
|
154
273
|
|
|
155
274
|
_cleanupNode(nodeId) {
|
|
275
|
+
const node = this.nodeMap.get(nodeId);
|
|
276
|
+
if (node) {
|
|
277
|
+
node.removeAllListeners();
|
|
278
|
+
}
|
|
279
|
+
|
|
156
280
|
this.nodeMap.delete(nodeId);
|
|
157
281
|
this._nodeStates.delete(nodeId);
|
|
158
282
|
this._failoverQueue.delete(nodeId);
|
|
@@ -186,7 +310,7 @@ class Aqua extends EventEmitter {
|
|
|
186
310
|
try {
|
|
187
311
|
this.emit("nodeFailover", failedNode);
|
|
188
312
|
|
|
189
|
-
const affectedPlayers =
|
|
313
|
+
const affectedPlayers = Array.from(failedNode.players);
|
|
190
314
|
if (affectedPlayers.length === 0) {
|
|
191
315
|
this._nodeStates.set(nodeId, { connected: false, failoverInProgress: false });
|
|
192
316
|
return;
|
|
@@ -215,22 +339,6 @@ class Aqua extends EventEmitter {
|
|
|
215
339
|
}
|
|
216
340
|
}
|
|
217
341
|
|
|
218
|
-
_getPlayersForNode(node) {
|
|
219
|
-
const affectedPlayers = [];
|
|
220
|
-
for (const player of this.players.values()) {
|
|
221
|
-
if (player.nodes === node || player.nodes?.name === node.name) {
|
|
222
|
-
affectedPlayers.push(player);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return affectedPlayers;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
_getAvailableNodesForFailover(failedNode) {
|
|
229
|
-
return this.leastUsedNodes.filter(node =>
|
|
230
|
-
node !== failedNode && node.name !== failedNode.name
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
342
|
async _migratePlayersWithRetry(players, availableNodes) {
|
|
235
343
|
const results = [];
|
|
236
344
|
|
|
@@ -275,7 +383,6 @@ class Aqua extends EventEmitter {
|
|
|
275
383
|
|
|
276
384
|
await this._restorePlayerState(newPlayer, playerState);
|
|
277
385
|
|
|
278
|
-
newPlayer.destroy();
|
|
279
386
|
if (playerState.current) {
|
|
280
387
|
newPlayer.queue.add(playerState.current);
|
|
281
388
|
}
|
|
@@ -316,13 +423,12 @@ class Aqua extends EventEmitter {
|
|
|
316
423
|
position: player.position || 0,
|
|
317
424
|
current: player.current ? { ...player.current } : null,
|
|
318
425
|
queue: player.queue?.tracks ? [...player.queue.tracks] : [],
|
|
319
|
-
repeat: player.
|
|
426
|
+
repeat: player.loop,
|
|
320
427
|
shuffle: player.shuffle,
|
|
321
428
|
deaf: player.deaf || false,
|
|
322
429
|
mute: player.mute || false,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
timestamp: Date.now()
|
|
430
|
+
timestamp: Date.now(),
|
|
431
|
+
connected: player.connected || false,
|
|
326
432
|
};
|
|
327
433
|
} catch (error) {
|
|
328
434
|
return null;
|
|
@@ -347,22 +453,18 @@ class Aqua extends EventEmitter {
|
|
|
347
453
|
if (!newPlayer || !playerState) return;
|
|
348
454
|
|
|
349
455
|
try {
|
|
350
|
-
// Batch operations where possible
|
|
351
456
|
const operations = [];
|
|
352
457
|
|
|
353
458
|
if (playerState.volume !== undefined) {
|
|
354
459
|
operations.push(newPlayer.setVolume(playerState.volume));
|
|
355
460
|
}
|
|
356
461
|
|
|
357
|
-
// Restore queue efficiently
|
|
358
462
|
if (playerState.queue?.length > 0) {
|
|
359
463
|
newPlayer.queue.add(...playerState.queue);
|
|
360
464
|
}
|
|
361
465
|
|
|
362
|
-
// Wait for all operations
|
|
363
466
|
await Promise.all(operations);
|
|
364
467
|
|
|
365
|
-
// Handle current track restoration
|
|
366
468
|
if (playerState.current && this.failoverOptions.preservePosition) {
|
|
367
469
|
newPlayer.queue.unshift(playerState.current);
|
|
368
470
|
|
|
@@ -370,7 +472,7 @@ class Aqua extends EventEmitter {
|
|
|
370
472
|
await newPlayer.play();
|
|
371
473
|
|
|
372
474
|
if (playerState.position > 0) {
|
|
373
|
-
await this._delay(300);
|
|
475
|
+
await this._delay(300);
|
|
374
476
|
await newPlayer.seek(playerState.position);
|
|
375
477
|
}
|
|
376
478
|
|
|
@@ -380,7 +482,6 @@ class Aqua extends EventEmitter {
|
|
|
380
482
|
}
|
|
381
483
|
}
|
|
382
484
|
|
|
383
|
-
// Restore other properties
|
|
384
485
|
Object.assign(newPlayer, {
|
|
385
486
|
repeat: playerState.repeat,
|
|
386
487
|
shuffle: playerState.shuffle
|
|
@@ -395,7 +496,6 @@ class Aqua extends EventEmitter {
|
|
|
395
496
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
396
497
|
}
|
|
397
498
|
|
|
398
|
-
// Optimized cleanup
|
|
399
499
|
async cleanupPlayer(player) {
|
|
400
500
|
if (!player) return;
|
|
401
501
|
try {
|
|
@@ -434,7 +534,6 @@ class Aqua extends EventEmitter {
|
|
|
434
534
|
}
|
|
435
535
|
}
|
|
436
536
|
|
|
437
|
-
// Optimized sorting with caching
|
|
438
537
|
const loadCache = new Map();
|
|
439
538
|
regionNodes.sort((a, b) => {
|
|
440
539
|
const loadA = loadCache.get(a) ?? (loadCache.set(a, this._calculateLoad(a)), loadCache.get(a));
|
|
@@ -469,7 +568,6 @@ class Aqua extends EventEmitter {
|
|
|
469
568
|
const player = new Player(this, node, options);
|
|
470
569
|
this.players.set(options.guildId, player);
|
|
471
570
|
|
|
472
|
-
// Use pre-bound method for better performance
|
|
473
571
|
player.once("destroy", this._boundHandlePlayerDestroy);
|
|
474
572
|
player.connect(options);
|
|
475
573
|
this.emit("playerCreate", player);
|
|
@@ -477,6 +575,10 @@ class Aqua extends EventEmitter {
|
|
|
477
575
|
}
|
|
478
576
|
|
|
479
577
|
_handlePlayerDestroy(player) {
|
|
578
|
+
const node = player.nodes;
|
|
579
|
+
if (node && node.players) {
|
|
580
|
+
node.players.delete(player);
|
|
581
|
+
}
|
|
480
582
|
this.players.delete(player.guildId);
|
|
481
583
|
this.emit("playerDestroy", player);
|
|
482
584
|
}
|
|
@@ -714,6 +816,44 @@ class Aqua extends EventEmitter {
|
|
|
714
816
|
});
|
|
715
817
|
}
|
|
716
818
|
|
|
819
|
+
// Auto-resume utility methods
|
|
820
|
+
getBrokenPlayersCount() {
|
|
821
|
+
return this._brokenPlayers.size;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
getBrokenPlayers() {
|
|
825
|
+
return Array.from(this._brokenPlayers.entries()).map(([guildId, state]) => ({
|
|
826
|
+
guildId,
|
|
827
|
+
originalNodeId: state.originalNodeId,
|
|
828
|
+
brokenAt: state.brokenAt,
|
|
829
|
+
hasCurrentTrack: !!state.current,
|
|
830
|
+
queueSize: state.queue?.length || 0
|
|
831
|
+
}));
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
clearBrokenPlayers() {
|
|
835
|
+
this._brokenPlayers.clear();
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
async forceBrokenPlayerRebuild(guildId) {
|
|
839
|
+
const brokenState = this._brokenPlayers.get(guildId);
|
|
840
|
+
if (!brokenState) return false;
|
|
841
|
+
|
|
842
|
+
try {
|
|
843
|
+
const targetNode = this.nodeMap.get(brokenState.originalNodeId) || this.leastUsedNodes[0];
|
|
844
|
+
if (!targetNode || !targetNode.connected) {
|
|
845
|
+
throw new Error("No available nodes for rebuild");
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
await this._rebuildPlayer(brokenState, targetNode);
|
|
849
|
+
this._brokenPlayers.delete(guildId);
|
|
850
|
+
return true;
|
|
851
|
+
} catch (error) {
|
|
852
|
+
this.emit("error", null, new Error(`Failed to force rebuild player for guild ${guildId}: ${error.message}`));
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
717
857
|
// Utility methods
|
|
718
858
|
resetFailoverAttempts(nodeId) {
|
|
719
859
|
this._failoverQueue.delete(nodeId);
|
package/build/structures/Node.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
2
|
const WebSocket = require('ws');
|
|
4
3
|
const Rest = require("./Rest");
|
|
5
4
|
|
|
@@ -30,6 +29,9 @@ class Node {
|
|
|
30
29
|
this.sessionId = sessionId;
|
|
31
30
|
this.regions = regions;
|
|
32
31
|
|
|
32
|
+
this.wsUrl = `ws${this.secure ? "s" : ""}://${this.host}:${this.port}/v4/websocket`;
|
|
33
|
+
this.rest = new Rest(aqua, this);
|
|
34
|
+
|
|
33
35
|
const {
|
|
34
36
|
resumeTimeout = 60,
|
|
35
37
|
autoResume = false,
|
|
@@ -44,24 +46,19 @@ class Node {
|
|
|
44
46
|
this.reconnectTries = reconnectTries;
|
|
45
47
|
this.infiniteReconnects = infiniteReconnects;
|
|
46
48
|
|
|
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
|
-
|
|
51
49
|
this.connected = false;
|
|
52
|
-
this.isDestroyed = false;
|
|
53
50
|
this.info = null;
|
|
54
51
|
this.ws = null;
|
|
55
52
|
this.reconnectAttempted = 0;
|
|
56
53
|
this.reconnectTimeoutId = null;
|
|
57
|
-
this.
|
|
54
|
+
this.isDestroyed = false;
|
|
58
55
|
|
|
59
|
-
// Pre-bind event handlers for efficiency
|
|
60
56
|
this._onOpen = this._onOpen.bind(this);
|
|
61
57
|
this._onError = this._onError.bind(this);
|
|
62
58
|
this._onMessage = this._onMessage.bind(this);
|
|
63
59
|
this._onClose = this._onClose.bind(this);
|
|
64
60
|
|
|
61
|
+
this._headers = this._constructHeaders();
|
|
65
62
|
this.initializeStats();
|
|
66
63
|
}
|
|
67
64
|
|
|
@@ -94,11 +91,7 @@ class Node {
|
|
|
94
91
|
async _onOpen() {
|
|
95
92
|
this.connected = true;
|
|
96
93
|
this.reconnectAttempted = 0;
|
|
97
|
-
this.lastHealthCheck = Date.now();
|
|
98
94
|
this.aqua.emit("debug", this.name, "WebSocket connection established");
|
|
99
|
-
|
|
100
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
101
|
-
this.reconnectTimeoutId = null;
|
|
102
95
|
|
|
103
96
|
if (this.aqua.bypassChecks?.nodeFetchInfo) return;
|
|
104
97
|
|
|
@@ -107,7 +100,7 @@ class Node {
|
|
|
107
100
|
this.aqua.emit("nodeConnected", this);
|
|
108
101
|
|
|
109
102
|
if (this.autoResume && this.sessionId) {
|
|
110
|
-
await this.
|
|
103
|
+
await this.aqua.loadPlayers();
|
|
111
104
|
}
|
|
112
105
|
} catch (err) {
|
|
113
106
|
this.info = null;
|
|
@@ -131,8 +124,7 @@ class Node {
|
|
|
131
124
|
const { op, guildId } = payload;
|
|
132
125
|
if (!op) return;
|
|
133
126
|
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
|
|
136
128
|
switch (op) {
|
|
137
129
|
case "stats":
|
|
138
130
|
this._updateStats(payload);
|
|
@@ -157,20 +149,22 @@ class Node {
|
|
|
157
149
|
const reasonStr = reason?.toString() || "No reason provided";
|
|
158
150
|
|
|
159
151
|
this.aqua.emit("nodeDisconnect", this, { code, reason: reasonStr });
|
|
152
|
+
|
|
160
153
|
this.aqua.handleNodeFailover(this);
|
|
154
|
+
|
|
161
155
|
this.scheduleReconnect(code);
|
|
162
156
|
}
|
|
163
157
|
|
|
164
158
|
scheduleReconnect(code) {
|
|
159
|
+
this.clearReconnectTimeout();
|
|
160
|
+
|
|
165
161
|
if (code === Node.WS_CLOSE_NORMAL || this.isDestroyed) {
|
|
166
|
-
this.aqua.emit("debug", this.name, "WebSocket closed normally, not reconnecting
|
|
162
|
+
this.aqua.emit("debug", this.name, "WebSocket closed normally, not reconnecting");
|
|
167
163
|
return;
|
|
168
164
|
}
|
|
169
165
|
|
|
170
|
-
if (this.reconnectTimeoutId) return;
|
|
171
|
-
|
|
172
166
|
if (this.infiniteReconnects) {
|
|
173
|
-
this.aqua.emit("nodeReconnect", this, "Infinite reconnects enabled, trying again in 10 seconds
|
|
167
|
+
this.aqua.emit("nodeReconnect", this, "Infinite reconnects enabled, trying again in 10 seconds");
|
|
174
168
|
this.reconnectTimeoutId = setTimeout(() => this.connect(), 10000);
|
|
175
169
|
return;
|
|
176
170
|
}
|
|
@@ -193,19 +187,26 @@ class Node {
|
|
|
193
187
|
}
|
|
194
188
|
|
|
195
189
|
calculateBackoff() {
|
|
196
|
-
const baseBackoff = this.reconnectTimeout * Math.pow(Node.BACKOFF_MULTIPLIER, this.reconnectAttempted
|
|
190
|
+
const baseBackoff = this.reconnectTimeout * Math.pow(Node.BACKOFF_MULTIPLIER, this.reconnectAttempted);
|
|
197
191
|
const jitter = Math.random() * Math.min(2000, baseBackoff * 0.2);
|
|
198
192
|
return Math.min(baseBackoff + jitter, Node.MAX_BACKOFF);
|
|
199
193
|
}
|
|
200
194
|
|
|
201
|
-
|
|
195
|
+
clearReconnectTimeout() {
|
|
196
|
+
if (this.reconnectTimeoutId) {
|
|
197
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
198
|
+
this.reconnectTimeoutId = null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async connect() {
|
|
202
203
|
if (this.isDestroyed) return;
|
|
203
204
|
|
|
204
205
|
if (this.ws && this.ws.readyState === Node.WS_OPEN) {
|
|
205
206
|
this.aqua.emit("debug", this.name, "WebSocket already connected");
|
|
206
207
|
return;
|
|
207
208
|
}
|
|
208
|
-
|
|
209
|
+
|
|
209
210
|
this.cleanupExistingConnection();
|
|
210
211
|
|
|
211
212
|
this.ws = new WebSocket(this.wsUrl, {
|
|
@@ -217,31 +218,28 @@ class Node {
|
|
|
217
218
|
this.ws.once("error", this._onError);
|
|
218
219
|
this.ws.on("message", this._onMessage);
|
|
219
220
|
this.ws.once("close", this._onClose);
|
|
221
|
+
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
cleanupExistingConnection() {
|
|
223
225
|
if (!this.ws) return;
|
|
224
226
|
|
|
225
227
|
this.ws.removeAllListeners();
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
|
|
229
|
+
if (this.ws.readyState === Node.WS_OPEN) {
|
|
230
|
+
try {
|
|
231
|
+
this.ws.close();
|
|
232
|
+
} catch (err) {
|
|
233
|
+
this.emitError(`Failed to close WebSocket: ${err.message}`);
|
|
229
234
|
}
|
|
230
|
-
} catch (err) {
|
|
231
|
-
this.emitError(`Failed to close WebSocket: ${err.message}`);
|
|
232
235
|
}
|
|
236
|
+
|
|
233
237
|
this.ws = null;
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
destroy(clean = false) {
|
|
237
|
-
if (this.isDestroyed) return;
|
|
238
241
|
this.isDestroyed = true;
|
|
239
|
-
|
|
240
|
-
if (this.reconnectTimeoutId) {
|
|
241
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
242
|
-
this.reconnectTimeoutId = null;
|
|
243
|
-
}
|
|
244
|
-
|
|
242
|
+
this.clearReconnectTimeout();
|
|
245
243
|
this.cleanupExistingConnection();
|
|
246
244
|
|
|
247
245
|
if (!clean) {
|
|
@@ -253,29 +251,61 @@ class Node {
|
|
|
253
251
|
this.aqua.emit("nodeDestroy", this);
|
|
254
252
|
this.info = null;
|
|
255
253
|
}
|
|
256
|
-
|
|
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
|
+
|
|
257
289
|
_updateStats(payload) {
|
|
258
290
|
if (!payload) return;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
this.stats.
|
|
263
|
-
this.stats.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (memory) {
|
|
268
|
-
Object.assign(this.stats.memory, memory);
|
|
291
|
+
|
|
292
|
+
this.stats.players = payload.players;
|
|
293
|
+
this.stats.playingPlayers = payload.playingPlayers;
|
|
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);
|
|
269
299
|
this._calculateMemoryPercentages();
|
|
270
300
|
}
|
|
271
301
|
|
|
272
|
-
if (cpu) {
|
|
273
|
-
Object.assign(this.stats.cpu, cpu);
|
|
302
|
+
if (payload.cpu) {
|
|
303
|
+
Object.assign(this.stats.cpu, payload.cpu);
|
|
274
304
|
this._calculateCpuPercentages();
|
|
275
305
|
}
|
|
276
306
|
|
|
277
|
-
if (frameStats) {
|
|
278
|
-
Object.assign(this.stats.frameStats, frameStats);
|
|
307
|
+
if (payload.frameStats) {
|
|
308
|
+
Object.assign(this.stats.frameStats, payload.frameStats);
|
|
279
309
|
}
|
|
280
310
|
}
|
|
281
311
|
|
|
@@ -294,16 +324,16 @@ class Node {
|
|
|
294
324
|
}
|
|
295
325
|
}
|
|
296
326
|
|
|
297
|
-
_handleReadyOp(
|
|
298
|
-
if (!sessionId) {
|
|
327
|
+
_handleReadyOp(payload) {
|
|
328
|
+
if (!payload.sessionId) {
|
|
299
329
|
this.emitError("Ready payload missing sessionId");
|
|
300
330
|
return;
|
|
301
331
|
}
|
|
302
332
|
|
|
303
|
-
this.sessionId = sessionId;
|
|
304
|
-
this.rest.setSessionId(sessionId);
|
|
333
|
+
this.sessionId = payload.sessionId;
|
|
334
|
+
this.rest.setSessionId(payload.sessionId);
|
|
305
335
|
this._headers = this._constructHeaders();
|
|
306
|
-
this.aqua.emit("
|
|
336
|
+
this.aqua.emit("nodeConnect", this);
|
|
307
337
|
}
|
|
308
338
|
|
|
309
339
|
async resumePlayers() {
|
|
@@ -314,7 +344,6 @@ class Node {
|
|
|
314
344
|
this.emitError(`Failed to resume session: ${err.message}`);
|
|
315
345
|
}
|
|
316
346
|
}
|
|
317
|
-
|
|
318
347
|
emitError(error) {
|
|
319
348
|
const errorObj = error instanceof Error ? error : new Error(error);
|
|
320
349
|
console.error(`[Aqua] [${this.name}] Error:`, errorObj);
|