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

prebid / Prebid.js / 22316888302

23 Feb 2026 05:16PM UTC coverage: 96.273% (+33.1%) from 63.163%
22316888302

Pull #14508

github

67be4c
patmmccann
OMS Adapter: isolate viewability helpers to avoid eager side effects
Pull Request #14508: OMS Adapter: extract shared OMS/Onomagic helper utilities

56016 of 68599 branches covered (81.66%)

25 of 27 new or added lines in 4 files covered. (92.59%)

47 existing lines in 2 files now uncovered.

214440 of 222742 relevant lines covered (96.27%)

70.9 hits per line

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

90.28
/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, getScreenOrientation} from './utils/winDimensions.js';
12
const consoleExists = Boolean(window.console);
8✔
13
const consoleLogExists = Boolean(consoleExists && window.console.log);
8✔
14
const consoleInfoExists = Boolean(consoleExists && window.console.info);
8✔
15
const consoleWarnExists = Boolean(consoleExists && window.console.warn);
8✔
16
const consoleErrorExists = Boolean(consoleExists && window.console.error);
8✔
17

18
let eventEmitter;
19

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

25
function emitEvent(...args) {
26
  if (eventEmitter != null) {
5,614✔
27
    eventEmitter(...args);
5,614✔
28
  }
29
}
30

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

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

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

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

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

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

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

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

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

125
/**
126
 * 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]]'
127
 */
128
export function sizesToSizeTuples(sizes) {
129
  if (typeof sizes === 'string') {
4,130✔
130
    // multiple sizes will be comma-separated
131
    return sizes
32✔
132
      .split(/\s*,\s*/)
133
      .map(sz => sz.match(/^(\d+)x(\d+)$/i))
35✔
134
      .filter(match => match)
35✔
135
      .map(([_, w, h]) => [parseInt(w, 10), parseInt(h, 10)])
28✔
136
  } else if (Array.isArray(sizes)) {
4,098✔
137
    if (isValidGPTSingleSize(sizes)) {
3,895✔
138
      return [sizes]
490✔
139
    }
140
    return sizes.filter(isValidGPTSingleSize);
3,405✔
141
  }
142
  return [];
203✔
143
}
144

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

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

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

166
export function sizeTupleToRtbSize(size) {
167
  return {w: size[0], h: size[1]};
3,215✔
168
}
169

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

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

183
export function getWindowTop() {
184
  return window.top;
4,782✔
185
}
186

187
export function getWindowSelf() {
188
  return window.self;
2,161✔
189
}
190

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

195
export function getDocument() {
196
  return document;
430✔
197
}
198

199
export function canAccessWindowTop() {
200
  try {
1,185✔
201
    if (internal.getWindowTop().location.href) {
1,185✔
202
      return true;
1,092✔
203
    }
204
  } catch (e) {
205
    return false;
93✔
206
  }
207
}
208

209
/**
210
 * Returns the window to use for fingerprinting reads: win if provided, otherwise top or self.
211
 * @param {Window} [win]
212
 * @returns {Window}
213
 */
214
export function getFallbackWindow(win) {
215
  if (win) {
266✔
216
    return win;
231✔
217
  }
218
  return canAccessWindowTop() ? internal.getWindowTop() : internal.getWindowSelf();
35!
219
}
220

221
/**
222
 * Wrappers to console.(log | info | warn | error). Takes N arguments, the same as the native methods
223
 */
224
// eslint-disable-next-line no-restricted-syntax
225
export function logMessage() {
226
  if (debugTurnedOn() && consoleLogExists) {
15,448✔
227
    // eslint-disable-next-line no-console
228
    console.log.apply(console, decorateLog(arguments, 'MESSAGE:'));
22✔
229
  }
230
}
231

232
// eslint-disable-next-line no-restricted-syntax
233
export function logInfo() {
234
  if (debugTurnedOn() && consoleInfoExists) {
13,785✔
235
    // eslint-disable-next-line no-console
236
    console.info.apply(console, decorateLog(arguments, 'INFO:'));
2✔
237
  }
238
}
239

