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

prebid / Prebid.js / 21696531338

05 Feb 2026 02:33AM UTC coverage: 96.231% (+0.009%) from 96.222%
21696531338

push

github

web-flow
percentInView: fix bug where viewability calculation is inaccurate inside friendly iframes (#14414)

* percentInView: fix bug where viewability calculation is inaccurate inside friendly iframes

* use boundingClientRect

41788 of 51368 branches covered (81.35%)

57 of 57 new or added lines in 7 files covered. (100.0%)

36 existing lines in 7 files now uncovered.

209722 of 217936 relevant lines covered (96.23%)

71.58 hits per line

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

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

8
import {
9
  deepAccess,
10
  deepClone,
11
  deepEqual,
12
  deepSetValue,
13
  isEmpty,
14
  isEmptyStr,
15
  isPlainObject,
16
  logError,
17
  logInfo,
18
  logWarn
19
} from '../src/utils.js';
20
import {fetch} from '../src/ajax.js';
21
import {submodule} from '../src/hook.js';
22
import {getRefererInfo} from '../src/refererDetection.js';
23
import {getStorageManager} from '../src/storageManager.js';
24
import {MODULE_TYPE_UID} from '../src/activities/modules.js';
25
import {PbPromise} from '../src/utils/promise.js';
26
import {loadExternalScript} from '../src/adloader.js';
27

28
/**
29
 * @typedef {import('../modules/userId/spec.ts').IdProviderSpec} Submodule
30
 * @typedef {import('../modules/userId/spec.ts').UserIdConfig} SubmoduleConfig
31
 * @typedef {import('../src/consentHandler').AllConsentData} ConsentData
32
 * @typedef {import('../modules/userId/spec.ts').ProviderResponse} ProviderResponse
33
 */
34

35
const MODULE_NAME = 'id5Id';
1✔
36
const GVLID = 131;
1✔
37
export const ID5_STORAGE_NAME = 'id5id';
1✔
38
const LOG_PREFIX = 'User ID - ID5 submodule: ';
1✔
39
const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid';
1✔
40
const ID5_DOMAIN = 'id5-sync.com';
1✔
41
const TRUE_LINK_SOURCE = 'true-link-id5-sync.com';
1✔
42

43
export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME});
1✔
44

45
/**
46
 * @typedef {Object} Id5Response
47
 * @property {string} [universal_uid] - The encrypted ID5 ID to pass to bidders
48
 * @property {Object} [ext] - The extensions object to pass to bidders
49
 * @property {Object} [ab_testing] - A/B testing configuration
50
 * @property {Object} [ids]
51
 * @property {string} signature
52
 * @property {number} [nbPage]
53
 * @property {string} [publisherTrueLinkId] - The publisher's TrueLink ID
54
 */
55

56
/**
57
 * @typedef {Object.<string, Id5Response>} PartnerId5Responses
58
 */
59

60
/**
61
 * @typedef {Id5Response} Id5PrebidResponse
62
 * @property {PartnerId5Responses} pbjs
63
 *
64
 */
65

66
/**
67
 * @typedef {Object} FetchCallConfig
68
 * @property {string} [url] - The URL for the fetch endpoint
69
 * @property {Object} [overrides] - Overrides to apply to fetch parameters
70
 */
71

72
/**
73
 * @typedef {Object} ExtensionsCallConfig
74
 * @property {string} [url] - The URL for the extensions endpoint
75
 * @property {string} [method] - Overrides the HTTP method to use to make the call
76
 * @property {Object} [body] - Specifies a body to pass to the extensions endpoint
77
 */
78

79
/**
80
 * @typedef {Object} DynamicConfig
81
 * @property {FetchCallConfig} [fetchCall] - The fetch call configuration
82
 * @property {ExtensionsCallConfig} [extensionsCall] - The extensions call configuration
83
 */
84

85
/**
86
 * @typedef {Object} ABTestingConfig
87
 * @property {boolean} enabled - Tells whether A/B testing is enabled for this instance
88
 * @property {number} controlGroupPct - A/B testing probability
89
 */
90

91
/**
92
 * @typedef {Object} Multiplexing
93
 * @property {boolean} [disabled] - Disable multiplexing (instance will work in single mode)
94
 */
