• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

prebid / Prebid.js / 20279026889

16 Dec 2025 06:45PM UTC coverage: 96.205% (+0.001%) from 96.204%
20279026889

push

github

web-flow
Update PR review and testing guidelines in AGENTS.md (#14268)

Clarify PR review guidelines and testing instructions.

41439 of 50987 branches covered (81.27%)

207294 of 215472 relevant lines covered (96.2%)

71.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.0
/modules/intentIqIdSystem.js
1
/**
1✔
2
 * This module adds IntentIqId to the User ID module
3
 * The {@link module:modules/userId} module is required
4
 * @module modules/intentIqIdSystem
5
 * @requires module:modules/userId
6
 */
7

8
import {logError, isPlainObject, isStr, isNumber} from '../src/utils.js';
9
import {ajax} from '../src/ajax.js';
10
import {submodule} from '../src/hook.js'
11
import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js';
12
import {appendSPData} from '../libraries/intentIqUtils/urlUtils.js';
13
import { isCHSupported } from '../libraries/intentIqUtils/chUtils.js'
14
import {appendVrrefAndFui} from '../libraries/intentIqUtils/getRefferer.js';
15
import { getCmpData } from '../libraries/intentIqUtils/getCmpData.js';
16
import {readData, storeData, defineStorageType, removeDataByKey, tryParse} from '../libraries/intentIqUtils/storageUtils.js';
17
import {
18
  FIRST_PARTY_KEY,
19
  CLIENT_HINTS_KEY,
20
  EMPTY,
21
  GVLID,
22
  VERSION, INVALID_ID, SYNC_REFRESH_MILL, META_DATA_CONSTANT, PREBID,
23
  HOURS_24, CH_KEYS
24
} from '../libraries/intentIqConstants/intentIqConstants.js';
25
import {SYNC_KEY} from '../libraries/intentIqUtils/getSyncKey.js';
26
import {iiqPixelServerAddress, iiqServerAddress} from '../libraries/intentIqUtils/intentIqConfig.js';
27
import { handleAdditionalParams } from '../libraries/intentIqUtils/handleAdditionalParams.js';
28
import { decryptData, encryptData } from '../libraries/intentIqUtils/cryptionUtils.js';
29
import { defineABTestingGroup } from '../libraries/intentIqUtils/defineABTestingGroupUtils.js';
30

31
/**
32
 * @typedef {import('../modules/userId/index.js').Submodule} Submodule
33
 * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig
34
 * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse
35
 */
36

37
const MODULE_NAME = 'intentIqId';
1✔
38

39
const encoderCH = {
1✔
40
  brands: 0,
41
  mobile: 1,
42
  platform: 2,
43
  architecture: 3,
44
  bitness: 4,
45
  model: 5,
46
  platformVersion: 6,
47
  wow64: 7,
48
  fullVersionList: 8
49
};
50
let sourceMetaData;
51
let sourceMetaDataExternal;
52
let globalName = ''
1✔
53

54
let FIRST_PARTY_KEY_FINAL = FIRST_PARTY_KEY;
1✔
55
let PARTNER_DATA_KEY;
56
let callCount = 0;
1✔
57
let failCount = 0;
1✔
58
let noDataCount = 0;
1✔
59

60
export let firstPartyData;
61
let partnerData;
62
let clientHints;
63
let actualABGroup
64

65
/**
66
 * Generate standard UUID string
67
 * @return {string}
68
 */
69
function generateGUID() {
70
  let d = new Date().getTime();
58✔
71
  const guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
58✔
72
    const r = (d + Math.random() * 16) % 16 | 0;
1,798✔
73
    d = Math.floor(d / 16);
1,798✔
74
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
1,798✔
75
  });
76
  return guid;
58✔
77
}
78

79
function addUniquenessToUrl(url) {
80
  url += '&tsrnd=' + Math.floor(Math.random() * 1000) + '_' + new Date().getTime();
19✔
81
  return url;
19✔
82
}
83

84
function appendFirstPartyData (url, firstPartyData, partnerData) {
85
  url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : '';
69!
86
  url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : '';
69✔
87
  url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : '';
69✔
88
  return url
69✔
89
}
90