240
// eslint-disable-next-line no-restricted-syntax
241
export function logWarn() {
242
  if (debugTurnedOn() && consoleWarnExists) {
3,795!
243
    // eslint-disable-next-line no-console
UNCOV
244
    console.warn.apply(console, decorateLog(arguments, 'WARNING:'));
×
245
  }
246
  emitEvent(EVENTS.AUCTION_DEBUG, { type: 'WARNING', arguments: arguments });
3,795✔
247
}
248

249
// eslint-disable-next-line no-restricted-syntax
250
export function logError() {
251
  if (debugTurnedOn() && consoleErrorExists) {
1,819!
252
    // eslint-disable-next-line no-console
UNCOV
253
    console.error.apply(console, decorateLog(arguments, 'ERROR:'));
×
254
  }
255
  emitEvent(EVENTS.AUCTION_DEBUG, { type: 'ERROR', arguments: arguments });
1,819✔
256
}
257

258
export function prefixLog(prefix) {
259
  function decorate(fn) {
260
    return function (...args) {
3,006✔
261
      fn(prefix, ...args);
1,399✔
262
    }
263
  }
264
  return {
156✔
265
    logError: decorate(logError),
266
    logWarn: decorate(logWarn),
267
    logMessage: decorate(logMessage),
268
    logInfo: decorate(logInfo),
269
  }
270
}
271

272
function decorateLog(args, prefix) {
273
  args = [].slice.call(args);
24✔
274
  const bidder = config.getCurrentBidder();
24✔
275

276
  prefix && args.unshift(prefix);
24✔
277
  if (bidder) {
24!
UNCOV
278
    args.unshift(label('#aaa'));
×
279
  }
280
  args.unshift(label('#3b88c3'));
24✔
281
  args.unshift('%cPrebid' + (bidder ? `%c${bidder}` : ''));
24!
282
  return args;
24✔
283

284
  function label(color) {
285
    return `display: inline-block; color: #fff; background: ${color}; padding: 1px 4px; border-radius: 3px;`
24✔
286
  }
287
}
288

289
export function hasConsoleLogger() {
UNCOV
290
  return consoleLogExists;
×
291
}
292

293
export function debugTurnedOn() {
294
  return !!config.getConfig('debug');
35,825✔
295
}
296

297
export const createIframe = (() => {
8✔
298
  const DEFAULTS = {
8✔
299
    border: '0px',
300
    hspace: '0',
301
    vspace: '0',
302
    marginWidth: '0',
303
    marginHeight: '0',
304
    scrolling: 'no',
305
    frameBorder: '0',
306
    allowtransparency: 'true'
307
  }
308
  return (doc, attrs, style = {}) => {
22✔
309
    const f = doc.createElement('iframe');
16✔
310
    Object.assign(f, Object.assign({}, DEFAULTS, attrs));
15✔
311
    Object.assign(f.style, style);
8✔
312
    return f;
7✔
313
  }
314
})();
315

316
export function createInvisibleIframe() {
317
  return createIframe(document, {
6✔
318
    id: getUniqueIdentifierStr(),
319
    width: 0,
320
    height: 0,
321
    src: 'about:blank'
322
  }, {
323
    display: 'none',
324
    height: '0px',
325
    width: '0px',
326
    border: '0px'
327
  });
328
}
329

330
/*
331
 *   Check if a given parameter name exists in query string
332
 *   and if it does return the value
333
 */
334
export function getParameterByName(name) {
335
  return parseQS(getWindowLocation().search)[name] || '';
1,756✔
336
}
337

338
/**
339
 * Return if the object is "empty";
340
 * this includes falsey, no keys, or no items at indices
341
 * @param {*} object object to test
342
 * @return {Boolean} if object is empty
343
 */
344
export function isEmpty(object) {
345
  if (!object) return true;
58,967✔
346
  if (isArray(object) || isStr(object)) {
53,345✔
347
    return !(object.length > 0);
24,474✔
348
  }
349
  return Object.keys(object).length <= 0;
28,871✔
350
}
351

352
/**
353
 * Return if string is empty, null, or undefined
354
 * @param str string to test
355
 * @returns {boolean} if string is empty
356
 */
357
export function isEmptyStr(str) {
358
  return isStr(str) && (!str || str.length === 0);
777✔
359
}
360

361
/**
362
 * Iterate object with the function
363
 * falls back to es5 `forEach`
364
 * @param {Array|Object} object
365
 * @param {Function} fn - The function to execute for each element. It receives three arguments: value, key, and the original object.
366
 * @returns {void}
367
 */