95

96
/**
97
 * @typedef {Object} Diagnostics
98
 * @property {boolean} [publishingDisabled] - Disable diagnostics publishing
99
 * @property {number} [publishAfterLoadInMsec] - Delay in ms after script load after which collected diagnostics are published
100
 * @property {boolean} [publishBeforeWindowUnload] - When true, diagnostics publishing is triggered on Window 'beforeunload' event
101
 * @property {number} [publishingSampleRatio] - Diagnostics publishing sample ratio
102
 */
103

104
/**
105
 * @typedef {Object} Segment
106
 * @property {string} [destination] - GVL ID or ID5-XX Partner ID. Mandatory
107
 * @property {Array<string>} [ids] - The segment IDs to push. Must contain at least one segment ID.
108
 */
109

110
/**
111
 * @typedef {Object} Id5PrebidConfig
112
 * @property {number} partner - The ID5 partner ID
113
 * @property {string} pd - The ID5 partner data string
114
 * @property {ABTestingConfig} abTesting - The A/B testing configuration
115
 * @property {boolean} disableExtensions - Disabled extensions call
116
 * @property {string} [externalModuleUrl] - URL for the id5 prebid external module
117
 * @property {Multiplexing} [multiplexing] - Multiplexing options. Only supported when loading the external module.
118
 * @property {Diagnostics} [diagnostics] - Diagnostics options. Supported only in multiplexing
119
 * @property {Array<Segment>} [segments] - A list of segments to push to partners. Supported only in multiplexing.
120
 * @property {boolean} [disableUaHints] - When true, look up of high entropy values through user agent hints is disabled.
121
 * @property {string} [gamTargetingPrefix] - When set, the GAM targeting tags will be set and use the specified prefix, for example 'id5'.
122
 * @property {boolean} [exposeTargeting] - When set, the ID5 targeting consumer mechanism will be enabled.
123
 */
124

125
/**
126
 * @typedef {SubmoduleConfig} Id5SubmoduleConfig
127
 * @property {Id5PrebidConfig} params
128
 */
129

130
const DEFAULT_EIDS = {
1✔
131
  'id5id': {
132
    getValue: function (data) {
133
      return data.uid;
11✔
134
    },
135
    source: ID5_DOMAIN,
136
    atype: 1,
137
    getUidExt: function (data) {
138
      if (data.ext) {
11✔
139
        return data.ext;
10✔
140
      }
141
    }
142
  },
143
  'euid': {
144
    getValue: function (data) {
145
      return data.uid;
2✔
146
    },
147
    getSource: function (data) {
148
      return data.source;
2✔
149
    },
150
    atype: 3,
151
    getUidExt: function (data) {
152
      if (data.ext) {
2✔
153
        return data.ext;
2✔
154
      }
155
    }
156
  },
157
  'trueLinkId': {
158
    getValue: function (data) {
159
      return data.uid;
2✔
160
    },
161
    getSource: function () {
162
      return TRUE_LINK_SOURCE;
2✔
163
    },
164
    atype: 1,
165
    getUidExt: function (data) {
166
      if (data.ext) {
2!
UNCOV
167
        return data.ext;
×
168
      }
169
    }
170
  }
171
};
172

