diff --git a/packages/sdk/browser/example-fdv2/src/app.ts b/packages/sdk/browser/example-fdv2/src/app.ts index 3e5a928c15..ffaa39e23d 100644 --- a/packages/sdk/browser/example-fdv2/src/app.ts +++ b/packages/sdk/browser/example-fdv2/src/app.ts @@ -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'; @@ -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')); @@ -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) { @@ -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(); diff --git a/packages/sdk/browser/src/BrowserClient.ts b/packages/sdk/browser/src/BrowserClient.ts index 5c4b3c55fc..6d6660aad4 100644 --- a/packages/sdk/browser/src/BrowserClient.ts +++ b/packages/sdk/browser/src/BrowserClient.ts @@ -7,6 +7,7 @@ import { Configuration, createDefaultSourceFactoryProvider, createFDv2DataManagerBase, + FDv2ConnectionMode, FlagManager, Hook, internal, @@ -318,6 +319,24 @@ class BrowserClientImpl extends LDClientImpl { return this._startPromise; } + setConnectionMode(mode?: FDv2ConnectionMode): void { + if (!this.dataManager.setConnectionMode) { + this.logger.warn( + 'setConnectionMode requires the FDv2 data system (dataSystem option). ' + + 'The call has no effect without it.', + ); + return; + } + if (mode !== undefined && !(mode in MODE_TABLE)) { + this.logger.warn( + `setConnectionMode called with invalid mode '${mode}'. ` + + `Valid modes: ${Object.keys(MODE_TABLE).join(', ')}.`, + ); + return; + } + this.dataManager.setConnectionMode(mode); + } + setStreaming(streaming?: boolean): void { this.dataManager.setForcedStreaming?.(streaming); } @@ -376,6 +395,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), diff --git a/packages/sdk/browser/src/LDClient.ts b/packages/sdk/browser/src/LDClient.ts index 6eadedee28..7de1ea0d2d 100644 --- a/packages/sdk/browser/src/LDClient.ts +++ b/packages/sdk/browser/src/LDClient.ts @@ -1,5 +1,6 @@ import { LDClient as CommonClient, + FDv2ConnectionMode, LDContext, LDIdentifyResult, LDWaitForInitializationOptions, @@ -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 & { /** * @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. * diff --git a/packages/sdk/browser/src/index.ts b/packages/sdk/browser/src/index.ts index ebae17c8ac..8889f1a9ec 100644 --- a/packages/sdk/browser/src/index.ts +++ b/packages/sdk/browser/src/index.ts @@ -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'; /** diff --git a/packages/sdk/browser/src/options.ts b/packages/sdk/browser/src/options.ts index e4e15d50a6..81893774f6 100644 --- a/packages/sdk/browser/src/options.ts +++ b/packages/sdk/browser/src/options.ts @@ -1,6 +1,8 @@ import { + LDClientDataSystemOptions, LDLogger, LDOptions as LDOptionsBase, + ManualModeSwitching, OptionMessages, TypeValidator, TypeValidators, @@ -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 { + /** + * @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). * diff --git a/packages/shared/sdk-client/src/DataManager.ts b/packages/shared/sdk-client/src/DataManager.ts index 644b06d399..ef07f6e37f 100644 --- a/packages/shared/sdk-client/src/DataManager.ts +++ b/packages/shared/sdk-client/src/DataManager.ts @@ -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 { @@ -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.