368
export function _each(object, fn) {
369
  if (isFn(object?.forEach)) return object.forEach(fn, this);
7,274✔
370
  Object.entries(object || {}).forEach(([k, v]) => fn.call(this, v, k));
36,319✔
371
}
372

373
export function contains(a, obj) {
374
  return isFn(a?.includes) && a.includes(obj);
31✔
375
}
376

377
/**
378
 * Map an array or object into another array
379
 * given a function
380
 * @param {Array|Object} object
381
 * @param {Function} callback - The function to execute for each element. It receives three arguments: value, key, and the original object.
382
 * @return {Array}
383
 */
384
export function _map(object, callback) {
385
  if (isFn(object?.map)) return object.map(callback);
454✔
386
  return Object.entries(object || {}).map(([k, v]) => callback(v, k, object))
7,489✔
387
}
388

389
/*
390
* Inserts an element(elm) as targets child, by default as first child
391
* @param {HTMLElement} elm
392
* @param {HTMLElement} [doc]
393
* @param {HTMLElement} [target]
394
* @param {Boolean} [asLastChildChild]
395
* @return {HTML Element}
396
*/
397
export function insertElement(elm, doc, target, asLastChildChild) {
398
  doc = doc || document;
20✔
399
  let parentEl;
400
  if (target) {
20✔
401
    parentEl = doc.getElementsByTagName(target);
16✔
402
  } else {
403
    parentEl = doc.getElementsByTagName('head');
4✔
404
  }
405
  try {
20✔
406
    parentEl = parentEl.length ? parentEl : doc.getElementsByTagName('body');
20!
407
    if (parentEl.length) {
20✔
408
      parentEl = parentEl[0];
20✔
409
      const insertBeforeEl = asLastChildChild ? null : parentEl.firstChild;
20✔
410
      return parentEl.insertBefore(elm, insertBeforeEl);
20✔
411
    }
412
  } catch (e) {}
413
}
414

415
/**
416
 * Returns a promise that completes when the given element triggers a 'load' or 'error' DOM event, or when
417
 * `timeout` milliseconds have elapsed.
418
 *
419
 * @param {HTMLElement} element
420
 * @param {Number} [timeout]
421
 * @returns {Promise}
422
 */
423
export function waitForElementToLoad(element, timeout) {
424
  let timer = null;
3✔
425
  return new PbPromise((resolve) => {
3✔
426
    const onLoad = function() {
3✔
427
      element.removeEventListener('load', onLoad);
3✔
428
      element.removeEventListener('error', onLoad);
3✔
429
      if (timer != null) {
3✔
430
        window.clearTimeout(timer);
1✔
431
      }
432
      resolve();
3✔
433
    };
434
    element.addEventListener('load', onLoad);
3✔
435
    element.addEventListener('error', onLoad);
3✔
436
    if (timeout != null) {
3✔
437
      timer = window.setTimeout(onLoad, timeout);
1✔
438
    }
439
  });
440
}
441

442
/**
443
 * Inserts an image pixel with the specified `url` for cookie sync
444
 * @param {string} url URL string of the image pixel to load
445
 * @param  {function} [done] an optional exit callback, used when this usersync pixel is added during an async process
446
 * @param  {Number} [timeout] an optional timeout in milliseconds for the image to load before calling `done`
447
 */
448
export function triggerPixel(url, done, timeout) {
449
  const img = new Image();
68✔
450
  if (done && internal.isFn(done)) {
68!
451
    waitForElementToLoad(img, timeout).then(done);
×
452
  }
453
  img.src = url;
68✔
454
}
455

456
/**
457
 * Inserts an empty iframe with the specified `html`, primarily used for tracking purposes
458
 * (though could be for other purposes)
459
 * @param {string} htmlCode snippet of HTML code used for tracking purposes
460
 */
461
export function insertHtmlIntoIframe(htmlCode) {
UNCOV
462
  if (!htmlCode) {
×
463
    return;
×
464
  }
UNCOV
465
  const iframe = createInvisibleIframe();
×
UNCOV
466
  internal.insertElement(iframe, document, 'body');
×
467

UNCOV
468
  ((doc) => {
×
UNCOV
469
    doc.open();
×
470
    doc.write(htmlCode);
×
471
    doc.close();
×
472
  })(iframe.contentWindow.document);
473
}
474

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

