Skip to content

Commit 31c1901

Browse files
yusintokinyoklionLaunchDarklyReleaseBot
authored
chore: implement identify (#304)
Initial implementation of identify, good enough for an alpha release. --------- Co-authored-by: Yusinto Ngadiman <[email protected]> Co-authored-by: Ryan Lamb <[email protected]> Co-authored-by: LaunchDarklyReleaseBot <[email protected]>
1 parent 95fef2c commit 31c1901

File tree

3 files changed

+74
-41
lines changed

3 files changed

+74
-41
lines changed

packages/shared/sdk-client/src/LDClientImpl.test.ts

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,35 @@
11
import { LDContext } from '@launchdarkly/js-sdk-common';
2-
import { basicPlatform } from '@launchdarkly/private-js-mocks';
2+
import { basicPlatform, logger } from '@launchdarkly/private-js-mocks';
33

4+
import LDEmitter from './api/LDEmitter';
45
import fetchFlags from './evaluation/fetchFlags';
56
import * as mockResponseJson from './evaluation/mockResponse.json';
67
import LDClientImpl from './LDClientImpl';
78

8-
jest.mock('./evaluation/fetchFlags', () => {
9-
const actual = jest.requireActual('./evaluation/fetchFlags');
10-
return {
11-
__esModule: true,
12-
...actual,
13-
default: jest.fn(),
14-
};
15-
});
9+
jest.mock('./api/LDEmitter');
10+
jest.mock('./evaluation/fetchFlags');
1611

1712
describe('sdk-client object', () => {
1813
const testSdkKey = 'test-sdk-key';
1914
const context: LDContext = { kind: 'org', key: 'Testy Pizza' };
2015
const mockFetchFlags = fetchFlags as jest.Mock;
2116

2217
let ldc: LDClientImpl;
18+
let mockEmitter: LDEmitter;
2319

24-
beforeEach(async () => {
20+
beforeEach(() => {
2521
mockFetchFlags.mockResolvedValue(mockResponseJson);
2622

27-
ldc = new LDClientImpl(testSdkKey, context, basicPlatform, {});
28-
await ldc.start();
23+
ldc = new LDClientImpl(testSdkKey, context, basicPlatform, { logger });
24+
[mockEmitter] = (LDEmitter as jest.Mock).mock.instances;
25+
});
26+
27+
afterEach(() => {
28+
jest.resetAllMocks();
2929
});
3030

3131
test('instantiate with blank options', () => {
32+
ldc = new LDClientImpl(testSdkKey, context, basicPlatform, {});
3233
expect(ldc.config).toMatchObject({
3334
allAttributesPrivate: false,
3435
baseUri: 'https://sdk.launchdarkly.com',
@@ -61,6 +62,7 @@ describe('sdk-client object', () => {
6162
});
6263

6364
test('all flags', async () => {
65+
await ldc.start();
6466
const all = ldc.allFlags();
6567

6668
expect(all).toEqual({
@@ -76,8 +78,45 @@ describe('sdk-client object', () => {
7678
});
7779

7880
test('variation', async () => {
81+
await ldc.start();
7982
const devTestFlag = ldc.variation('dev-test-flag');
8083

8184
expect(devTestFlag).toBe(true);
8285
});
86+
87+
test('identify success', async () => {
88+
mockResponseJson['dev-test-flag'].value = false;
89+
mockFetchFlags.mockResolvedValue(mockResponseJson);
90+
const carContext: LDContext = { kind: 'car', key: 'mazda-cx7' };
91+
92+
await ldc.identify(carContext);
93+
const c = ldc.getContext();
94+
const all = ldc.allFlags();
95+
96+
expect(carContext).toEqual(c);
97+
expect(all).toMatchObject({
98+
'dev-test-flag': false,
99+
});
100+
});
101+
102+
test('identify error invalid context', async () => {
103+
// @ts-ignore
104+
const carContext: LDContext = { kind: 'car', key: undefined };
105+
106+
await expect(ldc.identify(carContext)).rejects.toThrowError(/no key/);
107+
expect(logger.error).toBeCalledTimes(1);
108+
expect(mockEmitter.emit).toHaveBeenNthCalledWith(1, 'error', expect.any(Error));
109+
expect(ldc.getContext()).toEqual(context);
110+
});
111+
112+
test('identify error fetch error', async () => {
113+
// @ts-ignore
114+
mockFetchFlags.mockRejectedValue(new Error('unknown test fetch error'));
115+
const carContext: LDContext = { kind: 'car', key: 'mazda-3' };
116+
117+
await expect(ldc.identify(carContext)).rejects.toThrowError(/fetch error/);
118+
expect(logger.error).toBeCalledTimes(1);
119+
expect(mockEmitter.emit).toHaveBeenNthCalledWith(1, 'error', expect.any(Error));
120+
expect(ldc.getContext()).toEqual(context);
121+
});
83122
});

packages/shared/sdk-client/src/LDClientImpl.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,14 @@ export default class LDClientImpl implements LDClient {
4545
*/
4646
constructor(
4747
public readonly sdkKey: string,
48-
public readonly context: LDContext,
48+
public context: LDContext,
4949
public readonly platform: Platform,
5050
options: LDOptions,
5151
) {
5252
if (!sdkKey) {
5353
throw new Error('You must configure the client with a client-side SDK key');
5454
}
5555

56-
const checkedContext = Context.fromLDContext(context);
57-
if (!checkedContext.valid) {
58-
throw new Error('Context was unspecified or had no key');
59-
}
60-
6156
if (!platform.encoding) {
6257
throw new Error('Platform must implement Encoding because btoa is required.');
6358
}
@@ -78,12 +73,11 @@ export default class LDClientImpl implements LDClient {
7873

7974
async start() {
8075
try {
81-
this.flags = await fetchFlags(this.sdkKey, this.context, this.config, this.platform);
76+
await this.identify(this.context);
8277
this.emitter.emit('ready');
8378
} catch (error: any) {
84-
this.logger.error(error);
85-
this.emitter.emit('error', error);
8679
this.emitter.emit('failed', error);
80+
throw error;
8781
}
8882
}
8983

@@ -113,13 +107,24 @@ export default class LDClientImpl implements LDClient {
113107
return clone(this.context);
114108
}
115109

116-
identify(
117-
context: LDContext,
118-
hash?: string,
119-
onDone?: (err: Error | null, flags: LDFlagSet | null) => void,
120-
): Promise<LDFlagSet> {
121-
// TODO:
122-
return Promise.resolve({});
110+
// TODO: implement secure mode
111+
async identify(context: LDContext, hash?: string): Promise<void> {
112+
const checkedContext = Context.fromLDContext(context);
113+
if (!checkedContext.valid) {
114+
const error = new Error('Context was unspecified or had no key');
115+
this.logger.error(error);
116+
this.emitter.emit('error', error);
117+
throw error;
118+
}
119+
120+
try {
121+
this.flags = await fetchFlags(this.sdkKey, context, this.config, this.platform);
122+
this.context = context;
123+
} catch (error: any) {
124+
this.logger.error(error);
125+
this.emitter.emit('error', error);
126+
throw error;
127+
}
123128
}
124129

125130
off(eventName: EventName, listener?: Function): void {

packages/shared/sdk-client/src/api/LDClient.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,10 @@ export interface LDClient {
8989
* The context properties. Must contain at least the `key` property.
9090
* @param hash
9191
* The signed context key if you are using [Secure Mode](https://docs.launchdarkly.com/sdk/features/secure-mode#configuring-secure-mode-in-the-javascript-client-side-sdk).
92-
* @param onDone
93-
* A function which will be called as soon as the flag values for the new context are available,
94-
* with two parameters: an error value (if any), and an {@link LDFlagSet} containing the new values
95-
* (which can also be obtained by calling {@link variation}). If the callback is omitted, you will
96-
* receive a Promise instead.
9792
* @returns
98-
* If you provided a callback, then nothing. Otherwise, a Promise which resolve once the flag
99-
* values for the new context are available, providing an {@link LDFlagSet} containing the new values
100-
* (which can also be obtained by calling {@link variation}).
93+
* A Promise which resolve once the flag values for the new context are available.
10194
*/
102-
identify(
103-
context: LDContext,
104-
hash?: string,
105-
onDone?: (err: Error | null, flags: LDFlagSet | null) => void,
106-
): Promise<LDFlagSet>;
95+
identify(context: LDContext, hash?: string): Promise<void>;
10796

10897
/**
10998
* Returns the client's current context.

0 commit comments

Comments
 (0)