Skip to content

Commit 30fc40e

Browse files
committed
fix: js-client-sdk bootstrap data parsed 2x
1 parent 2066e70 commit 30fc40e

File tree

4 files changed

+124
-7
lines changed

4 files changed

+124
-7
lines changed

packages/sdk/browser/__tests__/BrowserClient.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,102 @@ describe('given a mock platform for a BrowserClient', () => {
231231
expect(client.getContext()).toEqual({ kind: 'user', key: 'bob' });
232232
});
233233

234+
it('parses bootstrap data only once when using start()', async () => {
235+
const bootstrapModule = await import('../src/bootstrap');
236+
const readFlagsFromBootstrapSpy = jest.spyOn(bootstrapModule, 'readFlagsFromBootstrap');
237+
238+
const client = makeClient(
239+
'client-side-id',
240+
{ kind: 'user', key: 'bob' },
241+
AutoEnvAttributes.Disabled,
242+
{
243+
streaming: false,
244+
logger,
245+
diagnosticOptOut: true,
246+
},
247+
platform,
248+
);
249+
250+
await client.start({
251+
identifyOptions: {
252+
bootstrap: goodBootstrapDataWithReasons,
253+
},
254+
});
255+
256+
expect(readFlagsFromBootstrapSpy).toHaveBeenCalledTimes(1);
257+
expect(readFlagsFromBootstrapSpy).toHaveBeenCalledWith(
258+
expect.anything(),
259+
goodBootstrapDataWithReasons,
260+
);
261+
262+
readFlagsFromBootstrapSpy.mockRestore();
263+
});
264+
265+
it('uses the latest bootstrap data when identify is called with new bootstrap data', async () => {
266+
const initialBootstrapData = {
267+
'string-flag': 'is bob',
268+
'my-boolean-flag': false,
269+
$flagsState: {
270+
'string-flag': {
271+
variation: 1,
272+
version: 3,
273+
},
274+
'my-boolean-flag': {
275+
variation: 1,
276+
version: 11,
277+
},
278+
},
279+
$valid: true,
280+
};
281+
282+
const newBootstrapData = {
283+
'string-flag': 'is alice',
284+
'my-boolean-flag': true,
285+
$flagsState: {
286+
'string-flag': {
287+
variation: 1,
288+
version: 4,
289+
},
290+
'my-boolean-flag': {
291+
variation: 0,
292+
version: 12,
293+
},
294+
},
295+
$valid: true,
296+
};
297+
298+
const client = makeClient(
299+
'client-side-id',
300+
{ kind: 'user', key: 'bob' },
301+
AutoEnvAttributes.Disabled,
302+
{
303+
streaming: false,
304+
logger,
305+
diagnosticOptOut: true,
306+
},
307+
platform,
308+
);
309+
310+
await client.start({
311+
identifyOptions: {
312+
bootstrap: initialBootstrapData,
313+
},
314+
});
315+
316+
expect(client.stringVariation('string-flag', 'default')).toBe('is bob');
317+
expect(client.boolVariation('my-boolean-flag', false)).toBe(false);
318+
319+
await client.identify(
320+
{ kind: 'user', key: 'alice' },
321+
{
322+
bootstrap: newBootstrapData,
323+
},
324+
);
325+
326+
expect(client.stringVariation('string-flag', 'default')).toBe('is alice');
327+
expect(client.boolVariation('my-boolean-flag', false)).toBe(true);
328+
});
329+
234330
it('can shed intermediate identify calls', async () => {
235331
const client = makeClient(
236332
'client-side-id',

packages/sdk/browser/src/BrowserClient.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,11 @@ class BrowserClientImpl extends LDClientImpl {
276276

277277
if (identifyOptions?.bootstrap) {
278278
try {
279-
const bootstrapData = readFlagsFromBootstrap(this.logger, identifyOptions.bootstrap);
280-
this.presetFlags(bootstrapData);
279+
identifyOptions.bootstrapParsed = readFlagsFromBootstrap(
280+
this.logger,
281+
identifyOptions.bootstrap,
282+
);
283+
this.presetFlags(identifyOptions.bootstrapParsed);
281284
} catch (error) {
282285
this.logger.error('Failed to bootstrap data', error);
283286
}

packages/sdk/browser/src/BrowserDataManager.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Context,
55
DataSourceErrorKind,
66
DataSourcePaths,
7+
DataSourceState,
78
FlagManager,
89
httpErrorMessage,
910
internal,
@@ -93,7 +94,7 @@ export default class BrowserDataManager extends BaseDataManager {
9394
this._secureModeHash = browserIdentifyOptions?.hash;
9495

9596
if (browserIdentifyOptions?.bootstrap) {
96-
this._finishIdentifyFromBootstrap(context, browserIdentifyOptions.bootstrap, identifyResolve);
97+
this._finishIdentifyFromBootstrap(context, browserIdentifyOptions, identifyResolve);
9798
} else {
9899
if (await this.flagManager.loadCached(context)) {
99100
this._debugLog('Identify - Flags loaded from cache. Continuing to initialize via a poll.');
@@ -160,7 +161,7 @@ export default class BrowserDataManager extends BaseDataManager {
160161
identifyReject: (err: Error) => void,
161162
) {
162163
try {
163-
this.dataSourceStatusManager.requestStateUpdate('INITIALIZING');
164+
this.dataSourceStatusManager.requestStateUpdate(DataSourceState.Initializing);
164165

165166
const payload = await this._requestPayload(context);
166167

@@ -186,12 +187,21 @@ export default class BrowserDataManager extends BaseDataManager {
186187

187188
private _finishIdentifyFromBootstrap(
188189
context: Context,
189-
bootstrap: unknown,
190+
browserIdentifyOptions: BrowserIdentifyOptions,
190191
identifyResolve: () => void,
191192
) {
192-
this.flagManager.setBootstrap(context, readFlagsFromBootstrap(this.logger, bootstrap));
193+
if (!browserIdentifyOptions.bootstrapParsed) {
194+
browserIdentifyOptions.bootstrapParsed = readFlagsFromBootstrap(
195+
this.logger,
196+
browserIdentifyOptions.bootstrap,
197+
);
198+
}
199+
this.flagManager.setBootstrap(context, browserIdentifyOptions.bootstrapParsed);
193200
this._debugLog('Identify - Initialization completed from bootstrap');
194201
identifyResolve();
202+
203+
// Clear the bootstrap parsed data to free up memory
204+
browserIdentifyOptions.bootstrapParsed = undefined;
195205
}
196206

197207
setForcedStreaming(streaming?: boolean) {

packages/sdk/browser/src/BrowserIdentifyOptions.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LDIdentifyOptions } from '@launchdarkly/js-client-sdk-common';
1+
import { ItemDescriptor, LDIdentifyOptions } from '@launchdarkly/js-client-sdk-common';
22

33
/**
44
* @property sheddable - If true, the identify operation will be sheddable. This means that if multiple identify operations are done, without
@@ -28,4 +28,12 @@ export interface BrowserIdentifyOptions extends Omit<LDIdentifyOptions, 'waitFor
2828
* For more information, see the [SDK Reference Guide](https://docs.launchdarkly.com/sdk/features/bootstrapping#javascript).
2929
*/
3030
bootstrap?: unknown;
31+
32+
/**
33+
* Parsed bootstrap data that could be stored to ensure that the bootstrap data is only parsed once during the intialization
34+
* process.
35+
*
36+
* @hidden
37+
*/
38+
bootstrapParsed?: { [key: string]: ItemDescriptor };
3139
}

0 commit comments

Comments
 (0)