Skip to content

Commit 5cfbf34

Browse files
committed
fix: Append domain query parameter to Trade Desk cookie syncs
1 parent 3cb1319 commit 5cfbf34

File tree

4 files changed

+265
-5
lines changed

4 files changed

+265
-5
lines changed

src/cookieSyncManager.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ const { InformationMessages } = Messages;
1313

1414
export const DAYS_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
1515

16+
// Partner module IDs for cookie sync configurations
17+
export const PARTNER_MODULE_IDS = {
18+
AdobeEventForwarder: 11,
19+
DoubleclickDFP: 41,
20+
AppNexus: 50,
21+
Lotame: 58,
22+
TradeDesk: 103,
23+
VerizonMedia: 155,
24+
} as const;
25+
1626
export type CookieSyncDates = Dictionary<number>;
1727

1828
export interface IPixelConfiguration {
@@ -112,7 +122,9 @@ export default function CookieSyncManager(
112122
}
113123

114124
// Url for cookie sync pixel
115-
const fullUrl = createCookieSyncUrl(mpid, pixelUrl, redirectUrl)
125+
// Add domain parameter for Trade Desk
126+
const domain = moduleId === PARTNER_MODULE_IDS.TradeDesk ? window.location.hostname : undefined;
127+
const fullUrl = createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain);
116128