492
/**
493
 * Creates a snippet of HTML that retrieves the specified `url`
494
 * @param  {string} url URL to be requested
495
 * @param encode
496
 * @return {string}     HTML snippet that contains the img src = set to `url`
497
 */
498
export function createTrackPixelHtml(url, encode = encodeURI) {
99✔
499
  if (!url) {
99!
UNCOV
500
    return '';
×
501
  }
502

503
  const escapedUrl = encode(url);
99✔
504
  let img = '<div style="position:absolute;left:0px;top:0px;visibility:hidden;">';
99✔
505
  img += '<img src="' + escapedUrl + '"></div>';
99✔
506
  return img;
99✔
507
};
508

509
/**
510
 * encodeURI, but preserves macros of the form '${MACRO}' (e.g. '${AUCTION_PRICE}')
511
 * @param url
512
 * @return {string}
513
 */
514
export function encodeMacroURI(url) {
515
  const macros = Array.from(url.matchAll(/\$({[^}]+})/g)).map(match => match[1]);
54✔
516
  return macros.reduce((str, macro) => {
42✔
517
    return str.replace('$' + encodeURIComponent(macro), '$' + macro)
27✔
518
  }, encodeURI(url))
519
}
520

521
/**
522
 * Creates a snippet of Iframe HTML that retrieves the specified `url`
523
 * @param  {string} url plain URL to be requested
524
 * @param  {string} encodeUri boolean if URL should be encoded before inserted. Defaults to true
525
 * @param  {string} sandbox string if provided the sandbox attribute will be included with the given value
526
 * @return {string}     HTML snippet that contains the iframe src = set to `url`
527
 */
528
export function createTrackPixelIframeHtml(url, encodeUri = true, sandbox = '') {
1!
529
  if (!url) {
1!
UNCOV
530
    return '';
×
531
  }
532
  if (encodeUri) {
1!
UNCOV
533
    url = encodeURI(url);
×
534
  }
535
  if (sandbox) {
1✔
536
    sandbox = `sandbox="${sandbox}"`;
1✔
537
  }
538

539
  return `<iframe ${sandbox} id="${getUniqueIdentifierStr()}"
1✔
540
      frameborder="0"
541
      allowtransparency="true"
542
      marginheight="0" marginwidth="0"
543
      width="0" hspace="0" vspace="0" height="0"
544
      style="height:0px;width:0px;display:none;"
545
      scrolling="no"
546
      src="${url}">
547
    </iframe>`;
548
}
549

550
export function uniques(value, index, arry) {
551
  return arry.indexOf(value) === index;
20,535✔
552
}
553

554
export function flatten(a, b) {
555
  return a.concat(b);
3,485✔
556
}
557

558
export function getBidRequest(id, bidderRequests) {
559
  if (!id) {
249✔
560
    return;
4✔
561
  }
562
  return bidderRequests.flatMap(br => br.bids)
331✔
563
    .find(bid => ['bidId', 'adId', 'bid_id'].some(prop => bid[prop] === id))
362✔
564
}
565

566
export function getValue(obj, key) {
567
  return obj[key];
213✔
568
}
569

570
export function getBidderCodes(adUnits) {
571
  // this could memoize adUnits
572
  return adUnits.map(unit => unit.bids.map(bid => bid.bidder)
1,180✔
573
    .reduce(flatten, [])).reduce(flatten, []).filter((bidder) => typeof bidder !== 'undefined').filter(uniques);
1,180✔
574
}
575

576
export function isGptPubadsDefined() {
577
  if (window.googletag && isFn(window.googletag.pubads) && isFn(window.googletag.pubads().getSlots)) {
879✔
578
    return true;
859✔
579
  }
580
}
581

582
export function isApnGetTagDefined() {
583
  if (window.apntag && isFn(window.apntag.getTag)) {
5✔
584
    return true;
4✔
585
  }
586
}
587

588
export const sortByHighestCpm = (a, b) => {
8✔
589
  return b.cpm - a.cpm;
638✔
590
}
591