91
function verifyIdType(value) {
92
  if (value === 0 || value === 1 || value === 3 || value === 4) return value;
23!
93
  return -1;
×
94
}
95

96
function appendPartnersFirstParty (url, configParams) {
97
  const partnerClientId = typeof configParams.partnerClientId === 'string' ? encodeURIComponent(configParams.partnerClientId) : '';
69✔
98
  const partnerClientIdType = typeof configParams.partnerClientIdType === 'number' ? verifyIdType(configParams.partnerClientIdType) : -1;
69✔
99

100
  if (partnerClientIdType === -1) return url;
69✔
101
  if (partnerClientId !== '') {
23✔
102
    url = url + '&pcid=' + partnerClientId;
21✔
103
    url = url + '&idtype=' + partnerClientIdType;
21✔
104
  }
105
  return url;
23✔
106
}
107

108
function appendCMPData (url, cmpData) {
109
  url += cmpData.uspString ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : '';
69✔
110
  url += cmpData.gppString ? '&gpp=' + encodeURIComponent(cmpData.gppString) : '';
69✔
111
  url += cmpData.gdprApplies
69✔
112
    ? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString) + '&gdpr=1'
113
    : '&gdpr=0';
114
  return url
69✔
115
}
116

117
function appendCounters (url) {
118
  url += '&jaesc=' + encodeURIComponent(callCount);
50✔
119
  url += '&jafc=' + encodeURIComponent(failCount);
50✔
120
  url += '&jaensc=' + encodeURIComponent(noDataCount);
50✔
121
  return url
50✔
122
}
123

124
/**
125
 * Translate and validate sourceMetaData
126
 */
127
export function translateMetadata(data) {
128
  try {
22✔
129
    const d = data.split('.');
22✔
130
    return (
22✔
131
      ((+d[0] * META_DATA_CONSTANT + +d[1]) * META_DATA_CONSTANT + +d[2]) * META_DATA_CONSTANT +
132
      +d[3]
133
    );
134
  } catch (e) {
135
    return NaN;
×
136
  }
137
}
138

139
/**
140
 * Add sourceMetaData to URL if valid
141
 */
142
function addMetaData(url, data) {
143
  if (typeof data !== 'number' || isNaN(data)) {
69✔
144
    return url;
50✔
145
  }
146
  return url + '&fbp=' + data;
19✔
147
}
148

149
export function initializeGlobalIIQ (partnerId) {
150
  if (!globalName || !window[globalName]) {
69✔
151
    globalName = `iiq_identity_${partnerId}`
1✔
152
    window[globalName] = {}
1✔
153
    return true
1✔
154
  }
155
  return false
68✔
156
}
157

158
export function createPixelUrl(firstPartyData, clientHints, configParams, partnerData, cmpData) {
159
  const browser = detectBrowser();
19✔
160

161
  let url = iiqPixelServerAddress(configParams, cmpData.gdprString);
19✔
162
  url += '/profiles_engine/ProfilesEngineServlet?at=20&mi=10&secure=1';
19✔
163
  url += '&dpi=' + configParams.partner;
19✔
164
  url = appendFirstPartyData(url, firstPartyData, partnerData);
19✔
165
  url = appendPartnersFirstParty(url, configParams);
19✔
166
  url = addUniquenessToUrl(url);
19✔
167
  url += partnerData?.clientType ? '&idtype=' + partnerData.clientType : '';
19!
168
  url += VERSION ? '&jsver=' + VERSION : '';
19!
169
  if (clientHints) url += '&uh=' + encodeURIComponent(clientHints);
19✔
170
  url = appendVrrefAndFui(url, configParams.domainName);
19✔
171
  url = appendCMPData(url, cmpData);
19✔
172
  url = addMetaData(url, sourceMetaDataExternal || sourceMetaData);
19✔
173
  url = handleAdditionalParams(browser, url, 0, configParams.additionalParams);
19✔
174
  url = appendSPData(url, firstPartyData)
19✔
175
  url += '&source=' + PREBID;
19✔
176
  return url;
19✔
177
}
178