117129
self.performCookieSync(
118130
fullUrl,

src/utils.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,18 +206,28 @@ const replaceAmpWithAmpersand = (value: string): string => value.replace(/&amp;/
206206
const createCookieSyncUrl = (
207207
mpid: MPID,
208208
pixelUrl: string,
209-
redirectUrl?: string
209+
redirectUrl?: string,
210+
domain?: string
210211
): string => {
211212
const modifiedPixelUrl = replaceAmpWithAmpersand(pixelUrl);
212213
const modifiedDirectUrl = redirectUrl
213214
? replaceAmpWithAmpersand(redirectUrl)
214215
: null;
215216

216-
const url = replaceMPID(modifiedPixelUrl, mpid);
217+
let url = replaceMPID(modifiedPixelUrl, mpid);
218+
217219
const redirect = modifiedDirectUrl
218220
? replaceMPID(modifiedDirectUrl, mpid)
219221
: '';
220-
return url + encodeURIComponent(redirect);
222+
223+
let fullUrl = url + encodeURIComponent(redirect);
224+
225+
if (domain) {
226+
const separator = fullUrl.includes('?') ? '&' : '?';
227+
fullUrl += `${separator}domain=${domain}`;
228+
}
229+
230+
return fullUrl;
221231
};
222232

223233
// FIXME: REFACTOR for V3

test/jest/cookieSyncManager.spec.ts

Lines changed: 213 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import CookieSyncManager, {
22
DAYS_IN_MILLISECONDS,
33
IPixelConfiguration,
44
CookieSyncDates,
5-
isLastSyncDateExpired
5+
isLastSyncDateExpired,
6+
PARTNER_MODULE_IDS
67
} from '../../src/cookieSyncManager';
78
import { IMParticleWebSDKInstance } from '../../src/mp-instance';
89
import { testMPID } from '../src/config/constants';
@@ -425,6 +426,217 @@ describe('CookieSyncManager', () => {
425426

426427
expect(cookieSyncManager.performCookieSync).not.toHaveBeenCalled();
427428
});
429+
430+
describe('Trade Desk domain parameter', () => {
431+
const originalLocation = window.location;
432+
433+
beforeEach(() => {
434+
// Mock window.location.hostname
435+
delete (window as any).location;
436+
(window as any).location = { ...originalLocation, hostname: 'example.com' };
437+
});
438+
439+
afterEach(() => {
440+
(window as any).location = originalLocation;
441+
});
442+
443+
it('should add domain parameter for Trade Desk (module ID 103)', () => {
444+
const tradeDeskPixelSettings: IPixelConfiguration = {
445+
...pixelSettings,
446+
moduleId: PARTNER_MODULE_IDS.TradeDesk, // 103
447+
pixelUrl: 'https://insight.adsrvr.org/track/up?adv=abc123',
448+
redirectUrl: '',
449+
};
450+
451+
const mockMPInstance = ({
452+
_Store: {
453+
webviewBridgeEnabled: false,
454+
pixelConfigurations: [tradeDeskPixelSettings],
455+
},
456+
_Persistence: {
457+
getPersistence: () => ({testMPID: {
458+
csd: {}
459+
}}),
460+
},
461+
_Consent: {
462+
isEnabledForUserConsent: jest.fn().mockReturnValue(true),
463+
},
464+
Identity: {
465+
getCurrentUser: jest.fn().mockReturnValue({
466+
getMPID: () => testMPID,
467+
}),
468+
},
469+
} as unknown) as IMParticleWebSDKInstance;
470+
471+
const cookieSyncManager = new CookieSyncManager(mockMPInstance);
472+
cookieSyncManager.performCookieSync = jest.fn();
473+
474+
cookieSyncManager.attemptCookieSync(testMPID, true);
475+
476+
expect(cookieSyncManager.performCookieSync).toHaveBeenCalledWith(
477+
'https://insight.adsrvr.org/track/up?adv=abc123&domain=example.com',
478+
'103',
479+
testMPID,
480+
{},
481+
);
482+
});
483+
484+
it('should not add domain parameter for non-Trade Desk partners', () => {
485+
const nonTradeDeskPixelSettings: IPixelConfiguration = {
486+
...pixelSettings,
487+
moduleId: PARTNER_MODULE_IDS.AppNexus, // 50
488+
pixelUrl: 'https://ib.adnxs.com/cookie_sync?adv=abc123',
489+
redirectUrl: '',
490+
};
491+
492+
const mockMPInstance = ({
493+
_Store: {
494+
webviewBridgeEnabled: false,
495+
pixelConfigurations: [nonTradeDeskPixelSettings],
496+
},
497+
_Persistence: {
498+
getPersistence: () => ({testMPID: {
499+
csd: {}
500+
}}),
501+
},
502+
_Consent: {
503+
isEnabledForUserConsent: jest.fn().mockReturnValue(true),
504+
},
505+
Identity: {
506+
getCurrentUser: jest.fn().mockReturnValue({
507+
getMPID: () => testMPID,
508+
}),
509+
},
510+
} as unknown) as IMParticleWebSDKInstance;
511+
512+
const cookieSyncManager = new CookieSyncManager(mockMPInstance);
513+
cookieSyncManager.performCookieSync = jest.fn();
514+
515+
cookieSyncManager.attemptCookieSync(testMPID, true);
516+
517+
expect(cookieSyncManager.performCookieSync).toHaveBeenCalledWith(
518+
'https://ib.adnxs.com/cookie_sync?adv=abc123',
519+
'50',
520+
testMPID,
521+
{},
522+
);
523+
});
524+
525+
it('should handle multiple pixel configurations with mixed Trade Desk and non-Trade Desk', () => {
526+
const tradeDeskPixelSettings: IPixelConfiguration = {
527+
...pixelSettings,
528+
moduleId: PARTNER_MODULE_IDS.TradeDesk,
529+
pixelUrl: 'https://insight.adsrvr.org/track/up?adv=ttd123',
530+
redirectUrl: '',
531+
};
532+
533+
const appNexusPixelSettings: IPixelConfiguration = {
534+
...pixelSettings,
535+
moduleId: PARTNER_MODULE_IDS.AppNexus,
536+
pixelUrl: 'https://ib.adnxs.com/cookie_sync?adv=anx123',
537+
redirectUrl: '',
538+
};
539+
540+
const mockMPInstance = ({
541+
_Store: {
542+
webviewBridgeEnabled: false,
543+
pixelConfigurations: [tradeDeskPixelSettings, appNexusPixelSettings],
544+
},
545+
_Persistence: {
546+
getPersistence: () => ({testMPID: {
547+
csd: {}
548+
}}),
549+
},
550+
_Consent: {
551+
isEnabledForUserConsent: jest.fn().mockReturnValue(true),
552+
},
553+
Identity: {
554+
getCurrentUser: jest.fn().mockReturnValue({
555+
getMPID: () => testMPID,
556+
}),
557+
},
558+
} as unknown) as IMParticleWebSDKInstance;
559+
560+
const cookieSyncManager = new CookieSyncManager(mockMPInstance);
561+
cookieSyncManager.performCookieSync = jest.fn();
562+
563+
cookieSyncManager.attemptCookieSync(testMPID, true);
564+
565+
expect(cookieSyncManager.performCookieSync).toHaveBeenCalledTimes(2);
566+
567+
// Check Trade Desk call (with domain)
568+
expect(cookieSyncManager.performCookieSync).toHaveBeenCalledWith(
569+
'https://insight.adsrvr.org/track/up?adv=ttd123&domain=example.com',
570+
'103',
571+
testMPID,
572+
{},
573+
);
574+
575+
// Check AppNexus call (without domain)
576+
expect(cookieSyncManager.performCookieSync).toHaveBeenCalledWith(
577+
'https://ib.adnxs.com/cookie_sync?adv=anx123',
578+
'50',
579+
testMPID,
580+
{},
581+
);
582+
});
583+
584+
it('should handle domain parameter with hyphens and subdomains', () => {
585+
// Mock a hostname with hyphens and subdomains
586+
delete (window as any).location;
587+
(window as any).location = { ...originalLocation, hostname: 'sub-domain.example.com' };
588+
589+
const tradeDeskPixelSettings: IPixelConfiguration = {
590+
...pixelSettings,
591+
moduleId: PARTNER_MODULE_IDS.TradeDesk,
592+
pixelUrl: 'https://insight.adsrvr.org/track/up',
593+
redirectUrl: '',
594+
};
595+
596+
const mockMPInstance = ({
597+
_Store: {
598+
webviewBridgeEnabled: false,
599+
pixelConfigurations: [tradeDeskPixelSettings],
600+
},
601+
_Persistence: {
602+
getPersistence: () => ({testMPID: {
603+
csd: {}
604+
}}),
605+
},
606+
_Consent: {
607+
isEnabledForUserConsent: jest.fn().mockReturnValue(true),
608+
},
609+
Identity: {
610+
getCurrentUser: jest.fn().mockReturnValue({
611+
getMPID: () => testMPID,
612+
}),
613+
},
614+
} as unknown) as IMParticleWebSDKInstance;
615+
616+
const cookieSyncManager = new CookieSyncManager(mockMPInstance);
617+
cookieSyncManager.performCookieSync = jest.fn();
618+
619+
cookieSyncManager.attemptCookieSync(testMPID, true);
620+
621+
expect(cookieSyncManager.performCookieSync).toHaveBeenCalledWith(
622+
'https://insight.adsrvr.org/track/up?domain=sub-domain.example.com',
623+
'103',
624+
testMPID,
625+
{},
626+
);
627+
});
628+
});
629+
});
630+
631+
describe('PARTNER_MODULE_IDS', () => {
632+
it('should contain all expected partner module IDs', () => {
633+
expect(PARTNER_MODULE_IDS.AdobeEventForwarder).toBe(11);
634+
expect(PARTNER_MODULE_IDS.DoubleclickDFP).toBe(41);
635+
expect(PARTNER_MODULE_IDS.AppNexus).toBe(50);
636+
expect(PARTNER_MODULE_IDS.Lotame).toBe(58);
637+
expect(PARTNER_MODULE_IDS.TradeDesk).toBe(103);
638+
expect(PARTNER_MODULE_IDS.VerizonMedia).toBe(155);
639+
});
428640
});
429641

430642
describe('#performCookieSync', () => {

test/jest/utils.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,32 @@ describe('Utils', () => {
240240
it('should return a cookieSyncUrl when pixelUrl is not null but redirectUrl is null', () => {
241241
expect(createCookieSyncUrl('testMPID', pixelUrl, null)).toBe('https://abc.abcdex.net/ibs:exampleid=12345&exampleuuid=testMPID&redir=');
242242
});
243+
244+
it('should add domain parameter when provided', () => {
245+
const simplePixelUrl = 'https://test.com/pixel';
246+
expect(createCookieSyncUrl('testMPID', simplePixelUrl, null, 'example.com')).toBe('https://test.com/pixel?domain=example.com');
247+
});
248+
249+
it('should handle domain parameter with hyphens and dots', () => {
250+
const simplePixelUrl = 'https://test.com/pixel';
251+
expect(createCookieSyncUrl('testMPID', simplePixelUrl, null, 'sub-domain.example.com')).toBe('https://test.com/pixel?domain=sub-domain.example.com');
252+
});
253+
254+
it('should handle domain parameter with redirect URL', () => {
255+
const simplePixelUrl = 'https://test.com/pixel';
256+
const simpleRedirectUrl = 'https://redirect.com/callback';
257+
expect(createCookieSyncUrl('testMPID', simplePixelUrl, simpleRedirectUrl, 'example.com')).toBe('https://test.com/pixelhttps%3A%2F%2Fredirect.com%2Fcallback?domain=example.com');
258+
});
259+
260+
it('should not add domain parameter when domain is undefined', () => {
261+
const simplePixelUrl = 'https://test.com/pixel';
262+
expect(createCookieSyncUrl('testMPID', simplePixelUrl, null, undefined)).toBe('https://test.com/pixel');
263+
});
264+
265+
it('should not add domain parameter when domain is empty string', () => {
266+
const simplePixelUrl = 'https://test.com/pixel';
267+
expect(createCookieSyncUrl('testMPID', simplePixelUrl, null, '')).toBe('https://test.com/pixel');
268+
});
243269
});
244270

245271
describe('#inArray', () => {

0 commit comments

Comments
 (0)