592
/**
593
 * Fisher–Yates shuffle
594
 * http://stackoverflow.com/a/6274398
595
 * https://bost.ocks.org/mike/shuffle/
596
 * istanbul ignore next
597
 */
598
export function shuffle(array) {
599
  let counter = array.length;
222✔
600

601
  // while there are elements in the array
602
  while (counter > 0) {
222✔
603
    // pick a random index
604
    const index = Math.floor(Math.random() * counter);
774✔
605

606
    // decrease counter by 1
607
    counter--;
774✔
608

609
    // and swap the last element with it
610
    const temp = array[counter];
774✔
611
    array[counter] = array[index];
774✔
612
    array[index] = temp;
774✔
613
  }
614

615
  return array;
222✔
616
}
617

618
export function inIframe() {
619
  try {
1,849✔
620
    return internal.getWindowSelf() !== internal.getWindowTop();
1,849✔
621
  } catch (e) {
UNCOV
622
    return true;
×
623
  }
624
}
625

626
/**
627
 * https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf
628
 */
629
export function isSafeFrameWindow() {
630
  if (!inIframe()) {
72!
UNCOV
631
    return false;
×
632
  }
633

634
  const ws = internal.getWindowSelf();
72✔
635
  return !!(ws.$sf && ws.$sf.ext);
72✔
636
}
637

638
/**
639
 * Returns the result of calling the function $sf.ext.geom() if it exists
640
 * @see https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf — 5.4 Function $sf.ext.geom
641
 * @returns {Object | undefined} geometric information about the container
642
 */
643
export function getSafeframeGeometry() {
644
  try {
4✔
645
    const ws = getWindowSelf();
4✔
646
    return (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : undefined;
4✔
647
  } catch (e) {
UNCOV
648
    logError('Error getting SafeFrame geometry', e);
×
UNCOV
649
    return undefined;
×
650
  }
651
}
652

653
export function isSafariBrowser() {
654
  return /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent);
73✔
655
}
656

657
export function replaceMacros(str, subs) {
658
  if (!str) return;
162✔
659
  return Object.entries(subs).reduce((str, [key, val]) => {
137✔
660
    return str.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), val || '');
137✔
661
  }, str);
662
}
663

664
export function replaceAuctionPrice(str, cpm) {
665
  return replaceMacros(str, {AUCTION_PRICE: cpm})
107✔
666
}
667

668
export function replaceClickThrough(str, clicktag) {
UNCOV
669
  if (!str || !clicktag || typeof clicktag !== 'string') return;
×
UNCOV
670
  return str.replace(/\${CLICKTHROUGH}/g, clicktag);
×
671
}
672

673
export function timestamp() {
674
  return new Date().getTime();
10,417✔
675
}
676

677
/**
678
 * The returned value represents the time elapsed since the time origin. @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
679
 * @returns {number}
680
 */
681
export function getPerformanceNow() {
682
  return (window.performance && window.performance.now && window.performance.now()) || 0;
9,503✔
683
}
684

685
/**
686
 * Retuns the difference between `timing.domLoading` and `timing.navigationStart`.
687
 * This function uses the deprecated `Performance.timing` API and should be removed in future.
688
 * It has not been updated yet because it is still used in some modules.
689
 * @deprecated
690
 * @param {Window} w The window object used to perform the api call. default to window.self
691
 * @returns {number}
692
 */
693
export function getDomLoadingDuration(w) {
694
  let domLoadingDuration = -1;
17✔
695

696
  w = w || getWindowSelf();
17!
697

698
  const performance = w.performance;
17✔
699

700
  if (w.performance?.timing) {
17✔
701
    if (w.performance.timing.navigationStart > 0) {
17✔
702
      const val = performance.timing.domLoading - performance.timing.navigationStart;
14✔
703
      if (val > 0) {
14✔
704
        domLoadingDuration = val;
14✔
705
      }
706
    }
707
  }
708

709
  return domLoadingDuration;
17✔
710
}
711

712
/**
713
 * When the deviceAccess flag config option is false, no cookies should be read or set
714
 * @returns {boolean}
715
 */
716
export function hasDeviceAccess() {
717
  return config.getConfig('deviceAccess') !== false;
19,021✔
718
}
719

720
/**
721
 * @returns {(boolean|undefined)}
722
 */