179
function sendSyncRequest(allowedStorage, url, partner, firstPartyData, newUser) {
180
  const lastSyncDate = Number(readData(SYNC_KEY(partner) || '', allowedStorage)) || false;
17!
181
  const lastSyncElapsedTime = Date.now() - lastSyncDate
17✔
182

183
  if (firstPartyData.isOptedOut) {
17!
184
    const needToDoSync = (Date.now() - (firstPartyData?.date || firstPartyData?.sCal || Date.now())) > SYNC_REFRESH_MILL
×
185
    if (newUser || needToDoSync) {
×
186
      ajax(url, () => {
×
187
      }, undefined, {method: 'GET', withCredentials: true});
188
      if (firstPartyData?.date) {
×
189
        firstPartyData.date = Date.now()
×
190
        storeData(FIRST_PARTY_KEY_FINAL, JSON.stringify(firstPartyData), allowedStorage, firstPartyData);
×
191
      }
192
    }
193
  } else if (!lastSyncDate || lastSyncElapsedTime > SYNC_REFRESH_MILL) {
17!
194
    storeData(SYNC_KEY(partner), Date.now() + '', allowedStorage);
17✔
195
    ajax(url, () => {
17✔
196
    }, undefined, {method: 'GET', withCredentials: true});
197
  }
198
}
199

200
/**
201
 * Configures and updates A/B testing group in Google Ad Manager (GAM).
202
 *
203
 * @param {object} gamObjectReference - Reference to the GAM object, expected to have a `cmd` queue and `pubads()` API.
204
 * @param {string} gamParameterName - The name of the GAM targeting parameter where the group value will be stored.
205
 * @param {string} userGroup - The A/B testing group assigned to the user (e.g., 'A', 'B', or a custom value).
206
 */
207
export function setGamReporting(gamObjectReference, gamParameterName, userGroup, isBlacklisted = false) {
72✔
208
  if (isBlacklisted) return;
72✔
209
  if (isPlainObject(gamObjectReference) && gamObjectReference.cmd) {
55✔
210
    gamObjectReference.cmd.push(() => {
31✔
211
      gamObjectReference
7✔
212
        .pubads()
213
        .setTargeting(gamParameterName, userGroup);
214
    });
215
  }
216
}
217

218
/**
219
 * Processes raw client hints data into a structured format.
220
 * @param {object} clientHints - Raw client hints data
221
 * @return {string} A JSON string of processed client hints or an empty string if no hints
222
 */
223
export function handleClientHints(clientHints) {
224
  const chParams = {};
71✔
225
  for (const key in clientHints) {
71✔
226
    if (clientHints.hasOwnProperty(key) && clientHints[key] !== '') {
639✔
227
      if (['brands', 'fullVersionList'].includes(key)) {
568✔
228
        let handledParam = '';
142✔
229
        clientHints[key].forEach((element, index) => {
142✔
230
          const isNotLast = index < clientHints[key].length - 1;
340✔
231
          handledParam += `"${element.brand}";v="${element.version}"${isNotLast ? ', ' : ''}`;
340✔
232
        });
233
        chParams[encoderCH[key]] = handledParam;
142✔
234
      } else if (typeof clientHints[key] === 'boolean') {
426✔
235
        chParams[encoderCH[key]] = `?${clientHints[key] ? 1 : 0}`;
142!
236
      } else {
237
        chParams[encoderCH[key]] = `"${clientHints[key]}"`;
284✔
238
      }
239
    }
240
  }
241
  return Object.keys(chParams).length ? JSON.stringify(chParams) : '';
71!
242
}
243

244
export function isCMPStringTheSame(fpData, cmpData) {
245
  const firstPartyDataCPString = `${fpData.gdprString}${fpData.gppString}${fpData.uspString}`;
76✔
246
  const cmpDataString = `${cmpData.gdprString}${cmpData.gppString}${cmpData.uspString}`;
76✔
247
  return firstPartyDataCPString === cmpDataString;
76✔
248
}
249

250
function updateCountersAndStore(runtimeEids, allowedStorage, partnerData) {
251
  if (!runtimeEids?.eids?.length) {
22✔
252
    noDataCount++;
15✔
253
  } else {
254
    callCount++;
7✔
255
  }
256
  storeCounters(allowedStorage, partnerData);
22✔
257
}
258

