Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions packages/sdk/browser/__tests__/BrowserClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,102 @@ describe('given a mock platform for a BrowserClient', () => {
expect(client.getContext()).toEqual({ kind: 'user', key: 'bob' });
});

it('parses bootstrap data only once when using start()', async () => {
const bootstrapModule = await import('../src/bootstrap');
const readFlagsFromBootstrapSpy = jest.spyOn(bootstrapModule, 'readFlagsFromBootstrap');

const client = makeClient(
'client-side-id',
{ kind: 'user', key: 'bob' },
AutoEnvAttributes.Disabled,
{
streaming: false,
logger,
diagnosticOptOut: true,
},
platform,
);

await client.start({
identifyOptions: {
bootstrap: goodBootstrapDataWithReasons,
},
});

expect(readFlagsFromBootstrapSpy).toHaveBeenCalledTimes(1);
expect(readFlagsFromBootstrapSpy).toHaveBeenCalledWith(
expect.anything(),
goodBootstrapDataWithReasons,
);

readFlagsFromBootstrapSpy.mockRestore();
});

it('uses the latest bootstrap data when identify is called with new bootstrap data', async () => {
const initialBootstrapData = {
'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 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 = makeClient(
'client-side-id',
{ kind: 'user', key: 'bob' },
AutoEnvAttributes.Disabled,
{
streaming: false,
logger,
diagnosticOptOut: true,
},
platform,
);

await client.start({
identifyOptions: {
bootstrap: initialBootstrapData,
},
});

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('can shed intermediate identify calls', async () => {
const client = makeClient(
'client-side-id',
Expand Down
7 changes: 5 additions & 2 deletions packages/sdk/browser/src/BrowserClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,11 @@ class BrowserClientImpl extends LDClientImpl {

if (identifyOptions?.bootstrap) {
try {
const bootstrapData = readFlagsFromBootstrap(this.logger, identifyOptions.bootstrap);
this.presetFlags(bootstrapData);
identifyOptions.bootstrapParsed = readFlagsFromBootstrap(
this.logger,
identifyOptions.bootstrap,
);
this.presetFlags(identifyOptions.bootstrapParsed);
} catch (error) {
this.logger.error('Failed to bootstrap data', error);
}
Expand Down
13 changes: 9 additions & 4 deletions packages/sdk/browser/src/BrowserDataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Context,
DataSourceErrorKind,
DataSourcePaths,
DataSourceState,
FlagManager,
httpErrorMessage,
internal,
Expand Down Expand Up @@ -93,7 +94,7 @@ export default class BrowserDataManager extends BaseDataManager {
this._secureModeHash = browserIdentifyOptions?.hash;

if (browserIdentifyOptions?.bootstrap) {
this._finishIdentifyFromBootstrap(context, browserIdentifyOptions.bootstrap, identifyResolve);
this._finishIdentifyFromBootstrap(context, browserIdentifyOptions, identifyResolve);
} else {
if (await this.flagManager.loadCached(context)) {
this._debugLog('Identify - Flags loaded from cache. Continuing to initialize via a poll.');
Expand Down Expand Up @@ -160,7 +161,7 @@ export default class BrowserDataManager extends BaseDataManager {
identifyReject: (err: Error) => void,
) {
try {
this.dataSourceStatusManager.requestStateUpdate('INITIALIZING');
this.dataSourceStatusManager.requestStateUpdate(DataSourceState.Initializing);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sneaking this in if reviewers don't mind... End value is the same, but looks better this way imo


const payload = await this._requestPayload(context);

Expand All @@ -186,10 +187,14 @@ export default class BrowserDataManager extends BaseDataManager {

private _finishIdentifyFromBootstrap(
context: Context,
bootstrap: unknown,
browserIdentifyOptions: BrowserIdentifyOptions,
identifyResolve: () => void,
) {
this.flagManager.setBootstrap(context, readFlagsFromBootstrap(this.logger, bootstrap));
let { bootstrapParsed } = browserIdentifyOptions;
if (!bootstrapParsed) {
bootstrapParsed = readFlagsFromBootstrap(this.logger, browserIdentifyOptions.bootstrap);
}
this.flagManager.setBootstrap(context, bootstrapParsed);
this._debugLog('Identify - Initialization completed from bootstrap');
identifyResolve();
}
Expand Down
10 changes: 9 additions & 1 deletion packages/sdk/browser/src/BrowserIdentifyOptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LDIdentifyOptions } from '@launchdarkly/js-client-sdk-common';
import { ItemDescriptor, 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 Down Expand Up @@ -28,4 +28,12 @@ export interface BrowserIdentifyOptions extends Omit<LDIdentifyOptions, 'waitFor
* 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 };
}