magmastream 2.10.6-alpha.5 → 2.10.6-alpha.6

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.
@@ -38,6 +38,11 @@ export declare class Manager extends EventEmitter {
38
38
  */
39
39
  constructor(options: StandaloneManagerOptions);
40
40
  constructor(options: ManagerOptions, isWrapper: true);
41
+ /**
42
+ * Waits until the Redis client is connected, at most `timeoutMs`. Never throws: when Redis is not
43
+ * ready in time, init continues and commands fail later with Redis's own error.
44
+ */
45
+ private waitForRedis;
41
46
  /**
42
47
  * Initiates the Manager.
43
48
  * @param clientId - The Discord client ID (only required when not using any of the magmastream wrappers).
@@ -179,6 +179,30 @@ class Manager extends events_1.EventEmitter {
179
179
  });
180
180
  }
181
181
  }
182
+ /**
183
+ * Waits until the Redis client is connected, at most `timeoutMs`. Never throws: when Redis is not
184
+ * ready in time, init continues and commands fail later with Redis's own error.
185
+ */
186
+ async waitForRedis(timeoutMs = 10_000) {
187
+ const redis = this.redis;
188
+ if (!redis || redis.status === "ready")
189
+ return;
190
+ await new Promise((resolve) => {
191
+ const done = (message) => {
192
+ clearTimeout(timer);
193
+ redis.off("ready", onReady);
194
+ redis.off("end", onEnd);
195
+ if (message)
196
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] ${message}`);
197
+ resolve();
198
+ };
199
+ const onReady = () => done();
200
+ const onEnd = () => done("Redis connection ended before it was ready.");
201
+ const timer = setTimeout(() => done(`Redis was not ready after ${timeoutMs}ms, continuing without waiting.`), timeoutMs);
202
+ redis.once("ready", onReady);
203
+ redis.once("end", onEnd);
204
+ });
205
+ }
182
206
  /**
183
207
  * Initiates the Manager.
184
208
  * @param clientId - The Discord client ID (only required when not using any of the magmastream wrappers).
@@ -209,6 +233,9 @@ class Manager extends events_1.EventEmitter {
209
233
  }
210
234
  if (this.options.stateStorage.type === Enums_1.StateStorageType.Redis) {
211
235
  this.redis = new ioredis_1.default(lodash_1.default.omit(this.options.stateStorage.redisConfig, "prefix"));
236
+ // Nodes read and write their session ids as soon as they connect. With enableOfflineQueue: false,
237
+ // any command sent before Redis is ready fails, so wait for the connection first.
238
+ await this.waitForRedis();
212
239
  }
213
240
  const results = await Promise.allSettled([...this.nodes.values()].map(async (node) => {
214
241
  await node.connect();
@@ -496,7 +496,14 @@ class Node {
496
496
  const hadPreviousSession = this.pendingResumeSessionId && this.pendingResumeSessionId !== payload.sessionId;
497
497
  this.pendingResumeSessionId = null;
498
498
  this.sessionId = payload.sessionId;
499
- await this.updateSessionId();
499
+ // Storing the session id only matters for resuming after a restart. A storage failure must not stop
500
+ // the node from loading its info and player states.
501
+ try {
502
+ await this.updateSessionId();
503
+ }
504
+ catch (err) {
505
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Could not store sessionId for node ${this.options.identifier}, session resume may not work after a restart: ${err instanceof Error ? (err.cause instanceof Error ? err.cause.message : err.message) : String(err)}`);
506
+ }
500
507
  this.info = await this.fetchInfo();
501
508
  this.isNodeLink = this.isNodeLink || Boolean(this.info.isNodelink);
502
509
  this.rest.isNodeLink = this.isNodeLink;
@@ -238,7 +238,17 @@ class AutoPlayUtils {
238
238
  if (apiKey) {
239
239
  return await this.getRecommendedTracksFromLastFm(track, apiKey);
240
240
  }
241
- const enabledSources = node.info.sourceManagers;
241
+ // node.info is only set once the node's "ready" message is handled, so a just-connected node has none yet
242
+ let info = node.info;
243
+ if (!info) {
244
+ try {
245
+ info = node.info = await node.fetchInfo();
246
+ }
247
+ catch (error) {
248
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[AUTOPLAY] Node ${node.options.identifier} has no info yet: ${error instanceof Error ? error.message : String(error)}`);
249
+ }
250
+ }
251
+ const enabledSources = info?.sourceManagers ?? [];
242
252
  const autoPlaySearchPlatforms = this.manager.options.autoPlaySearchPlatforms;
243
253
  // Iterate over autoplay platforms in order of priority
244
254
  for (const platform of autoPlaySearchPlatforms) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magmastream",
3
- "version": "2.10.6-alpha.5",
3
+ "version": "2.10.6-alpha.6",
4
4
  "description": "A user-friendly Lavalink client designed for NodeJS.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "prepare": "npm run build",
12
12
  "build": "tsc",
13
13
  "types": "rtb --dist dist",
14
- "test": "npm run build && node --test tests/player-state.test.cjs tests/hot-paths.test.cjs tests/restore-performance.test.cjs tests/lyrics.test.cjs",
14
+ "test": "npm run build && node --test tests/player-state.test.cjs tests/hot-paths.test.cjs tests/restore-performance.test.cjs tests/lyrics.test.cjs tests/autoplay.test.cjs tests/session-storage.test.cjs",
15
15
  "format": "prettier --write .",
16
16
  "format:check": "prettier --check .",
17
17
  "lint": "eslint \"src/**/*.{ts,js}\"",