723
export function checkCookieSupport() {
724
  // eslint-disable-next-line no-restricted-properties
725
  if (window.navigator.cookieEnabled || !!document.cookie.length) {
1,284!
726
    return true;
1,284✔
727
  }
728
}
729

730
/**
731
 * Given a function, return a function which only executes the original after
732
 * it's been called numRequiredCalls times.
733
 *
734
 * Note that the arguments from the previous calls will *not* be forwarded to the original function.
735
 * Only the final call's arguments matter.
736
 *
737
 * @param {function} func The function which should be executed, once the returned function has been executed
738
 *   numRequiredCalls times.
739
 * @param {number} numRequiredCalls The number of times which the returned function needs to be called before
740
 *   func is.
741
 */
742
export function delayExecution(func, numRequiredCalls) {
743
  if (numRequiredCalls < 1) {
343!
UNCOV
744
    throw new Error(`numRequiredCalls must be a positive number. Got ${numRequiredCalls}`);
×
745
  }
746
  let numCalls = 0;
343✔
747
  return function () {
343✔
748
    numCalls++;
183✔
749
    if (numCalls === numRequiredCalls) {
183✔
750
      func.apply(this, arguments);
159✔
751
    }
752
  }
753
}
754

755
/**
756
 * https://stackoverflow.com/a/34890276/428704
757
 * @param {Array} xs
758
 * @param {string} key
759
 * @returns {Object} {${key_value}: ${groupByArray}, key_value: {groupByArray}}
760
 */
761
export function groupBy(xs, key) {
762
  return xs.reduce(function(rv, x) {
1,276✔
763
    (rv[x[key]] = rv[x[key]] || []).push(x);
1,460✔
764
    return rv;
1,460✔
765
  }, {});
766
}
767

768
/**
769
 * Validates an adunit's `mediaTypes` parameter
770
 * @param mediaTypes mediaTypes parameter to validate
771
 * @return If object is valid
772
 */
773
export function isValidMediaTypes(mediaTypes) {
774
  const SUPPORTED_MEDIA_TYPES = ['banner', 'native', 'video', 'audio'];
1,045✔
775
  const SUPPORTED_STREAM_TYPES = ['instream', 'outstream', 'adpod'];
1,045✔
776

777
  const types = Object.keys(mediaTypes);
1,045✔
778

779
  if (!types.every(type => SUPPORTED_MEDIA_TYPES.includes(type))) {
1,047!
UNCOV
780
    return false;
×
781
  }
782

783
  if (FEATURES.VIDEO && mediaTypes.video && mediaTypes.video.context) {
1,045!
UNCOV
784
    return SUPPORTED_STREAM_TYPES.includes(mediaTypes.video.context);
×
785
  }
786

787
  return true;
1,045✔
788
}
789

790
/**
791
 * Returns user configured bidder params from adunit
792
 * @param {Object} adUnits
793
 * @param {string} adUnitCode code
794
 * @param {string} bidder code
795
 * @return {Array} user configured param for the given bidder adunit configuration
796
 */
797
export function getUserConfiguredParams(adUnits, adUnitCode, bidder) {
798
  return adUnits
52✔
799
    .filter(adUnit => adUnit.code === adUnitCode)
81✔
800
    .flatMap((adUnit) => adUnit.bids)
26✔
801
    .filter((bidderData) => bidderData.bidder === bidder)
172✔
802
    .map((bidderData) => bidderData.params || {});
25✔
803
}
804

805
export const compareCodeAndSlot = (slot, adUnitCode) => slot.getAdUnitPath() === adUnitCode || slot.getSlotElementId() === adUnitCode;
647✔
806

807
/**
808
 * Returns filter function to match adUnitCode in slot
809
 * @param slot GoogleTag slot
810
 * @return filter function
811
 */
812
export function isAdUnitCodeMatchingSlot(slot) {
813
  return (adUnitCode) => compareCodeAndSlot(slot, adUnitCode);
222✔
814
}
815

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

825
  return `
7✔
826
    ${adUnit.code} is a ${mediaType} ad unit
827
    containing bidders that don't support ${mediaType}: ${bidder}.
828
    This bidder won't fetch demand.
829
  `;
830
}
831

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

840
/**
841
 * Create a new object with selected properties.  Also allows property renaming and transform functions.
842
 * @param obj the original object
843
 * @param properties An array of desired properties
844
 */