173
/** @type {Submodule} */
174
export const id5IdSubmodule = {
1✔
175
  /**
176
   * used to link submodule with config
177
   * @type {string}
178
   */
179
  name: 'id5Id',
180

181
  /**
182
   * Vendor id of ID5
183
   * @type {Number}
184
   */
185
  gvlid: GVLID,
186

187
  /**
188
   * decode the stored id value for passing to bid requests
189
   * @function decode
190
   * @param {Id5PrebidResponse|Id5Response} value
191
   * @param {Id5SubmoduleConfig} config
192
   * @returns {(Object|undefined)}
193
   */
194
  decode(value, config) {
195
    const partnerResponse = getPartnerResponse(value, config.params)
37✔
196
    // get generic/legacy response in case no partner specific
197
    // it may happen in case old cached value found
198
    // or overwritten by other integration (older version)
199
    return this._decodeResponse(partnerResponse || value, config);
37✔
200
  },
201

202
  /**
203
   *
204
   * @param {Id5Response} value
205
   * @param {Id5SubmoduleConfig} config
206
   * @private
207
   */
208
  _decodeResponse(value, config) {
209
    if (value && value.ids !== undefined) {
37✔
210
      const responseObj = {};
8✔
211
      const eids = {};
8✔
212
      Object.entries(value.ids).forEach(([key, value]) => {
15✔
213
        const eid = value.eid;
15✔
214
        const uid = eid?.uids?.[0]
15✔
215
        responseObj[key] = {
15✔
216
          uid: uid?.id,
217
          ext: uid?.ext
218
        };
219
        eids[key] = function () {
15✔
220
          return eid;
14✔
221
        }; // register function to get eid for each id (key) decoded
222
      });
223
      this.eids = eids; // overwrite global eids
8✔
224
      updateTargeting(value, config);
8✔
225
      return responseObj;
8✔
226
    }
227

228
    let universalUid, publisherTrueLinkId;
229
    let ext = {};
29✔
230

231
    if (value && typeof value.universal_uid === 'string') {
29✔
232
      universalUid = value.universal_uid;
28✔
233
      ext = value.ext || ext;
28!
234
      publisherTrueLinkId = value.publisherTrueLinkId;
28✔
235
    } else {
236
      return undefined;
1✔
237
    }
238
    this.eids = DEFAULT_EIDS;
28✔
239
    const responseObj = {
28✔
240
      id5id: {
241
        uid: universalUid,
242
        ext: ext
243
      }
244
    };
245

246
    if (isPlainObject(ext.euid)) {
28✔
247
      responseObj.euid = {
3✔
248
        uid: ext.euid.uids[0].id,
249
        source: ext.euid.source,
250
        ext: {provider: ID5_DOMAIN}
251
      };
252
    }
253

254
    if (publisherTrueLinkId) {
28✔
255
      responseObj.trueLinkId = {
3✔
256
        uid: publisherTrueLinkId
257
      };
258
    }
259

260
    const abTestingResult = deepAccess(value, 'ab_testing.result');
28✔
261
    switch (abTestingResult) {
28✔
262
      case 'control':
263
        // A/B Testing is enabled and user is in the Control Group
264
        logInfo(LOG_PREFIX + 'A/B Testing - user is in the Control Group: ID5 ID is NOT exposed');
1✔
265
        deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', true);
1✔
266
        break;
1✔
267
      case 'error':
268
        // A/B Testing is enabled, but configured improperly, so skip A/B testing
269
        logError(LOG_PREFIX + 'A/B Testing ERROR! controlGroupPct must be a number >= 0 and <= 1');
1✔
270
        break;
1✔
271
      case 'normal':
272
        // A/B Testing is enabled but user is not in the Control Group, so ID5 ID is shared
273
        logInfo(LOG_PREFIX + 'A/B Testing - user is NOT in the Control Group');
1✔
274
        deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', false);
1✔
275
        break;
1✔
276
    }
277

278
    logInfo(LOG_PREFIX + 'Decoded ID', responseObj);
28✔
279
    updateTargeting(value, config);
28✔
280

281
    return responseObj;
28✔
282
  },
283

284
  /**
285
   * performs action to obtain id and return a value in the callback's response argument
286
   * @function getId
287
   * @param {Id5SubmoduleConfig} submoduleConfig
288
   * @param {ConsentData} consentData
289
   * @param {(Object|undefined)} cacheIdObj
290
   * @returns {ProviderResponse}
291
   */
292
  getId(submoduleConfig, consentData, cacheIdObj) {
293
    if (!validateConfig(submoduleConfig)) {
62✔
294
      return undefined;
18✔
295
    }
296

297
    if (!hasWriteConsentToLocalStorage(consentData?.gdpr)) {
44!
UNCOV
298
      logInfo(LOG_PREFIX + 'Skipping ID5 local storage write because no consent given.');
×
UNCOV
299
      return undefined;
×
300
    }
301

302
    const resp = function (cbFunction) {
44✔
303
      const fetchFlow = new IdFetchFlow(submoduleConfig, consentData?.gdpr, cacheIdObj, consentData?.usp, consentData?.gpp);
41✔
304
      fetchFlow.execute()
41✔
305
        .then(response => {
306
          cbFunction(createResponse(response, submoduleConfig.params, cacheIdObj));
34✔
307
        })
308
        .catch(error => {
UNCOV
309
          logError(LOG_PREFIX + 'getId fetch encountered an error', error);
×
UNCOV
310
          cbFunction();
×
311
        });
312
    };
313
    return {callback: resp};
44✔
314
  },
315

316
  /**
317
   * Similar to Submodule#getId, this optional method returns response to for id that exists already.
318
   *  If IdResponse#id is defined, then it will be written to the current active storage even if it exists already.
319
   *  If IdResponse#callback is defined, then it'll called at the end of auction.
320
   *  It's permissible to return neither, one, or both fields.
321
   * @function extendId
322
   * @param {Id5SubmoduleConfig} config
323
   * @param {ConsentData} consentData
324
   * @param {Id5PrebidResponse} cacheIdObj - existing id, if any
325
   * @return {ProviderResponse} A response object that contains id.
326
   */
327
  extendId(config, consentData, cacheIdObj) {
328
    if (!hasWriteConsentToLocalStorage(consentData?.gdpr)) {
23✔
329
      logInfo(LOG_PREFIX + 'No consent given for ID5 local storage writing, skipping nb increment.');
9✔
330
      return {id: cacheIdObj};
9✔
331
    }
332
    if (getPartnerResponse(cacheIdObj, config.params)) { // response for partner is present
14✔
333
      logInfo(LOG_PREFIX + 'using cached ID', cacheIdObj);
1✔
334
      const updatedObject = deepClone(cacheIdObj);
1✔
335
      const responseToUpdate = getPartnerResponse(updatedObject, config.params);
1✔
336
      responseToUpdate.nbPage = incrementNb(responseToUpdate);
1✔
337
      return {id: updatedObject};
1✔
338
    } else {
339
      logInfo(LOG_PREFIX + ' refreshing ID.  Cached object does not have ID for partner', cacheIdObj);
13✔
340
      return this.getId(config, consentData, cacheIdObj);
13✔
341
    }
342
  },
343
  primaryIds: ['id5id', 'trueLinkId'],
344
  eids: DEFAULT_EIDS,
345
  _reset() {
346
    this.eids = DEFAULT_EIDS;
12✔
347
  }
348
};
349

