Skip to content

Commit 7b6e45f

Browse files
committed
feat: adding start() method to common client sdk package
1 parent 10f582a commit 7b6e45f

12 files changed

Lines changed: 479 additions & 717 deletions

File tree

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

Lines changed: 14 additions & 511 deletions
Large diffs are not rendered by default.

packages/sdk/browser/src/BrowserClient.ts

Lines changed: 2 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ import {
2121
LDIdentifyResult,
2222
LDPluginEnvironmentMetadata,
2323
LDWaitForInitializationOptions,
24-
LDWaitForInitializationResult,
2524
MODE_TABLE,
2625
Platform,
27-
readFlagsFromBootstrap,
2826
resolveForegroundMode,
2927
safeRegisterDebugOverridePlugins,
3028
} from '@launchdarkly/js-client-sdk-common';
@@ -45,11 +43,6 @@ class BrowserClientImpl extends LDClientImpl {
4543
private readonly _goalManager?: GoalManager;
4644
private readonly _plugins?: LDPlugin[];
4745

48-
private _initialContext?: LDContext;
49-
50-
// NOTE: This also keeps track of when we tried to initialize the client.
51-
private _startPromise?: Promise<LDWaitForInitializationResult>;
52-
5346
constructor(
5447
clientSideId: string,
5548
autoEnvAttributes: AutoEnvAttributes,
@@ -156,6 +149,7 @@ class BrowserClientImpl extends LDClientImpl {
156149
getImplementationHooks: (environmentMetadata: LDPluginEnvironmentMetadata) =>
157150
internal.safeGetHooks(logger, environmentMetadata, validatedBrowserOptions.plugins),
158151
credentialType: 'clientSideId',
152+
requiresStart: true,
159153
});
160154

161155
this.setEventSendingEnabled(true, false);
@@ -234,10 +228,6 @@ class BrowserClientImpl extends LDClientImpl {
234228
}
235229
}
236230

237-
setInitialContext(context: LDContext): void {
238-
this._initialContext = context;
239-
}
240-
241231
override async identify(context: LDContext, identifyOptions?: LDIdentifyOptions): Promise<void> {
242232
return super.identify(context, identifyOptions);
243233
}
@@ -246,79 +236,11 @@ class BrowserClientImpl extends LDClientImpl {
246236
context: LDContext,
247237
identifyOptions?: LDIdentifyOptions,
248238
): Promise<LDIdentifyResult> {
249-
if (!this._startPromise) {
250-
this.logger.error(
251-
'Client must be started before it can identify a context, did you forget to call start()?',
252-
);
253-
return { status: 'error', error: new Error('Identify called before start') };
254-
}
255-
256-
const identifyOptionsWithUpdatedDefaults = {
257-
...identifyOptions,
258-
};
259-
if (identifyOptions?.sheddable === undefined) {
260-
identifyOptionsWithUpdatedDefaults.sheddable = true;
261-
}
262-
263-
const res = await super.identifyResult(context, identifyOptionsWithUpdatedDefaults);
264-
239+
const res = await super.identifyResult(context, identifyOptions);
265240
this._goalManager?.startTracking();
266241
return res;
267242
}
268243