845
export function pick(obj, properties) {
846
  if (typeof obj !== 'object') {
1,984✔
847
    return {};
3✔
848
  }
849
  return properties.reduce((newObj, prop, i) => {
1,981✔
850
    if (typeof prop === 'function') {
23,860✔
851
      return newObj;
10,017✔
852
    }
853

854
    let newProp = prop;
13,843✔
855
    const match = prop.match(/^(.+?)\sas\s(.+?)$/i);
13,843✔
856

857
    if (match) {
13,843✔
858
      prop = match[1];
482✔
859
      newProp = match[2];
482✔
860
    }
861

862
    let value = obj[prop];
13,843✔
863
    if (typeof properties[i + 1] === 'function') {
13,843✔
864
      value = properties[i + 1](value, newObj);
10,017✔
865
    }
866
    if (typeof value !== 'undefined') {
13,843✔
867
      newObj[newProp] = value;
9,917✔
868
    }
869

870
    return newObj;
13,843✔
871
  }, {});
872
}
873

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

891
export function formatQS(query) {
892
  return Object
502✔
893
    .keys(query)
894
    .map(k => Array.isArray(query[k])
2,786✔
895
      ? query[k].map(v => `${k}[]=${v}`).join('&')
17✔
896
      : `${k}=${query[k]}`)
897
    .join('&');
898
}
899

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

921
export function buildUrl(obj) {
922
  return (obj.protocol || 'http') + '://' +
418✔
923
    (obj.host ||
527✔
924
      obj.hostname + (obj.port ? `:${obj.port}` : '')) +
109✔
925
    (obj.pathname || '') +
419✔
926
    (obj.search ? `?${internal.formatQS(obj.search || '')}` : '') +
681!
927
    (obj.hash ? `#${obj.hash}` : '');
418✔
928
}
929

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

942
  // If either is null or not an object, do a direct equality check
943
  if (
978✔
944
    typeof obj1 !== 'object' || obj1 === null ||
2,365✔
945
    typeof obj2 !== 'object' || obj2 === null
946
  ) {
947
    return false;
520✔
948
  }
949
  // Cache the Array checks
950
  const isArr1 = Array.isArray(obj1);
458✔
951
  const isArr2 = Array.isArray(obj2);
458✔
952
  // Special case: both are arrays
953
  if (isArr1 && isArr2) {
458✔
954
    if (obj1.length !== obj2.length) return false;
53✔
955
    for (let i = 0; i < obj1.length; i++) {
38✔
956
      if (!deepEqual(obj1[i], obj2[i], { checkTypes })) {
60✔
957
        return false;
9✔
958
      }
959
    }
960
    return true;
29✔
961
  } else if (isArr1 || isArr2) {
405!
UNCOV
962
    return false;
×
963
  }
964

965
  // If we’re checking types, compare constructors (e.g., plain object vs. Date)
966
  if (checkTypes && obj1.constructor !== obj2.constructor) {
405✔
967
    return false;
1✔
968
  }
969

970
  // Compare object keys. Cache keys for both to avoid repeated calls.
971
  const keys1 = Object.keys(obj1);
404✔
972
  const keys2 = Object.keys(obj2);
404✔
973

974
  if (keys1.length !== keys2.length) return false;
404✔
975

976
  for (const key of keys1) {
393✔
977
    // If `obj2` doesn't have this key or sub-values aren't equal, bail out.
978
    if (!Object.prototype.hasOwnProperty.call(obj2, key)) {
436✔
979
      return false;
33✔
980
    }
981
    if (!deepEqual(obj1[key], obj2[key], { checkTypes })) {
403✔
982
      return false;
333✔
983
    }
984
  }
985

986
  return true;
27✔
987
}
988

989
export function mergeDeep(target, ...sources) {
990
  for (let i = 0; i < sources.length; i++) {
17,146✔
991
    const source = sources[i];
25,575!
992
    if (!isPlainObject(source)) {
25,575✔
993
      continue;
9,098✔
994
    }
995
    mergeDeepHelper(target, source);
16,477✔
996
  }
997
  return target;
17,146✔
998
}
999

