Skip to content

Conversation

@joker23
Copy link
Contributor

@joker23 joker23 commented Dec 16, 2025

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

sdk-1667

Describe the solution you've provided

  • Introduced start method in BrowserClient to handle client initialization.
  • Replaced direct calls to identify with setInitialContext and start for better context management.
  • Updated example app to reflect changes in client initialization and context handling.
  • Added tests to ensure proper functionality of new initialization flow.

Additional context

  • The main motiviation behind this change is to present a more organized front on when a client is starting.
  • I'll be making a task to push this behavior down to the shared client code as an optional start and start adding a deprecated message on the old flow.

Note

Adds an explicit client start() flow and new createClient API (with required initial context), updates initialization/identify behavior, and refreshes tests, examples, and compat layer accordingly.

  • API/Initialization:
    • Add LDClient.start(options?) returning LDWaitForInitializationResult; cache promise; support bootstrap via start/identifyOptions.
    • Replace initialize(...) with createClient(clientSideId, context, options); makeClient now requires an initial LDContext.
    • Disallow identify before start (returns { status: 'error' } and logs); initial identify is non-sheddable.
    • Normalize init statuses: waitForInitialization returns { status: 'complete' | 'failed' | 'timeout' } (note: some tests updated from errorfailed).
    • Make LDWaitForInitializationOptions.timeout optional; add LDStartOptions.
  • BrowserClient:
    • Track _initialContext, _startPromise, _initializedPromise; preset flags from bootstrap before identify.
    • Update plugin registration/hook calls to run through new flow; continue goal tracking logic, URL transformation for events.
  • Compat (/compat):
    • initialize(envKey, context, options) now builds via makeClient(envKey, context, ...); immediately start() then perform identify to mirror old behavior; re-export unchanged surface minus start/setInitialContext.
  • Examples/Contract Tests:
    • Update example app and contract entity to use createClient(..., context) and client.start() + waitForInitialization().
  • Tests:
    • Revise unit tests to use start() and required initial context; add cases for start idempotency, shedding order, bootstrap pre-eval, URL filtering, waitForInitialization timing/failure.

Written by Cursor Bugbot for commit 846ae61. This will update automatically on new commits. Configure here.

@joker23 joker23 requested a review from a team as a code owner December 16, 2025 20:45
@github-actions
Copy link
Contributor

@launchdarkly/browser size report
This is the brotli compressed size of the ESM build.
Compressed size: 169118 bytes
Compressed size limit: 200000
Uncompressed size: 789399 bytes

@github-actions
Copy link
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 25394 bytes
Compressed size limit: 26000
Uncompressed size: 124693 bytes

@github-actions
Copy link
Contributor

github-actions bot commented Dec 16, 2025

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 18527 bytes
Compressed size limit: 20000
Uncompressed size: 95226 bytes

@github-actions
Copy link
Contributor

github-actions bot commented Dec 16, 2025

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 23128 bytes
Compressed size limit: 25000
Uncompressed size: 80393 bytes

@joker23 joker23 force-pushed the skz/sdk-1677/init-flow-specs branch from ee357a3 to b00509f Compare December 16, 2025 20:58
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Compat layer bootstrap data now silently ignored

The removal of bootstrap handling from identifyResult breaks the compat layer's bootstrap functionality. LDClientCompatImpl._initIdentify passes bootstrap to this._client.identify(), which maps to identifyResult. However, identifyResult no longer processes the bootstrap option (that code was moved to start() only). This causes bootstrap data passed through the compat API to be silently ignored, breaking backwards compatibility for compat layer users who rely on bootstrap.

packages/sdk/browser/src/BrowserClient.ts#L274-L285

const res = await super.identifyResult(context, identifyOptionsWithUpdatedDefaults);
if (res.status === 'completed') {
this._initializeResult = { status: 'complete' };
this._initResolve?.(this._initializeResult);
} else if (res.status === 'error') {
this._initializeResult = { status: 'failed', error: res.error };
this._initResolve?.(this._initializeResult);
}
this._goalManager?.startTracking();
return res;

Fix in Cursor Fix in Web


/**
* Optional identify options to use for the identify operation. {@link LDIdentifyOptions}
*/
identifyOptions?: LDIdentifyOptions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, bootstrap would be in start instead of in the options.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I wasn't sure if bootstrap data is something users will typically tie with their context? If not then 100% should be in start function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the bootstrap option in start, I kept the additional bootstrap option in case it is more natural for users to tie a certain set of identify options to a context

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Compat layer calls identify before start, breaking initialization

