Skip to content

Commit 1d73462

Browse files
authored
fix: Send identify event. (#407)
1 parent 69d2dcc commit 1d73462

File tree

7 files changed

+163
-27
lines changed

7 files changed

+163
-27
lines changed

packages/shared/common/src/internal/events/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import ClientMessages from './ClientMessages';
2-
import EventProcessor from './EventProcessor';
2+
import EventProcessor, { EventProcessorOptions } from './EventProcessor';
33
import InputCustomEvent from './InputCustomEvent';
44
import InputEvalEvent from './InputEvalEvent';
55
import InputEvent from './InputEvent';
@@ -17,6 +17,7 @@ export {
1717
InputIdentifyEvent,
1818
InputMigrationEvent,
1919
EventProcessor,
20+
EventProcessorOptions,
2021
shouldSample,
2122
NullEventProcessor,
2223
LDInternalOptions,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { ClientContext, internal, subsystem } from '@common';
2+
3+
export const MockEventProcessor = jest.fn();
4+
5+
export const setupMockEventProcessor = () => {
6+
MockEventProcessor.mockImplementation(
7+
(
8+
_config: internal.EventProcessorOptions,
9+
_clientContext: ClientContext,
10+
_contextDeduplicator?: subsystem.LDContextDeduplicator,
11+
_diagnosticsManager?: internal.DiagnosticsManager,
12+
_start: boolean = true,
13+
) => ({
14+
close: jest.fn(),
15+
flush: jest.fn(),
16+
sendEvent: jest.fn(),
17+
}),
18+
);
19+
};

packages/shared/mocks/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { clientContext } from './clientContext';
22
import ContextDeduplicator from './contextDeduplicator';
33
import { hasher } from './crypto';
4+
import { MockEventProcessor, setupMockEventProcessor } from './eventProcessor';
45
import logger from './logger';
56
import mockFetch from './mockFetch';
67
import { basicPlatform } from './platform';
@@ -13,6 +14,8 @@ export {
1314
mockFetch,
1415
logger,
1516
ContextDeduplicator,
17+
MockEventProcessor,
18+
setupMockEventProcessor,
1619
MockStreamingProcessor,
1720
setupMockStreamingProcessor,
1821
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { AutoEnvAttributes, clone, LDContext } from '@launchdarkly/js-sdk-common';
2+
import { InputIdentifyEvent } from '@launchdarkly/js-sdk-common/dist/internal';
3+
import {
4+
basicPlatform,
5+
hasher,
6+
logger,
7+
MockEventProcessor,
8+
setupMockEventProcessor,
9+
setupMockStreamingProcessor,
10+
} from '@launchdarkly/private-js-mocks';
11+
12+
import * as mockResponseJson from './evaluation/mockResponse.json';
13+
import LDClientImpl from './LDClientImpl';
14+
import { Flags } from './types';
15+
16+
jest.mock('@launchdarkly/js-sdk-common', () => {
17+
const actual = jest.requireActual('@launchdarkly/js-sdk-common');
18+
const m = jest.requireActual('@launchdarkly/private-js-mocks');
19+
return {
20+
...actual,
21+
...{
22+
internal: {
23+
...actual.internal,
24+
StreamingProcessor: m.MockStreamingProcessor,
25+
EventProcessor: m.MockEventProcessor,
26+
},
27+
},
28+
};
29+
});
30+
31+
const testSdkKey = 'test-sdk-key';
32+
let ldc: LDClientImpl;
33+
let defaultPutResponse: Flags;
34+
35+
describe('sdk-client object', () => {
36+
beforeEach(() => {
37+
defaultPutResponse = clone<Flags>(mockResponseJson);
38+
setupMockEventProcessor();
39+
setupMockStreamingProcessor(false, defaultPutResponse);
40+
basicPlatform.crypto.randomUUID.mockReturnValue('random1');
41+
hasher.digest.mockReturnValue('digested1');
42+
43+
ldc = new LDClientImpl(testSdkKey, AutoEnvAttributes.Enabled, basicPlatform, {
44+
logger,
45+
});
46+
jest
47+
.spyOn(LDClientImpl.prototype as any, 'createStreamUriPath')
48+
.mockReturnValue('/stream/path');
49+
});
50+
51+
afterEach(() => {
52+
jest.resetAllMocks();
53+
});
54+
55+
test('identify event', async () => {
56+
defaultPutResponse['dev-test-flag'].value = false;
57+
const carContext: LDContext = { kind: 'car', key: 'test-car' };
58+
59+
await ldc.identify(carContext);
60+
61+
expect(MockEventProcessor).toHaveBeenCalled();
62+
expect(ldc.eventProcessor!.sendEvent).toHaveBeenNthCalledWith(
63+
1,
64+
expect.objectContaining<InputIdentifyEvent>({
65+
kind: 'identify',
66+
context: expect.objectContaining({
67+
contexts: expect.objectContaining({
68+
car: { key: 'test-car' },
69+
}),
70+
}),
71+
creationDate: expect.any(Number),
72+
samplingRatio: expect.any(Number),
73+
}),
74+
);
75+
});
76+
});

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,32 @@ describe('sdk-client storage', () => {
159159
});
160160
});
161161

162+
test('not emitting change event', async () => {
163+
jest.doMock('./utils', () => {
164+
const actual = jest.requireActual('./utils');
165+
return {
166+
...actual,
167+
calculateFlagChanges: () => [],
168+
};
169+
});
170+
let LDClientImplTestNoChange;
171+
jest.isolateModules(async () => {
172+
LDClientImplTestNoChange = jest.requireActual('./LDClientImpl').default;
173+
ldc = new LDClientImplTestNoChange(testSdkKey, AutoEnvAttributes.Enabled, basicPlatform, {
174+
logger,
175+
sendEvents: false,
176+
});
177+
});
178+
179+
// @ts-ignore
180+
emitter = ldc.emitter;
181+
jest.spyOn(emitter as LDEmitter, 'emit');
182+
183+
await identifyGetAllFlags(true, defaultPutResponse);
184+
185+
expect(emitter.emit).not.toHaveBeenCalled();
186+
});
187+
162188
test('no storage, cold start from streamer', async () => {
163189
// fake previously cached flags even though there's no storage for this context
164190
// @ts-ignore
@@ -174,7 +200,9 @@ describe('sdk-client storage', () => {
174200
'org:Testy Pizza',
175201
JSON.stringify(defaultPutResponse),
176202
);
177-
expect(ldc.logger.debug).toHaveBeenCalledWith('Not emitting changes from PUT');
203+
expect(ldc.logger.debug).toHaveBeenCalledWith(
204+
'OnIdentifyResolve no changes to emit from: streamer PUT.',
205+
);
178206

179207
// this is defaultPutResponse
180208
expect(ldc.allFlags()).toEqual({
@@ -205,14 +233,13 @@ describe('sdk-client storage', () => {
205233

206234
test('syncing storage when a flag is added', async () => {
207235
const putResponse = clone<Flags>(defaultPutResponse);
208-
const newFlag = {
236+
putResponse['another-dev-test-flag'] = {
209237
version: 1,
210238
flagVersion: 2,
211239
value: false,
212240
variation: 1,
213241
trackEvents: false,
214242
};
215-
putResponse['another-dev-test-flag'] = newFlag;
216243
const allFlags = await identifyGetAllFlags(false, putResponse);
217244

218245
expect(allFlags).toMatchObject({ 'another-dev-test-flag': false });

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

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,8 @@ export default class LDClientImpl implements LDClient {
168168
deserializeData: JSON.parse,
169169
processJson: async (dataJson: Flags) => {
170170
this.logger.debug(`Streamer PUT: ${Object.keys(dataJson)}`);
171-
const changedKeys = calculateFlagChanges(this.flags, dataJson);
172-
this.context = context;
173-
this.flags = dataJson;
171+
this.onIdentifyResolve(identifyResolve, dataJson, context, 'streamer PUT');
174172
await this.platform.storage?.set(canonicalKey, JSON.stringify(this.flags));
175-
176-
if (changedKeys.length > 0) {
177-
this.logger.debug(`Emitting changes from PUT: ${changedKeys}`);
178-
// emitting change resolves identify
179-
this.emitter.emit('change', context, changedKeys);
180-
} else {
181-
// manually resolve identify
182-
this.logger.debug('Not emitting changes from PUT');
183-
identifyResolve();
184-
}
185173
},
186174
});
187175

@@ -249,7 +237,7 @@ export default class LDClientImpl implements LDClient {
249237
);
250238
}
251239

252-
private createPromiseWithListeners() {
240+
private createIdentifyPromise() {
253241
let res: any;
254242
const p = new Promise<void>((resolve, reject) => {
255243
res = resolve;
@@ -263,7 +251,6 @@ export default class LDClientImpl implements LDClient {
263251

264252
this.identifyChangeListener = (c: LDContext, changedKeys: string[]) => {
265253
this.logger.debug(`change: context: ${JSON.stringify(c)}, flags: ${changedKeys}`);
266-
resolve();
267254
};
268255
this.identifyErrorListener = (c: LDContext, err: any) => {
269256
this.logger.debug(`error: ${err}, context: ${JSON.stringify(c)}`);
@@ -297,17 +284,14 @@ export default class LDClientImpl implements LDClient {
297284
return Promise.reject(error);
298285
}
299286

300-
const { identifyPromise, identifyResolve } = this.createPromiseWithListeners();
287+
this.eventProcessor?.sendEvent(this.eventFactoryDefault.identifyEvent(checkedContext));
288+
const { identifyPromise, identifyResolve } = this.createIdentifyPromise();
301289
this.logger.debug(`Identifying ${JSON.stringify(context)}`);
302290

303291
const flagsStorage = await this.getFlagsFromStorage(checkedContext.canonicalKey);
304292
if (flagsStorage) {
305293
this.logger.debug('Using storage');
306-
307-
const changedKeys = calculateFlagChanges(this.flags, flagsStorage);
308-
this.context = context;
309-
this.flags = flagsStorage;
310-
this.emitter.emit('change', context, changedKeys);
294+
this.onIdentifyResolve(identifyResolve, flagsStorage, context, 'identify storage');
311295
}
312296

313297
if (this.isOffline()) {
@@ -342,6 +326,33 @@ export default class LDClientImpl implements LDClient {
342326
return identifyPromise;
343327
}
344328

329+
/**
330+
* Performs common tasks when resolving the identify promise:
331+
* - resolve the promise
332+
* - update in memory context
333+
* - update in memory flags
334+
* - emit change event if needed
335+
*
336+
* @param resolve
337+
* @param flags
338+
* @param context
339+
* @param source For logging purposes
340+
* @private
341+
*/
342+
private onIdentifyResolve(resolve: any, flags: Flags, context: LDContext, source: string) {
343+
resolve();
344+
const changedKeys = calculateFlagChanges(this.flags, flags);
345+
this.context = context;
346+
this.flags = flags;
347+
348+
if (changedKeys.length > 0) {
349+
this.emitter.emit('change', context, changedKeys);
350+
this.logger.debug(`OnIdentifyResolve emitting changes from: ${source}.`);
351+
} else {
352+
this.logger.debug(`OnIdentifyResolve no changes to emit from: ${source}.`);
353+
}
354+
}
355+
345356
off(eventName: EventName, listener: Function): void {
346357
this.emitter.off(eventName, listener);
347358
}

packages/shared/sdk-client/src/events/createEventProcessor.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { ClientContext, internal, Platform } from '@launchdarkly/js-sdk-common';
2-
import { EventProcessor } from '@launchdarkly/js-sdk-common/dist/internal';
32

43
import Configuration from '../configuration';
54

@@ -9,7 +8,7 @@ const createEventProcessor = (
98
platform: Platform,
109
diagnosticsManager?: internal.DiagnosticsManager,
1110
start: boolean = false,
12-
): EventProcessor | undefined => {
11+
): internal.EventProcessor | undefined => {
1312
if (config.sendEvents) {
1413
return new internal.EventProcessor(
1514
{ ...config, eventsCapacity: config.capacity },

0 commit comments

Comments
 (0)