Skip to content

Commit c3a6f58

Browse files
authored
feat: live plugin support and enrichment closure (#1010)
* feat: signals support and enrichment closure * test: fix test failures * test: add unit test for enrichment closure * fix: fix lint issues * feat: make enrichment closure a property of the event * fix: lint fix * refactor: revert changes of disabling hermes on sample app --------- Co-authored-by: Wenxi Zeng <wzeng@twilio.com>
1 parent a3a947c commit c3a6f58

File tree

14 files changed

+233
-28
lines changed

14 files changed

+233
-28
lines changed

examples/AnalyticsReactNativeExample/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ PODS:
499499
- RNScreens (3.27.0):
500500
- RCT-Folly (= 2021.07.22.00)
501501
- React-Core
502-
- segment-analytics-react-native (2.19.4):
502+
- segment-analytics-react-native (2.19.5):
503503
- React-Core
504504
- sovran-react-native
505505
- SocketRocket (0.6.1)
@@ -752,12 +752,12 @@ SPEC CHECKSUMS:
752752
RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489
753753
RNGestureHandler: 32a01c29ecc9bb0b5bf7bc0a33547f61b4dc2741
754754
RNScreens: 3c2d122f5e08c192e254c510b212306da97d2581
755-
segment-analytics-react-native: 49ce29a68e86b38c084f1ce07b0c128273d169f9
755+
segment-analytics-react-native: 4bac3da03dd4a1eed178786b1d7025cd2c0ed6c9
756756
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
757757
sovran-react-native: 5f02bd2d111ffe226d00c7b0435290eae6f10934
758758
Yoga: eddf2bbe4a896454c248a8f23b4355891eb720a6
759759
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
760760

761761
PODFILE CHECKSUM: 329f06ebb76294acf15c298d0af45530e2797740
762762

763-
COCOAPODS: 1.11.3
763+
COCOAPODS: 1.15.2

packages/core/src/__tests__/internal/fetchSettings.test.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,12 @@ describe('internal #getSettings', () => {
5050
await client.fetchSettings();
5151

5252
expect(fetch).toHaveBeenCalledWith(
53-
`${settingsCDN}/${clientArgs.config.writeKey}/settings`
53+
`${settingsCDN}/${clientArgs.config.writeKey}/settings`,
54+
{
55+
headers: {
56+
'Cache-Control': 'no-cache',
57+
},
58+
}
5459
);
5560

5661
expect(setSettingsSpy).toHaveBeenCalledWith(mockJSONResponse.integrations);
@@ -66,7 +71,12 @@ describe('internal #getSettings', () => {
6671
await client.fetchSettings();
6772

6873
expect(fetch).toHaveBeenCalledWith(
69-
`${settingsCDN}/${clientArgs.config.writeKey}/settings`
74+
`${settingsCDN}/${clientArgs.config.writeKey}/settings`,
75+
{
76+
headers: {
77+
'Cache-Control': 'no-cache',
78+
},
79+
}
7080
);
7181

7282
expect(setSettingsSpy).toHaveBeenCalledWith(
@@ -92,7 +102,12 @@ describe('internal #getSettings', () => {
92102
await anotherClient.fetchSettings();
93103

94104
expect(fetch).toHaveBeenCalledWith(
95-
`${settingsCDN}/${clientArgs.config.writeKey}/settings`
105+
`${settingsCDN}/${clientArgs.config.writeKey}/settings`,
106+
{
107+
headers: {
108+
'Cache-Control': 'no-cache',
109+
},
110+
}
96111
);
97112
expect(setSettingsSpy).not.toHaveBeenCalled();
98113
});
@@ -113,7 +128,12 @@ describe('internal #getSettings', () => {
113128
await anotherClient.fetchSettings();
114129

115130
expect(fetch).toHaveBeenCalledWith(
116-
`${settingsCDN}/${clientArgs.config.writeKey}/settings`
131+
`${settingsCDN}/${clientArgs.config.writeKey}/settings`,
132+
{
133+
headers: {
134+
'Cache-Control': 'no-cache',
135+
},
136+
}
117137
);
118138
expect(setSettingsSpy).not.toHaveBeenCalled();
119139
});

packages/core/src/__tests__/methods/group.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ describe('methods #group', () => {
4343
};
4444

4545
expect(client.process).toHaveBeenCalledTimes(1);
46-
expect(client.process).toHaveBeenCalledWith(expectedEvent);
46+
expect(client.process).toHaveBeenCalledWith(expectedEvent, undefined);
4747
});
4848
});

packages/core/src/__tests__/methods/process.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,49 @@ describe('process', () => {
112112
expect.objectContaining(expectedEvent)
113113
);
114114
});
115+
116+
it('enrichment closure gets applied', async () => {
117+
const client = new SegmentClient(clientArgs);
118+
jest.spyOn(client.isReady, 'value', 'get').mockReturnValue(true);
119+
120+
// @ts-ignore
121+
const timeline = client.timeline;
122+
jest.spyOn(timeline, 'process');
123+
124+
await client.track('Some Event', { id: 1 }, (event) => {
125+
if (event.context == null) {
126+
event.context = {};
127+
}
128+
event.context.__eventOrigin = {
129+
type: 'signals',
130+
};
131+
event.anonymousId = 'foo';
132+
133+
return event;
134+
});
135+
136+
const expectedEvent = {
137+
event: 'Some Event',
138+
properties: {
139+
id: 1,
140+
},
141+
type: EventType.TrackEvent,
142+
context: {
143+
__eventOrigin: {
144+
type: 'signals',
145+
},
146+
...store.context.get(),
147+
},
148+
userId: store.userInfo.get().userId,
149+
anonymousId: 'foo',
150+
} as SegmentEvent;
151+
152+
// @ts-ignore
153+
const pendingEvents = client.store.pendingEvents.get();
154+
expect(pendingEvents.length).toBe(0);
155+
156+
expect(timeline.process).toHaveBeenCalledWith(
157+
expect.objectContaining(expectedEvent)
158+
);
159+
});
115160
});