259
function clearCountersAndStore(allowedStorage, partnerData) {
260
  callCount = 0;
46✔
261
  failCount = 0;
46✔
262
  noDataCount = 0;
46✔
263
  storeCounters(allowedStorage, partnerData);
46✔
264
}
265

266
function storeCounters(storage, partnerData) {
267
  partnerData.callCount = callCount;
68✔
268
  partnerData.failCount = failCount;
68✔
269
  partnerData.noDataCounter = noDataCount;
68✔
270
  storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), storage, firstPartyData);
68✔
271
}
272

273
/** @type {Submodule} */
274
export const intentIqIdSubmodule = {
1✔
275
  /**
276
   * used to link submodule with config
277
   * @type {string}
278
   */
279
  name: MODULE_NAME,
280
  gvlid: GVLID,
281
  /**
282
   * decode the stored id value for passing to bid requests
283
   * @function
284
   * @param {{string}} value
285
   * @returns {{intentIqId: {string}}|undefined}
286
   */
287
  decode(value) {
288
    return value && INVALID_ID !== value ? {'intentIqId': value} : undefined;
3!
289
  },
290

291
  /**
292
   * performs action to obtain id and return a value in the callback's response argument
293
   * @function
294
   * @param {SubmoduleConfig} [config]
295
   * @returns {IdResponse|undefined}
296
   */
297
  getId(config) {
298
    const configParams = (config?.params) || {};
71!
299

300
    const firePartnerCallback = () => {
71✔
301
      if (configParams.callback && !callbackFired) {
100✔
302
        callbackFired = true;
2✔
303
        if (callbackTimeoutID) clearTimeout(callbackTimeoutID);
2✔
304
        let data = runtimeEids;
2✔
305
        if (data?.eids?.length === 1 && typeof data.eids[0] === 'string') data = data.eids[0];
2!
306
        configParams.callback(data);
2✔
307
      }
308
      updateGlobalObj()
100✔
309
    }
310

311
    if (typeof configParams.partner !== 'number') {
71✔
312
      logError('User ID - intentIqId submodule requires a valid partner to be defined');
3✔
313
      firePartnerCallback()
3✔
314
      return;
3✔
315
    }
316

317
    initializeGlobalIIQ(configParams.partner)
68✔
318

319
    let decryptedData, callbackTimeoutID;
320
    let callbackFired = false;
68✔
321
    let runtimeEids = { eids: [] };
68✔
322

323
    const gamObjectReference = isPlainObject(configParams.gamObjectReference) ? configParams.gamObjectReference : undefined;
68✔
324
    const gamParameterName = configParams.gamParameterName ? configParams.gamParameterName : 'intent_iq_group';
68✔
325
    const groupChanged = typeof configParams.groupChanged === 'function' ? configParams.groupChanged : undefined;
68✔
326
    const siloEnabled = typeof configParams.siloEnabled === 'boolean' ? configParams.siloEnabled : false;
68✔
327
    sourceMetaData = isStr(configParams.sourceMetaData) ? translateMetadata(configParams.sourceMetaData) : '';
68✔
328
    sourceMetaDataExternal = isNumber(configParams.sourceMetaDataExternal) ? configParams.sourceMetaDataExternal : undefined;
68✔
329
    const additionalParams = configParams.additionalParams ? configParams.additionalParams : undefined;
68✔
330
    const chTimeout = Number(configParams?.chTimeout) >= 0 ? Number(configParams.chTimeout) : 10;
68✔
331
    PARTNER_DATA_KEY = `${FIRST_PARTY_KEY}_${configParams.partner}`;
68✔
332

333
    const allowedStorage = defineStorageType(config.enabledStorageTypes);
68✔
334
    partnerData = tryParse(readData(PARTNER_DATA_KEY, allowedStorage)) || {};
68✔
335

336
    let rrttStrtTime = 0;
68✔
337
    let shouldCallServer = false;
68✔
338
    FIRST_PARTY_KEY_FINAL = `${FIRST_PARTY_KEY}${siloEnabled ? '_p_' + configParams.partner : ''}`;
68✔
339
    const cmpData = getCmpData();
68✔
340
    const gdprDetected = cmpData.gdprString;
68✔
341
    firstPartyData = tryParse(readData(FIRST_PARTY_KEY_FINAL, allowedStorage));
68✔
342
    actualABGroup = defineABTestingGroup(configParams, partnerData?.terminationCause);
68✔
343
    const currentBrowserLowerCase = detectBrowser();
68✔
344
    const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : '';
68✔
345
    const isBlacklisted = browserBlackList?.includes(currentBrowserLowerCase);
68✔
346
    let newUser = false;
68✔
347

348
    setGamReporting(gamObjectReference, gamParameterName, actualABGroup, isBlacklisted);
68✔
349

350
    if (groupChanged) groupChanged(actualABGroup, partnerData?.terminationCause);
68✔
351

352
    callbackTimeoutID = setTimeout(() => {
68✔
353
      firePartnerCallback();
68✔
354
    }, configParams.timeoutInMillis || 500
136✔
355
    );
356

357
    if (!firstPartyData?.pcid) {
68✔
358
      const firstPartyId = generateGUID();
58✔
359
      firstPartyData = {
58✔
360
        pcid: firstPartyId,
361
        pcidDate: Date.now(),
362
        uspString: EMPTY,
363
        gppString: EMPTY,
364
        gdprString: EMPTY,
365
        date: Date.now()
366
      };
367
      newUser = true;
58✔
368
      storeData(FIRST_PARTY_KEY_FINAL, JSON.stringify(firstPartyData), allowedStorage, firstPartyData);
58✔
369
    } else if (!firstPartyData.pcidDate) {
10✔
370
      firstPartyData.pcidDate = Date.now();
7✔
371
      storeData(FIRST_PARTY_KEY_FINAL, JSON.stringify(firstPartyData), allowedStorage, firstPartyData);
7✔
372
    }
373

374
    if (gdprDetected && !('isOptedOut' in firstPartyData)) {
68✔
375
      firstPartyData.isOptedOut = true;
4✔
376
    }
377

378
    // Read client hints from storage
379
    clientHints = readData(CLIENT_HINTS_KEY, allowedStorage);
68✔
380
    const chSupported = isCHSupported();
68✔
381
    let chPromise = null;
68✔
382

383
    function fetchAndHandleCH() {
384
      return navigator.userAgentData.getHighEntropyValues(CH_KEYS)
67✔
385
        .then(raw => {
386
          const nextCH = handleClientHints(raw) || '';
66!
387
          const prevCH = clientHints || '';
66✔
388
          if (nextCH !== prevCH) {
66✔
389
            clientHints = nextCH;
66✔
390
            storeData(CLIENT_HINTS_KEY, clientHints, allowedStorage, firstPartyData);
66✔
391
          }
392
          return nextCH;
66✔
393
        })
394
        .catch(err => {
395
          logError('CH fetch failed', err);
1✔
396
          if (clientHints !== '') {
1✔
397
            clientHints = '';
1✔
398
            removeDataByKey(CLIENT_HINTS_KEY, allowedStorage)
1✔
399
          }
400
          return '';
1✔
401
        });
402
    }
403

404
    if (chSupported) {
68✔
405
      chPromise = fetchAndHandleCH();
67✔
406
      chPromise.catch(err => {
67✔
407
        logError('fetchAndHandleCH failed', err);
×
408
      });
409
    } else {
410
      clientHints = '';
1✔
411
      removeDataByKey(CLIENT_HINTS_KEY, allowedStorage)
1✔
412
    }
413

414
    function waitOnCH(timeoutMs) {
415
      const timeout = new Promise(resolve => setTimeout(() => resolve(''), timeoutMs));
58✔
416
      return Promise.race([chPromise, timeout]);
58✔
417
    }
418

419
    if (typeof partnerData.callCount === 'number') callCount = partnerData.callCount;
68✔
420
    if (typeof partnerData.failCount === 'number') failCount = partnerData.failCount;
68✔
421
    if (typeof partnerData.noDataCounter === 'number') noDataCount = partnerData.noDataCounter;
68✔
422
    if (partnerData.wsrvcll) {
68!
423
      partnerData.wsrvcll = false;
×
424
      storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData);
×
425
    }
426

427
    if (partnerData.data) {
68✔
428
      if (partnerData.data.length) { // encrypted data
1✔
429
        decryptedData = tryParse(decryptData(partnerData.data));
1✔
430
        runtimeEids = decryptedData;
1✔
431
      }
432
    }
433

434
    function updateGlobalObj () {
435
      if (globalName) {
150✔
436
        window[globalName].partnerData = partnerData
150✔
437
        window[globalName].firstPartyData = firstPartyData
150✔
438
        window[globalName].clientHints = clientHints
150✔
439
        window[globalName].actualABGroup = actualABGroup
150✔
440
      }
441
    }
442

443
    let hasPartnerData = !!Object.keys(partnerData).length;
68✔
444
    if (!isCMPStringTheSame(firstPartyData, cmpData) ||
68!
445
          !firstPartyData.sCal ||
446
          (hasPartnerData && (!partnerData.cttl || !partnerData.date || Date.now() - partnerData.date > partnerData.cttl))) {
447
      firstPartyData.uspString = cmpData.uspString;
66✔
448
      firstPartyData.gppString = cmpData.gppString;
66✔
449
      firstPartyData.gdprString = cmpData.gdprString;
66✔
450
      shouldCallServer = true;
66✔
451
      storeData(FIRST_PARTY_KEY_FINAL, JSON.stringify(firstPartyData), allowedStorage, firstPartyData);
66✔
452
      storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData);
66✔
453
    }
