Skip to content

Commit 9019808

Browse files
authored
feat: expose setConnectionMode on browser SDK (#1232)
## Summary - Adds `setConnectionMode(mode?: FDv2ConnectionMode)` to the browser SDK's `LDClient` interface as an EAP method, giving users direct control over the connection mode with higher priority than `setStreaming` - Introduces `BrowserDataSystemOptions` type that restricts `automaticModeSwitching` to `false | ManualModeSwitching` only, removing the `automatic` option which has no impact for the browser SDK - Adds optional `setConnectionMode` to the shared `DataManager` interface following the same pattern as `setForcedStreaming` - Adds connection mode control buttons to the FDv2 example app for testing all modes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it adds a new (experimental) client API that can override connection behavior and wires through to FDv2-only data managers; incorrect use could alter flag update/connection semantics. > > **Overview** > Adds an **experimental** `LDClient.setConnectionMode(mode?: FDv2ConnectionMode)` to the browser SDK, wiring it through `BrowserClient` to an optional `DataManager.setConnectionMode` implementation with validation and warnings when FDv2 isn’t enabled. > > Introduces `BrowserDataSystemOptions` (exported from the package) to constrain browser `dataSystem.automaticModeSwitching` to `false | ManualModeSwitching`, and updates the FDv2 example app with UI controls to force/clear connection mode overrides for testing. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 52eafe2. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 92b8e26 commit 9019808

File tree

6 files changed

+152
-6
lines changed

6 files changed

+152
-6
lines changed

packages/sdk/browser/example-fdv2/src/app.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
// Temporary app for testing FDv2 functionality.
2-
import { basicLogger, createClient, type LDClient } from '@launchdarkly/js-client-sdk';
2+
import {
3+
basicLogger,
4+
createClient,
5+
type FDv2ConnectionMode,
6+
type LDClient,
7+
} from '@launchdarkly/js-client-sdk';
38

49
// Set clientSideID to your LaunchDarkly client-side ID
510
const clientSideID = 'LD_CLIENT_SIDE_ID';
@@ -98,6 +103,26 @@ function buildUI() {
98103
streamSection.appendChild(btnUndef);
99104
controls.appendChild(streamSection);
100105

106+
// Connection mode control
107+
const modeSection = el('div');
108+
modeSection.appendChild(el('h3'));
109+
modeSection.querySelector('h3')!.textContent = 'Connection Mode';
110+
const modeStatus = el('span', { id: 'mode-status' });
111+
modeStatus.textContent = 'undefined (automatic)';
112+
modeSection.appendChild(modeStatus);
113+
modeSection.appendChild(el('br'));
114+
const modes: FDv2ConnectionMode[] = ['streaming', 'polling', 'offline', 'one-shot', 'background'];
115+
modes.forEach((mode) => {
116+
const btn = el('button', { id: `btn-mode-${mode}` });
117+
btn.textContent = mode;
118+
modeSection.appendChild(btn);
119+
modeSection.appendChild(text(' '));
120+
});
121+
const btnModeClear = el('button', { id: 'btn-mode-clear' });
122+
btnModeClear.textContent = 'Clear';
123+
modeSection.appendChild(btnModeClear);
124+
controls.appendChild(modeSection);
125+
101126
// Log
102127
const logSection = el('div');
103128
logSection.appendChild(el('h3'));
@@ -149,6 +174,15 @@ function updateEvtStatus() {
149174
}
150175
}
151176

177+
function updateModeStatus(mode: FDv2ConnectionMode | undefined) {
178+
const label = document.getElementById('mode-status')!;
179+
if (mode !== undefined) {
180+
label.textContent = `${mode} (override active)`;
181+
} else {
182+
label.textContent = 'undefined (automatic)';
183+
}
184+
}
185+
152186
function updateStreamStatus(value: boolean | undefined) {
153187
const label = document.getElementById('stream-status')!;
154188
if (value === true) {
@@ -240,6 +274,29 @@ const main = async () => {
240274
log('setStreaming(undefined)');
241275
});
242276

277+
// Connection mode controls
278+
const connectionModes: FDv2ConnectionMode[] = [
279+
'streaming',
280+
'polling',
281+
'offline',
282+
'one-shot',
283+
'background',
284+
];
285+
connectionModes.forEach((mode) => {
286+
document.getElementById(`btn-mode-${mode}`)!.addEventListener('click', () => {
287+
// @ts-ignore setConnectionMode is @internal — experimental FDv2 opt-in
288+
client.setConnectionMode(mode);
289+
updateModeStatus(mode);
290+
log(`setConnectionMode('${mode}')`);
291+
});
292+
});
293+
document.getElementById('btn-mode-clear')!.addEventListener('click', () => {
294+
// @ts-ignore setConnectionMode is @internal — experimental FDv2 opt-in
295+
client.setConnectionMode(undefined);
296+
updateModeStatus(undefined);
297+
log('setConnectionMode(undefined)');
298+
});
299+
243300
// Start
244301
client.start();
245302
const { status } = await client.waitForInitialization();

packages/sdk/browser/src/BrowserClient.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Configuration,
88
createDefaultSourceFactoryProvider,
99
createFDv2DataManagerBase,
10+
FDv2ConnectionMode,
1011
FlagManager,
1112
Hook,
1213
internal,
@@ -318,6 +319,24 @@ class BrowserClientImpl extends LDClientImpl {
318319
return this._startPromise;
319320
}
320321

322+
setConnectionMode(mode?: FDv2ConnectionMode): void {
323+
if (!this.dataManager.setConnectionMode) {
324+
this.logger.warn(
325+
'setConnectionMode requires the FDv2 data system (dataSystem option). ' +
326+
'The call has no effect without it.',
327+
);
328+
return;
329+
}
330+
if (mode !== undefined && !(mode in MODE_TABLE)) {
331+
this.logger.warn(
332+
`setConnectionMode called with invalid mode '${mode}'. ` +
333+
`Valid modes: ${Object.keys(MODE_TABLE).join(', ')}.`,
334+
);
335+
return;
336+
}
337+
this.dataManager.setConnectionMode(mode);
338+
}
339+
321340
setStreaming(streaming?: boolean): void {
322341
this.dataManager.setForcedStreaming?.(streaming);
323342
}
@@ -376,6 +395,7 @@ export function makeClient(
376395
on: (key: LDEmitterEventName, callback: (...args: any[]) => void) => impl.on(key, callback),
377396
off: (key: LDEmitterEventName, callback: (...args: any[]) => void) => impl.off(key, callback),
378397
flush: () => impl.flush(),
398+
setConnectionMode: (mode?: FDv2ConnectionMode) => impl.setConnectionMode(mode),
379399
setStreaming: (streaming?: boolean) => impl.setStreaming(streaming),
380400
identify: (pristineContext: LDContext, identifyOptions?: LDIdentifyOptions) =>
381401
impl.identifyResult(pristineContext, identifyOptions),

packages/sdk/browser/src/LDClient.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
LDClient as CommonClient,
3+
FDv2ConnectionMode,
34
LDContext,
45
LDIdentifyResult,
56
LDWaitForInitializationOptions,
@@ -32,17 +33,37 @@ export interface LDStartOptions extends LDWaitForInitializationOptions {
3233
* For more information, see the [SDK Reference Guide](https://docs.launchdarkly.com/sdk/client-side/javascript).
3334
*/
3435

35-
export type LDClient = Omit<
36-
CommonClient,
37-
'setConnectionMode' | 'getConnectionMode' | 'getOffline' | 'identify'
38-
> & {
36+
export type LDClient = Omit<CommonClient, 'getConnectionMode' | 'getOffline' | 'identify'> & {
3937
/**
4038
* @ignore
41-
* Implementation Note: We are not supporting dynamically setting the connection mode on the LDClient.
4239
* Implementation Note: The SDK does not support offline mode. Instead bootstrap data can be used.
4340
* Implementation Note: The browser SDK has different identify options, so omits the base implementation
4441
* from the interface.
4542
*/
43+
/**
44+
* @internal
45+
*
46+
* This feature is experimental and should NOT be considered ready for
47+
* production use. It may change or be removed without notice and is not
48+
* subject to backwards compatibility guarantees.
49+
*
50+
* Sets the connection mode for the SDK's data system.
51+
*
52+
* When set, this mode is used exclusively, overriding all automatic mode
53+
* selection (including {@link setStreaming}). The {@link setStreaming} state
54+
* is not modified -- {@link setConnectionMode} acts as a higher-priority
55+
* override.
56+
*
57+
* Pass `undefined` (or call with no arguments) to clear the override and
58+
* return to automatic mode selection.
59+
*
60+
* This method requires the FDv2 data system (`dataSystem` option). If
61+
* FDv2 is not enabled, the call logs a warning and has no effect.
62+
*
63+
* @param mode The connection mode to use, or `undefined` to clear the override.
64+
*/
65+
setConnectionMode(mode?: FDv2ConnectionMode): void;
66+
4667
/**
4768
* Specifies whether or not to open a streaming connection to LaunchDarkly for live flag updates.
4869
*

packages/sdk/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { BrowserOptions as LDOptions } from './options';
1818

1919
export * from './common';
2020
export type { LDClient, LDOptions, LDStartOptions };
21+
export type { BrowserDataSystemOptions } from './options';
2122
export type { LDPlugin } from './LDPlugin';
2223

2324
/**

packages/sdk/browser/src/options.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
2+
LDClientDataSystemOptions,
23
LDLogger,
34
LDOptions as LDOptionsBase,
5+
ManualModeSwitching,
46
OptionMessages,
57
TypeValidator,
68
TypeValidators,
@@ -10,10 +12,45 @@ import { LDPlugin } from './LDPlugin';
1012

1113
const DEFAULT_FLUSH_INTERVAL_SECONDS = 2;
1214

15+
/**
16+
* Data system options for the browser SDK.
17+
*
18+
* The browser SDK does not support automatic mode switching (lifecycle or
19+
* network-based). Use `false` (the default) to disable automatic switching,
20+
* or {@link ManualModeSwitching} to specify an explicit initial connection mode.
21+
*
22+
* This interface is not stable, and not subject to any backwards compatibility
23+
* guarantees or semantic versioning. It is in early access. If you want access
24+
* to this feature please join the EAP.
25+
* https://launchdarkly.com/docs/sdk/features/data-saving-mode
26+
*/
27+
export interface BrowserDataSystemOptions extends Omit<
28+
LDClientDataSystemOptions,
29+
'automaticModeSwitching'
30+
> {
31+
automaticModeSwitching?: false | ManualModeSwitching;
32+
}
33+
1334
/**
1435
* Initialization options for the LaunchDarkly browser SDK.
1536
*/
1637
export interface BrowserOptions extends Omit<LDOptionsBase, 'initialConnectionMode'> {
38+
/**
39+
* @internal
40+
*
41+
* This feature is experimental and should NOT be considered ready for
42+
* production use. It may change or be removed without notice and is not
43+
* subject to backwards compatibility guarantees.
44+
*
45+
* Configuration for the FDv2 data system. When present, the SDK uses
46+
* the FDv2 protocol for flag delivery instead of the default FDv1
47+
* protocol.
48+
*
49+
* The browser SDK restricts `automaticModeSwitching` to `false` or
50+
* {@link ManualModeSwitching} only — automatic switching has no effect
51+
* in browser environments.
52+
*/
53+
dataSystem?: BrowserDataSystemOptions;
1754
/**
1855
* Whether the client should make a request to LaunchDarkly for Experimentation metrics (goals).
1956
*

packages/shared/sdk-client/src/DataManager.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
subsystem,
1111
} from '@launchdarkly/js-sdk-common';
1212

13+
import type FDv2ConnectionMode from './api/datasource/FDv2ConnectionMode';
1314
import { LDIdentifyOptions } from './api/LDIdentifyOptions';
1415
import { Configuration } from './configuration/Configuration';
1516
import {
@@ -73,6 +74,15 @@ export interface DataManager {
7374
*/
7475
setAutomaticStreamingState?(streaming: boolean): void;
7576

77+
/**
78+
* Set an explicit connection mode override. When set, only this mode is
79+
* used, bypassing all automatic behavior. Pass undefined to clear the
80+
* override.
81+
*
82+
* Optional — only FDv2 data managers implement this.
83+
*/
84+
setConnectionMode?(mode?: FDv2ConnectionMode): void;
85+
7686
/**
7787
* Set a callback to flush pending analytics events. Called immediately
7888
* (not debounced) when the lifecycle transitions to background.

0 commit comments

Comments
 (0)