magmastream 2.10.2-dev.4 → 2.10.3-alpha.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.
|
@@ -23,7 +23,8 @@ export declare class Node {
|
|
|
23
23
|
private reconnectTimeout?;
|
|
24
24
|
private reconnectAttempts;
|
|
25
25
|
private redisPrefix?;
|
|
26
|
-
|
|
26
|
+
/** Session ID sent in the reconnect header for resumption — cleared once the ready op is received. */
|
|
27
|
+
private pendingResumeSessionId;
|
|
27
28
|
/**
|
|
28
29
|
* Creates an instance of Node.
|
|
29
30
|
* @param manager - The manager for the node.
|
package/dist/structures/Node.js
CHANGED
|
@@ -31,7 +31,8 @@ class Node {
|
|
|
31
31
|
reconnectTimeout;
|
|
32
32
|
reconnectAttempts = 1;
|
|
33
33
|
redisPrefix;
|
|
34
|
-
|
|
34
|
+
/** Session ID sent in the reconnect header for resumption — cleared once the ready op is received. */
|
|
35
|
+
pendingResumeSessionId = null;
|
|
35
36
|
/**
|
|
36
37
|
* Creates an instance of Node.
|
|
37
38
|
* @param manager - The manager for the node.
|
|
@@ -153,8 +154,6 @@ class Node {
|
|
|
153
154
|
try {
|
|
154
155
|
const raw = fs_1.default.readFileSync(filePath, "utf-8").trim();
|
|
155
156
|
this.sessionId = raw.length ? raw : null;
|
|
156
|
-
if (this.sessionId)
|
|
157
|
-
this.sessionIdsMap.set(this.getCompositeKey(), this.sessionId);
|
|
158
157
|
}
|
|
159
158
|
catch {
|
|
160
159
|
this.sessionId = null;
|
|
@@ -169,7 +168,6 @@ class Node {
|
|
|
169
168
|
const sid = await this.manager.redis.hget(key, compositeKey);
|
|
170
169
|
this.sessionId = sid ?? null;
|
|
171
170
|
if (this.sessionId) {
|
|
172
|
-
this.sessionIdsMap.set(compositeKey, this.sessionId);
|
|
173
171
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Restored sessionId for ${compositeKey}: ${this.sessionId}`);
|
|
174
172
|
}
|
|
175
173
|
}
|
|
@@ -210,7 +208,6 @@ class Node {
|
|
|
210
208
|
if (this.sessionId) {
|
|
211
209
|
fs_1.default.writeFileSync(tmpPath, this.sessionId, "utf-8");
|
|
212
210
|
fs_1.default.renameSync(tmpPath, filePath);
|
|
213
|
-
this.sessionIdsMap.set(this.getCompositeKey(), this.sessionId);
|
|
214
211
|
}
|
|
215
212
|
else {
|
|
216
213
|
try {
|
|
@@ -218,7 +215,6 @@ class Node {
|
|
|
218
215
|
fs_1.default.unlinkSync(filePath);
|
|
219
216
|
}
|
|
220
217
|
catch { }
|
|
221
|
-
this.sessionIdsMap.delete(this.getCompositeKey());
|
|
222
218
|
}
|
|
223
219
|
}
|
|
224
220
|
async updateSessionIdRedis() {
|
|
@@ -228,11 +224,9 @@ class Node {
|
|
|
228
224
|
try {
|
|
229
225
|
if (this.sessionId) {
|
|
230
226
|
await this.manager.redis.hset(key, compositeKey, this.sessionId);
|
|
231
|
-
this.sessionIdsMap.set(compositeKey, this.sessionId);
|
|
232
227
|
}
|
|
233
228
|
else {
|
|
234
229
|
await this.manager.redis.hdel(key, compositeKey);
|
|
235
|
-
this.sessionIdsMap.delete(compositeKey);
|
|
236
230
|
}
|
|
237
231
|
}
|
|
238
232
|
catch (err) {
|
|
@@ -262,8 +256,16 @@ class Node {
|
|
|
262
256
|
"User-Id": this.manager.options.clientId,
|
|
263
257
|
"Client-Name": this.manager.options.clientName,
|
|
264
258
|
};
|
|
265
|
-
|
|
266
|
-
|
|
259
|
+
// Capture resume session ID for the WS header, then clear this.sessionId.
|
|
260
|
+
// REST calls guard on this.sessionId being non-null — keeping the stale value
|
|
261
|
+
// would let updatePlayer/destroyPlayer fire with an invalid session during the
|
|
262
|
+
// reconnect window (between connect() and the 'ready' op).
|
|
263
|
+
// pendingResumeSessionId is kept so the 'ready' handler can still compute
|
|
264
|
+
// hadPreviousSession correctly. this.sessionId is re-set by 'ready'.
|
|
265
|
+
this.pendingResumeSessionId = this.sessionId;
|
|
266
|
+
this.sessionId = null;
|
|
267
|
+
if (typeof this.pendingResumeSessionId === "string" && this.pendingResumeSessionId.length > 0) {
|
|
268
|
+
headers["Session-Id"] = this.pendingResumeSessionId;
|
|
267
269
|
}
|
|
268
270
|
this.socket = new ws_1.default(`ws${this.options.useSSL ? "s" : ""}://${this.address}/v4/websocket`, { headers });
|
|
269
271
|
this.socket.on("open", this.open.bind(this));
|
|
@@ -274,7 +276,7 @@ class Node {
|
|
|
274
276
|
const debugInfo = {
|
|
275
277
|
connected: this.connected,
|
|
276
278
|
address: this.address,
|
|
277
|
-
|
|
279
|
+
pendingResumeSessionId: this.pendingResumeSessionId,
|
|
278
280
|
options: {
|
|
279
281
|
clientId: this.manager.options.clientId,
|
|
280
282
|
clientName: this.manager.options.clientName,
|
|
@@ -295,8 +297,6 @@ class Node {
|
|
|
295
297
|
* @returns {Promise<void>} A promise that resolves when the node and its resources have been destroyed.
|
|
296
298
|
*/
|
|
297
299
|
async destroy() {
|
|
298
|
-
if (!this.connected)
|
|
299
|
-
return;
|
|
300
300
|
const debugInfo = {
|
|
301
301
|
connected: this.connected,
|
|
302
302
|
identifier: this.options.identifier,
|
|
@@ -310,10 +310,16 @@ class Node {
|
|
|
310
310
|
if (players.size) {
|
|
311
311
|
await Promise.all(Array.from(players.values(), (player) => player.autoMoveNode()));
|
|
312
312
|
}
|
|
313
|
-
|
|
314
|
-
this.socket.removeAllListeners();
|
|
315
|
-
this.reconnectAttempts = 1;
|
|
313
|
+
// Always clear reconnect state regardless of connection status
|
|
316
314
|
clearTimeout(this.reconnectTimeout);
|
|
315
|
+
this.reconnectTimeout = undefined;
|
|
316
|
+
this.reconnectAttempts = 1;
|
|
317
|
+
// Only close the socket if it is actually open
|
|
318
|
+
if (this.connected) {
|
|
319
|
+
this.socket.close(1000, "destroy");
|
|
320
|
+
this.socket.removeAllListeners();
|
|
321
|
+
}
|
|
322
|
+
this.socket = null;
|
|
317
323
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeDestroy, this);
|
|
318
324
|
this.manager.nodes.delete(this.options.identifier);
|
|
319
325
|
}
|
|
@@ -487,7 +493,11 @@ class Node {
|
|
|
487
493
|
case "ready":
|
|
488
494
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Node message: ${Utils_1.JSONUtils.safe(payload, 2)}`);
|
|
489
495
|
this.rest.setSessionId(payload.sessionId);
|
|
490
|
-
|
|
496
|
+
// pendingResumeSessionId holds what we sent in Session-Id header (if anything).
|
|
497
|
+
// Use it — not this.sessionId which was nulled in connect() — to detect whether
|
|
498
|
+
// we attempted resumption with a different session than what Lavalink gave back.
|
|
499
|
+
const hadPreviousSession = this.pendingResumeSessionId && this.pendingResumeSessionId !== payload.sessionId;
|
|
500
|
+
this.pendingResumeSessionId = null;
|
|
491
501
|
this.sessionId = payload.sessionId;
|
|
492
502
|
await this.updateSessionId();
|
|
493
503
|
this.info = await this.fetchInfo();
|
|
@@ -520,6 +530,11 @@ class Node {
|
|
|
520
530
|
return;
|
|
521
531
|
const track = await player.queue.getCurrent();
|
|
522
532
|
const type = payload.type;
|
|
533
|
+
const TRACK_EVENTS = ["TrackStartEvent", "TrackEndEvent", "TrackStuckEvent", "TrackExceptionEvent"];
|
|
534
|
+
if (!track && TRACK_EVENTS.includes(type)) {
|
|
535
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[Node] Received ${type} for guild ${payload.guildId} but queue has no current track — ignoring.`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
523
538
|
let error;
|
|
524
539
|
switch (type) {
|
|
525
540
|
case "TrackStartEvent":
|
|
@@ -943,7 +943,12 @@ class Player {
|
|
|
943
943
|
context: { guildId: this.guildId },
|
|
944
944
|
});
|
|
945
945
|
}
|
|
946
|
-
|
|
946
|
+
// Only destroy the player on the old node if it is still reachable.
|
|
947
|
+
// If the server is down the REST call would fail anyway; skipping it
|
|
948
|
+
// also prevents a spurious error when switching nodes during an outage.
|
|
949
|
+
if (this.node.connected) {
|
|
950
|
+
await this.node.rest.destroyPlayer(this.guildId).catch(() => { });
|
|
951
|
+
}
|
|
947
952
|
this.manager.players.delete(this.guildId);
|
|
948
953
|
this.node = node;
|
|
949
954
|
this.manager.players.set(this.guildId, this);
|