From 7ee09129fd044ff0a0bac4f40ad8fceb49b327e2 Mon Sep 17 00:00:00 2001 From: Ray <3608878+Pecacheu@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:38:20 -0400 Subject: [PATCH] fix: Race condition on init & add method for overriding config options (#172) * 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> * fix: Cleanup lock when finished Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> * fix: Improve comments Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> * fix: Add config param to preConfig Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> --------- Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> --- src/Client.ts | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Client.ts b/src/Client.ts index ecf34c73..bf647983 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,10 @@ export class Client extends AsyncEventEmitter { #setConnectionFailureCount: Setter; #reconnectTimeout: number | undefined; #slowmodeTimers = new Map>(); + /** * 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 +259,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; @@ -343,13 +344,28 @@ export class Client extends AsyncEventEmitter { } /** - * Fetches the configuration of the server if it has not been already fetched. + * 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((config) => { + * config.ws = "wss://example.com"; + * }); + * ``` */ - async #fetchConfiguration(): Promise { - if (!this.configuration) { - this.configuration = await this.api.get("/"); - this.#setConfigured(true); + 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?.(this.configuration); + this.#setConfigured(true); + this.#configLock = undefined; + })(); } + return this.#configLock; } /** @@ -370,7 +386,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 +409,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 +630,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; } }