454
    if (!shouldCallServer) {
68✔
455
      if (!hasPartnerData && !firstPartyData.isOptedOut) {
2✔
456
        shouldCallServer = true;
1✔
457
      } else shouldCallServer = Date.now() > firstPartyData.sCal + HOURS_24;
1✔
458
    }
459

460
    if (firstPartyData.isOptedOut) {
68✔
461
      partnerData.data = runtimeEids = { eids: [] };
5✔
462
      firePartnerCallback()
5✔
463
    }
464

465
    if (runtimeEids?.eids?.length) {
68✔
466
      firePartnerCallback()
1✔
467
    }
468

469
    function buildAndSendPixel(ch) {
470
      const url = createPixelUrl(firstPartyData, ch, configParams, partnerData, cmpData);
17✔
471
      sendSyncRequest(allowedStorage, url, configParams.partner, firstPartyData, newUser);
17✔
472
    }
473

474
    // Check if current browser is in blacklist
475
    if (isBlacklisted) {
68✔
476
      logError('User ID - intentIqId submodule: browser is in blacklist! Data will be not provided.');
17✔
477
      if (configParams.callback) configParams.callback('');
17✔
478

479
      if (chSupported) {
17!
480
        if (clientHints) {
17✔
481
          buildAndSendPixel(clientHints)
1✔
482
        } else {
483
          waitOnCH(chTimeout)
16✔
484
            .then(ch => buildAndSendPixel(ch || ''));
16!
485
        }
486
      } else {
487
        buildAndSendPixel('');
×
488
      }
489
      return;
17✔
490
    }
