From b3e127b0c5cace1f04773054af3c1038ec1c0840 Mon Sep 17 00:00:00 2001 From: Pecacheu <3608878+Pecacheu@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:30:23 -0400 Subject: [PATCH 1/4] fix: Race condition on init & add method for overriding config options before configured() is set Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> --- src/Client.ts | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/Client.ts b/src/Client.ts index ecf34c73..5db58c34 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -189,7 +189,8 @@ export class Client extends AsyncEventEmitter { readonly options: ClientOptions; readonly events: EventClient<1>; - configuration: RevoltConfig | undefined; + readonly configuration: RevoltConfig | undefined; + #configLock?: Promise; #session: Session | undefined; user: User | undefined; @@ -203,8 +204,9 @@ export class Client extends AsyncEventEmitter { #setConnectionFailureCount: Setter; #reconnectTimeout: number | undefined; #slowmodeTimers = new Map>(); - /** - * Create Stoat.js Client + + /** Create Stoat.js Client + @param configuration Deprecated - Please use `Client.initConfig` if you need to override config. */ constructor(options?: Partial, configuration?: RevoltConfig) { super(); @@ -256,8 +258,6 @@ export class Client extends AsyncEventEmitter { this.configured = configured; this.#setConfigured = setConfigured; - this.#fetchConfiguration(); - const [ready, setReady] = createSignal(false); this.ready = ready; this.#setReady = setReady; @@ -342,14 +342,20 @@ export class Client extends AsyncEventEmitter { ); } - /** - * Fetches the configuration of the server if it has not been already fetched. - */ - async #fetchConfiguration(): Promise { - if (!this.configuration) { - this.configuration = await this.api.get("/"); - this.#setConfigured(true); + /** Fetches the server config. This is called automatically by login() or loginBot(), + * but you can call it manually first if you need to override any config options. + * @param preConfig Called after fetch, but before configured() flag is set */ + async initConfig(preConfig?: () => void): Promise { + if (!this.#configLock && !this.configuration) { + //Create promise lock to avoid race condition + this.#configLock = (async () => { + //@ts-expect-error readonly override + this.configuration = await this.api.get("/"); + preConfig?.(); + this.#setConfigured(true); + })(); } + return this.#configLock; } /** @@ -370,7 +376,7 @@ export class Client extends AsyncEventEmitter { * @returns An on-boarding function if on-boarding is required, undefined otherwise */ async login(details: DataLogin): Promise { - await this.#fetchConfiguration(); + await this.initConfig(); const data = await this.api.post("/auth/session/login", details); if (data.result === "Success") { this.#session = data; @@ -393,7 +399,7 @@ export class Client extends AsyncEventEmitter { * @param token Bot token */ async loginBot(token: string): Promise { - await this.#fetchConfiguration(); + await this.initConfig(); this.#session = token; this.#updateHeaders(); this.connect(); @@ -614,10 +620,6 @@ export class Client extends AsyncEventEmitter { * Backend enforced limits for the logged in user */ get limits(): UserLimits | undefined { - if (!this.configured() || !this.user) { - return; - } - - return this.user.limits; + if (this.configured() && this.user) return this.user.limits; } } From 93bffede5b14161a89290f2149e198072f427387 Mon Sep 17 00:00:00 2001 From: Pecacheu <3608878+Pecacheu@users.noreply.github.com> Date: Thu, 23 Jul 2026 12:01:13 -0400 Subject: [PATCH 2/4] fix: Cleanup lock when finished Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> --- src/Client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Client.ts b/src/Client.ts index 5db58c34..8dcf9b92 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -353,6 +353,7 @@ export class Client extends AsyncEventEmitter { this.configuration = await this.api.get("/"); preConfig?.(); this.#setConfigured(true); + this.#configLock = undefined; })(); } return this.#configLock; From db60a7c156af04d8f714a21a78d9e3547de7da02 Mon Sep 17 00:00:00 2001 From: Pecacheu <3608878+Pecacheu@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:37:18 -0400 Subject: [PATCH 3/4] fix: Improve comments Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> --- src/Client.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Client.ts b/src/Client.ts index 8dcf9b92..9b3eb788 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -205,8 +205,9 @@ export class Client extends AsyncEventEmitter { #reconnectTimeout: number | undefined; #slowmodeTimers = new Map>(); - /** Create Stoat.js Client - @param configuration Deprecated - Please use `Client.initConfig` if you need to override config. + /** + * Create Stoat.js Client + * @param configuration Deprecated - Please use `Client.initConfig` if you need to override config. */ constructor(options?: Partial, configuration?: RevoltConfig) { super(); @@ -342,9 +343,17 @@ export class Client extends AsyncEventEmitter { ); } - /** Fetches the server config. This is called automatically by login() or loginBot(), - * but you can call it manually first if you need to override any config options. - * @param preConfig Called after fetch, but before configured() flag is set */ + /** + * Fetches the server config. This is called automatically by `login()` or `loginBot()`, + * but you can call it first manually if you need to override any config options. + * + * Override example: + * ```ts + * await client.initConfig(() => { + * cli.configuration!.ws = "wss://example.com"; + * }); + * ``` + */ async initConfig(preConfig?: () => void): Promise { if (!this.#configLock && !this.configuration) { //Create promise lock to avoid race condition From 48ce01898b3d93c866f167ef450f26a17e52a25d Mon Sep 17 00:00:00 2001 From: Pecacheu <3608878+Pecacheu@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:41:00 -0400 Subject: [PATCH 4/4] fix: Add config param to preConfig Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> --- src/Client.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Client.ts b/src/Client.ts index 9b3eb788..bf647983 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -349,18 +349,18 @@ export class Client extends AsyncEventEmitter { * * Override example: * ```ts - * await client.initConfig(() => { - * cli.configuration!.ws = "wss://example.com"; + * await client.initConfig((config) => { + * config.ws = "wss://example.com"; * }); * ``` */ - async initConfig(preConfig?: () => void): Promise { + async initConfig(preConfig?: (config: RevoltConfig) => void): Promise { if (!this.#configLock && !this.configuration) { //Create promise lock to avoid race condition this.#configLock = (async () => { //@ts-expect-error readonly override this.configuration = await this.api.get("/"); - preConfig?.(); + preConfig?.(this.configuration); this.#setConfigured(true); this.#configLock = undefined; })();