Skip to content

Commit 673a062

Browse files
fix: Add support for TikTok and Google Integrations
1 parent b8a1a8f commit 673a062

File tree

3 files changed

+193
-16
lines changed

3 files changed

+193
-16
lines changed

src/integrationCapture.ts

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getCookies,
66
getHref,
77
isEmpty,
8+
valueof,
89
} from './utils';
910

1011
export interface IntegrationCaptureProcessorFunction {
@@ -48,32 +49,76 @@ export const facebookClickIdProcessor: IntegrationCaptureProcessorFunction = (
4849

4950
return `fb.${subdomainIndex}.${_timestamp}.${clickId}`;
5051
};
52+
53+
const IntegrationOutputs = {
54+
CUSTOM_FLAGS: 'custom_flags',
55+
PARTNER_IDENTITIES: 'partner_identities',
56+
} as const;
57+
58+
interface IntegrationMappingItem {
59+
mappedKey: string;
60+
output: valueof<typeof IntegrationOutputs>;
61+
processor?: IntegrationCaptureProcessorFunction;
62+
}
63+
5164
interface IntegrationIdMapping {
52-
[key: string]: {
53-
mappedKey: string;
54-
processor?: IntegrationCaptureProcessorFunction;
55-
};
65+
[key: string]: IntegrationMappingItem
5666
}
5767

5868
const integrationMapping: IntegrationIdMapping = {
69+
// Facebook / Meta
5970
fbclid: {
6071
mappedKey: 'Facebook.ClickId',
6172
processor: facebookClickIdProcessor,
73+
output: IntegrationOutputs.CUSTOM_FLAGS,
6274
},
6375
_fbp: {
6476
mappedKey: 'Facebook.BrowserId',
77+
output: IntegrationOutputs.CUSTOM_FLAGS,
6578
},
6679
_fbc: {
6780
mappedKey: 'Facebook.ClickId',
81+
output: IntegrationOutputs.CUSTOM_FLAGS,
82+
},
83+
84+
// GOOGLE
85+
gclid: {
86+
mappedKey: 'GoogleEnhancedConversions.Gclid',
87+
output: IntegrationOutputs.CUSTOM_FLAGS,
88+
},
89+
gbraid: {
90+
mappedKey: 'GoogleEnhancedConversions.Gbraid',
91+
output: IntegrationOutputs.CUSTOM_FLAGS,
92+
},
93+
wbraid: {
94+
mappedKey: 'GoogleEnhancedConversions.Wbraid',
95+
output: IntegrationOutputs.CUSTOM_FLAGS,
96+
},
97+
98+
// TIK TOK
99+
_ttp: {
100+
mappedKey: 'tiktok_cookie_id',
101+
output: IntegrationOutputs.PARTNER_IDENTITIES,
102+
},
103+
ttclid: {
104+
mappedKey: 'TikTok.ClickId',
105+
output: IntegrationOutputs.CUSTOM_FLAGS,
68106
},
69107
};
70108

71109
export default class IntegrationCapture {
72110
public clickIds: Dictionary<string>;
73111
public readonly initialTimestamp: number;
74112

113+
public readonly filteredPartnerIdentityMappings: IntegrationIdMapping;
114+
public readonly filteredCustomFlagMappings: IntegrationIdMapping;
115+
75116
constructor() {
76117
this.initialTimestamp = Date.now();
118+
119+
// Cache filtered mappings for faster access
120+
this.filteredPartnerIdentityMappings = this.filterMappings(IntegrationOutputs.PARTNER_IDENTITIES);
121+
this.filteredCustomFlagMappings = this.filterMappings(IntegrationOutputs.CUSTOM_FLAGS);
77122
}
78123

79124
/**
@@ -121,22 +166,42 @@ export default class IntegrationCapture {
121166
* @returns {SDKEventCustomFlags} The custom flags.
122167
*/
123168
public getClickIdsAsCustomFlags(): SDKEventCustomFlags {
124-
const customFlags: SDKEventCustomFlags = {};
169+
return this.getClickIds(this.clickIds, this.filteredCustomFlagMappings);
170+
}
171+
172+
/**
173+
* Converts the captured click IDs to partner identities.
174+
* @returns {Dictionary<string>} The partner identities.
175+
*/
176+
public getClickIdsAsPartnerIdentities(): Dictionary<string> {
177+
return this.getClickIds(this.clickIds, this.filteredPartnerIdentityMappings);
178+
}
125179

126-
if (!this.clickIds) {
127-
return customFlags;
180+
private getClickIds(
181+
clickIds: Dictionary<string>,
182+
mappingList: IntegrationIdMapping
183+
): Dictionary<string> {
184+
const mappedClickIds: Dictionary<string> = {};
185+
186+
if (!clickIds) {
187+
return mappedClickIds;
128188
}
129189

130-
for (const [key, value] of Object.entries(this.clickIds)) {
131-
const mappedKey = integrationMapping[key]?.mappedKey;
190+
for (const [key, value] of Object.entries(clickIds)) {
191+
const mappedKey = mappingList[key]?.mappedKey;
132192
if (!isEmpty(mappedKey)) {
133-
customFlags[mappedKey] = value;
193+
mappedClickIds[mappedKey] = value;
134194
}
135195
}
136-
return customFlags;
196+
197+
return mappedClickIds;
137198
}
138199

139-
private applyProcessors(clickIds: Dictionary<string>, url?: string, timestamp?: number): Dictionary<string> {
200+
private applyProcessors(
201+
clickIds: Dictionary<string>,
202+
url?: string,
203+
timestamp?: number
204+
): Dictionary<string> {
140205
const processedClickIds: Dictionary<string> = {};
141206

142207
for (const [key, value] of Object.entries(clickIds)) {
@@ -150,4 +215,14 @@ export default class IntegrationCapture {
150215

151216
return processedClickIds;
152217
}
218+
219+
private filterMappings(
220+
outputType: valueof<typeof IntegrationOutputs>
221+
): IntegrationIdMapping {
222+
return Object.fromEntries(
223+
Object.entries(integrationMapping).filter(
224+
([, value]) => value.output === outputType
225+
)
226+
);
227+
}
153228
}

src/sdkToEventsApiConverter.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@ import {
1313
SDKCCPAConsentState,
1414
} from './consent';
1515
import Types from './types';
16-
import { isEmpty } from './utils';
16+
import { Dictionary, isEmpty } from './utils';
1717
import { ISDKUserIdentity } from './identity-user-interfaces';
1818
import { SDKIdentityTypeEnum } from './identity.interfaces';
1919

20+
type PartnerIdentities = Dictionary<string>;
21+
22+
// FIXME: Event Models version of Batch references `partner_identity` as a string
23+
// when it should be a dictionary of strings called `partner_identities`
24+
interface Batch extends EventsApi.Batch {
25+
partner_identities?: PartnerIdentities;
26+
}
27+
2028
export function convertEvents(
2129
mpid: string,
2230
sdkEvents: SDKEvent[],
2331
mpInstance: MParticleWebSDK
24-
): EventsApi.Batch | null {
32+
): Batch | null {
2533
if (!mpid) {
2634
return null;
2735
}
@@ -56,7 +64,7 @@ export function convertEvents(
5664
currentConsentState = user.getConsentState();
5765
}
5866

59-
const upload: EventsApi.Batch = {
67+
const upload: Batch = {
6068
source_request_id: mpInstance._Helpers.generateUniqueId(),
6169
mpid,
6270
timestamp_unixtime_ms: new Date().getTime(),
@@ -102,6 +110,12 @@ export function convertEvents(
102110
},
103111
};
104112
}
113+
114+
const capturedPartnerIdentities: SDKPartnerIdentities = mpInstance._IntegrationCapture.getClickIdsAsPartnerIdentities();
115+
if (!isEmpty(capturedPartnerIdentities)) {
116+
upload.partner_identities = capturedPartnerIdentities;
117+
}
118+
105119
return upload;
106120
}
107121

test/jest/integration-capture.spec.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@ describe('Integration Capture', () => {
99
const integrationCapture = new IntegrationCapture();
1010
expect(integrationCapture.clickIds).toBeUndefined();
1111
});
12+
13+
it('should initialize with a filtered list of partner identity mappings', () => {
14+
const integrationCapture = new IntegrationCapture();
15+
const mappings = integrationCapture.filteredPartnerIdentityMappings;
16+
expect(Object.keys(mappings)).toEqual(['_ttp']);
17+
});
18+
19+
it('should initialize with a filtered list of custom flag mappings', () => {
20+
const integrationCapture = new IntegrationCapture();
21+
const mappings = integrationCapture.filteredCustomFlagMappings;
22+
expect(Object.keys(mappings)).toEqual([
23+
'fbclid',
24+
'_fbp',
25+
'_fbc',
26+
'gclid',
27+
'gbraid',
28+
'wbraid',
29+
'ttclid',
30+
]);
31+
});
1232
});
1333

1434
describe('#capture', () => {
@@ -65,6 +85,7 @@ describe('Integration Capture', () => {
6585
});
6686
});
6787

88+
describe('Facebook Click Ids', () => {
6889
it('should format fbclid correctly', () => {
6990
jest.spyOn(Date, 'now').mockImplementation(() => 42);
7091

@@ -147,7 +168,7 @@ describe('Integration Capture', () => {
147168
fbclid: 'fb.2.42.12345',
148169
});
149170
});
150-
171+
});
151172
});
152173

153174
describe('#captureQueryParams', () => {
@@ -184,6 +205,8 @@ describe('Integration Capture', () => {
184205

185206
expect(clickIds).toEqual({
186207
fbclid: 'fb.2.42.67890',
208+
ttclid: '12345',
209+
gclid: '54321',
187210
});
188211
});
189212

@@ -253,19 +276,63 @@ describe('Integration Capture', () => {
253276
});
254277
});
255278

279+
describe('#getClickIdsAsPartnerIdentites', () => {
280+
it('should return clickIds as partner identities', () => {
281+
const integrationCapture = new IntegrationCapture();
282+
integrationCapture.clickIds = {
283+
_ttp: '1234123999.123123',
284+
};
285+
286+
const partnerIdentities = integrationCapture.getClickIdsAsPartnerIdentities();
287+
288+
expect(partnerIdentities).toEqual({
289+
tiktok_cookie_id: '1234123999.123123',
290+
});
291+
});
292+
293+
it('should return empty object if clickIds is empty or undefined', () => {
294+
const integrationCapture = new IntegrationCapture();
295+
const partnerIdentities = integrationCapture.getClickIdsAsPartnerIdentities();
296+
297+
expect(partnerIdentities).toEqual({});
298+
});
299+
300+
it.only('should only return mapped clickIds as partner identities', () => {
301+
const integrationCapture = new IntegrationCapture();
302+
integrationCapture.clickIds = {
303+
fbclid: '67890',
304+
_fbp: '54321',
305+
ttclid: '12345',
306+
_ttp: '1234123999.123123',
307+
gclid: '123233.23131',
308+
invalidId: '12345',
309+
};
310+
311+
const partnerIdentities = integrationCapture.getClickIdsAsPartnerIdentities();
312+
313+
expect(partnerIdentities).toEqual({
314+
tiktok_cookie_id: '1234123999.123123',
315+
});
316+
});
317+
});
318+
256319
describe('#getClickIdsAsCustomFlags', () => {
257320
it('should return clickIds as custom flags', () => {
258321
const integrationCapture = new IntegrationCapture();
259322
integrationCapture.clickIds = {
260323
fbclid: '67890',
261324
_fbp: '54321',
325+
ttclid: '12345',
326+
gclid: '123233.23131',
262327
};
263328

264329
const customFlags = integrationCapture.getClickIdsAsCustomFlags();
265330

266331
expect(customFlags).toEqual({
267332
'Facebook.ClickId': '67890',
268333
'Facebook.BrowserId': '54321',
334+
'TikTok.ClickId': '12345',
335+
'GoogleEnhancedConversions.Gclid': '123233.23131',
269336
});
270337
});
271338

@@ -275,6 +342,27 @@ describe('Integration Capture', () => {
275342

276343
expect(customFlags).toEqual({});
277344
});
345+
346+
it('should only return mapped clickIds as custom flags', () => {
347+
const integrationCapture = new IntegrationCapture();
348+
integrationCapture.clickIds = {
349+
fbclid: '67890',
350+
_fbp: '54321',
351+
_ttp: '0823422223.23234',
352+
ttclid: '12345',
353+
gclid: '123233.23131',
354+
invalidId: '12345',
355+
};
356+
357+
const customFlags = integrationCapture.getClickIdsAsCustomFlags();
358+
359+
expect(customFlags).toEqual({
360+
'Facebook.ClickId': '67890',
361+
'Facebook.BrowserId': '54321',
362+
'TikTok.ClickId': '12345',
363+
'GoogleEnhancedConversions.Gclid': '123233.23131',
364+
});
365+
});
278366
});
279367

280368
describe('#facebookClickIdProcessor', () => {

0 commit comments

Comments
 (0)