491

492
    if (!shouldCallServer) {
51✔
493
      firePartnerCallback();
1✔
494
      updateCountersAndStore(runtimeEids, allowedStorage, partnerData);
1✔
495
      return { id: runtimeEids.eids };
1✔
496
    }
497

498
    updateGlobalObj() // update global object before server request, to make sure analytical adapter will have it even if the server is "not in time"
50✔
499

500
    // use protocol relative urls for http or https
501
    let url = `${iiqServerAddress(configParams, gdprDetected)}/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`;
50✔
502
    url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : '';
50✔
503
    url = appendFirstPartyData(url, firstPartyData, partnerData);
50✔
504
    url = appendPartnersFirstParty(url, configParams);
50✔
505
    url += (partnerData.cttl) ? '&cttl=' + encodeURIComponent(partnerData.cttl) : '';
50✔
506
    url += (partnerData.rrtt) ? '&rrtt=' + encodeURIComponent(partnerData.rrtt) : '';
50✔
507
    url = appendCMPData(url, cmpData);
50✔
508
    url += '&japs=' + encodeURIComponent(configParams.siloEnabled === true);
50✔
509
    url = appendCounters(url);
50✔
510
    url += VERSION ? '&jsver=' + VERSION : '';
50!
511
    url += actualABGroup ? '&testGroup=' + encodeURIComponent(actualABGroup) : '';
