Skip to content
25 changes: 1 addition & 24 deletions packages/sdk/browser/src/BrowserIdentifyOptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ItemDescriptor, LDIdentifyOptions } from '@launchdarkly/js-client-sdk-common';
import { LDIdentifyOptions } from '@launchdarkly/js-client-sdk-common';

/**
* @property sheddable - If true, the identify operation will be sheddable. This means that if multiple identify operations are done, without
Expand All @@ -13,27 +13,4 @@ export interface BrowserIdentifyOptions extends Omit<LDIdentifyOptions, 'waitFor
* (https://docs.launchdarkly.com/sdk/features/secure-mode#configuring-secure-mode-in-the-javascript-client-side-sdk).
*/
hash?: string;

/**
* The initial set of flags to use until the remote set is retrieved.
*
* Bootstrap data can be generated by server SDKs. When bootstrap data is provided the SDK the
* identification operation will complete without waiting for any values from LaunchDarkly and
* the variation calls can be used immediately.
*
* If streaming is activated, either it is configured to always be used, or is activated
* via setStreaming, or via the addition of change handlers, then a streaming connection will
* subsequently be established.
*
* For more information, see the [SDK Reference Guide](https://docs.launchdarkly.com/sdk/features/bootstrapping#javascript).
*/
bootstrap?: unknown;

/**
* Parsed bootstrap data that could be stored to ensure that the bootstrap data is only parsed once during the intialization
* process.
*
* @hidden
*/
bootstrapParsed?: { [key: string]: ItemDescriptor };
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
} from '@launchdarkly/js-client-sdk-common';

import ElectronDataManager from '../src/ElectronDataManager';
import type { ElectronIdentifyOptions } from '../src/ElectronIdentifyOptions';
import { ValidatedOptions } from '../src/options';
import ElectronCrypto from '../src/platform/ElectronCrypto';
import ElectronEncoding from '../src/platform/ElectronEncoding';
Expand Down Expand Up @@ -200,7 +199,7 @@ describe('given an ElectronDataManager with mocked dependencies', () => {

await dataManager.identify(identifyResolve, identifyReject, context, {
bootstrap: goodBootstrapData,
} as ElectronIdentifyOptions);
} as LDIdentifyOptions);