packages/core/src/__tests__/methods/screen.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ describe('methods #screen', () => {
4343
};
4444

4545
expect(client.process).toHaveBeenCalledTimes(1);
46-
expect(client.process).toHaveBeenCalledWith(expectedEvent);
46+
expect(client.process).toHaveBeenCalledWith(expectedEvent, undefined);
4747
});
4848
});

packages/core/src/__tests__/methods/track.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,6 @@ describe('methods #track', () => {
4444
};
4545

4646
expect(client.process).toHaveBeenCalledTimes(1);
47-
expect(client.process).toHaveBeenCalledWith(expectedEvent);
47+
expect(client.process).toHaveBeenCalledWith(expectedEvent, undefined);
4848
});
4949
});

packages/core/src/analytics.ts

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@ import {
3535
Watchable,
3636
} from './storage';
3737
import { Timeline } from './timeline';
38-
import { DestinationFilters, EventType, SegmentAPISettings } from './types';
38+
import {
39+
DestinationFilters,
40+
EventType,
41+
SegmentAPISettings,
42+
SegmentAPIConsentSettings,
43+
EdgeFunctionSettings,
44+
EnrichmentClosure,
45+
} from './types';
3946
import {
4047
Config,
4148
Context,
@@ -59,7 +66,6 @@ import {
5966
SegmentError,
6067
translateHTTPError,
6168
} from './errors';
62-
import type { SegmentAPIConsentSettings } from '.';
6369

6470
type OnPluginAddedCallback = (plugin: Plugin) => void;
6571

@@ -125,6 +131,11 @@ export class SegmentClient {
125131
*/
126132
readonly consentSettings: Watchable<SegmentAPIConsentSettings | undefined>;
127133

134+
/**
135+
* Access or subscribe to edge functions settings
136+
*/
137+
readonly edgeFunctionSettings: Watchable<EdgeFunctionSettings | undefined>;
138+
128139
/**
129140
* Access or subscribe to destination filter settings
130141
*/
@@ -212,6 +223,11 @@ export class SegmentClient {
212223
onChange: this.store.consentSettings.onChange,
213224
};
214225

226+
this.edgeFunctionSettings = {
227+
get: this.store.edgeFunctionSettings.get,
228+
onChange: this.store.edgeFunctionSettings.onChange,
229+
};
230+
215231
this.filters = {
216232
get: this.store.filters.get,
217233
onChange: this.store.filters.onChange,
@@ -307,20 +323,26 @@ export class SegmentClient {
307323
const settingsEndpoint = `${settingsPrefix}/${this.config.writeKey}/settings`;
308324

309325
try {
310-
const res = await fetch(settingsEndpoint);
326+
const res = await fetch(settingsEndpoint, {
327+
headers: {
328+
'Cache-Control': 'no-cache',
329+
},
330+
});
311331
checkResponseForErrors(res);
312332

313333
const resJson: SegmentAPISettings =
314334
(await res.json()) as SegmentAPISettings;
315335
const integrations = resJson.integrations;
316336
const consentSettings = resJson.consentSettings;
337+
const edgeFunctionSettings = resJson.edgeFunction;
317338
const filters = this.generateFiltersMap(
318339
resJson.middlewareSettings?.routingRules ?? []
319340
);
320341
this.logger.info('Received settings from Segment succesfully.');
321342
await Promise.all([
322343
this.store.settings.set(integrations),
323344
this.store.consentSettings.set(consentSettings),
345+
this.store.edgeFunctionSettings.set(edgeFunctionSettings),
324346
this.store.filters.set(filters),
325347
]);
326348
} catch (e) {
@@ -422,8 +444,9 @@ export class SegmentClient {
422444
this.timeline.remove(plugin);
423445
}
424446

425-
async process(incomingEvent: SegmentEvent) {
447+
async process(incomingEvent: SegmentEvent, enrichment?: EnrichmentClosure) {
426448
const event = this.applyRawEventData(incomingEvent);
449+
event.enrichment = enrichment;
427450

428451
if (this.isReady.value) {
429452
return this.startTimelineProcessing(event);
@@ -536,47 +559,63 @@ export class SegmentClient {
536559
}
537560
}
538561

539-
async screen(name: string, options?: JsonMap) {
562+
async screen(
563+
name: string,
564+
options?: JsonMap,
565+
enrichment?: EnrichmentClosure
566+
) {
540567
const event = createScreenEvent({
541568
name,
542569
properties: options,
543570
});
544571

545-
await this.process(event);
572+
await this.process(event, enrichment);
546573
this.logger.info('SCREEN event saved', event);
547574
}
548575

549-
async track(eventName: string, options?: JsonMap) {
576+
async track(
577+
eventName: string,
578+
options?: JsonMap,
579+
enrichment?: EnrichmentClosure
580+
) {
550581
const event = createTrackEvent({
551582
event: eventName,
552583
properties: options,
553584
});
554585

555-
await this.process(event);
586+
await this.process(event, enrichment);
556587
this.logger.info('TRACK event saved', event);
557588
}
558589

559-
async identify(userId?: string, userTraits?: UserTraits) {
590+
async identify(
591+
userId?: string,
592+
userTraits?: UserTraits,
593+
enrichment?: EnrichmentClosure
594+
) {
560595
const event = createIdentifyEvent({
561596
userId: userId,
562597
userTraits: userTraits,
563598
});
564599

565-
await this.process(event);
600+
await this.process(event, enrichment);
566601
this.logger.info('IDENTIFY event saved', event);
567602
}
568603

569-
async group(groupId: string, groupTraits?: GroupTraits) {
604+
async group(
605+
groupId: string,
606+
groupTraits?: GroupTraits,
607+
enrichment?: EnrichmentClosure
608+
) {
570609
const event = createGroupEvent({
571610
groupId,
572611
groupTraits,
573612
});
574613

575-
await this.process(event);
614+
await this.process(event, enrichment);
576615
this.logger.info('GROUP event saved', event);
577616
}
578617

579-
async alias(newUserId: string) {
618+
async alias(newUserId: string, enrichment?: EnrichmentClosure) {
580619
// We don't use a concurrency safe version of get here as we don't want to lock the values yet,
581620
// we will update the values correctly when InjectUserInfo processes the change
582621
const { anonymousId, userId: previousUserId } = this.store.userInfo.get();
@@ -587,7 +626,7 @@ export class SegmentClient {
587626
newUserId,
588627
});
589628

590-
await this.process(event);
629+
await this.process(event, enrichment);
591630
this.logger.info('ALIAS event saved', event);
592631
}
593632

@@ -721,7 +760,11 @@ export class SegmentClient {
721760
* @param callback Function to call
722761
*/
723762
onPluginLoaded(callback: OnPluginAddedCallback) {
724-
this.onPluginAddedObservers.push(callback);
763+
const i = this.onPluginAddedObservers.push(callback);
764+
765+
return () => {
766+
this.onPluginAddedObservers.splice(i, 1);
767+
};
725768
}
726769

727770
private triggerOnPluginLoaded(plugin: Plugin) {

packages/core/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { defaultConfig } from './constants';
12
export * from './client';
23
export * from './plugin';
34
export * from './types';
@@ -12,8 +13,12 @@ export {
1213
objectToString,
1314
unknownToString,
1415
deepCompare,
16+
chunk,
1517
} from './util';
1618
export { SegmentClient } from './analytics';
19+
export { QueueFlushingPlugin } from './plugins/QueueFlushingPlugin';
20+
export { createTrackEvent } from './events';
21+
export { uploadEvents } from './api';
1722
export { SegmentDestination } from './plugins/SegmentDestination';
1823
export {
1924
type CategoryConsentStatusProvider,

0 commit comments

Comments
 (0)