50!
512
    url = addMetaData(url, sourceMetaDataExternal || sourceMetaData);
50✔
513
    url = handleAdditionalParams(currentBrowserLowerCase, url, 1, additionalParams);
50✔
514
    url = appendSPData(url, firstPartyData)
50✔
515
    url += '&source=' + PREBID;
50✔
516
    url += '&ABTestingConfigurationSource=' + configParams.ABTestingConfigurationSource
50✔
517
    url += '&abtg=' + encodeURIComponent(actualABGroup)
50✔
518

519
    // Add vrref and fui to the URL
520
    url = appendVrrefAndFui(url, configParams.domainName);
50✔
521

522
    const storeFirstPartyData = () => {
50✔
523
      partnerData.eidl = runtimeEids?.eids?.length || -1
20✔
524
      storeData(FIRST_PARTY_KEY_FINAL, JSON.stringify(firstPartyData), allowedStorage, firstPartyData);
20✔
525
      storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData);
20✔
526
    }
527

528
    const resp = function (callback) {
50✔
529
      const callbacks = {
46✔
530
        success: response => {
531
          const respJson = tryParse(response);
22✔
532
          // If response is a valid json and should save is true
533
          if (respJson) {
22✔
534
            partnerData.date = Date.now();
21✔
535
            firstPartyData.sCal = Date.now();
21✔
536
            const defineEmptyDataAndFireCallback = () => {
21✔
537
              respJson.data = partnerData.data = runtimeEids = { eids: [] };
1✔
538
              storeFirstPartyData()
1✔
539
              firePartnerCallback()
1✔
540
              callback(runtimeEids)
1✔
541
            }
542
            if (callbackTimeoutID) clearTimeout(callbackTimeoutID)
21✔
543
            if ('cttl' in respJson) {
21!
544
              partnerData.cttl = respJson.cttl;
×
545
            } else partnerData.cttl = HOURS_24;
21✔
546

547
            if ('tc' in respJson) {
21✔
548
              partnerData.terminationCause = respJson.tc;
5✔
549
              actualABGroup = defineABTestingGroup(configParams, respJson.tc,);
5✔
550

551
              if (gamObjectReference) setGamReporting(gamObjectReference, gamParameterName, actualABGroup);
5✔
552
              if (groupChanged) groupChanged(actualABGroup, partnerData?.terminationCause);
5✔
553
            }
554
            if ('isOptedOut' in respJson) {
21✔
555
              if (respJson.isOptedOut !== firstPartyData.isOptedOut) {
5✔
556
                firstPartyData.isOptedOut = respJson.isOptedOut;
4✔
557
              }
558
              if (respJson.isOptedOut === true) {
5✔
559
                respJson.data = partnerData.data = runtimeEids = { eids: [] };
1✔
560

561
                const keysToRemove = [
1✔
562
                  PARTNER_DATA_KEY,
563
                  CLIENT_HINTS_KEY
564
                ];
565

566
                keysToRemove.forEach(key => removeDataByKey(key, allowedStorage));
2✔
567

568
                storeData(FIRST_PARTY_KEY_FINAL, JSON.stringify(firstPartyData), allowedStorage, firstPartyData);
1✔
569
                firePartnerCallback();
1✔
570
                callback(runtimeEids);
1✔
571
                return
1✔
572
              }
573
            }
574
            if ('pid' in respJson) {
20✔
575
              firstPartyData.pid = respJson.pid;
6✔
576
            }
577
            if ('dbsaved' in respJson) {
20✔
578
              firstPartyData.dbsaved = respJson.dbsaved;
1✔
579
            }
580
            if ('ls' in respJson) {
20✔
581
              if (respJson.ls === false) {
9✔
582
                defineEmptyDataAndFireCallback()
1✔
583
                return
1✔
584
              }
585
              // If data is empty, means we should save as INVALID_ID
586
              if (respJson.data === '') {
8!
587
                respJson.data = INVALID_ID;
×
588
              } else {
589
                // If data is a single string, assume it is an id with source intentiq.com
590
                if (respJson.data && typeof respJson.data === 'string') {
8✔
591
                  respJson.data = {eids: [respJson.data]}
6✔
592
                }
593
              }
594
              partnerData.data = respJson.data;
8✔
595
            }
596

597
            if ('ct' in respJson) {
19✔
598
              partnerData.clientType = respJson.ct;
1✔
599
            }
600

601
            if ('sid' in respJson) {
19!
602
              partnerData.siteId = respJson.sid;
×
603
            }
604

605
            if ('spd' in respJson) {
19✔
606
              // server provided data
607
              firstPartyData.spd = respJson.spd;
1✔
608
            }
609

610
            if ('abTestUuid' in respJson) {
19!
611
              if ('ls' in respJson && respJson.ls === true) {
×
612
                partnerData.abTestUuid = respJson.abTestUuid;
×
613
              }
614
            }
615

616
            if ('gpr' in respJson) {
19!
617
              // GAM prediction reporting
618
              partnerData.gpr = respJson.gpr;
×
619
            } else {
620
              delete partnerData.gpr // remove prediction flag in case server doesn't provide it
19✔
621
            }
622

623
            if (rrttStrtTime && rrttStrtTime > 0) {
19✔
624
              partnerData.rrtt = Date.now() - rrttStrtTime;
19✔
625
            }
626

627
            if (respJson.data?.eids) {
19✔
628
              runtimeEids = respJson.data
10✔
629
              callback(respJson.data.eids);
10✔
630
              firePartnerCallback()
10✔
631
              const encryptedData = encryptData(JSON.stringify(respJson.data));
10✔
632
              partnerData.data = encryptedData;
10✔
633
            } else {
634
              callback(runtimeEids);
9✔
635
              firePartnerCallback()
9✔
636
            }
637
            updateCountersAndStore(runtimeEids, allowedStorage, partnerData);
19✔
638
            storeFirstPartyData();
19✔
639
          } else {
640
            callback(runtimeEids);
1✔
641
            firePartnerCallback()
1✔
642
          }
643
        },
644
        error: error => {
645
          logError(MODULE_NAME + ': ID fetch encountered an error', error);
2✔
646
          failCount++;
2✔
647
          updateCountersAndStore(runtimeEids, allowedStorage, partnerData);
2✔
648
          callback(runtimeEids);
2✔
649
        }
650
      };
651
      rrttStrtTime = Date.now();
46✔
652

653
      partnerData.wsrvcll = true;
46✔
654
      storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData);
