Skip to content

feat: Add experimental FDv2 support for React Native.#1243

Open
kinyoklion wants to merge 6 commits intomainfrom
rlamb/react-native-fdv2
Open

feat: Add experimental FDv2 support for React Native.#1243
kinyoklion wants to merge 6 commits intomainfrom
rlamb/react-native-fdv2

Conversation

@kinyoklion
Copy link
Copy Markdown
Member

@kinyoklion kinyoklion commented Apr 3, 2026

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

Provide links to any issues in this repository or elsewhere relating to this pull request.

Describe the solution you've provided

Provide a clear and concise description of what you expect to happen.

Describe alternatives you've considered

Provide a clear and concise description of any alternative solutions or features you've considered.

Additional context

Add any other context about the pull request here.


Note

Medium Risk
Introduces an alternate FDv2 data path and new connection-mode semantics in ReactNativeLDClient, which can affect flag delivery and lifecycle/network handling behavior even though it is gated behind the new dataSystem option.

Overview
Adds an experimental dataSystem configuration option (RNDataSystemOptions) to enable FDv2 flag delivery in the React Native SDK, and exports the new type from the public entrypoint.

Updates ReactNativeLDClient to instantiate either the existing FDv1 MobileDataManager or an FDv2 data manager (via createFDv2DataManagerBase), wiring app/network state directly into FDv2 and extending setConnectionMode/getConnectionMode to support FDv2-only modes ('one-shot', 'background') with validation and warnings.

Updates the shared LDClientImpl to expose an isFDv2 flag (derived from config.dataSystem) so platform clients can branch behavior based on the selected data system.

Reviewed by Cursor Bugbot for commit cabb3f1. Bugbot is set up for automated code reviews on this repo. Configure here.


Open with Devin

@kinyoklion kinyoklion force-pushed the rlamb/react-native-fdv2 branch from f44eae2 to 6f8f006 Compare April 3, 2026 21:53
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 25661 bytes
Compressed size limit: 29000
Uncompressed size: 126143 bytes

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 31742 bytes
Compressed size limit: 34000
Uncompressed size: 112969 bytes

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

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

* https://launchdarkly.com/docs/sdk/features/data-saving-mode
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface RNDataSystemOptions extends LDClientDataSystemOptions {}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This may not be strictly required. But if we decide to add RN specific options, then we will have a type to do it in. Where if we use LDClientDataSystemOptions, then we have to do name shenanigans. Or risk breaking things.

The public API shows setConnectionMode(mode: ConnectionMode) and
getConnectionMode(): ConnectionMode. The FDv2-specific overloads
accepting FDv2ConnectionMode and undefined are stripped from
declarations via @internal + stripInternal.
@kinyoklion
Copy link
Copy Markdown
Member Author

bugbot review

Copy link
Copy Markdown

@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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 21b3f73. Configure here.

@kinyoklion kinyoklion changed the title feat: React Native FDv2 support. feat: Add experimental FDv2 support for React Native. Apr 3, 2026
@kinyoklion kinyoklion marked this pull request as ready for review April 7, 2026 22:34
@kinyoklion kinyoklion requested a review from a team as a code owner April 7, 2026 22:34

// Wire state detection directly to FDv2 data manager.
const stateDetector = new RNStateDetector();
this._stateDetector = stateDetector;
Copy link
Copy Markdown
Contributor

@joker23 joker23 Apr 8, 2026

Choose a reason for hiding this comment

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

I don't see another reference to this._stateDetector could we get rid of this?

cursor[bot]

This comment was marked as resolved.

Add protected readonly isFDv2 to LDClientImpl, derived from the
validated configuration.dataSystem rather than raw user options.
This prevents a mismatch when validation rejects the dataSystem
value but the flag still indicates FDv2 is active.
private _eventSendingEnabled: boolean = false;
private _baseHeaders: LDHeaders;
protected dataManager: DataManager;
protected readonly isFDv2: boolean;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is so we don't have to infer from other signal. We just know.

