From f5ce2b962e13e2d938b21185bcbc12c61e999a67 Mon Sep 17 00:00:00 2001 From: Ron Cohen Date: Thu, 24 Sep 2026 21:05:45 +0200 Subject: [PATCH] fix(browser-sdk): avoid false initialization diagnostics --- .changeset/quiet-flags-initialize.md | 5 +++++ packages/browser-sdk/src/client.ts | 6 +---- packages/browser-sdk/src/flag/flags.ts | 7 ++++++ packages/browser-sdk/test/usage.test.ts | 29 +++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 .changeset/quiet-flags-initialize.md diff --git a/.changeset/quiet-flags-initialize.md b/.changeset/quiet-flags-initialize.md new file mode 100644 index 000000000..5520b56ec --- /dev/null +++ b/.changeset/quiet-flags-initialize.md @@ -0,0 +1,5 @@ +--- +"@reflag/browser-sdk": patch +--- + +Avoid reporting `CLIENT_NOT_INITIALIZED` when a flag is evaluated from the initial flag state while client initialization is completing. diff --git a/packages/browser-sdk/src/client.ts b/packages/browser-sdk/src/client.ts index d4f0f80fe..4f63bbbef 100644 --- a/packages/browser-sdk/src/client.ts +++ b/packages/browser-sdk/src/client.ts @@ -501,7 +501,6 @@ function shouldShowToolbar(opts: InitOptions) { */ export class ReflagClient { private state: State = "idle"; - private initializationFinished = false; private contextUpdateLoading = false; private readonly publishableKey: string; private context: ReflagContext; @@ -740,7 +739,6 @@ export class ReflagClient { "ms" + (this.config.offline ? " (offline mode)" : ""), ); - this.initializationFinished = true; this.setState("initialized"); } @@ -1422,9 +1420,7 @@ export class ReflagClient { getFlag(flagKey: string): Flag { const f = this.getFlags()[flagKey]; const evaluatedBeforeInitialization = - !this.initializationFinished && - !this.config.offline && - !this.config.bootstrapped; + !this.flagsClient.hasFlagState() && !this.config.offline; // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; diff --git a/packages/browser-sdk/src/flag/flags.ts b/packages/browser-sdk/src/flag/flags.ts index 8ac9b0448..aed3e47c9 100644 --- a/packages/browser-sdk/src/flag/flags.ts +++ b/packages/browser-sdk/src/flag/flags.ts @@ -298,6 +298,7 @@ export class FlagsClient { private initialized = false; private bootstrapped = false; private initializationComplete = false; + private flagStateAvailable = false; private rateLimiter: RateLimiter; private readonly logger: Logger; @@ -425,6 +426,10 @@ export class FlagsClient { return this.fetchedFlags; } + hasFlagState(): boolean { + return this.flagStateAvailable; + } + requestOptInFlags() { this.optInFlagsRequested = true; @@ -545,6 +550,8 @@ export class FlagsClient { triggerEvent = true, flagStateVersion?: number, ) { + // Mark the state available before notifying consumers from updateFlags(). + this.flagStateAvailable = true; // Create a new fetched flags object making sure to clone the flags this.fetchedFlags = { ...fetchedFlags }; this.fetchedFlagStateVersion = flagStateVersion; diff --git a/packages/browser-sdk/test/usage.test.ts b/packages/browser-sdk/test/usage.test.ts index 44e001f70..60e8a3752 100644 --- a/packages/browser-sdk/test/usage.test.ts +++ b/packages/browser-sdk/test/usage.test.ts @@ -485,6 +485,35 @@ describe(`sends "check" events `, () => { ); }); + it("does not add initialization diagnostics once flag state is available", async () => { + const sendCheckEventSpy = vi.spyOn( + FlagsClient.prototype, + "sendCheckEvent", + ); + const client = new ReflagClient({ publishableKey: KEY }); + const evaluatedDuringInitialization = vi.fn(); + + client.on("flagsUpdated", () => { + expect(client.getState()).toBe("initializing"); + expect(client.getFlag("flagA").isEnabled).toBe(true); + evaluatedDuringInitialization(); + }); + + await client.initialize(); + + expect(evaluatedDuringInitialization).toHaveBeenCalledOnce(); + expect(sendCheckEventSpy).toHaveBeenCalledWith( + expect.objectContaining({ + action: "check-is-enabled", + key: "flagA", + value: true, + version: 1, + evaluationErrors: flagsResult.flagA.evaluationErrors, + }), + expect.any(Function), + ); + }); + it("does not add initialization diagnostics to bootstrapped evaluations", () => { const sendCheckEventSpy = vi.spyOn( FlagsClient.prototype,