Skip to content
Merged
59 changes: 58 additions & 1 deletion packages/sdk/browser/example-fdv2/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Temporary app for testing FDv2 functionality.
import { basicLogger, createClient, type LDClient } from '@launchdarkly/js-client-sdk';
import {
basicLogger,
createClient,
type FDv2ConnectionMode,
type LDClient,
} from '@launchdarkly/js-client-sdk';

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

// Connection mode control
const modeSection = el('div');
modeSection.appendChild(el('h3'));
modeSection.querySelector('h3')!.textContent = 'Connection Mode';
const modeStatus = el('span', { id: 'mode-status' });
modeStatus.textContent = 'undefined (automatic)';
modeSection.appendChild(modeStatus);
modeSection.appendChild(el('br'));
const modes: FDv2ConnectionMode[] = ['streaming', 'polling', 'offline', 'one-shot', 'background'];
modes.forEach((mode) => {
const btn = el('button', { id: `btn-mode-${mode}` });
btn.textContent = mode;
modeSection.appendChild(btn);
modeSection.appendChild(text(' '));
});
const btnModeClear = el('button', { id: 'btn-mode-clear' });
btnModeClear.textContent = 'Clear';
modeSection.appendChild(btnModeClear);
controls.appendChild(modeSection);

// Log
const logSection = el('div');
logSection.appendChild(el('h3'));
Expand Down Expand Up @@ -149,6 +174,15 @@ function updateEvtStatus() {
}
}

function updateModeStatus(mode: FDv2ConnectionMode | undefined) {
const label = document.getElementById('mode-status')!;
if (mode !== undefined) {
label.textContent = `${mode} (override active)`;
} else {
label.textContent = 'undefined (automatic)';
}
}