this.emitter,
this._diagnosticsManager,
);
this.isFDv2 = !!this._config.dataSystem;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Validated data system config, not the input options.

@kinyoklion kinyoklion requested a review from joker23 April 9, 2026 21:55
Copy link
Copy Markdown

@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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cabb3f1. Configure here.

state === NetworkState.Available ? 'available' : 'unavailable',
);
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FDv2 stateDetector is unreachable, preventing cleanup on close

Medium Severity

The stateDetector in the FDv2 path is a local variable with no stored reference. RNStateDetector's constructor registers a permanent AppState.addEventListener callback, keeping it alive indefinitely. After close() is called on the client, the stateDetector continues firing. When the app transitions to background, setLifecycleState('background') is called on the closed data manager, which invokes flushCallback?.() (set on line 143 and not cleared by the data manager's close()), triggering this.flush() on the closed client. Unlike the FDv1 path where _connectionManager is stored as an instance field allowing potential cleanup, the FDv2 stateDetector is completely unreachable after construction.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cabb3f1. Configure here.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +148 to +162
if (validatedRnOptions.automaticBackgroundHandling) {
stateDetector.setApplicationStateListener((state) => {
fdv2DataManager.setLifecycleState(
state === ApplicationState.Foreground ? 'foreground' : 'background',
);
});
}

if (validatedRnOptions.automaticNetworkHandling) {
stateDetector.setNetworkStateListener((state) => {
fdv2DataManager.setNetworkState(
state === NetworkState.Available ? 'available' : 'unavailable',
);
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 FDv2 path ignores dataSystem.automaticModeSwitching config for lifecycle/network listener wiring

The FDv2 constructor path uses legacy RN options (validatedRnOptions.automaticBackgroundHandling / validatedRnOptions.automaticNetworkHandling) to decide whether to register lifecycle and network state listeners, instead of consulting the FDv2-specific dataSystem.automaticModeSwitching config. The AutomaticModeSwitchingConfig type (packages/shared/sdk-client/src/api/datasource/LDClientDataSystemOptions.ts:62) provides explicit lifecycle and network boolean fields, and the docs state that false means "disable all automatic switching." However, none of these are checked. A user who sets dataSystem: { automaticModeSwitching: false } (or { type: 'automatic', lifecycle: false }) still gets automatic lifecycle-based mode switching because the legacy automaticBackgroundHandling defaults to true. The setLifecycleState calls flow through the debounce manager and transition table (MOBILE_TRANSITION_TABLE at packages/shared/sdk-client/src/datasource/ModeResolver.ts:46-50), causing the data source to switch to background mode despite the user explicitly requesting no automatic switching.

Prompt for agents
In ReactNativeLDClient.ts, the FDv2 constructor path (lines 148-162) should read the validated dataSystem.automaticModeSwitching config to decide whether to wire lifecycle and network listeners, rather than using the legacy FDv1 options (validatedRnOptions.automaticBackgroundHandling / automaticNetworkHandling).

The automaticModeSwitching config can be:
- true (boolean): enable all automatic switching
- false (boolean): disable all automatic switching
- { type: 'automatic', lifecycle?: boolean, network?: boolean }: granular control
- { type: 'manual', initialConnectionMode: ... }: disable all automatic switching

The config is available on the validated Configuration object (this._config.dataSystem.automaticModeSwitching), but _config is private to LDClientImpl. You could either:
1. Access it via the options object which is still in scope (options.dataSystem?.automaticModeSwitching), though this would be unvalidated.
2. Expose a method or property on LDClientImpl to access the relevant dataSystem config.
3. Read automaticModeSwitching from the configuration passed to the dataManagerFactory closure (which has access to configuration.dataSystem).

When automaticModeSwitching is false or ManualModeSwitching, neither listener should be wired. When it is AutomaticModeSwitchingConfig, use its lifecycle and network fields (defaulting to true for mobile per MOBILE_DATA_SYSTEM_DEFAULTS). When it is true (the default), wire both listeners.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

2 participants