269-
start(options?: LDStartOptions): Promise<LDWaitForInitializationResult> {
270-
if (this.initializeResult) {
271-
return Promise.resolve(this.initializeResult);
272-
}
273-
if (this._startPromise) {
274-
return this._startPromise;
275-
}
276-
if (!this._initialContext) {
277-
this.logger.error('Initial context not set');
278-
return Promise.resolve({ status: 'failed', error: new Error('Initial context not set') });
279-
}
280-
281-
// When we get to this point, we assume this is the first time that start is being
282-
// attempted. This line should only be called once during the lifetime of the client.
283-
const identifyOptions = {
284-
...(options?.identifyOptions ?? {}),
285-
286-
// Initial identify operations are not sheddable.
287-
sheddable: false,
288-
};
289-
290-
// If the bootstrap data is provided in the start options, and the identify options do not have bootstrap data,
291-
// then use the bootstrap data from the start options.
292-
if (options?.bootstrap && !identifyOptions.bootstrap) {
293-
identifyOptions.bootstrap = options.bootstrap;
294-
}
295-
296-
if (identifyOptions?.bootstrap) {
297-
try {
298-
if (!identifyOptions.bootstrapParsed) {
299-
identifyOptions.bootstrapParsed = readFlagsFromBootstrap(
300-
this.logger,
301-
identifyOptions.bootstrap,
302-
);
303-
}
304-
this.presetFlags(identifyOptions.bootstrapParsed!);
305-
} catch (error) {
306-
this.logger.error('Failed to bootstrap data', error);
307-
}
308-
}
309-
310-
if (!this.initializedPromise) {
311-
this.initializedPromise = new Promise((resolve) => {
312-
this.initResolve = resolve;
313-
});
314-
}
315-
316-
this._startPromise = this.promiseWithTimeout(this.initializedPromise!, options?.timeout ?? 5);
317-
318-
this.identifyResult(this._initialContext!, identifyOptions);
319-
return this._startPromise;
320-
}
321-
322244
setConnectionMode(mode?: FDv2ConnectionMode): void {
323245
if (!this.dataManager.setConnectionMode) {
324246
this.logger.warn(

packages/sdk/browser/src/LDClient.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
LDClient as CommonClient,
3+
LDStartOptions as CommonLDStartOptions,
34
FDv2ConnectionMode,
45
LDContext,
56
LDIdentifyResult,
@@ -9,18 +10,11 @@ import {
910

1011
import { BrowserIdentifyOptions as LDIdentifyOptions } from './BrowserIdentifyOptions';
1112

12-
export interface LDStartOptions extends LDWaitForInitializationOptions {
13-
/**
14-
* Optional bootstrap data to use for the identify operation. If {@link LDIdentifyOptions.bootstrap} is provided, it will be ignored.
15-
*/
16-
bootstrap?: unknown;
17-
18-
/**
19-
* Optional identify options to use for the identify operation. See {@link LDIdentifyOptions} for more information.
20-
*
21-
* @remarks
22-
* Since the first identify option should never be sheddable, we omit the sheddable option from the interface to avoid confusion.
23-
*/
13+
/**
14+
* Browser-specific start options that extend the common start options
15+
* with browser-specific identify options (see {@link LDIdentifyOptions}).
16+
*/
17+
export interface LDStartOptions extends CommonLDStartOptions {
2418
identifyOptions?: Omit<LDIdentifyOptions, 'sheddable'>;
2519
}
2620

packages/sdk/electron/__tests__/ElectronClient.test.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -766,10 +766,7 @@ it('can use bootstrap data with identify', async () => {
766766
expect(mockedCreateEventSource).toHaveBeenCalled();
767767
});
768768

769-
it('parses bootstrap data only once when identify is called with bootstrap', async () => {
770-
const commonModule = await import('@launchdarkly/js-client-sdk-common');
771-
const readFlagsFromBootstrapSpy = jest.spyOn(commonModule, 'readFlagsFromBootstrap');
772-
769+
it('parses bootstrap data when start is called with bootstrap', async () => {
773770
(ElectronPlatform as jest.Mock).mockReturnValue({
774771
crypto: new ElectronCrypto(),
775772
info: new ElectronInfo(),
@@ -796,7 +793,8 @@ it('parses bootstrap data only once when identify is called with bootstrap', asy
796793

797794
await client.start({ bootstrap: goodBootstrapData });
798795

799-
expect(readFlagsFromBootstrapSpy).toHaveBeenCalledTimes(1);
800-
expect(readFlagsFromBootstrapSpy).toHaveBeenCalledWith(expect.anything(), goodBootstrapData);
801-
readFlagsFromBootstrapSpy.mockRestore();
796+
// Verify that bootstrap data was parsed and flags are available.
797+
expect(client.allFlags().killswitch).toBe(true);
798+
expect(client.allFlags()['string-flag']).toBe('is bob');
799+
expect(client.allFlags().cat).toBe(false);
802800
});

packages/sdk/electron/src/ElectronClient.ts

Lines changed: 2 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@ import {
1717
LDFlagValue,
1818
LDHeaders,
1919
LDIdentifyOptions,
20-
LDIdentifyResult,
2120
LDPluginEnvironmentMetadata,
2221
LDWaitForInitializationOptions,
2322
LDWaitForInitializationResult,
2423
mobileFdv1Endpoints,
25-
readFlagsFromBootstrap,
2624
} from '@launchdarkly/js-client-sdk-common';
2725

2826
import ElectronDataManager from './ElectronDataManager';
@@ -40,10 +38,6 @@ import validateOptions, { filterToBaseOptions } from './options';
4038
import ElectronPlatform from './platform/ElectronPlatform';
4139

4240
export class ElectronClient extends LDClientImpl {
43-
private readonly _initialContext: LDContext;
44-
45-
private _startPromise?: Promise<LDWaitForInitializationResult>;
46-
4741
private readonly _plugins: LDPlugin[];
4842

4943
private _ipcNamespace?: string;
@@ -90,6 +84,7 @@ export class ElectronClient extends LDClientImpl {
9084
getImplementationHooks: (_environmentMetadata: LDPluginEnvironmentMetadata) =>
9185
internal.safeGetHooks(logger, _environmentMetadata, validatedElectronOptions.plugins),
9286
credentialType: useClientSideId ? 'clientSideId' : 'mobileKey',
87+
requiresStart: true,
9388
};
9489

9590
const platform = new ElectronPlatform(logger, credential, options);
@@ -122,7 +117,7 @@ export class ElectronClient extends LDClientImpl {
122117
internalOptions,
123118
);
124119

125-
this._initialContext = initialContext;
120+
this.setInitialContext(initialContext);
126121
this._plugins = validatedElectronOptions.plugins;
127122
this.setEventSendingEnabled(!this.isOffline(), false);
128123

@@ -139,78 +134,6 @@ export class ElectronClient extends LDClientImpl {
139134
internal.safeRegisterPlugins(this.logger, this.environmentMetadata, client, this._plugins);
140135
}
141136

142-
start(options?: LDStartOptions): Promise<LDWaitForInitializationResult> {
143-
if (this.initializeResult !== undefined) {
144-
return Promise.resolve(this.initializeResult);
145-
}
146-
if (this._startPromise) {
147-
return this._startPromise;
148-
}
149-
if (!this._initialContext) {
150-
this.logger.error('Initial context not set');
151-
return Promise.resolve({ status: 'failed', error: new Error('Initial context not set') });
152-
}
153-
154-
const identifyOptions: LDIdentifyOptions = {
155-
...(options?.identifyOptions ?? {}),
156-
sheddable: false,
157-
};
158-
159-
if (
160-
options?.bootstrap !== undefined &&
161-
options?.bootstrap !== null &&
162-
!identifyOptions.bootstrap
163-
) {
164-
identifyOptions.bootstrap = options.bootstrap;
165-
}
166-
167-
if (identifyOptions.bootstrap) {
168-
try {
169-
if (!identifyOptions.bootstrapParsed) {
170-
identifyOptions.bootstrapParsed = readFlagsFromBootstrap(
171-
this.logger,
172-
identifyOptions.bootstrap,
173-
);
174-
}
175-
this.presetFlags(identifyOptions.bootstrapParsed!);
176-
} catch (error) {
177-
this.logger.error('Failed to bootstrap data', error);
178-
}
179-
}
180-
181-
if (!this.initializedPromise) {
182-
this.initializedPromise = new Promise((resolve) => {
183-
this.initResolve = resolve;
184-
});
185-
}
186-
187-
this._startPromise = this.promiseWithTimeout(this.initializedPromise!, options?.timeout ?? 5);
188-
189-
this.identifyResult(this._initialContext, identifyOptions);
190-
return this._startPromise;
191-
}
192-
193-
override async identifyResult(
194-
pristineContext: LDContext,
195-
identifyOptions?: LDIdentifyOptions,
196-
): Promise<LDIdentifyResult> {
197-
if (!this._startPromise) {
198-
this.logger.error(
199-
'Client must be started before it can identify a context, did you forget to call start()?',
200-
);
201-
return { status: 'error', error: new Error('Identify called before start') };
202-
}
203-
204-
const identifyOptionsWithUpdatedDefaults = {
205-
...identifyOptions,
206-
};
207-
if (identifyOptions?.sheddable === undefined) {
208-
identifyOptionsWithUpdatedDefaults.sheddable = true;
209-
}
210-
211-
return super.identifyResult(pristineContext, identifyOptionsWithUpdatedDefaults);
212-
}
213-
214137
async setConnectionMode(mode: ConnectionMode): Promise<void> {
215138
if (mode === 'offline') {
216139
this.setEventSendingEnabled(false, true);

packages/sdk/electron/src/LDClient.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,11 @@ import type {
44
LDContext,
55
LDIdentifyOptions,
66
LDIdentifyResult,
7-
LDWaitForInitializationOptions,
7+
LDStartOptions,
88
LDWaitForInitializationResult,
99
} from '@launchdarkly/js-client-sdk-common';
1010

11-
export interface LDStartOptions extends LDWaitForInitializationOptions {
12-
/**
13-
* Optional bootstrap data to use for the identify operation. If
14-
* {@link LDIdentifyOptions.bootstrap} is provided in identifyOptions, it takes precedence.
15-
*/
16-
bootstrap?: unknown;
17-
18-
/**
19-
* Optional identify options to use for the first identify. Since the first identify is not
20-
* sheddable, the sheddable option is omitted from this type.
21-
*/
22-
identifyOptions?: Omit<LDIdentifyOptions, 'sheddable'>;
23-
}
11+
export type { LDStartOptions };
2412

2513
export interface LDClient extends Omit<LDClientBase, 'identify'> {
2614
/**

0 commit comments

Comments
 (0)