350
export class IdFetchFlow {
351
  constructor(submoduleConfig, gdprConsentData, cacheIdObj, usPrivacyData, gppData) {
352
    this.submoduleConfig = submoduleConfig;
41✔
353
    this.gdprConsentData = gdprConsentData;
41✔
354
    this.cacheIdObj = isPlainObject(cacheIdObj?.pbjs) ? cacheIdObj.pbjs[submoduleConfig.params.partner] : cacheIdObj;
41✔
355
    this.usPrivacyData = usPrivacyData;
41✔
356
    this.gppData = gppData;
41✔
357
  }
358

359
  /**
360
   * Calls the ID5 Servers to fetch an ID5 ID
361
   * @returns {Promise<Id5Response>} The result of calling the server side
362
   */
363
  async execute() {
364
    const configCallPromise = this.#callForConfig();
41✔
365
    if (this.#isExternalModule()) {
41✔
366
      try {
2✔
367
        return await this.#externalModuleFlow(configCallPromise);
2✔
368
      } catch (error) {
369
        logError(LOG_PREFIX + 'Error while performing ID5 external module flow. Continuing with regular flow.', error);
1✔
370
        return this.#regularFlow(configCallPromise);
1✔
371
      }
372
    } else {
373
      return this.#regularFlow(configCallPromise);
39✔
374
    }
375
  }
376

377
  #isExternalModule() {
378
    return typeof this.submoduleConfig.params.externalModuleUrl === 'string';
41✔
379
  }
380

381
  async #externalModuleFlow(configCallPromise) {
382
    await loadExternalModule(this.submoduleConfig.params.externalModuleUrl);
2✔
383
    const fetchFlowConfig = await configCallPromise;