The compat layer's _initIdentify method calls this._client.identify() directly without calling start() first. However, the new identifyResult implementation now requires _startPromise to be set before identify can be called, otherwise it returns { status: 'error', error: new Error('Identify called before start') }. Since the compat layer never calls start(), all initialization attempts through the compat layer will fail immediately. The unit tests don't catch this because they mock identify to return success without exercising the real BrowserClient code. The compat layer needs to call start() with the bootstrap/hash options instead of calling identify() directly.

packages/sdk/browser/src/compat/LDClientCompatImpl.ts#L47-L62

this.logger = this._client.logger;
this._initIdentify(context, bootstrap, hash);
}
private async _initIdentify(
context: LDContext,
bootstrap?: LDFlagSet,
hash?: string,
): Promise<void> {
try {
const result = await this._client.identify(context, {
noTimeout: true,
bootstrap,
hash,
sheddable: false,
});

packages/sdk/browser/src/BrowserClient.ts#L233-L239

): Promise<LDIdentifyResult> {
if (!this._startPromise) {
this.logger.error(
'Client must be started before it can identify a context, did you forget to call start()?',
);
return { status: 'error', error: new Error('Identify called before start') };
}

Fix in Cursor Fix in Web


@joker23 joker23 force-pushed the skz/sdk-1677/init-flow-specs branch from 437b864 to ad18342 Compare December 17, 2025 19:23
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Compat layer fails because identify requires start first

The compat layer's _initIdentify method calls this._client.identify() without first calling start(). However, the new identifyResult implementation in BrowserClient now checks if _startPromise is set and returns an error status if not. Since the compat layer never calls start(), every initialization via the compat path will fail with "Identify called before start" error. The compat layer needs to call start() instead of identify() for its initial identification, or the identifyResult check needs to allow calls from the compat path.

packages/sdk/browser/src/compat/LDClientCompatImpl.ts#L56-L62

try {
const result = await this._client.identify(context, {
noTimeout: true,
bootstrap,
hash,
sheddable: false,
});

packages/sdk/browser/src/BrowserClient.ts#L233-L238

): Promise<LDIdentifyResult> {
if (!this._startPromise) {
this.logger.error(
'Client must be started before it can identify a context, did you forget to call start()?',
);
return { status: 'error', error: new Error('Identify called before start') };

Fix in Cursor Fix in Web


bootstrap,
hash,
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Missing sheddable:false allows initial identify to be discarded

The compat layer's initial identify no longer passes sheddable: false. The old code explicitly set sheddable: false when calling identify(), but the new start() call doesn't include it. Since identifyResult() defaults sheddable to true when undefined, the initial identify can now be discarded if a user immediately calls identify() on the returned client. The comment on line 69 even states 'shed' cannot happen with sheddable: false, but this invariant no longer holds.

Fix in Cursor Fix in Web

@joker23 joker23 force-pushed the skz/sdk-1677/init-flow-specs branch from 3f446dd to b5172cd Compare December 17, 2025 20:51
- Introduced `start` method in `BrowserClient` to handle client initialization with context.
- Replaced direct calls to `identify` with `setInitialContext` and `start` for better context management.
- Updated example app to reflect changes in client initialization and context handling.
- Added tests to ensure proper functionality of new initialization flow.
- Implemented a test to verify that multiple calls to the `start` method of `BrowserClient` return the same promise and resolve to the same result.
- Ensured that only one identify call is made during the initialization process, confirming the promise caching behavior.
- Fix contract tests to use the new initialization method
- require `start` to be called before additional `identify`
@joker23 joker23 force-pushed the skz/sdk-1677/init-flow-specs branch from b5172cd to 97e9856 Compare December 17, 2025 21:08
@joker23 joker23 requested a review from kinyoklion December 17, 2025 21:22
- Added logic to use bootstrap data from start options if not provided in identify options.
- Updated LDStartOptions interface to include an optional bootstrap property for identify operations.
To align the api to more of what it does.
// start the client, then immediately kick off an identify operation
// in order to preserve the behavior of the previous SDK.
this._client.start();
this._initIdentify(context, bootstrap, hash);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Compat layer triggers duplicate non-sheddable identify operations

The compat layer calls start() on line 51, which internally calls identifyResult with sheddable: false for the initial context. Then it immediately calls _initIdentify() on line 52, which calls identify() also with sheddable: false for the same context. Since both operations are non-sheddable, neither can be discarded, resulting in two full network requests to LaunchDarkly for the same context. This is a regression from the previous SDK behavior where only one identify operation occurred.

Additional Locations (1)

Fix in Cursor Fix in Web

@joker23 joker23 merged commit eff6a55 into main Dec 18, 2025
36 checks passed
@joker23 joker23 deleted the skz/sdk-1677/init-flow-specs branch December 18, 2025 21:13
@github-actions github-actions bot mentioned this pull request Dec 18, 2025
joker23 pushed a commit that referenced this pull request Dec 18, 2025
🤖 I have created a release *beep* *boop*
---


<details><summary>browser-telemetry: 1.0.15</summary>

##
[1.0.15](browser-telemetry-v1.0.14...browser-telemetry-v1.0.15)
(2025-12-18)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/js-client-sdk bumped from 0.10.0 to 0.11.0
</details>

<details><summary>jest: 0.1.15</summary>

##
[0.1.15](jest-v0.1.14...jest-v0.1.15)
(2025-12-18)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
* @launchdarkly/react-native-client-sdk bumped from ~10.12.2 to ~10.12.3
</details>

<details><summary>js-client-sdk: 0.11.0</summary>

##
[0.11.0](js-client-sdk-v0.10.0...js-client-sdk-v0.11.0)
(2025-12-18)


### ⚠ BREAKING CHANGES

* align browser v4 intialization flow to specs
([#1040](#1040))

### Features

* adding support for debug override plugins
([#1033](#1033))
([17f5e7d](17f5e7d))
* allow clients to evaluate bootstrapped flags when not ready
([#1036](#1036))
([9b4542a](9b4542a))
* implement `waitForInitialization` for browser sdk 4.x
([#1028](#1028))
([156532a](156532a))


### Code Refactoring

* align browser v4 intialization flow to specs
([#1040](#1040))
([eff6a55](eff6a55))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/js-client-sdk-common bumped from 1.15.2 to 1.16.0
</details>

<details><summary>js-client-sdk-common: 1.16.0</summary>

##
[1.16.0](js-client-sdk-common-v1.15.2...js-client-sdk-common-v1.16.0)
(2025-12-18)


### Features

* adding support for debug override plugins
([#1033](#1033))
([17f5e7d](17f5e7d))
* allow clients to evaluate bootstrapped flags when not ready
([#1036](#1036))
([9b4542a](9b4542a))
* implement `waitForInitialization` for browser sdk 4.x
([#1028](#1028))
([156532a](156532a))
</details>

<details><summary>react-native-client-sdk: 10.12.3</summary>

##
[10.12.3](react-native-client-sdk-v10.12.2...react-native-client-sdk-v10.12.3)
(2025-12-18)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/js-client-sdk-common bumped from 1.15.2 to 1.16.0
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Release browser SDK 0.11.0 (breaking init flow, new features) with
related bumps to common 1.16.0, React Native 10.12.3, telemetry 1.0.15,
and jest 0.1.15.
> 
> - **SDK Releases**
>   - **Browser (`@launchdarkly/js-client-sdk` 0.11.0)**:
>     - Breaking: align v4 initialization flow to specs.
> - Features: debug override plugins, evaluate bootstrapped flags when
not ready, `waitForInitialization`.
>     - Deps: `@launchdarkly/js-client-sdk-common` → `1.16.0`.
> - Version metadata updated in
`packages/sdk/browser/src/platform/BrowserInfo.ts`.
>   - **Common (`@launchdarkly/js-client-sdk-common` 1.16.0)**:
> - Features: debug override plugins, bootstrapped flag evaluation
pre-ready, `waitForInitialization` support.
> - **React Native (`@launchdarkly/react-native-client-sdk` 10.12.3)**
>   - Deps: `@launchdarkly/js-client-sdk-common` → `1.16.0`.
> - Version metadata updated in
`packages/sdk/react-native/src/platform/PlatformInfo.ts`.
> - **Telemetry (`@launchdarkly/browser-telemetry` 1.0.15)**
>   - DevDeps: `@launchdarkly/js-client-sdk` → `0.11.0`.
> - **Tooling (`@launchdarkly/jest` 0.1.15)**
>   - Deps: `@launchdarkly/react-native-client-sdk` → `~10.12.3`.
> - **Manifest**
> - `.release-please-manifest.json` updated to new versions across
affected packages.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8978f79. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants