Skip to content

Commit 98ce3a0

Browse files
dmytro-poDimaIntentIQdLepetynskyiIntentiqeyvazahmadzada
authored
IntentIq ID & Analytics Modules: Updates for adType, placementId and sync request (prebid#12903)
* AGT-403: Add adType parameter to payload in report * AGT-403: Test for partner report, adType parameter * AGT-403: Test refactoring * AGT-403: Documentation updated * IntentIq Analytics Module: adUnitCode or placemetId to report (#9) * AGT-446: adUnitCode or placemetId to report * AGT-446: Description of changes, example * AGT-446: Fix documentation * AGT-446: Changes after review * Agt 409 full url in prebid modules (#8) * add sync * update full url * set url param * fix comment * fix issue fix sync mode * update url * update full url * add test * move browser_blacklist (#10) * add is optedOut (#11) * update date in FPD after sync (#12) * update sync logic for new user (#13) * fix unit tests --------- Co-authored-by: DimaIntentIQ <[email protected]> Co-authored-by: dLepetynskyiIntentiq <[email protected]> Co-authored-by: DimaIntentIQ <[email protected]> Co-authored-by: Eyvaz <[email protected]>
1 parent d5cb105 commit 98ce3a0

9 files changed

+393
-61
lines changed

libraries/intentIqConstants/intentIqConstants.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const FIRST_PARTY_KEY = '_iiq_fdata';
2+
23
export const SUPPORTED_TYPES = ['html5', 'cookie']
34

45
export const WITH_IIQ = 'A';
@@ -8,4 +9,21 @@ export const BLACK_LIST = 'L';
89
export const CLIENT_HINTS_KEY = '_iiq_ch';
910
export const EMPTY = 'EMPTY';
1011
export const GVLID = '1323';
11-
export const VERSION = 0.27;
12+
export const VERSION = 0.28;
13+
14+
export const VR_ENDPOINT = 'https://api.intentiq.com';
15+
export const GDPR_ENDPOINT = 'https://api-gdpr.intentiq.com';
16+
export const INVALID_ID = 'INVALID_ID';
17+
18+
export const SYNC_ENDPOINT = 'https://sync.intentiq.com'
19+
export const GDPR_SYNC_ENDPOINT = 'https://sync-gdpr.intentiq.com'
20+
export const SCREEN_PARAMS = {
21+
0: 'windowInnerHeight',
22+
1: 'windowInnerWidth',
23+
2: 'devicePixelRatio',
24+
3: 'windowScreenHeight',
25+
4: 'windowScreenWidth',
26+
5: 'language'
27+
};
28+
29+
export const SYNC_REFRESH_MILL = 3600000;

libraries/intentIqUtils/getRefferer.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ import { getWindowTop, logError, getWindowLocation, getWindowSelf } from '../../
66
*/
77
export function getReferrer() {
88
try {
9-
if (getWindowSelf() === getWindowTop()) {
10-
return encodeURIComponent(getWindowLocation().href);
11-
} else {
12-
return encodeURIComponent(getWindowTop().location.href);
9+
const url = getWindowSelf() === getWindowTop()
10+
? getWindowLocation().href
11+
: getWindowTop().location.href;
12+
13+
if (url.length >= 50) {
14+
const { origin } = new URL(url);
15+
return origin;
1316
}
17+
18+
return url;
1419
} catch (error) {
1520
logError(`Error accessing location: ${error}`);
1621
return '';
@@ -26,7 +31,7 @@ export function getReferrer() {
2631
* @return {string} The modified URL with appended `vrref` or `fui` parameters.
2732
*/
2833
export function appendVrrefAndFui(url, domainName) {
29-
const fullUrl = getReferrer();
34+
const fullUrl = encodeURIComponent(getReferrer());
3035
if (fullUrl) {
3136
return (url += '&vrref=' + getRelevantRefferer(domainName, fullUrl));
3237
}

libraries/intentIqUtils/getSyncKey.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const SYNC_KEY = (partner) => '_iiq_sync' + '_' + partner

modules/intentIqAnalyticsAdapter.js

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ const PARAMS_NAMES = {
5555
prebidVersion: 'pbjsver',
5656
partnerId: 'partnerId',
5757
firstPartyId: 'pcid',
58-
placementId: 'placementId'
58+
placementId: 'placementId',
59+
adType: 'adType'
5960
};
6061

6162
let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({defaultUrl: REPORT_ENDPOINT, analyticsType}), {
@@ -129,7 +130,7 @@ function initReadLsIds() {
129130
iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause = pData.terminationCause
130131
iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = pData.data;
131132
iiqAnalyticsAnalyticsAdapter.initOptions.eidl = pData.eidl || -1;
132-
iiqAnalyticsAnalyticsAdapter.initOptions.ct = pData.ct || null;
133+
iiqAnalyticsAnalyticsAdapter.initOptions.clientType = pData.clientType || null;
133134
iiqAnalyticsAnalyticsAdapter.initOptions.siteId = pData.siteId || null;
134135
iiqAnalyticsAnalyticsAdapter.initOptions.wsrvcll = pData.wsrvcll || false;
135136
iiqAnalyticsAnalyticsAdapter.initOptions.rrtt = pData.rrtt || null;
@@ -188,7 +189,7 @@ export function preparePayload(data) {
188189
result[PARAMS_NAMES.referrer] = getReferrer();
189190
result[PARAMS_NAMES.terminationCause] = iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause;
190191
result[PARAMS_NAMES.abTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup;
191-
result[PARAMS_NAMES.clientType] = iiqAnalyticsAnalyticsAdapter.initOptions.ct;
192+
result[PARAMS_NAMES.clientType] = iiqAnalyticsAnalyticsAdapter.initOptions.clientType;
192193
result[PARAMS_NAMES.siteId] = iiqAnalyticsAnalyticsAdapter.initOptions.siteId;
193194
result[PARAMS_NAMES.wasServerCalled] = iiqAnalyticsAnalyticsAdapter.initOptions.wsrvcll;
194195
result[PARAMS_NAMES.requestRtt] = iiqAnalyticsAnalyticsAdapter.initOptions.rrtt;
@@ -214,6 +215,8 @@ function fillEidsData(result) {
214215
}
215216

216217
function prepareData (data, result) {
218+
const adTypeValue = data.adType || data.mediaType;
219+
217220
if (data.bidderCode) {
218221
result.bidderCode = data.bidderCode;
219222
}
@@ -235,30 +238,52 @@ function prepareData (data, result) {
235238
if (data.auctionId) {
236239
result.prebidAuctionId = data.auctionId;
237240
}
238-
if (data.placementId) {
239-
result.placementId = data.placementId;
240-
} else {
241-
// Simplified placementId determination
242-
let placeIdFound = false;
243-
if (data.params && Array.isArray(data.params)) {
244-
for (let i = 0; i < data.params.length; i++) {
245-
const param = data.params[i];
246-
if (param.placementId) {
247-
result.placementId = param.placementId;
248-
placeIdFound = true;
249-
break;
250-
}
251-
}
252-
}
253-
if (!placeIdFound && data.adUnitCode) {
254-
result.placementId = data.adUnitCode;
255-
}
241+
if (adTypeValue) {
242+
result[PARAMS_NAMES.adType] = adTypeValue;
243+
}
244+
const iiqConfig = getIntentIqConfig();
245+
const adUnitConfig = iiqConfig.params?.adUnitConfig;
246+
247+
switch (adUnitConfig) {
248+
case 1:
249+
// adUnitCode or placementId
250+
result.placementId = data.adUnitCode || extractPlacementId(data) || '';
251+
break;
252+
case 2:
253+
// placementId or adUnitCode
254+
result.placementId = extractPlacementId(data) || data.adUnitCode || '';
255+
break;
256+
case 3:
257+
// Only adUnitCode
258+
result.placementId = data.adUnitCode || '';
259+
break;
260+
case 4:
261+
// Only placementId
262+
result.placementId = extractPlacementId(data) || '';
263+
break;
264+
default:
265+
// Default (like in case #1)
266+
result.placementId = data.adUnitCode || extractPlacementId(data) || '';
256267
}
257268

258269
result.biddingPlatformId = 1;
259270
result.partnerAuctionId = 'BW';
260271
}
261272

273+
function extractPlacementId(data) {
274+
if (data.placementId) {
275+
return data.placementId;
276+
}
277+
if (data.params && Array.isArray(data.params)) {
278+
for (let i = 0; i < data.params.length; i++) {
279+
if (data.params[i].placementId) {
280+
return data.params[i].placementId;
281+
}
282+
}
283+
}
284+
return null;
285+
}
286+
262287
function getDefaultDataObject() {
263288
return {
264289
'inbbl': false,

modules/intentIqAnalyticsAdapter.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ currency: 'USD', // Currency for the CPM value.
6060
originalCpm: 1.5, // Original CPM value.
6161
originalCurrency: 'USD', // Original currency.
6262
status: 'rendered', // Auction status, e.g., 'rendered'.
63-
placementId: 'div-1' // ID of the ad placement.
63+
placementId: 'div-1', // ID of the ad placement.
64+
adType: 'banner' // Specifies the type of ad served
6465
}
6566
```
6667

@@ -76,6 +77,7 @@ placementId: 'div-1' // ID of the ad placement.
7677
| originalCurrency | String | Currency of the original auction | USD | No |
7778
| status | String | Status of the impression. Leave empty or undefined if Prebid is not the bidding platform | rendered | No |
7879
| placementId | String | Unique identifier of the ad unit on the webpage that showed this ad | div-1 | No |
80+
| adType | String | Specifies the type of ad served. Possible values: “banner“, “video“, “native“, “audio“. | banner | No |
7981

8082

8183
To report the auction win, call the function as follows:

modules/intentIqIdSystem.js

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import {
2222
CLIENT_HINTS_KEY,
2323
EMPTY,
2424
GVLID,
25-
VERSION,
25+
VERSION, INVALID_ID, GDPR_ENDPOINT, VR_ENDPOINT, SYNC_ENDPOINT, SCREEN_PARAMS, GDPR_SYNC_ENDPOINT, SYNC_REFRESH_MILL
2626
} from '../libraries/intentIqConstants/intentIqConstants.js';
27+
import {SYNC_KEY} from '../libraries/intentIqUtils/getSyncKey.js';
2728

2829
/**
2930
* @typedef {import('../modules/userId/index.js').Submodule} Submodule
@@ -44,9 +45,7 @@ const encoderCH = {
4445
wow64: 7,
4546
fullVersionList: 8
4647
};
47-
const INVALID_ID = 'INVALID_ID';
48-
const ENDPOINT = 'https://api.intentiq.com';
49-
const GDPR_ENDPOINT = 'https://api-gdpr.intentiq.com';
48+
5049
export let firstPartyData;
5150

5251
/**
@@ -82,6 +81,89 @@ export function decryptData(encryptedText) {
8281
return bytes.toString(Utf8);
8382
}
8483

84+
function collectDeviceInfo() {
85+
return {
86+
windowInnerHeight: window.innerHeight,
87+
windowInnerWidth: window.innerWidth,
88+
devicePixelRatio: window.devicePixelRatio,
89+
windowScreenHeight: window.screen.height,
90+
windowScreenWidth: window.screen.width,
91+
language: navigator.language
92+
}
93+
}
94+
95+
function addUniquenessToUrl(url) {
96+
url += '&tsrnd=' + Math.floor(Math.random() * 1000) + '_' + new Date().getTime();
97+
return url;
98+
}
99+
100+
function appendDeviceInfoToUrl(url, deviceInfo) {
101+
const screenParamsString = Object.entries(SCREEN_PARAMS)
102+
.map(([index, param]) => {
103+
const value = (deviceInfo)[param];
104+
return `${index}:${value}`;
105+
})
106+
.join(',');
107+
108+
url += `&cz=${encodeURIComponent(screenParamsString)}`;
109+
url += `&dw=${deviceInfo.windowScreenWidth}&dh=${deviceInfo.windowScreenHeight}&dpr=${deviceInfo.devicePixelRatio}&lan=${deviceInfo.language}`;
110+
return url;
111+
}
112+
113+
function appendFirstPartyData (url, firstPartyData, partnerData) {
114+
url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : '';
115+
url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : '';
116+
url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : '';
117+
return url
118+
}
119+
120+
function appendCMPData (url, cmpData) {
121+
url += cmpData.uspString ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : '';
122+
url += cmpData.gppString ? '&gpp=' + encodeURIComponent(cmpData.gppString) : '';
123+
url += cmpData.gdprApplies
124+
? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString) + '&gdpr=1'
125+
: '&gdpr=0';
126+
return url
127+
}
128+
129+
export function createPixelUrl(firstPartyData, clientHints, configParams, partnerData, cmpData) {
130+
const deviceInfo = collectDeviceInfo()
131+
132+
let url = cmpData.gdprString ? GDPR_SYNC_ENDPOINT : SYNC_ENDPOINT;
133+
url += '/profiles_engine/ProfilesEngineServlet?at=20&mi=10&secure=1'
134+
url += '&dpi=' + configParams.partner;
135+
url = appendFirstPartyData(url, firstPartyData, partnerData);
136+
url = addUniquenessToUrl(url);
137+
url += partnerData?.clientType ? '&idtype=' + partnerData.clientType : '';
138+
if (deviceInfo) url = appendDeviceInfoToUrl(url, deviceInfo)
139+
url += VERSION ? '&jsver=' + VERSION : '';
140+
if (clientHints) url += '&uh=' + encodeURIComponent(clientHints);
141+
url = appendVrrefAndFui(url, configParams.domainName);
142+
url = appendCMPData(url, cmpData)
143+
return url;
144+
}
145+
146+
function sendSyncRequest(allowedStorage, url, partner, firstPartyData, newUser) {
147+
const lastSyncDate = Number(readData(SYNC_KEY(partner) || '', allowedStorage)) || false;
148+
const lastSyncElapsedTime = Date.now() - lastSyncDate
149+
150+
if (firstPartyData.isOptedOut) {
151+
const needToDoSync = (Date.now() - (firstPartyData?.date || firstPartyData?.sCal || Date.now())) > SYNC_REFRESH_MILL
152+
if (newUser || needToDoSync) {
153+
ajax(url, () => {
154+
}, undefined, {method: 'GET', withCredentials: true});
155+
if (firstPartyData?.date) {
156+
firstPartyData.date = Date.now()
157+
storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage, firstPartyData);
158+
}
159+
}
160+
} else if (!lastSyncDate || lastSyncElapsedTime > SYNC_REFRESH_MILL) {
161+
storeData(SYNC_KEY(partner), Date.now() + '', allowedStorage);
162+
ajax(url, () => {
163+
}, undefined, {method: 'GET', withCredentials: true});
164+
}
165+
}
166+
85167
/**
86168
* Parse json if possible, else return null
87169
* @param data
@@ -161,6 +243,7 @@ export const intentIqIdSubmodule = {
161243
decode(value) {
162244
return value && value != '' && INVALID_ID != value ? {'intentIqId': value} : undefined;
163245
},
246+
164247
/**
165248
* performs action to obtain id and return a value in the callback's response argument
166249
* @function
@@ -210,13 +293,7 @@ export const intentIqIdSubmodule = {
210293

211294
const currentBrowserLowerCase = detectBrowser();
212295
const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : '';
213-
214-
// Check if current browser is in blacklist
215-
if (browserBlackList?.includes(currentBrowserLowerCase)) {
216-
logError('User ID - intentIqId submodule: browser is in blacklist!');
217-
if (configParams.callback) configParams.callback('', BLACK_LIST);
218-
return;
219-
}
296+
let newUser = false;
220297

221298
if (!firstPartyData?.pcid) {
222299
const firstPartyId = generateGUID();
@@ -230,6 +307,7 @@ export const intentIqIdSubmodule = {
230307
gdprString: EMPTY,
231308
date: Date.now()
232309
};
310+
newUser = true;
233311
storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage, firstPartyData);
234312
} else if (!firstPartyData.pcidDate) {
235313
firstPartyData.pcidDate = Date.now();
@@ -284,7 +362,6 @@ export const intentIqIdSubmodule = {
284362
firstPartyData.uspString = cmpData.uspString;
285363
firstPartyData.gppString = cmpData.gppString;
286364
firstPartyData.gdprString = cmpData.gdprString;
287-
firstPartyData.date = Date.now();
288365
shouldCallServer = true;
289366
storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage, firstPartyData);
290367
storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData);
@@ -297,26 +374,29 @@ export const intentIqIdSubmodule = {
297374
firePartnerCallback()
298375
}
299376

377+
// Check if current browser is in blacklist
378+
if (browserBlackList?.includes(currentBrowserLowerCase)) {
379+
logError('User ID - intentIqId submodule: browser is in blacklist! Data will be not provided.');
380+
if (configParams.callback) configParams.callback('', BLACK_LIST);
381+
const url = createPixelUrl(firstPartyData, clientHints, configParams, partnerData, cmpData)
382+
sendSyncRequest(allowedStorage, url, configParams.partner, firstPartyData, newUser)
383+
return
384+
}
385+
300386
if (!shouldCallServer) {
301387
if (isGroupB) runtimeEids = { eids: [] };
302388
firePartnerCallback();
303389
return { id: runtimeEids.eids };
304390
}
305391

306392
// use protocol relative urls for http or https
307-
let url = `${gdprDetected ? GDPR_ENDPOINT : ENDPOINT}/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`;
393+
let url = `${gdprDetected ? GDPR_ENDPOINT : VR_ENDPOINT}/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`;
308394
url += configParams.pcid ? '&pcid=' + encodeURIComponent(configParams.pcid) : '';
309395
url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : '';
310-
url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : '';
311-
url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : '';
396+
url = appendFirstPartyData(url, firstPartyData, partnerData);
312397
url += (partnerData.cttl) ? '&cttl=' + encodeURIComponent(partnerData.cttl) : '';
313398
url += (partnerData.rrtt) ? '&rrtt=' + encodeURIComponent(partnerData.rrtt) : '';
314-
url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : '';
315-
url += cmpData.uspString ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : '';
316-
url += cmpData.gppString ? '&gpp=' + encodeURIComponent(cmpData.gppString) : '';
317-
url += cmpData.gdprApplies
318-
? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString) + '&gdpr=1'
319-
: '&gdpr=0';
399+
url = appendCMPData(url, cmpData)
320400
url += clientHints ? '&uh=' + encodeURIComponent(clientHints) : '';
321401
url += VERSION ? '&jsver=' + VERSION : '';
322402
url += firstPartyData?.group ? '&testGroup=' + encodeURIComponent(firstPartyData.group) : '';
@@ -403,7 +483,7 @@ export const intentIqIdSubmodule = {
403483
}
404484

405485
if ('ct' in respJson) {
406-
partnerData.ct = respJson.ct;
486+
partnerData.clientType = respJson.ct;
407487
}
408488

409489
if ('sid' in respJson) {

0 commit comments

Comments
 (0)