2✔
384

385
    return this.#getExternalIntegration().fetchId5Id(fetchFlowConfig, this.submoduleConfig.params, getRefererInfo(), this.gdprConsentData, this.usPrivacyData, this.gppData);
2✔
386
  }
387

388
  #getExternalIntegration() {
389
    return window.id5Prebid && window.id5Prebid.integration;
2✔
390
  }
391

392
  async #regularFlow(configCallPromise) {
393
    const fetchFlowConfig = await configCallPromise;
40✔
394
    const extensionsData = await this.#callForExtensions(fetchFlowConfig.extensionsCall);
33✔
395
    const fetchCallResponse = await this.#callId5Fetch(fetchFlowConfig.fetchCall, extensionsData);
33✔
396
    return this.#processFetchCallResponse(fetchCallResponse);
33✔
397
  }
398

399
  async #callForConfig() {
400
    const url = this.submoduleConfig.params.configUrl || ID5_API_CONFIG_URL; // override for debug/test purposes only
41✔
401
    const response = await fetch(url, {
41✔
402
      method: 'POST',
403
      body: JSON.stringify({
404
        ...this.submoduleConfig,
405
        bounce: true
406
      }),
407
      credentials: 'include'
408
    });
409
    if (!response.ok) {
34!
UNCOV
410
      throw new Error('Error while calling config endpoint: ', response);
×
411
    }
412
    const dynamicConfig = await response.json();
34✔
413
    logInfo(LOG_PREFIX + 'config response received from the server', dynamicConfig);
34✔
414
    return dynamicConfig;
34✔
415
  }
416

417
  async #callForExtensions(extensionsCallConfig) {
418
    if (extensionsCallConfig === undefined) {
33✔
419
      return undefined;
31✔
420
    }
421
    const extensionsUrl = extensionsCallConfig.url;
2✔
422
    const method = extensionsCallConfig.method || 'GET';
2!
423
    const body = method === 'GET' ? undefined : JSON.stringify(extensionsCallConfig.body || {});
2!
424
    const response = await fetch(extensionsUrl, {method, body});
2✔
425
    if (!response.ok) {
2!
UNCOV
426
      throw new Error('Error while calling extensions endpoint: ', response);
×
427
    }
428
    const extensions = await response.json();
2✔
429
    logInfo(LOG_PREFIX + 'extensions response received from the server', extensions);
2✔
430
    return extensions;
2✔
431
  }
432

433
  async #callId5Fetch(fetchCallConfig, extensionsData) {
434
    const fetchUrl = fetchCallConfig.url;
33✔
435
    const additionalData = fetchCallConfig.overrides || {};
33✔
436
    const body = JSON.stringify({
33✔
437
      ...this.#createFetchRequestData(),
438
      ...additionalData,
439
      extensions: extensionsData
440
    });
441
    const response = await fetch(fetchUrl, {method: 'POST', body, credentials: 'include'});
33✔
442
    if (!response.ok) {
33!
UNCOV
443
      throw new Error('Error while calling fetch endpoint: ', response);
×
444
    }
445
    const fetchResponse = await response.json();
33✔
446
    logInfo(LOG_PREFIX + 'fetch response received from the server', fetchResponse);
33✔
447
    return fetchResponse;
33✔
448
  }
449

450
  #createFetchRequestData() {
451
    const params = this.submoduleConfig.params;
33✔
452
    const hasGdpr = (this.gdprConsentData && typeof this.gdprConsentData.gdprApplies === 'boolean' && this.gdprConsentData.gdprApplies) ? 1 : 0;
33✔
453
    const referer = getRefererInfo();
33✔
454
    const signature = this.cacheIdObj ? this.cacheIdObj.signature : undefined;
33✔
455
    const nbPage = incrementNb(this.cacheIdObj);
33✔
456
    const trueLinkInfo = window.id5Bootstrap ? window.id5Bootstrap.getTrueLinkInfo() : {booted: false};
33✔
457

458
    const data = {
33✔
459
      'partner': params.partner,
460
      'gdpr': hasGdpr,
461
      'nbPage': nbPage,
462
      'o': 'pbjs',
463
      'tml': referer.topmostLocation,
464
      'ref': referer.ref,
465
      'cu': referer.canonicalUrl,
466
      'top': referer.reachedTop ? 1 : 0,
33!
467
      'u': referer.stack[0] || window.location.href,
33!
468
      'v': '$prebid.version$',
469
      'storage': this.submoduleConfig.storage,
470
      'localStorage': storage.localStorageIsEnabled() ? 1 : 0,
33✔
471
      'true_link': trueLinkInfo
472
    };
473

474
    // pass in optional data, but only if populated
475
    if (hasGdpr && this.gdprConsentData.consentString !== undefined && !isEmpty(this.gdprConsentData.consentString) && !isEmptyStr(this.gdprConsentData.consentString)) {
33✔
476
      data.gdpr_consent = this.gdprConsentData.consentString;
2✔
477
    }
478
    if (this.usPrivacyData !== undefined && !isEmpty(this.usPrivacyData) && !isEmptyStr(this.usPrivacyData)) {
33✔
479
      data.us_privacy = this.usPrivacyData;
1✔
480
    }
481
    if (this.gppData) {
33✔
482
      data.gpp_string = this.gppData.gppString;
1✔
483
      data.gpp_sid = this.gppData.applicableSections;
1✔
484
    }
485

486
    if (signature !== undefined && !isEmptyStr(signature)) {
33✔
487
      data.s = signature;
15✔
488
    }
489
    if (params.pd !== undefined && !isEmptyStr(params.pd)) {
33✔
490
      data.pd = params.pd;
1✔
491
    }
492
    if (params.provider !== undefined && !isEmptyStr(params.provider)) {
33!
UNCOV
493
      data.provider = params.provider;
×
494
    }
495
    const abTestingConfig = params.abTesting || {enabled: false};
33✔
496

497
    if (abTestingConfig.enabled) {
33✔
498
      data.ab_testing = {
1✔
499
        enabled: true, control_group_pct: abTestingConfig.controlGroupPct // The server validates
500
      };
501
    }
502
    return data;
33✔
503
  }
504

505
  #processFetchCallResponse(fetchCallResponse) {
506
    try {
33✔
507
      if (fetchCallResponse.privacy) {
33!
UNCOV
508
        if (window.id5Bootstrap && window.id5Bootstrap.setPrivacy) {
×
UNCOV
509
          window.id5Bootstrap.setPrivacy(fetchCallResponse.privacy);
×
510
        }
511
      }
512
    } catch (error) {
UNCOV
513
      logError(LOG_PREFIX + 'Error while writing privacy info into local storage.', error);
×
514
    }
515
    return fetchCallResponse;
33✔
516
  }
517
}
518

519
async function loadExternalModule(url) {
520
  return new PbPromise((resolve, reject) => {
2✔
521
    if (window.id5Prebid) {
2✔
522
      // Already loaded
523
      resolve();
1✔
524
    } else {
525
      try {
1✔
526
        loadExternalScript(url, MODULE_TYPE_UID, 'id5', resolve);
1✔
527
      } catch (error) {
UNCOV
528
        reject(error);
×
529
      }
530
    }
531
  });
532
}
533

534
function validateConfig(config) {
535
  if (!config || !config.params || !config.params.partner) {
62✔
536
    logError(LOG_PREFIX + 'partner required to be defined');
13✔
537
    return false;
13✔
538
  }
539

540
  const partner = config.params.partner;
49✔
541
  if (typeof partner === 'string' || partner instanceof String) {
49✔
542
    const parsedPartnerId = parseInt(partner);
2✔
543
    if (isNaN(parsedPartnerId) || parsedPartnerId < 0) {
2✔
544
      logError(LOG_PREFIX + 'partner required to be a number or a String parsable to a positive integer');
1✔
545
      return false;
1✔
546
    } else {
547
      config.params.partner = parsedPartnerId;
1✔
548
    }
549
  } else if (typeof partner !== 'number') {
47!
UNCOV
550
    logError(LOG_PREFIX + 'partner required to be a number or a String parsable to a positive integer');
×
UNCOV
551
    return false;
×
552
  }
553

554
  if (!config.storage || !config.storage.type || !config.storage.name) {
48✔
555
    logError(LOG_PREFIX + 'storage required to be set');
4✔
556
    return false;
4✔
557
  }
558
  if (config.storage.name !== ID5_STORAGE_NAME) {
44✔
559
    logWarn(LOG_PREFIX + `storage name recommended to be '${ID5_STORAGE_NAME}'.`);
1✔
560
  }
561

562
  return true;
44✔
563
}
564

565
function incrementNb(cachedObj) {
566
  if (cachedObj && cachedObj.nbPage !== undefined) {
34✔
567
    return cachedObj.nbPage + 1;
3✔
568
  } else {
569
    return 1;
31✔
570
  }
571
}
572

573
function updateTargeting(fetchResponse, config) {
574
  const tags = fetchResponse.tags;
36✔
575
  if (tags) {
36✔
576
    if (config.params.gamTargetingPrefix) {
10✔
577
      window.googletag = window.googletag || {cmd: []};
10!
578
      window.googletag.cmd = window.googletag.cmd || [];
10!
579
      window.googletag.cmd.push(() => {
10✔
580
        for (const tag in tags) {
1✔
581
          window.googletag.setConfig({targeting: {[config.params.gamTargetingPrefix + '_' + tag]: tags[tag]}});
3✔
582
        }
583
      });
584
    }
585

586
    if (config.params.exposeTargeting && !deepEqual(window.id5tags?.tags, tags)) {
10✔
587
      window.id5tags = window.id5tags || {cmd: []};
7✔
588
      window.id5tags.cmd = window.id5tags.cmd || [];
7!
589
      window.id5tags.cmd.forEach(tagsCallback => {
7✔
590
        setTimeout(() => tagsCallback(tags), 0);
7✔
591
      });
592
      window.id5tags.cmd.push = function (tagsCallback) {
7✔
593
        tagsCallback(tags)
1✔
594
        Array.prototype.push.call(window.id5tags.cmd, tagsCallback);
1✔
595
      };
596
      window.id5tags.tags = tags
7✔
597
    }
598
  }
599
}
600

601
/**
602
 * Check to see if we can write to local storage based on purpose consent 1, and that we have vendor consent (ID5=131)
603
 * @param {ConsentData} consentData
604
 * @returns {boolean}
605
 */
606
function hasWriteConsentToLocalStorage(consentData) {
607
  const hasGdpr = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies;
67✔
608
  const localstorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`);
67✔
609
  const id5VendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID.toString()}`);
67✔
610
  return !(hasGdpr && (!localstorageConsent || !id5VendorConsent));
67✔
611
}
612

613
/**
614
 *
615
 * @param response {Id5Response|Id5PrebidResponse}
616
 * @param config {Id5PrebidConfig}
617
 */
618
function getPartnerResponse(response, config) {
619
  if (response?.pbjs && isPlainObject(response.pbjs)) {
52✔
620
    return response.pbjs[config.partner];
13✔
621
  }
622
  return undefined;
39✔
623
}
624

625
/**
626
 *
627
 *  @param {Id5Response} response
628
 *  @param {Id5PrebidConfig} config
629
 *  @param {Id5PrebidResponse} cacheIdObj
630
 *  @returns {Id5PrebidResponse}
631
 */
632
function createResponse(response, config, cacheIdObj) {
633
  let responseObj = {}
34✔
634
  if (isPlainObject(cacheIdObj) && (cacheIdObj.universal_uid !== undefined || isPlainObject(cacheIdObj.pbjs))) {
34✔
635
    Object.assign(responseObj, deepClone(cacheIdObj));
16✔
636
  }
637
  Object.assign(responseObj, deepClone(response)); // assign the whole response for old versions
34✔
638
  responseObj.signature = response.signature; // update signature in case it was erased in response
34✔
639
  if (!isPlainObject(responseObj.pbjs)) {
34✔
640
    responseObj.pbjs = {};
28✔
641
  }
642
  responseObj.pbjs[config.partner] = deepClone(response);
34✔
643
  return responseObj;
34✔
644
}
645

646
submodule('userId', id5IdSubmodule);
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