function updateStreamStatus(value: boolean | undefined) {
const label = document.getElementById('stream-status')!;
if (value === true) {
Expand Down Expand Up @@ -240,6 +274,29 @@ const main = async () => {
log('setStreaming(undefined)');
});

// Connection mode controls
const connectionModes: FDv2ConnectionMode[] = [
'streaming',
'polling',
'offline',
'one-shot',
'background',
];
connectionModes.forEach((mode) => {
document.getElementById(`btn-mode-${mode}`)!.addEventListener('click', () => {
// @ts-ignore setConnectionMode is @internal — experimental FDv2 opt-in
client.setConnectionMode(mode);
updateModeStatus(mode);
log(`setConnectionMode('${mode}')`);
});
});
document.getElementById('btn-mode-clear')!.addEventListener('click', () => {
// @ts-ignore setConnectionMode is @internal — experimental FDv2 opt-in
client.setConnectionMode(undefined);
updateModeStatus(undefined);
log('setConnectionMode(undefined)');
});

// Start
client.start();
const { status } = await client.waitForInitialization();
Expand Down
13 changes: 13 additions & 0 deletions packages/sdk/browser/src/BrowserClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Configuration,
createDefaultSourceFactoryProvider,
createFDv2DataManagerBase,
FDv2ConnectionMode,
FlagManager,
Hook,
internal,
Expand Down Expand Up @@ -318,6 +319,17 @@ class BrowserClientImpl extends LDClientImpl {
return this._startPromise;
}

setConnectionMode(mode?: FDv2ConnectionMode): void {
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.

For future: setConnectionMode only affects FDv2 so, after we have this out of experimental, we should probably do something to signal that this function is a NOOP when using FDv1

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.

I should probably add a warning now.

if (!this.dataManager.setConnectionMode) {
this.logger.warn(
'setConnectionMode requires the FDv2 data system (dataSystem option). ' +
'The call has no effect without it.',
);
return;
}
this.dataManager.setConnectionMode(mode);
}

setStreaming(streaming?: boolean): void {
this.dataManager.setForcedStreaming?.(streaming);
}
Expand Down Expand Up @@ -376,6 +388,7 @@ export function makeClient(
on: (key: LDEmitterEventName, callback: (...args: any[]) => void) => impl.on(key, callback),
off: (key: LDEmitterEventName, callback: (...args: any[]) => void) => impl.off(key, callback),
flush: () => impl.flush(),
setConnectionMode: (mode?: FDv2ConnectionMode) => impl.setConnectionMode(mode),
setStreaming: (streaming?: boolean) => impl.setStreaming(streaming),
identify: (pristineContext: LDContext, identifyOptions?: LDIdentifyOptions) =>
impl.identifyResult(pristineContext, identifyOptions),
Expand Down
31 changes: 26 additions & 5 deletions packages/sdk/browser/src/LDClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
LDClient as CommonClient,
FDv2ConnectionMode,
LDContext,
LDIdentifyResult,
LDWaitForInitializationOptions,
Expand Down Expand Up @@ -32,17 +33,37 @@ export interface LDStartOptions extends LDWaitForInitializationOptions {
* For more information, see the [SDK Reference Guide](https://docs.launchdarkly.com/sdk/client-side/javascript).
*/

export type LDClient = Omit<
CommonClient,
'setConnectionMode' | 'getConnectionMode' | 'getOffline' | 'identify'
> & {
export type LDClient = Omit<CommonClient, 'getConnectionMode' | 'getOffline' | 'identify'> & {
/**
* @ignore
* Implementation Note: We are not supporting dynamically setting the connection mode on the LDClient.
* Implementation Note: The SDK does not support offline mode. Instead bootstrap data can be used.
* Implementation Note: The browser SDK has different identify options, so omits the base implementation
* from the interface.
*/
/**
* @internal
*
* This feature is experimental and should NOT be considered ready for
* production use. It may change or be removed without notice and is not
* subject to backwards compatibility guarantees.
*
* Sets the connection mode for the SDK's data system.
*
* When set, this mode is used exclusively, overriding all automatic mode
* selection (including {@link setStreaming}). The {@link setStreaming} state
* is not modified -- {@link setConnectionMode} acts as a higher-priority
* override.
*
* Pass `undefined` (or call with no arguments) to clear the override and
* return to automatic mode selection.
*
* This method requires the FDv2 data system (`dataSystem` option). If
* FDv2 is not enabled, the call logs a warning and has no effect.
*
* @param mode The connection mode to use, or `undefined` to clear the override.
*/
setConnectionMode(mode?: FDv2ConnectionMode): void;

/**
* Specifies whether or not to open a streaming connection to LaunchDarkly for live flag updates.
*
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { BrowserOptions as LDOptions } from './options';

export * from './common';
export type { LDClient, LDOptions, LDStartOptions };
export type { BrowserDataSystemOptions } from './options';
export type { LDPlugin } from './LDPlugin';

/**
Expand Down
37 changes: 37 additions & 0 deletions packages/sdk/browser/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
LDClientDataSystemOptions,
LDLogger,
LDOptions as LDOptionsBase,
ManualModeSwitching,
OptionMessages,
TypeValidator,
TypeValidators,
Expand All @@ -10,10 +12,45 @@ import { LDPlugin } from './LDPlugin';

const DEFAULT_FLUSH_INTERVAL_SECONDS = 2;

/**
* Data system options for the browser SDK.
*
* The browser SDK does not support automatic mode switching (lifecycle or
* network-based). Use `false` (the default) to disable automatic switching,
* or {@link ManualModeSwitching} to specify an explicit initial connection mode.
*
* This interface is not stable, and not subject to any backwards compatibility
* guarantees or semantic versioning. It is in early access. If you want access
* to this feature please join the EAP.
* https://launchdarkly.com/docs/sdk/features/data-saving-mode
*/
export interface BrowserDataSystemOptions extends Omit<
LDClientDataSystemOptions,
'automaticModeSwitching'
> {
automaticModeSwitching?: false | ManualModeSwitching;
}

/**
* Initialization options for the LaunchDarkly browser SDK.
*/
export interface BrowserOptions extends Omit<LDOptionsBase, 'initialConnectionMode'> {
/**
* @internal
*
* This feature is experimental and should NOT be considered ready for
* production use. It may change or be removed without notice and is not
* subject to backwards compatibility guarantees.
*
* Configuration for the FDv2 data system. When present, the SDK uses
* the FDv2 protocol for flag delivery instead of the default FDv1
* protocol.
*
* The browser SDK restricts `automaticModeSwitching` to `false` or
* {@link ManualModeSwitching} only — automatic switching has no effect
* in browser environments.
*/
dataSystem?: BrowserDataSystemOptions;
/**
* Whether the client should make a request to LaunchDarkly for Experimentation metrics (goals).
*
Expand Down
10 changes: 10 additions & 0 deletions packages/shared/sdk-client/src/DataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
subsystem,
} from '@launchdarkly/js-sdk-common';

import type FDv2ConnectionMode from './api/datasource/FDv2ConnectionMode';
import { LDIdentifyOptions } from './api/LDIdentifyOptions';
import { Configuration } from './configuration/Configuration';
import {
Expand Down Expand Up @@ -73,6 +74,15 @@ export interface DataManager {
*/
setAutomaticStreamingState?(streaming: boolean): void;

/**
* Set an explicit connection mode override. When set, only this mode is
* used, bypassing all automatic behavior. Pass undefined to clear the
* override.
*
* Optional — only FDv2 data managers implement this.
*/
setConnectionMode?(mode?: FDv2ConnectionMode): void;

/**
* Set a callback to flush pending analytics events. Called immediately
* (not debounced) when the lifecycle transitions to background.
Expand Down
Loading