46✔
655
      clearCountersAndStore(allowedStorage, partnerData);
46✔
656

657
      const sendAjax = uh => {
46✔
658
        if (uh) url += '&uh=' + encodeURIComponent(uh);
46✔
659
        ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true });
46✔
660
      }
661

662
      if (chSupported) {
46✔
663
        if (clientHints) {
45✔
664
          // CH found in LS: send immediately; background fetch will refresh/clear later
665
          sendAjax(clientHints);
3✔
666
        } else {
667
          // No CH in LS: wait up to chTimeout, then send
668
          waitOnCH(chTimeout).then(ch => {
42✔
669
            // Send with received CH or without it
670
            sendAjax(ch || '');
42!
671
          })
672
        }
673
      } else {
674
        // CH not supported: send without uh
675
        sendAjax('');
1✔
676
      }
677
    };
678
    const respObj = {callback: resp};
50✔
679

680
    if (runtimeEids?.eids?.length) respObj.id = runtimeEids.eids;
50✔
681
    return respObj
50✔
682
  },
683
  eids: {
684
    'intentIqId': {
685
      source: 'intentiq.com',
686
      atype: 1,
687
      getSource: function (data) {
688
        return data.source;
×
689
      },
690
      getValue: function (data) {
691
        if (data?.uids?.length) {
×
692
          return data.uids[0].id
×
693
        }
694
        return null
×
695
      },
696
      getUidExt: function (data) {
697
        if (data?.uids?.length) {
×
698
          return data.uids[0].ext;
×
699
        }
700
        return null
×
701
      }
702
    },
703
  }
704
};
705

706
submodule('userId', intentIqIdSubmodule);
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc