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

prebid / Prebid.js / 17956843100

23 Sep 2025 07:24PM UTC coverage: 96.236% (-0.004%) from 96.24%
17956843100

push

github

web-flow
FWSSP Adapter: update schain logic (#13925)

39884 of 49035 branches covered (81.34%)

24 of 24 new or added lines in 2 files covered. (100.0%)

126 existing lines in 11 files now uncovered.

198197 of 205949 relevant lines covered (96.24%)

124.84 hits per line

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

88.84
/src/utils.js
1
import {config} from './config.js';
2

3
import {EVENTS} from './constants.js';
4
import {PbPromise} from './utils/promise.js';
5
import deepAccess from 'dlv/index.js';
6
import {isArray, isFn, isStr, isPlainObject} from './utils/objects.js';
7

8
export { deepAccess };
9
export { dset as deepSetValue } from 'dset';
10
export * from './utils/objects.js'
11
export {getWinDimensions, resetWinDimensions} from './utils/winDimensions.js';
12

13
const consoleExists = Boolean(window.console);
4✔
14
const consoleLogExists = Boolean(consoleExists && window.console.log);
4✔
15
const consoleInfoExists = Boolean(consoleExists && window.console.info);
4✔
16
const consoleWarnExists = Boolean(consoleExists && window.console.warn);
4✔
17
const consoleErrorExists = Boolean(consoleExists && window.console.error);
4✔
18

19
let eventEmitter;
20

21
export function _setEventEmitter(emitFn) {
22
  // called from events.js - this hoop is to avoid circular imports
23
  eventEmitter = emitFn;
4✔
24
}
25

26
function emitEvent(...args) {
27
  if (eventEmitter != null) {
6,571✔
28
    eventEmitter(...args);
6,571✔
29
  }
30
}
31

32
// this allows stubbing of utility functions that are used internally by other utility functions
33
export const internal = {
4✔
34
  checkCookieSupport,
35
  createTrackPixelIframeHtml,
36
  getWindowSelf,
37
  getWindowTop,
38
  canAccessWindowTop,
39
  getWindowLocation,
40
  insertUserSyncIframe,
41
  insertElement,
42
  isFn,
43
  triggerPixel,
44
  logError,
45
  logWarn,
46
  logMessage,
47
  logInfo,
48
  parseQS,
49
  formatQS,
50
  deepEqual,
51
};
52

53
const prebidInternal = {};
4✔
54
/**
55
 * Returns object that is used as internal prebid namespace
56
 */
57
export function getPrebidInternal() {
UNCOV
58
  return prebidInternal;
×
59
}
60

61
/* utility method to get incremental integer starting from 1 */
62
var getIncrementalInteger = (function () {
4✔
63
  var count = 0;
4✔
64
  return function () {
4✔
65
    count++;
935✔
66
    return count;
935✔
67
  };
68
})();
69

70
// generate a random string (to be used as a dynamic JSONP callback)
71
export function getUniqueIdentifierStr() {
72
  return getIncrementalInteger() + Math.random().toString(16).substr(2);
935✔
73
}
74

75
/**
76
 * Returns a random v4 UUID of the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
77
 * where each x is replaced with a random hexadecimal digit from 0 to f,
78
 * and y is replaced with a random hexadecimal digit from 8 to b.
79
 * https://gist.github.com/jed/982883 via node-uuid
80
 */
81
export function generateUUID(placeholder) {
82
  return placeholder
218,568✔
83
    ? (placeholder ^ _getRandomData() >> placeholder / 4).toString(16)
84
    : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, generateUUID);
85
}
86

87
/**
88
 * Returns random data using the Crypto API if available and Math.random if not
89
 * Method is from https://gist.github.com/jed/982883 like generateUUID, direct link https://gist.github.com/jed/982883#gistcomment-45104
90
 */
91
function _getRandomData() {
92
  if (window && window.crypto && window.crypto.getRandomValues) {
211,738!
93
    return crypto.getRandomValues(new Uint8Array(1))[0] % 16;
211,738✔
94
  } else {
UNCOV
95
    return Math.random() * 16;
×
96
  }
97
}
98

99
export function getBidIdParameter(key, paramsObj) {
100
  return paramsObj?.[key] || '';
3,477✔
101
}
102

103
// parse a query string object passed in bid params
104
// bid params should be an object such as {key: "value", key1 : "value1"}
105
// aliases to formatQS
106
export function parseQueryStringParameters(queryObj) {
107
  let result = '';
78✔
108
  for (var k in queryObj) {
78✔
109
    if (queryObj.hasOwnProperty(k)) { result += k + '=' + encodeURIComponent(queryObj[k]) + '&'; }
975✔
110
  }
111
  result = result.replace(/&$/, '');
78✔
112
  return result;
78✔
113
}
114

115
// transform an AdServer targeting bids into a query string to send to the adserver
116
export function transformAdServerTargetingObj(targeting) {
117
  // we expect to receive targeting for a single slot at a time
118
  if (targeting && Object.getOwnPropertyNames(targeting).length > 0) {
3✔
119
    return Object.keys(targeting)
2✔
120
      .map(key => `${key}=${encodeURIComponent(targeting[key])}`).join('&');
66✔
121
  } else {
122
    return '';
1✔
123
  }
124
}
125

126
/**
127
 * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of width, height tuples `[[300, 250]]` or '[[300,250], [970,90]]'
128
 */
129
export function sizesToSizeTuples(sizes) {
130
  if (typeof sizes === 'string') {
3,744✔
131
    // multiple sizes will be comma-separated
132
    return sizes
32✔
133
      .split(/\s*,\s*/)
134
      .map(sz => sz.match(/^(\d+)x(\d+)$/i))
35✔
135
      .filter(match => match)
35✔
136
      .map(([_, w, h]) => [parseInt(w, 10), parseInt(h, 10)])
28✔
137
  } else if (Array.isArray(sizes)) {
3,712✔
138
    if (isValidGPTSingleSize(sizes)) {
3,526✔
139
      return [sizes]
448✔
140
    }
141
    return sizes.filter(isValidGPTSingleSize);
3,078✔
142
  }
143
  return [];
186✔
144
}
145

146
/**
147
 * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']'
148
 * @param  {(Array.<number[]>|Array.<number>)} sizeObj Input array or double array [300,250] or [[300,250], [728,90]]
149
 * @return {Array.<string>}  Array of strings like `["300x250"]` or `["300x250", "728x90"]`
150
 */
151
export function parseSizesInput(sizeObj) {
152
  return sizesToSizeTuples(sizeObj).map(sizeTupleToSizeString);
1,930✔
153
}
154

155
export function sizeTupleToSizeString(size) {
156
  return size[0] + 'x' + size[1]
3,360✔
157
}
158

159
// Parse a GPT style single size array, (i.e [300, 250])
160
// into an AppNexus style string, (i.e. 300x250)
161
export function parseGPTSingleSizeArray(singleSize) {
162
  if (isValidGPTSingleSize(singleSize)) {
32✔
163
    return sizeTupleToSizeString(singleSize);
22✔
164
  }
165
}
166

167
export function sizeTupleToRtbSize(size) {
168
  return {w: size[0], h: size[1]};
2,850✔
169
}
170

171
// Parse a GPT style single size array, (i.e [300, 250])
172
// into OpenRTB-compatible (imp.banner.w/h, imp.banner.format.w/h, imp.video.w/h) object(i.e. {w:300, h:250})
173
export function parseGPTSingleSizeArrayToRtbSize(singleSize) {
174
  if (isValidGPTSingleSize(singleSize)) {
307✔
175
    return sizeTupleToRtbSize(singleSize)
301✔
176
  }
177
}
178

179
function isValidGPTSingleSize(singleSize) {
180
  // if we aren't exactly 2 items in this array, it is invalid
181
  return isArray(singleSize) && singleSize.length === 2 && (!isNaN(singleSize[0]) && !isNaN(singleSize[1]));
9,321✔
182
}
183

184
export function getWindowTop() {
185
  return window.top;
3,283✔
186
}
187

188
export function getWindowSelf() {
189
  return window.self;
1,899✔
190
}
191

192
export function getWindowLocation() {
193
  return window.location;
1,897✔
194
}
195

196
export function getDocument() {
197
  return document;
395✔
198
}
199

200
export function canAccessWindowTop() {
201
  try {
710✔
202
    if (internal.getWindowTop().location.href) {
710✔
203
      return true;
555✔
204
    }
205
  } catch (e) {
206
    return false;
155✔
207
  }
208
}
209

210
/**
211
 * Wrappers to console.(log | info | warn | error). Takes N arguments, the same as the native methods
212
 */
213
// eslint-disable-next-line no-restricted-syntax
214
export function logMessage() {
215
  if (debugTurnedOn() && consoleLogExists) {
16,822✔
216
    // eslint-disable-next-line no-console
217
    console.log.apply(console, decorateLog(arguments, 'MESSAGE:'));
19✔
218
  }
219
}
220

221
// eslint-disable-next-line no-restricted-syntax
222
export function logInfo() {
223
  if (debugTurnedOn() && consoleInfoExists) {
12,524✔
224
    // eslint-disable-next-line no-console
225
    console.info.apply(console, decorateLog(arguments, 'INFO:'));
8✔
226
  }
227
}
228

229
// eslint-disable-next-line no-restricted-syntax
230
export function logWarn() {
231
  if (debugTurnedOn() && consoleWarnExists) {
4,618✔
232
    // eslint-disable-next-line no-console
233
    console.warn.apply(console, decorateLog(arguments, 'WARNING:'));
7✔
234
  }
235
  emitEvent(EVENTS.AUCTION_DEBUG, { type: 'WARNING', arguments: arguments });
4,618✔
236
}
237

238
// eslint-disable-next-line no-restricted-syntax
239
export function logError() {
240
  if (debugTurnedOn() && consoleErrorExists) {
1,953✔
241
    // eslint-disable-next-line no-console
242
    console.error.apply(console, decorateLog(arguments, 'ERROR:'));
2✔
243
  }
244
  emitEvent(EVENTS.AUCTION_DEBUG, { type: 'ERROR', arguments: arguments });
1,953✔
245
}
246

247
export function prefixLog(prefix) {
248
  function decorate(fn) {
249
    return function (...args) {
2,364✔
250
      fn(prefix, ...args);
1,080✔
251
    }
252
  }
253
  return {
151✔
254
    logError: decorate(logError),
255
    logWarn: decorate(logWarn),
256
    logMessage: decorate(logMessage),
257
    logInfo: decorate(logInfo),
258
  }
259
}
260

261
function decorateLog(args, prefix) {
262
  args = [].slice.call(args);
36✔
263
  const bidder = config.getCurrentBidder();
36✔
264

265
  prefix && args.unshift(prefix);
36✔
266
  if (bidder) {
36!
UNCOV
267
    args.unshift(label('#aaa'));
×
268
  }
269
  args.unshift(label('#3b88c3'));
36✔
270
  args.unshift('%cPrebid' + (bidder ? `%c${bidder}` : ''));
36!
271
  return args;
36✔
272

273
  function label(color) {
274
    return `display: inline-block; color: #fff; background: ${color}; padding: 1px 4px; border-radius: 3px;`
36✔
275
  }
276
}
277

278
export function hasConsoleLogger() {
UNCOV
279
  return consoleLogExists;
×
280
}
281

282
export function debugTurnedOn() {
283
  return !!config.getConfig('debug');
36,808✔
284
}
285

286
export const createIframe = (() => {
4✔
287
  const DEFAULTS = {
4✔
288
    border: '0px',
289
    hspace: '0',
290
    vspace: '0',
291
    marginWidth: '0',
292
    marginHeight: '0',
293
    scrolling: 'no',
294
    frameBorder: '0',
295
    allowtransparency: 'true'
296
  }
297
  return (doc, attrs, style = {}) => {
18✔
298
    const f = doc.createElement('iframe');
16✔
299
    Object.assign(f, Object.assign({}, DEFAULTS, attrs));
15✔
300
    Object.assign(f.style, style);
8✔
301
    return f;
7✔
302
  }
303
})();
304

305
export function createInvisibleIframe() {
306
  return createIframe(document, {
6✔
307
    id: getUniqueIdentifierStr(),
308
    width: 0,
309
    height: 0,
310
    src: 'about:blank'
311
  }, {
312
    display: 'none',
313
    height: '0px',
314
    width: '0px',
315
    border: '0px'
316
  });
317
}
318

319
/*
320
 *   Check if a given parameter name exists in query string
321
 *   and if it does return the value
322
 */
323
export function getParameterByName(name) {
324
  return parseQS(getWindowLocation().search)[name] || '';
1,737✔
325
}
326

327
/**
328
 * Return if the object is "empty";
329
 * this includes falsey, no keys, or no items at indices
330
 * @param {*} object object to test
331
 * @return {Boolean} if object is empty
332
 */
333
export function isEmpty(object) {
334
  if (!object) return true;
54,852✔
335
  if (isArray(object) || isStr(object)) {
49,835✔
336
    return !(object.length > 0);
22,501✔
337
  }
338
  return Object.keys(object).length <= 0;
27,334✔
339
}
340

341
/**
342
 * Return if string is empty, null, or undefined
343
 * @param str string to test
344
 * @returns {boolean} if string is empty
345
 */
346
export function isEmptyStr(str) {
347
  return isStr(str) && (!str || str.length === 0);
519✔
348
}
349

350
/**
351
 * Iterate object with the function
352
 * falls back to es5 `forEach`
353
 * @param {Array|Object} object
354
 * @param {Function} fn - The function to execute for each element. It receives three arguments: value, key, and the original object.
355
 * @returns {void}
356
 */
357
export function _each(object, fn) {
358
  if (isFn(object?.forEach)) return object.forEach(fn, this);
7,216✔
359
  Object.entries(object || {}).forEach(([k, v]) => fn.call(this, v, k));
35,846✔
360
}
361

362
export function contains(a, obj) {
363
  return isFn(a?.includes) && a.includes(obj);
31✔
364
}
365

366
/**
367
 * Map an array or object into another array
368
 * given a function
369
 * @param {Array|Object} object
370
 * @param {Function} callback - The function to execute for each element. It receives three arguments: value, key, and the original object.
371
 * @return {Array}
372
 */
373
export function _map(object, callback) {
374
  if (isFn(object?.map)) return object.map(callback);
454✔
375
  return Object.entries(object || {}).map(([k, v]) => callback(v, k, object))
6,738✔
376
}
377

378
/*
379
* Inserts an element(elm) as targets child, by default as first child
380
* @param {HTMLElement} elm
381
* @param {HTMLElement} [doc]
382
* @param {HTMLElement} [target]
383
* @param {Boolean} [asLastChildChild]
384
* @return {HTML Element}
385
*/
386
export function insertElement(elm, doc, target, asLastChildChild) {
387
  doc = doc || document;
19✔
388
  let parentEl;
389
  if (target) {
19✔
390
    parentEl = doc.getElementsByTagName(target);
15✔
391
  } else {
392
    parentEl = doc.getElementsByTagName('head');
4✔
393
  }
394
  try {
19✔
395
    parentEl = parentEl.length ? parentEl : doc.getElementsByTagName('body');
19!
396
    if (parentEl.length) {
19✔
397
      parentEl = parentEl[0];
19✔
398
      const insertBeforeEl = asLastChildChild ? null : parentEl.firstChild;
19✔
399
      return parentEl.insertBefore(elm, insertBeforeEl);
19✔
400
    }
401
  } catch (e) {}
402
}
403

404
/**
405
 * Returns a promise that completes when the given element triggers a 'load' or 'error' DOM event, or when
406
 * `timeout` milliseconds have elapsed.
407
 *
408
 * @param {HTMLElement} element
409
 * @param {Number} [timeout]
410
 * @returns {Promise}
411
 */
412
export function waitForElementToLoad(element, timeout) {
413
  let timer = null;
3✔
414
  return new PbPromise((resolve) => {
3✔
415
    const onLoad = function() {
3✔
416
      element.removeEventListener('load', onLoad);
3✔
417
      element.removeEventListener('error', onLoad);
3✔
418
      if (timer != null) {
3✔
419
        window.clearTimeout(timer);
1✔
420
      }
421
      resolve();
3✔
422
    };
423
    element.addEventListener('load', onLoad);
3✔
424
    element.addEventListener('error', onLoad);
3✔
425
    if (timeout != null) {
3✔
426
      timer = window.setTimeout(onLoad, timeout);
1✔
427
    }
428
  });
429
}
430

431
/**
432
 * Inserts an image pixel with the specified `url` for cookie sync
433
 * @param {string} url URL string of the image pixel to load
434
 * @param  {function} [done] an optional exit callback, used when this usersync pixel is added during an async process
435
 * @param  {Number} [timeout] an optional timeout in milliseconds for the image to load before calling `done`
436
 */
437
export function triggerPixel(url, done, timeout) {
438
  const img = new Image();
88✔
439
  if (done && internal.isFn(done)) {
88!
UNCOV
440
    waitForElementToLoad(img, timeout).then(done);
×
441
  }
442
  img.src = url;
88✔
443
}
444

445
/**
446
 * Inserts an empty iframe with the specified `html`, primarily used for tracking purposes
447
 * (though could be for other purposes)
448
 * @param {string} htmlCode snippet of HTML code used for tracking purposes
449
 */
450
export function insertHtmlIntoIframe(htmlCode) {
UNCOV
451
  if (!htmlCode) {
×
UNCOV
452
    return;
×
453
  }
UNCOV
454
  const iframe = createInvisibleIframe();
×
UNCOV
455
  internal.insertElement(iframe, document, 'body');
×
456

UNCOV
457
  ((doc) => {
×
UNCOV
458
    doc.open();
×
UNCOV
459
    doc.write(htmlCode);
×
UNCOV
460
    doc.close();
×
461
  })(iframe.contentWindow.document);
462
}
463

464
/**
465
 * Inserts empty iframe with the specified `url` for cookie sync
466
 * @param  {string} url URL to be requested
467
 * @param  {function} [done] an optional exit callback, used when this usersync pixel is added during an async process
468
 * @param  {Number} [timeout] an optional timeout in milliseconds for the iframe to load before calling `done`
469
 */
470
export function insertUserSyncIframe(url, done, timeout) {
471
  const iframeHtml = internal.createTrackPixelIframeHtml(url, false, 'allow-scripts allow-same-origin');
1✔
472
  const div = document.createElement('div');
1✔
473
  div.innerHTML = iframeHtml;
1✔
474
  const iframe = div.firstChild;
1✔
475
  if (done && internal.isFn(done)) {
1!
UNCOV
476
    waitForElementToLoad(iframe, timeout).then(done);
×
477
  }
478
  internal.insertElement(iframe, document, 'html', true);
1✔
479
}
480

481
/**
482
 * Creates a snippet of HTML that retrieves the specified `url`
483
 * @param  {string} url URL to be requested
484
 * @param encode
485
 * @return {string}     HTML snippet that contains the img src = set to `url`
486
 */
487
export function createTrackPixelHtml(url, encode = encodeURI) {
97✔
488
  if (!url) {
97!
489
    return '';
×
490
  }
491

492
  const escapedUrl = encode(url);
97✔
493
  let img = '<div style="position:absolute;left:0px;top:0px;visibility:hidden;">';
97✔
494
  img += '<img src="' + escapedUrl + '"></div>';
97✔
495
  return img;
97✔
496
};
497

498
/**
499
 * encodeURI, but preserves macros of the form '${MACRO}' (e.g. '${AUCTION_PRICE}')
500
 * @param url
501
 * @return {string}
502
 */
503
export function encodeMacroURI(url) {
504
  const macros = Array.from(url.matchAll(/\$({[^}]+})/g)).map(match => match[1]);
48✔
505
  return macros.reduce((str, macro) => {
39✔
506
    return str.replace('$' + encodeURIComponent(macro), '$' + macro)
25✔
507
  }, encodeURI(url))
508
}
509

510
/**
511
 * Creates a snippet of Iframe HTML that retrieves the specified `url`
512
 * @param  {string} url plain URL to be requested
513
 * @param  {string} encodeUri boolean if URL should be encoded before inserted. Defaults to true
514
 * @param  {string} sandbox string if provided the sandbox attribute will be included with the given value
515
 * @return {string}     HTML snippet that contains the iframe src = set to `url`
516
 */
517
export function createTrackPixelIframeHtml(url, encodeUri = true, sandbox = '') {
1!
518
  if (!url) {
1!
UNCOV
519
    return '';
×
520
  }
521
  if (encodeUri) {
1!
UNCOV
522
    url = encodeURI(url);
×
523
  }
524
  if (sandbox) {
1✔
525
    sandbox = `sandbox="${sandbox}"`;
1✔
526
  }
527

528
  return `<iframe ${sandbox} id="${getUniqueIdentifierStr()}"
1✔
529
      frameborder="0"
530
      allowtransparency="true"
531
      marginheight="0" marginwidth="0"
532
      width="0" hspace="0" vspace="0" height="0"
533
      style="height:0px;width:0px;display:none;"
534
      scrolling="no"
535
      src="${url}">
536
    </iframe>`;
537
}
538

539
export function uniques(value, index, arry) {
540
  return arry.indexOf(value) === index;
18,308✔
541
}
542

543
export function flatten(a, b) {
544
  return a.concat(b);
3,378✔
545
}
546

547
export function getBidRequest(id, bidderRequests) {
548
  if (!id) {
248✔
549
    return;
4✔
550
  }
551
  return bidderRequests.flatMap(br => br.bids)
329✔
552
    .find(bid => ['bidId', 'adId', 'bid_id'].some(prop => bid[prop] === id))
361✔
553
}
554

555
export function getValue(obj, key) {
556
  return obj[key];
101✔
557
}
558

559
export function getBidderCodes(adUnits) {
560
  // this could memoize adUnits
561
  return adUnits.map(unit => unit.bids.map(bid => bid.bidder)
1,141✔
562
    .reduce(flatten, [])).reduce(flatten, []).filter((bidder) => typeof bidder !== 'undefined').filter(uniques);
1,141✔
563
}
564

565
export function isGptPubadsDefined() {
566
  if (window.googletag && isFn(window.googletag.pubads) && isFn(window.googletag.pubads().getSlots)) {
834✔
567
    return true;
814✔
568
  }
569
}
570

571
export function isApnGetTagDefined() {
572
  if (window.apntag && isFn(window.apntag.getTag)) {
5✔
573
    return true;
4✔
574
  }
575
}
576

577
export const sortByHighestCpm = (a, b) => {
4✔
578
  return b.cpm - a.cpm;
659✔
579
}
580

581
/**
582
 * Fisher–Yates shuffle
583
 * http://stackoverflow.com/a/6274398
584
 * https://bost.ocks.org/mike/shuffle/
585
 * istanbul ignore next
586
 */
587
export function shuffle(array) {
588
  let counter = array.length;
128✔
589

590
  // while there are elements in the array
591
  while (counter > 0) {
128✔
592
    // pick a random index
593
    const index = Math.floor(Math.random() * counter);
754✔
594

595
    // decrease counter by 1
596
    counter--;
754✔
597

598
    // and swap the last element with it
599
    const temp = array[counter];
754✔
600
    array[counter] = array[index];
754✔
601
    array[index] = temp;
754✔
602
  }
603

604
  return array;
128✔
605
}
606

607
export function inIframe() {
608
  try {
1,614✔
609
    return internal.getWindowSelf() !== internal.getWindowTop();
1,614✔
610
  } catch (e) {
UNCOV
611
    return true;
×
612
  }
613
}
614

615
/**
616
 * https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf
617
 */
618
export function isSafeFrameWindow() {
619
  if (!inIframe()) {
72!
UNCOV
620
    return false;
×
621
  }
622

623
  const ws = internal.getWindowSelf();
72✔
624
  return !!(ws.$sf && ws.$sf.ext);
72✔
625
}
626

627
/**
628
 * Returns the result of calling the function $sf.ext.geom() if it exists
629
 * @see https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf — 5.4 Function $sf.ext.geom
630
 * @returns {Object | undefined} geometric information about the container
631
 */
632
export function getSafeframeGeometry() {
633
  try {
4✔
634
    const ws = getWindowSelf();
4✔
635
    return (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : undefined;
4✔
636
  } catch (e) {
UNCOV
637
    logError('Error getting SafeFrame geometry', e);
×
UNCOV
638
    return undefined;
×
639
  }
640
}
641

642
export function isSafariBrowser() {
643
  return /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent);
74✔
644
}
645

646
export function replaceMacros(str, subs) {
647
  if (!str) return;
157✔
648
  return Object.entries(subs).reduce((str, [key, val]) => {
131✔
649
    return str.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), val || '');
131✔
650
  }, str);
651
}
652

653
export function replaceAuctionPrice(str, cpm) {
654
  return replaceMacros(str, {AUCTION_PRICE: cpm})
105✔
655
}
656

657
export function replaceClickThrough(str, clicktag) {
UNCOV
658
  if (!str || !clicktag || typeof clicktag !== 'string') return;
×
UNCOV
659
  return str.replace(/\${CLICKTHROUGH}/g, clicktag);
×
660
}
661

662
export function timestamp() {
663
  return new Date().getTime();
11,700✔
664
}
665

666
/**
667
 * The returned value represents the time elapsed since the time origin. @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
668
 * @returns {number}
669
 */
670
export function getPerformanceNow() {
671
  return (window.performance && window.performance.now && window.performance.now()) || 0;
10,735✔
672
}
673

674
/**
675
 * Retuns the difference between `timing.domLoading` and `timing.navigationStart`.
676
 * This function uses the deprecated `Performance.timing` API and should be removed in future.
677
 * It has not been updated yet because it is still used in some modules.
678
 * @deprecated
679
 * @param {Window} w The window object used to perform the api call. default to window.self
680
 * @returns {number}
681
 */
682
export function getDomLoadingDuration(w) {
683
  let domLoadingDuration = -1;
17✔
684

685
  w = w || getWindowSelf();
17!
686

687
  const performance = w.performance;
17✔
688

689
  if (w.performance?.timing) {
17✔
690
    if (w.performance.timing.navigationStart > 0) {
17✔
691
      const val = performance.timing.domLoading - performance.timing.navigationStart;
14✔
692
      if (val > 0) {
14✔
693
        domLoadingDuration = val;
14✔
694
      }
695
    }
696
  }
697

698
  return domLoadingDuration;
17✔
699
}
700

701
/**
702
 * When the deviceAccess flag config option is false, no cookies should be read or set
703
 * @returns {boolean}
704
 */
705
export function hasDeviceAccess() {
706
  return config.getConfig('deviceAccess') !== false;
18,874✔
707
}
708

709
/**
710
 * @returns {(boolean|undefined)}
711
 */
712
export function checkCookieSupport() {
713
  // eslint-disable-next-line no-restricted-properties
714
  if (window.navigator.cookieEnabled || !!document.cookie.length) {
1,240!
715
    return true;
1,240✔
716
  }
717
}
718

719
/**
720
 * Given a function, return a function which only executes the original after
721
 * it's been called numRequiredCalls times.
722
 *
723
 * Note that the arguments from the previous calls will *not* be forwarded to the original function.
724
 * Only the final call's arguments matter.
725
 *
726
 * @param {function} func The function which should be executed, once the returned function has been executed
727
 *   numRequiredCalls times.
728
 * @param {number} numRequiredCalls The number of times which the returned function needs to be called before
729
 *   func is.
730
 */
731
export function delayExecution(func, numRequiredCalls) {
732
  if (numRequiredCalls < 1) {
392!
UNCOV
733
    throw new Error(`numRequiredCalls must be a positive number. Got ${numRequiredCalls}`);
×
734
  }
735
  let numCalls = 0;
392✔
736
  return function () {
392✔
737
    numCalls++;
183✔
738
    if (numCalls === numRequiredCalls) {
183✔
739
      func.apply(this, arguments);
159✔
740
    }
741
  }
742
}
743

744
/**
745
 * https://stackoverflow.com/a/34890276/428704
746
 * @param {Array} xs
747
 * @param {string} key
748
 * @returns {Object} {${key_value}: ${groupByArray}, key_value: {groupByArray}}
749
 */
750
export function groupBy(xs, key) {
751
  return xs.reduce(function(rv, x) {
1,125✔
752
    (rv[x[key]] = rv[x[key]] || []).push(x);
1,715✔
753
    return rv;
1,715✔
754
  }, {});
755
}
756

757
/**
758
 * Validates an adunit's `mediaTypes` parameter
759
 * @param mediaTypes mediaTypes parameter to validate
760
 * @return If object is valid
761
 */
762
export function isValidMediaTypes(mediaTypes) {
763
  const SUPPORTED_MEDIA_TYPES = ['banner', 'native', 'video', 'audio'];
1,020✔
764
  const SUPPORTED_STREAM_TYPES = ['instream', 'outstream', 'adpod'];
1,020✔
765

766
  const types = Object.keys(mediaTypes);
1,020✔
767

768
  if (!types.every(type => SUPPORTED_MEDIA_TYPES.includes(type))) {
1,020!
UNCOV
769
    return false;
×
770
  }
771

772
  if (FEATURES.VIDEO && mediaTypes.video && mediaTypes.video.context) {
1,020!
UNCOV
773
    return SUPPORTED_STREAM_TYPES.includes(mediaTypes.video.context);
×
774
  }
775

776
  return true;
1,020✔
777
}
778

779
/**
780
 * Returns user configured bidder params from adunit
781
 * @param {Object} adUnits
782
 * @param {string} adUnitCode code
783
 * @param {string} bidder code
784
 * @return {Array} user configured param for the given bidder adunit configuration
785
 */
786
export function getUserConfiguredParams(adUnits, adUnitCode, bidder) {
787
  return adUnits
341✔
788
    .filter(adUnit => adUnit.code === adUnitCode)
657✔
789
    .flatMap((adUnit) => adUnit.bids)
317✔
790
    .filter((bidderData) => bidderData.bidder === bidder)
3,525✔
791
    .map((bidderData) => bidderData.params || {});
316✔
792
}
793

794
/**
795
 * Returns Do Not Track state
796
 */
797
export function getDNT() {
798
  return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1' || navigator.doNotTrack === 'yes';
726✔
799
}
800

801
export const compareCodeAndSlot = (slot, adUnitCode) => slot.getAdUnitPath() === adUnitCode || slot.getSlotElementId() === adUnitCode;
905✔
802

803
/**
804
 * Returns filter function to match adUnitCode in slot
805
 * @param slot GoogleTag slot
806
 * @return filter function
807
 */
808
export function isAdUnitCodeMatchingSlot(slot) {
809
  return (adUnitCode) => compareCodeAndSlot(slot, adUnitCode);
218✔
810
}
811

812
/**
813
 * Constructs warning message for when unsupported bidders are dropped from an adunit
814
 * @param {Object} adUnit ad unit from which the bidder is being dropped
815
 * @param {string} bidder bidder code that is not compatible with the adUnit
816
 * @return {string} warning message to display when condition is met
817
 */
818
export function unsupportedBidderMessage(adUnit, bidder) {
819
  const mediaType = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}).join(', ');
6!
820

821
  return `
6✔
822
    ${adUnit.code} is a ${mediaType} ad unit
823
    containing bidders that don't support ${mediaType}: ${bidder}.
824
    This bidder won't fetch demand.
825
  `;
826
}
827

828
/**
829
 * Returns a new object with undefined properties removed from given object
830
 * @param obj the object to clean
831
 */
832
export function cleanObj(obj) {
833
  return Object.fromEntries(Object.entries(obj).filter(([_, v]) => typeof v !== 'undefined'))
318✔
834
}
835

836
/**
837
 * Create a new object with selected properties.  Also allows property renaming and transform functions.
838
 * @param obj the original object
839
 * @param properties An array of desired properties
840
 */
841
export function pick(obj, properties) {
842
  if (typeof obj !== 'object') {
1,915✔
843
    return {};
3✔
844
  }
845
  return properties.reduce((newObj, prop, i) => {
1,912✔
846
    if (typeof prop === 'function') {
21,864✔
847
      return newObj;
9,060✔
848
    }
849

850
    let newProp = prop;
12,804✔
851
    const match = prop.match(/^(.+?)\sas\s(.+?)$/i);
12,804✔
852

853
    if (match) {
12,804✔
854
      prop = match[1];
478✔
855
      newProp = match[2];
478✔
856
    }
857

858
    let value = obj[prop];
12,804✔
859
    if (typeof properties[i + 1] === 'function') {
12,804✔
860
      value = properties[i + 1](value, newObj);
9,060✔
861
    }
862
    if (typeof value !== 'undefined') {
12,804✔
863
      newObj[newProp] = value;
8,967✔
864
    }
865

866
    return newObj;
12,804✔
867
  }, {});
868
}
869

870
export function parseQS(query) {
871
  return !query ? {} : query
2,901✔
872
    .replace(/^\?/, '')
873
    .split('&')
874
    .reduce((acc, criteria) => {
875
      let [k, v] = criteria.split('=');
3,277✔
876
      if (/\[\]$/.test(k)) {
3,277!
UNCOV
877
        k = k.replace('[]', '');
×
UNCOV
878
        acc[k] = acc[k] || [];
×
UNCOV
879
        acc[k].push(v);
×
880
      } else {
881
        acc[k] = v || '';
3,277✔
882
      }
883
      return acc;
3,277✔
884
    }, {});
885
}
886

887
export function formatQS(query) {
888
  return Object
471✔
889
    .keys(query)
890
    .map(k => Array.isArray(query[k])
2,641✔
891
      ? query[k].map(v => `${k}[]=${v}`).join('&')
19✔
892
      : `${k}=${query[k]}`)
893
    .join('&');
894
}
895

896
export function parseUrl(url, options) {
897
  const parsed = document.createElement('a');
1,160✔
898
  if (options && 'noDecodeWholeURL' in options && options.noDecodeWholeURL) {
1,160✔
899
    parsed.href = url;
127✔
900
  } else {
901
    parsed.href = decodeURIComponent(url);
1,033✔
902
  }
903
  // in window.location 'search' is string, not object
904
  const qsAsString = (options && 'decodeSearchAsString' in options && options.decodeSearchAsString);
1,160✔
905
  return {
1,160✔
906
    href: parsed.href,
907
    protocol: (parsed.protocol || '').replace(/:$/, ''),
1,160!
908
    hostname: parsed.hostname,
909
    port: +parsed.port,
910
    pathname: parsed.pathname.replace(/^(?!\/)/, '/'),
911
    search: (qsAsString) ? parsed.search : internal.parseQS(parsed.search || ''),
2,361✔
912
    hash: (parsed.hash || '').replace(/^#/, ''),
2,311✔
913
    host: parsed.host || window.location.host
1,160!
914
  };
915
}
916

917
export function buildUrl(obj) {
918
  return (obj.protocol || 'http') + '://' +
400✔
919
    (obj.host ||
499✔
920
      obj.hostname + (obj.port ? `:${obj.port}` : '')) +
99✔
921
    (obj.pathname || '') +
401✔
922
    (obj.search ? `?${internal.formatQS(obj.search || '')}` : '') +
648!
923
    (obj.hash ? `#${obj.hash}` : '');
400✔
924
}
925

926
/**
927
 * This function deeply compares two objects checking for their equivalence.
928
 * @param {Object} obj1
929
 * @param {Object} obj2
930
 * @param {Object} [options] - Options for comparison.
931
 * @param {boolean} [options.checkTypes=false] - If set, two objects with identical properties but different constructors will *not* be considered equivalent.
932
 * @returns {boolean} - Returns `true` if the objects are equivalent, `false` otherwise.
933
 */
934
export function deepEqual(obj1, obj2, { checkTypes = false } = {}) {
1,952✔
935
  // Quick reference check
936
  if (obj1 === obj2) return true;
1,952✔
937

938
  // If either is null or not an object, do a direct equality check
939
  if (
942✔
940
    typeof obj1 !== 'object' || obj1 === null ||
2,289✔
941
    typeof obj2 !== 'object' || obj2 === null
942
  ) {
943
    return false;
493✔
944
  }
945
  // Cache the Array checks
946
  const isArr1 = Array.isArray(obj1);
449✔
947
  const isArr2 = Array.isArray(obj2);
449✔
948
  // Special case: both are arrays
949
  if (isArr1 && isArr2) {
449✔
950
    if (obj1.length !== obj2.length) return false;
90✔
951
    for (let i = 0; i < obj1.length; i++) {
60✔
952
      if (!deepEqual(obj1[i], obj2[i], { checkTypes })) {
83✔
953
        return false;
31✔
954
      }
955
    }
956
    return true;
29✔
957
  } else if (isArr1 || isArr2) {
359!
UNCOV
958
    return false;
×
959
  }
960

961
  // If we’re checking types, compare constructors (e.g., plain object vs. Date)
962
  if (checkTypes && obj1.constructor !== obj2.constructor) {
359✔
963
    return false;
1✔
964
  }
965

966
  // Compare object keys. Cache keys for both to avoid repeated calls.
967
  const keys1 = Object.keys(obj1);
358✔
968
  const keys2 = Object.keys(obj2);
358✔
969

970
  if (keys1.length !== keys2.length) return false;
358✔
971

972
  for (const key of keys1) {
351✔
973
    // If `obj2` doesn't have this key or sub-values aren't equal, bail out.
974
    if (!Object.prototype.hasOwnProperty.call(obj2, key)) {
392!
UNCOV
975
      return false;
×
976
    }
977
    if (!deepEqual(obj1[key], obj2[key], { checkTypes })) {
392✔
978
      return false;
324✔
979
    }
980
  }
981

982
  return true;
27✔
983
}
984

985
export function mergeDeep(target, ...sources) {
986
  for (let i = 0; i < sources.length; i++) {
167,974✔
987
    const source = sources[i];
329,128!
988
    if (!isPlainObject(source)) {
329,128✔
989
      continue;
8,347✔
990
    }
991
    mergeDeepHelper(target, source);
320,781✔
992
  }
993
  return target;
167,974✔
994
}
995

996
function mergeDeepHelper(target, source) {
997
  // quick check
998
  if (!isPlainObject(target) || !isPlainObject(source)) {
334,965✔
999
    return;
37✔
1000
  }
1001

1002
  const keys = Object.keys(source);
334,928✔
1003
  for (let i = 0; i < keys.length; i++) {
334,928✔
1004
    const key = keys[i];
347,690✔
1005
    if (key === '__proto__' || key === 'constructor') {
347,690!
UNCOV
1006
      continue;
×
1007
    }
1008
    const val = source[key];
347,690✔
1009

1010
    if (isPlainObject(val)) {
347,690✔
1011
      if (!target[key]) {
14,184✔
1012
        target[key] = {};
12,864✔
1013
      }
1014
      mergeDeepHelper(target[key], val);
14,184✔
1015
    } else if (Array.isArray(val)) {
333,506✔
1016
      if (!Array.isArray(target[key])) {
309,972✔
1017
        target[key] = [...val];
2,503✔
1018
      } else {
1019
        // deduplicate
1020
        val.forEach(obj => {
307,469✔
1021
          if (!target[key].some(item => deepEqual(item, obj))) {
709✔
1022
            target[key].push(obj);
97✔
1023
          }
1024
        });
1025
      }
1026
    } else {
1027
      // direct assignment
1028
      target[key] = val;
23,534✔
1029
    }
1030
  }
1031
}
1032

1033
/**
1034
 * returns a hash of a string using a fast algorithm
1035
 * source: https://stackoverflow.com/a/52171480/845390
1036
 * @param str
1037
 * @param seed (optional)
1038
 * @returns {string}
1039
 */
1040
export function cyrb53Hash(str, seed = 0) {
162✔
1041
  // IE doesn't support imul
1042
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul#Polyfill
1043
  const imul = function(opA, opB) {
162✔
1044
    if (isFn(Math.imul)) {
12,288!
1045
      return Math.imul(opA, opB);
12,288✔
1046
    } else {
UNCOV
1047
      opB |= 0; // ensure that opB is an integer. opA will automatically be coerced.
×
1048
      // floating points give us 53 bits of precision to work with plus 1 sign bit
1049
      // automatically handled for our convienence:
1050
      // 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001
1051
      //    0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/
UNCOV
1052
      var result = (opA & 0x003fffff) * opB;
×
1053
      // 2. We can remove an integer coersion from the statement above because:
1054
      //    0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001
1055
      //    0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/
UNCOV
1056
      if (opA & 0xffc00000) result += (opA & 0xffc00000) * opB | 0;
×
UNCOV
1057
      return result | 0;
×
1058
    }
1059
  };
1060

1061
  let h1 = 0xdeadbeef ^ seed;
162✔
1062
  let h2 = 0x41c6ce57 ^ seed;
162✔
1063
  for (let i = 0, ch; i < str.length; i++) {
162✔
1064
    ch = str.charCodeAt(i);
5,820✔
1065
    h1 = imul(h1 ^ ch, 2654435761);
5,820✔
1066
    h2 = imul(h2 ^ ch, 1597334677);
5,820✔
1067
  }
1068
  h1 = imul(h1 ^ (h1 >>> 16), 2246822507) ^ imul(h2 ^ (h2 >>> 13), 3266489909);
162✔
1069
  h2 = imul(h2 ^ (h2 >>> 16), 2246822507) ^ imul(h1 ^ (h1 >>> 13), 3266489909);
162✔
1070
  return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString();
162✔
1071
}
1072

1073
/**
1074
 * returns the result of `JSON.parse(data)`, or undefined if that throws an error.
1075
 * @param data
1076
 * @returns {any}
1077
 */
1078
export function safeJSONParse(data) {
1079
  try {
137✔
1080
    return JSON.parse(data);
137✔
1081
  } catch (e) {}
1082
}
1083

1084
export function safeJSONEncode(data) {
1085
  try {
481✔
1086
    return JSON.stringify(data);
481✔
1087
  } catch (e) {
1088
    return '';
1✔
1089
  }
1090
}
1091

1092
/**
1093
 * Returns a memoized version of `fn`.
1094
 *
1095
 * @param fn
1096
 * @param key cache key generator, invoked with the same arguments passed to `fn`.
1097
 *        By default, the first argument is used as key.
1098
 * @return {*}
1099
 */
1100
export function memoize(fn, key = function (arg) { return arg; }) {
1,335✔
1101
  const cache = new Map();
338✔
1102
  const memoized = function () {
338✔
1103
    const cacheKey = key.apply(this, arguments);
2,171✔
1104
    if (!cache.has(cacheKey)) {
2,171✔
1105
      cache.set(cacheKey, fn.apply(this, arguments));
875✔
1106
    }
1107
    return cache.get(cacheKey);
2,171✔
1108
  }
1109
  memoized.clear = cache.clear.bind(cache);
338✔
1110
  return memoized;
338✔
1111
}
1112

1113
/**
1114
 * Returns a Unix timestamp for given time value and unit.
1115
 * @param {number} timeValue numeric value, defaults to 0 (which means now)
1116
 * @param {string} timeUnit defaults to days (or 'd'), use 'm' for minutes. Any parameter that isn't 'd' or 'm' will return Date.now().
1117
 * @returns {number}
1118
 */
1119
export function getUnixTimestampFromNow(timeValue = 0, timeUnit = 'd') {
723✔
1120
  const acceptableUnits = ['m', 'd'];
723✔
1121
  if (acceptableUnits.indexOf(timeUnit) < 0) {
723✔
1122
    return Date.now();
1✔
1123
  }
1124
  const multiplication = timeValue / (timeUnit === 'm' ? 1440 : 1);
722✔
1125
  return Date.now() + (timeValue && timeValue > 0 ? (1000 * 60 * 60 * 24 * multiplication) : 0);
722✔
1126
}
1127

1128
/**
1129
 * Converts given object into an array, so {key: 1, anotherKey: 'fred', third: ['fred']} is turned
1130
 * into [{key: 1}, {anotherKey: 'fred'}, {third: ['fred']}]
1131
 * @param {Object} obj the object
1132
 * @returns {Array}
1133
 */
1134
export function convertObjectToArray(obj) {
1135
  return Object.keys(obj).map(key => {
10✔
1136
    return {[key]: obj[key]};
28✔
1137
  });
1138
}
1139

1140
/**
1141
 * Sets dataset attributes on a script
1142
 * @param {HTMLScriptElement} script
1143
 * @param {object} attributes
1144
 */
1145
export function setScriptAttributes(script, attributes) {
1146
  Object.entries(attributes).forEach(([k, v]) => script.setAttribute(k, v))
5✔
1147
}
1148

1149
/**
1150
 * Perform a binary search for `el` on an ordered array `arr`.
1151
 *
1152
 * @returns the lowest nonnegative integer I that satisfies:
1153
 *   key(arr[i]) >= key(el) for each i between I and arr.length
1154
 *
1155
 *   (if one or more matches are found for `el`, returns the index of the first;
1156
 *   if the element is not found, return the index of the first element that's greater;
1157
 *   if no greater element exists, return `arr.length`)
1158
 */
1159
export function binarySearch(arr, el, key = (el) => el) {
55✔
1160
  let left = 0;
55✔
1161
  let right = arr.length && arr.length - 1;
55✔
1162
  const target = key(el);
55✔
1163
  while (right - left > 1) {
55✔
1164
    const middle = left + Math.round((right - left) / 2);
14✔
1165
    if (target > key(arr[middle])) {
14✔
1166
      left = middle;
7✔
1167
    } else {
1168
      right = middle;
7✔
1169
    }
1170
  }
1171
  while (arr.length > left && target > key(arr[left])) {
55✔
1172
    left++;
16✔
1173
  }
1174
  return left;
55✔
1175
}
1176

1177
/**
1178
 * Checks if an object has non-serializable properties.
1179
 * Non-serializable properties are functions and RegExp objects.
1180
 *
1181
 * @param {Object} obj - The object to check.
1182
 * @param {Set} checkedObjects - A set of properties that have already been checked.
1183
 * @returns {boolean} - Returns true if the object has non-serializable properties, false otherwise.
1184
 */
1185
export function hasNonSerializableProperty(obj, checkedObjects = new Set()) {
115✔
1186
  for (const key in obj) {
115✔
1187
    const value = obj[key];
380✔
1188
    const type = typeof value;
380✔
1189

1190
    if (
380✔
1191
      value === undefined ||
3,398✔
1192
      type === 'function' ||
1193
      type === 'symbol' ||
1194
      value instanceof RegExp ||
1195
      value instanceof Map ||
1196
      value instanceof Set ||
1197
      value instanceof Date ||
1198
      (value !== null && type === 'object' && value.hasOwnProperty('toJSON'))
1199
    ) {
1200
      return true;
14✔
1201
    }
1202
    if (value !== null && type === 'object' && value.constructor === Object) {
366✔
1203
      if (checkedObjects.has(value)) {
12!
1204
        // circular reference, means we have a non-serializable property
UNCOV
1205
        return true;
×
1206
      }
1207
      checkedObjects.add(value);
12✔
1208
      if (hasNonSerializableProperty(value, checkedObjects)) {
12✔
1209
        return true;
8✔
1210
      }
1211
    }
1212
  }
1213
  return false;
93✔
1214
}
1215

1216
/**
1217
 * Returns the value of a nested property in an array of objects.
1218
 *
1219
 * @param {Array} collection - Array of objects.
1220
 * @param {String} key - Key of nested property.
1221
 * @returns {any|undefined} - Value of nested property.
1222
 */
1223
export function setOnAny(collection, key) {
1224
  for (let i = 0, result; i < collection.length; i++) {
651✔
1225
    result = deepAccess(collection[i], key);
812✔
1226
    if (result) {
812✔
1227
      return result;
90✔
1228
    }
1229
  }
1230
  return undefined;
561✔
1231
}
1232

1233
export function extractDomainFromHost(pageHost) {
1234
  let domain = null;
19✔
1235
  try {
19✔
1236
    const domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(pageHost);
19✔
1237
    if (domains != null && domains.length > 0) {
19✔
1238
      domain = domains[0];
19✔
1239
      for (let i = 1; i < domains.length; i++) {
19✔
1240
        if (domains[i].length > domain.length) {
19!
UNCOV
1241
          domain = domains[i];
×
1242
        }
1243
      }
1244
    }
1245
  } catch (e) {
UNCOV
1246
    domain = null;
×
1247
  }
1248
  return domain;
19✔
1249
}
1250

1251
export function triggerNurlWithCpm(bid, cpm) {
1252
  if (isStr(bid.nurl) && bid.nurl !== '') {
2✔
1253
    bid.nurl = bid.nurl.replace(
2✔
1254
      /\${AUCTION_PRICE}/,
1255
      cpm
1256
    );
1257
    triggerPixel(bid.nurl);
2✔
1258
  }
1259
}
1260

1261
// To ensure that isGzipCompressionSupported() doesn’t become an overhead, we have used memoization to cache the result after the first execution.
1262
// This way, even if the function is called multiple times, it will only perform the actual check once and return the cached result in subsequent calls.
1263
export const isGzipCompressionSupported = (function () {
4✔
1264
  let cachedResult; // Store the result
1265

1266
  return function () {
4✔
UNCOV
1267
    if (cachedResult !== undefined) {
×
UNCOV
1268
      return cachedResult; // Return cached result if already computed
×
1269
    }
1270

UNCOV
1271
    try {
×
UNCOV
1272
      if (typeof window.CompressionStream === 'undefined') {
×
UNCOV
1273
        cachedResult = false;
×
1274
      } else {
UNCOV
1275
        (() => new window.CompressionStream('gzip'))();
×
UNCOV
1276
        cachedResult = true;
×
1277
      }
1278
    } catch (error) {
UNCOV
1279
      cachedResult = false;
×
1280
    }
1281

UNCOV
1282
    return cachedResult;
×
1283
  };
1284
})();
1285

1286
// Make sure to use isGzipCompressionSupported before calling this function
1287
export async function compressDataWithGZip(data) {
1288
  if (typeof data !== 'string') { // TextEncoder (below) expects a string
2✔
1289
    data = JSON.stringify(data);
1✔
1290
  }
1291

1292
  const encoder = new TextEncoder();
2✔
1293
  const encodedData = encoder.encode(data);
2✔
1294
  const compressedStream = new Blob([encodedData])
2✔
1295
    .stream()
1296
    .pipeThrough(new window.CompressionStream('gzip'));
1297

1298
  const compressedBlob = await new Response(compressedStream).blob();
2✔
1299
  const compressedArrayBuffer = await compressedBlob.arrayBuffer();
2✔
1300
  return new Uint8Array(compressedArrayBuffer);
2✔
1301
}
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