expect(flagManager.setBootstrap).toHaveBeenCalledWith(
context,
Expand Down
8 changes: 4 additions & 4 deletions packages/sdk/electron/src/ElectronClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import {
LDEmitterEventName,
LDFlagValue,
LDHeaders,
LDIdentifyOptions,
LDIdentifyResult,
LDPluginEnvironmentMetadata,
LDWaitForInitializationResult,
readFlagsFromBootstrap,
} from '@launchdarkly/js-client-sdk-common';

import ElectronDataManager from './ElectronDataManager';
import type { ElectronIdentifyOptions } from './ElectronIdentifyOptions';
import type { ElectronOptions, ElectronOptions as LDOptions } from './ElectronOptions';
import type { LDClient, LDStartOptions } from './LDClient';
import type { LDPlugin } from './LDPlugin';
Expand Down Expand Up @@ -156,7 +156,7 @@ export class ElectronClient extends LDClientImpl {
return Promise.resolve({ status: 'failed', error: new Error('Initial context not set') });
}

const identifyOptions: ElectronIdentifyOptions = {
const identifyOptions: LDIdentifyOptions = {
...(options?.identifyOptions ?? {}),
sheddable: false,
};
Expand Down Expand Up @@ -197,7 +197,7 @@ export class ElectronClient extends LDClientImpl {

override async identifyResult(
pristineContext: LDContext,
identifyOptions?: ElectronIdentifyOptions,
identifyOptions?: LDIdentifyOptions,
): Promise<LDIdentifyResult> {
if (!this._startPromise) {
this.logger.error(
Expand Down Expand Up @@ -276,7 +276,7 @@ export function makeClient(
off: (key: string, callback: (...args: unknown[]) => void) =>
impl.off(key as LDEmitterEventName, callback as (...args: unknown[]) => void),
flush: () => impl.flush(),
identify: (ctx: LDContext, identifyOptions?: ElectronIdentifyOptions) =>
identify: (ctx: LDContext, identifyOptions?: LDIdentifyOptions) =>
impl.identifyResult(ctx, identifyOptions),
getContext: () => impl.getContext(),
close: () => impl.close(),
Expand Down
14 changes: 6 additions & 8 deletions packages/sdk/electron/src/ElectronDataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
readFlagsFromBootstrap,
} from '@launchdarkly/js-client-sdk-common';

import type { ElectronIdentifyOptions } from './ElectronIdentifyOptions';
import type { ValidatedOptions } from './options';

const logTag = '[ElectronDataManager]';
Expand Down Expand Up @@ -68,12 +67,11 @@ export default class ElectronDataManager extends BaseDataManager {

// When bootstrap is provided, we will resolve the identify immediately. Then we will fallthrough to connect
// to the configured connection mode.
const electronIdentifyOptions = identifyOptions as ElectronIdentifyOptions | undefined;
if (electronIdentifyOptions?.bootstrap) {
this._finishIdentifyFromBootstrap(context, electronIdentifyOptions, identifyResolve);
if (identifyOptions?.bootstrap) {
this._finishIdentifyFromBootstrap(context, identifyOptions, identifyResolve);
}
// Bootstrap path already called resolve so we use this to prevent duplicate resolve calls.
const resolvedFromBootstrap = !!electronIdentifyOptions?.bootstrap;
const resolvedFromBootstrap = !!identifyOptions?.bootstrap;

const offline = this.connectionMode === 'offline';
// In offline mode we do not support waiting for results.
Expand Down Expand Up @@ -109,12 +107,12 @@ export default class ElectronDataManager extends BaseDataManager {

private _finishIdentifyFromBootstrap(
context: Context,
electronIdentifyOptions: ElectronIdentifyOptions,
identifyOpts: LDIdentifyOptions,
identifyResolve: () => void,
): void {
let { bootstrapParsed } = electronIdentifyOptions;
let { bootstrapParsed } = identifyOpts;
if (!bootstrapParsed) {
bootstrapParsed = readFlagsFromBootstrap(this.logger, electronIdentifyOptions.bootstrap);
bootstrapParsed = readFlagsFromBootstrap(this.logger, identifyOpts.bootstrap);
}
this.flagManager.setBootstrap(context, bootstrapParsed);
this._debugLog('Identify - Initialization completed from bootstrap');
Expand Down
29 changes: 0 additions & 29 deletions packages/sdk/electron/src/ElectronIdentifyOptions.ts

This file was deleted.

14 changes: 5 additions & 9 deletions packages/sdk/electron/src/LDClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,24 @@ import type {
ConnectionMode,
LDClient as LDClientBase,
LDContext,
LDIdentifyOptions,
LDIdentifyResult,
LDWaitForInitializationOptions,
LDWaitForInitializationResult,
} from '@launchdarkly/js-client-sdk-common';

import type { ElectronIdentifyOptions } from './ElectronIdentifyOptions';

export interface LDStartOptions extends LDWaitForInitializationOptions {
/**
* Optional bootstrap data to use for the identify operation. If
* {@link ElectronIdentifyOptions.bootstrap} is provided in identifyOptions, it takes precedence.
* {@link LDIdentifyOptions.bootstrap} is provided in identifyOptions, it takes precedence.
*/
bootstrap?: unknown;

/**
* Optional identify options to use for the first identify. Since the first identify is not
* sheddable, the sheddable option is omitted from this type.
*/
identifyOptions?: Omit<ElectronIdentifyOptions, 'sheddable'>;
identifyOptions?: Omit<LDIdentifyOptions, 'sheddable'>;
}

export interface LDClient extends Omit<LDClientBase, 'identify'> {
Expand All @@ -31,13 +30,10 @@ export interface LDClient extends Omit<LDClientBase, 'identify'> {
* Must not be called before {@link LDClient.start} has been called.
*
* @param context The context to identify.
* @param identifyOptions Optional configuration including {@link ElectronIdentifyOptions.bootstrap}.
* @param identifyOptions Optional configuration including {@link LDIdentifyOptions.bootstrap}.
* @returns A promise which resolves to an object containing the result of the identify operation.
*/
identify(
context: LDContext,
identifyOptions?: ElectronIdentifyOptions,
): Promise<LDIdentifyResult>;
identify(context: LDContext, identifyOptions?: LDIdentifyOptions): Promise<LDIdentifyResult>;

/**
* Starts the client by performing the first identify with the initial context passed to
Expand Down
5 changes: 1 addition & 4 deletions packages/sdk/electron/src/LDCommon.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { BasicLogger, BasicLoggerOptions, LDLogger } from '@launchdarkly/js-client-sdk-common';

import { ElectronIdentifyOptions as LDIdentifyOptions } from './ElectronIdentifyOptions';

export type { LDIdentifyOptions };

export type {
LDIdentifyOptions,
AutoEnvAttributes,
BasicLogger,
BasicLoggerOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { AutoEnvAttributes, LDLogger } from '@launchdarkly/js-client-sdk-common';

import PlatformCrypto from '../src/platform/crypto';
import PlatformEncoding from '../src/platform/PlatformEncoding';
import PlatformInfo from '../src/platform/PlatformInfo';
import PlatformStorage from '../src/platform/PlatformStorage';
import ReactNativeLDClient from '../src/ReactNativeLDClient';

jest.mock('../src/platform', () => ({
__esModule: true,
default: jest.fn((logger: LDLogger) => ({
crypto: new PlatformCrypto(),
info: new PlatformInfo(logger, {}),
requests: {
createEventSource: jest.fn(),
fetch: jest.fn(),
getEventSourceCapabilities: jest.fn(),
},
encoding: new PlatformEncoding(),
storage: new PlatformStorage(logger),
})),
}));

const goodBootstrapData = {
'string-flag': 'is bob',
'my-boolean-flag': false,
$flagsState: {
'string-flag': {
variation: 1,
version: 3,
},
'my-boolean-flag': {
variation: 1,
version: 11,
},
},
$valid: true,
};

const goodBootstrapDataWithReasons = {
'string-flag': 'is bob',
'my-boolean-flag': false,
json: ['a', 'b', 'c', 'd'],
$flagsState: {
'string-flag': {
variation: 1,
version: 3,
reason: { kind: 'OFF' },
},
'my-boolean-flag': {
variation: 1,
version: 11,
reason: { kind: 'OFF' },
},
json: {
variation: 1,
version: 3,
reason: { kind: 'OFF' },
},
},
$valid: true,
};

describe('ReactNativeLDClient bootstrap', () => {
let logger: LDLogger;

beforeEach(() => {
logger = {
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
};
});

it('can use bootstrap data with identify', async () => {
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
sendEvents: false,
initialConnectionMode: 'offline',
logger,
});

await client.identify(
{ kind: 'user', key: 'bob' },
{ bootstrap: goodBootstrapDataWithReasons },
);

expect(client.jsonVariationDetail('json', undefined)).toEqual({
reason: { kind: 'OFF' },
value: ['a', 'b', 'c', 'd'],
variationIndex: 1,
});
});

it('can evaluate string and boolean flags from bootstrap data', async () => {
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
sendEvents: false,
initialConnectionMode: 'offline',
logger,
});

await client.identify({ kind: 'user', key: 'bob' }, { bootstrap: goodBootstrapData });

expect(client.stringVariation('string-flag', 'default')).toBe('is bob');
expect(client.boolVariation('my-boolean-flag', true)).toBe(false);
});

it('uses the latest bootstrap data when re-identifying with new bootstrap data', async () => {
const newBootstrapData = {
'string-flag': 'is alice',
'my-boolean-flag': true,
$flagsState: {
'string-flag': {
variation: 1,
version: 4,
},
'my-boolean-flag': {
variation: 0,
version: 12,
},
},
$valid: true,
};

const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
sendEvents: false,
initialConnectionMode: 'offline',
logger,
});

await client.identify({ kind: 'user', key: 'bob' }, { bootstrap: goodBootstrapData });

expect(client.stringVariation('string-flag', 'default')).toBe('is bob');
expect(client.boolVariation('my-boolean-flag', false)).toBe(false);

await client.identify({ kind: 'user', key: 'alice' }, { bootstrap: newBootstrapData });

expect(client.stringVariation('string-flag', 'default')).toBe('is alice');
expect(client.boolVariation('my-boolean-flag', false)).toBe(true);
});

it('returns defaults when no bootstrap data is provided', async () => {
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
sendEvents: false,
initialConnectionMode: 'offline',
logger,
});

await client.identify({ kind: 'user', key: 'bob' });

expect(client.stringVariation('string-flag', 'default')).toBe('default');
expect(client.boolVariation('my-boolean-flag', true)).toBe(true);
});
});
Loading