1000
function mergeDeepHelper(target, source) {
1001
  // quick check
1002
  if (!isPlainObject(target) || !isPlainObject(source)) {
34,817✔
1003
    return;
36✔
1004
  }
1005

1006
  const keys = Object.keys(source);
34,781✔
1007
  for (let i = 0; i < keys.length; i++) {
34,781✔
1008
    const key = keys[i];
53,288✔
1009
    if (key === '__proto__' || key === 'constructor') {
53,288!
UNCOV
1010
      continue;
×
1011
    }
1012
    const val = source[key];
53,288✔
1013

1014
    if (isPlainObject(val)) {
53,288✔
1015
      if (!target[key]) {
18,340✔
1016
        target[key] = {};
16,802✔
1017
      }
1018
      mergeDeepHelper(target[key], val);
18,340✔
1019
    } else if (Array.isArray(val)) {
34,948✔
1020
      if (!Array.isArray(target[key])) {
4,714✔
1021
        target[key] = [...val];
2,986✔
1022
      } else {
1023
        // deduplicate
1024
        val.forEach(obj => {
1,728✔
1025
          if (!target[key].some(item => deepEqual(item, obj))) {
780✔
1026
            target[key].push(obj);
134✔
1027
          }
1028
        });
1029
      }
1030
    } else {
1031
      // direct assignment
1032
      target[key] = val;
30,234✔
1033
    }
1034
  }
1035
}
1036

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

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

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

1088
export function safeJSONEncode(data) {
1089
  try {
542✔
1090
    return JSON.stringify(data);
542✔
1091
  } catch (e) {
1092
    return '';
1✔
1093
  }
1094
}
1095

1096
/**
1097
 * Returns a memoized version of `fn`.
1098
 *
1099
 * @param fn
1100
 * @param key cache key generator, invoked with the same arguments passed to `fn`.
1101
 *        By default, the first argument is used as key.
1102
 * @return {*}
1103
 */
1104
export function memoize(fn, key = function (arg) { return arg; }) {
3,248✔
1105
  const cache = new Map();
295✔
1106
  const memoized = function () {
295✔
1107
    const cacheKey = key.apply(this, arguments);
3,918✔
1108
    if (!cache.has(cacheKey)) {
3,918✔
1109
      cache.set(cacheKey, fn.apply(this, arguments));
856✔
1110
    }
1111
    return cache.get(cacheKey);
3,918✔
1112
  }
1113
  memoized.clear = cache.clear.bind(cache);
295✔
1114
  return memoized;
295✔
1115
}
1116

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

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

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

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

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

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

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

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

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

1265
// To ensure that isGzipCompressionSupported() doesn’t become an overhead, we have used memoization to cache the result after the first execution.
1266
// 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.
1267
export const isGzipCompressionSupported = (function () {
8✔
1268
  let cachedResult; // Store the result
1269

1270
  return function () {
8✔
1271
    if (cachedResult !== undefined) {
3✔
1272
      return cachedResult; // Return cached result if already computed
2✔
1273
    }
1274

1275
    try {
1✔
1276
      if (typeof window.CompressionStream === 'undefined') {
1!
UNCOV
1277
        cachedResult = false;
×
1278
      } else {
1279
        (() => new window.CompressionStream('gzip'))();
1✔
1280
        cachedResult = true;
1✔
1281
      }
1282
    } catch (error) {
UNCOV
1283
      cachedResult = false;
×
1284
    }
1285

1286
    return cachedResult;
1✔
1287
  };
1288
})();
1289

1290
// Make sure to use isGzipCompressionSupported before calling this function
1291
export async function compressDataWithGZip(data) {
1292
  if (typeof data !== 'string') { // TextEncoder (below) expects a string
3✔
1293
    data = JSON.stringify(data);
1✔
1294
  }
1295

1296
  const encoder = new TextEncoder();
3✔
1297
  const encodedData = encoder.encode(data);
3✔
1298
  const compressedStream = new Blob([encodedData])
3✔
1299
    .stream()
1300
    .pipeThrough(new window.CompressionStream('gzip'));
1301

1302
  const compressedBlob = await new Response(compressedStream).blob();
3✔
1303
  const compressedArrayBuffer = await compressedBlob.arrayBuffer();
3✔
1304
  return new Uint8Array(compressedArrayBuffer);
3✔
1305
}
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