@highfivve/ad-tag 5.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +149 -0
  3. package/lib/ads/a9.js +190 -0
  4. package/lib/ads/adPipeline.js +159 -0
  5. package/lib/ads/adService.js +251 -0
  6. package/lib/ads/adUnitPath.js +37 -0
  7. package/lib/ads/auctions/adRequestThrottling.js +22 -0
  8. package/lib/ads/auctions/biddersDisabling.js +90 -0
  9. package/lib/ads/auctions/frequencyCapping.js +189 -0
  10. package/lib/ads/auctions/interstitialContext.js +92 -0
  11. package/lib/ads/auctions/previousBidCpms.js +33 -0
  12. package/lib/ads/auctions/resume.js +3 -0
  13. package/lib/ads/bridge/bridge.js +62 -0
  14. package/lib/ads/consent.js +63 -0
  15. package/lib/ads/eventService.js +44 -0
  16. package/lib/ads/globalAuctionContext.js +98 -0
  17. package/lib/ads/googleAdManager.js +380 -0
  18. package/lib/ads/keyValues.js +1 -0
  19. package/lib/ads/labelConfigService.js +42 -0
  20. package/lib/ads/modules/ad-reload/adVisibilityService.js +158 -0
  21. package/lib/ads/modules/ad-reload/index.js +163 -0
  22. package/lib/ads/modules/ad-reload/userActivityService.js +70 -0
  23. package/lib/ads/modules/adex/adex-mapping.js +77 -0
  24. package/lib/ads/modules/adex/adexUtiq.js +15 -0
  25. package/lib/ads/modules/adex/index.js +142 -0
  26. package/lib/ads/modules/adex/sendAdvertisingId.js +20 -0
  27. package/lib/ads/modules/blocklist-url/index.js +118 -0
  28. package/lib/ads/modules/cleanup/index.js +93 -0
  29. package/lib/ads/modules/confiant/index.js +47 -0
  30. package/lib/ads/modules/emetriq/index.js +154 -0
  31. package/lib/ads/modules/emetriq/trackInApp.js +34 -0
  32. package/lib/ads/modules/emetriq/trackLoginEvent.js +59 -0
  33. package/lib/ads/modules/generic-skin/index.js +150 -0
  34. package/lib/ads/modules/identitylink/index.js +58 -0
  35. package/lib/ads/modules/interstitial/index.js +38 -0
  36. package/lib/ads/modules/interstitial/interstitialAd.js +111 -0
  37. package/lib/ads/modules/lazy-load/index.js +191 -0
  38. package/lib/ads/modules/lazy-load/selectInfiniteSlot.js +11 -0
  39. package/lib/ads/modules/prebid-first-party-data/index.js +115 -0
  40. package/lib/ads/modules/pubstack/index.js +59 -0
  41. package/lib/ads/modules/sticky-footer-ad/desktopFloorAd.js +63 -0
  42. package/lib/ads/modules/sticky-footer-ad/index.js +43 -0
  43. package/lib/ads/modules/sticky-footer-ad/mobileSticky.js +93 -0
  44. package/lib/ads/modules/sticky-footer-ad-v2/footerStickyAd.js +118 -0
  45. package/lib/ads/modules/sticky-footer-ad-v2/index.js +43 -0
  46. package/lib/ads/modules/sticky-header-ad/fadeOutCallback.js +25 -0
  47. package/lib/ads/modules/sticky-header-ad/index.js +103 -0
  48. package/lib/ads/modules/sticky-header-ad/renderResult.js +24 -0
  49. package/lib/ads/modules/utiq/index.js +79 -0
  50. package/lib/ads/modules/yield-optimization/dynamicFloorPrice.js +86 -0
  51. package/lib/ads/modules/yield-optimization/index.js +57 -0
  52. package/lib/ads/modules/yield-optimization/isYieldOptimizationConfigDynamic.js +6 -0
  53. package/lib/ads/modules/yield-optimization/yieldOptimizationService.js +169 -0
  54. package/lib/ads/modules/zeotap/index.js +111 -0
  55. package/lib/ads/moli.js +645 -0
  56. package/lib/ads/moliGlobal.js +11 -0
  57. package/lib/ads/prebid-outstream.js +13 -0
  58. package/lib/ads/prebid.js +406 -0
  59. package/lib/ads/sizeConfigService.js +49 -0
  60. package/lib/ads/spa.js +32 -0
  61. package/lib/bundle/adReload.js +2 -0
  62. package/lib/bundle/adex.js +2 -0
  63. package/lib/bundle/blocklistUrls.js +2 -0
  64. package/lib/bundle/cleanup.js +2 -0
  65. package/lib/bundle/confiant.js +2 -0
  66. package/lib/bundle/configureFromEndpoint.js +40 -0
  67. package/lib/bundle/emetriq.js +2 -0
  68. package/lib/bundle/identityLink.js +2 -0
  69. package/lib/bundle/init.js +2 -0
  70. package/lib/bundle/interstitialModule.js +2 -0
  71. package/lib/bundle/lazyLoad.js +2 -0
  72. package/lib/bundle/prebidFirstPartyData.js +2 -0
  73. package/lib/bundle/pubstack.js +2 -0
  74. package/lib/bundle/skin.js +2 -0
  75. package/lib/bundle/stickyFooterAd.js +2 -0
  76. package/lib/bundle/stickyFooterAds2.js +2 -0
  77. package/lib/bundle/stickyHeaderAd.js +2 -0
  78. package/lib/bundle/utiq.js +2 -0
  79. package/lib/bundle/yieldOptimization.js +2 -0
  80. package/lib/bundle/zeotap.js +2 -0
  81. package/lib/console/components/adSlotConfig.js +225 -0
  82. package/lib/console/components/consentConfig.js +138 -0
  83. package/lib/console/components/globalConfig.js +634 -0
  84. package/lib/console/components/labelConfigDebug.js +19 -0
  85. package/lib/console/components/sizeConfigDebug.js +34 -0
  86. package/lib/console/components/tag.js +4 -0
  87. package/lib/console/debug.js +38 -0
  88. package/lib/console/util/array.js +1 -0
  89. package/lib/console/util/calculateAdDensity.js +36 -0
  90. package/lib/console/util/debounce.js +12 -0
  91. package/lib/console/util/debugLogger.js +6 -0
  92. package/lib/console/util/extractPositionFromPath.js +8 -0
  93. package/lib/console/util/prebid.js +3 -0
  94. package/lib/console/util/stringUtils.js +23 -0
  95. package/lib/console/util/themingService.js +31 -0
  96. package/lib/console/util/windowResizeService.js +22 -0
  97. package/lib/console/validations/adReloadValidations.js +47 -0
  98. package/lib/console/validations/bucketValidations.js +78 -0
  99. package/lib/console/validations/sizesConfigValidations.js +97 -0
  100. package/lib/gen/packageJson.js +3 -0
  101. package/lib/index.js +3 -0
  102. package/lib/types/apstag.js +1 -0
  103. package/lib/types/dom.js +1 -0
  104. package/lib/types/emetriq.js +1 -0
  105. package/lib/types/googletag.js +1 -0
  106. package/lib/types/identitylink.js +1 -0
  107. package/lib/types/module.js +1 -0
  108. package/lib/types/moliConfig.js +1 -0
  109. package/lib/types/moliRuntime.js +1 -0
  110. package/lib/types/prebidjs.js +45 -0
  111. package/lib/types/supplyChainObject.js +1 -0
  112. package/lib/types/tcfapi.js +48 -0
  113. package/lib/util/addNewInfiniteSlotToConfig.js +11 -0
  114. package/lib/util/arrayUtils.js +9 -0
  115. package/lib/util/assetLoaderService.js +91 -0
  116. package/lib/util/browserStorageKeys.js +7 -0
  117. package/lib/util/debugDelay.js +12 -0
  118. package/lib/util/domready.js +15 -0
  119. package/lib/util/environmentOverride.js +16 -0
  120. package/lib/util/extractAdTagVersion.js +16 -0
  121. package/lib/util/extractTopPrivateDomainFromHostname.js +17 -0
  122. package/lib/util/localStorage.js +26 -0
  123. package/lib/util/logging.js +106 -0
  124. package/lib/util/objectUtils.js +29 -0
  125. package/lib/util/query.js +40 -0
  126. package/lib/util/queryParameters.js +5 -0
  127. package/lib/util/resolveOverrides.js +21 -0
  128. package/lib/util/resolveStoredRequestIdInOrtb2Object.js +12 -0
  129. package/lib/util/sizes.js +3 -0
  130. package/lib/util/test-slots.js +150 -0
  131. package/lib/util/uuid.js +10 -0
  132. package/package.json +127 -0
@@ -0,0 +1,93 @@
1
+ const adStickyContainerDataRef = '[data-ref=sticky-ad]';
2
+ const adStickyCloseButtonDataRef = '[data-ref=sticky-ad-close]';
3
+ const adVisibleClass = 'h5-sticky-ad--visible';
4
+ const hideAdSlot = (adSticky) => {
5
+ adSticky.style.setProperty('display', 'none');
6
+ adSticky.classList.remove(adVisibleClass);
7
+ };
8
+ const showAdSlot = (adSticky) => {
9
+ adSticky.style.setProperty('display', 'block');
10
+ adSticky.classList.add(adVisibleClass);
11
+ };
12
+ const stickyRenderedEvent = (adSticky, mobileStickyDomId, disallowedAdvertiserIds, window) => new Promise(resolve => {
13
+ const listener = (event) => {
14
+ if (event.slot.getSlotElementId() !== mobileStickyDomId) {
15
+ return;
16
+ }
17
+ if (event.isEmpty) {
18
+ if (adSticky) {
19
+ hideAdSlot(adSticky);
20
+ }
21
+ resolve('empty');
22
+ }
23
+ else if (!!event.advertiserId && disallowedAdvertiserIds.includes(event.advertiserId)) {
24
+ resolve('disallowed');
25
+ }
26
+ else {
27
+ resolve('standard');
28
+ }
29
+ window.googletag.pubads().removeEventListener('slotRenderEnded', listener);
30
+ };
31
+ window.googletag.cmd.push(() => {
32
+ window.googletag.pubads().addEventListener('slotRenderEnded', listener);
33
+ });
34
+ });
35
+ const stickyOnLoadEvent = (mobileStickyDomId, window) => new Promise(resolve => {
36
+ const listener = (event) => {
37
+ if (event.slot.getSlotElementId() !== mobileStickyDomId) {
38
+ return;
39
+ }
40
+ resolve();
41
+ window.googletag.pubads().removeEventListener('slotOnload', listener);
42
+ };
43
+ window.googletag.pubads().addEventListener('slotOnload', listener);
44
+ });
45
+ export const initMobileAdSticky = (window, env, log, mobileStickyDomId, disallowedAdvertiserIds, initiallyHidden) => {
46
+ const adSticky = window.document.querySelector(adStickyContainerDataRef);
47
+ const closeButton = window.document.querySelector(adStickyCloseButtonDataRef);
48
+ if (adSticky && closeButton) {
49
+ log.debug('mobile-sticky-ad', 'Running initAdSticky with defined sticky container and close button');
50
+ if (!initiallyHidden) {
51
+ showAdSlot(adSticky);
52
+ }
53
+ closeButton.addEventListener('click', () => {
54
+ hideAdSlot(adSticky);
55
+ const slot = window.googletag
56
+ .pubads()
57
+ .getSlots()
58
+ .find(slot => slot.getSlotElementId() === mobileStickyDomId);
59
+ if (slot) {
60
+ window.googletag.destroySlots([slot]);
61
+ }
62
+ }, { once: true, passive: true });
63
+ if (env === 'production') {
64
+ const onRenderResult = ([renderResult]) => {
65
+ log.debug('mobile-sticky-ad', `result ${renderResult}`);
66
+ if (renderResult === 'disallowed') {
67
+ log.debug('mobile-sticky-ad', 'hide mobile sticky container');
68
+ if (adSticky) {
69
+ hideAdSlot(adSticky);
70
+ }
71
+ return Promise.resolve();
72
+ }
73
+ else if (renderResult === 'standard') {
74
+ if (initiallyHidden) {
75
+ showAdSlot(adSticky);
76
+ }
77
+ return Promise.all([
78
+ stickyRenderedEvent(adSticky, mobileStickyDomId, disallowedAdvertiserIds, window),
79
+ stickyOnLoadEvent(mobileStickyDomId, window)
80
+ ]).then(onRenderResult);
81
+ }
82
+ return Promise.resolve();
83
+ };
84
+ Promise.all([
85
+ stickyRenderedEvent(adSticky, mobileStickyDomId, disallowedAdvertiserIds, window),
86
+ stickyOnLoadEvent(mobileStickyDomId, window)
87
+ ]).then(onRenderResult);
88
+ }
89
+ }
90
+ else {
91
+ log.warn('[mobile-sticky]', `Could not find adSticky container ${adStickyContainerDataRef} or closeButton ${adStickyCloseButtonDataRef}`, adSticky, closeButton);
92
+ }
93
+ };
@@ -0,0 +1,118 @@
1
+ const adStickyContainerDataRef = '[data-ref=h5v-sticky-ad]';
2
+ const adStickyCloseButtonDataRef = '[data-ref=h5v-sticky-ad-close]';
3
+ const adStickyCloseButtonContent = '.h5v-closeButtonContent';
4
+ const adStickyHidingClass = 'h5v-footerAd--hidden';
5
+ const desktopInitialHidingClass = 'h5v-footerAd--hidden-d';
6
+ const mobileInitialHidingClass = 'h5v-footerAd--hidden-m';
7
+ const stickyRenderedEvent = (mobileStickyDomId, disallowedAdvertiserIds, window) => new Promise(resolve => {
8
+ const listener = (event) => {
9
+ if (event.slot.getSlotElementId() !== mobileStickyDomId) {
10
+ return;
11
+ }
12
+ if (event.isEmpty) {
13
+ resolve('empty');
14
+ }
15
+ else if (event.advertiserId && disallowedAdvertiserIds.includes(event.advertiserId)) {
16
+ resolve('disallowed');
17
+ }
18
+ else {
19
+ resolve('standard');
20
+ }
21
+ window.googletag.pubads().removeEventListener('slotRenderEnded', listener);
22
+ };
23
+ window.googletag.cmd.push(() => {
24
+ window.googletag.pubads().addEventListener('slotRenderEnded', listener);
25
+ });
26
+ });
27
+ const stickyOnLoadEvent = (footerStickyDomId, window) => new Promise(resolve => {
28
+ const listener = (event) => {
29
+ if (event.slot.getSlotElementId() !== footerStickyDomId) {
30
+ return;
31
+ }
32
+ resolve();
33
+ window.googletag.pubads().removeEventListener('slotOnload', listener);
34
+ };
35
+ window.googletag.pubads().addEventListener('slotOnload', listener);
36
+ });
37
+ const hideAdSlot = (element) => {
38
+ element.classList.add(adStickyHidingClass);
39
+ };
40
+ const showAdSlot = (element) => {
41
+ element.classList.remove(adStickyHidingClass, desktopInitialHidingClass, mobileInitialHidingClass);
42
+ };
43
+ export const initAdSticky = (window, env, log, footerStickyDomId, disallowedAdvertiserIds, closingButtonText) => {
44
+ const stickyAd = 'sticky-ad';
45
+ const adSticky = window.document.querySelector(adStickyContainerDataRef);
46
+ const closeButton = window.document.querySelector(adStickyCloseButtonDataRef);
47
+ const closeButtonContent = window.document.querySelector(adStickyCloseButtonContent);
48
+ if (adSticky && closeButton) {
49
+ log.debug(stickyAd, 'Running initAdSticky with defined sticky container and close button');
50
+ if (!closeButtonContent) {
51
+ if (!closingButtonText) {
52
+ if (!closeButton.querySelector('svg')) {
53
+ const closeButtonSvg = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
54
+ closeButtonSvg.setAttribute('width', '24');
55
+ closeButtonSvg.setAttribute('height', '24');
56
+ const closeButtonPath = window.document.createElementNS('http://www.w3.org/2000/svg', 'path');
57
+ closeButtonPath.classList.add(adStickyCloseButtonContent);
58
+ closeButtonPath.setAttribute('d', 'M7 10l5 5 5-5z');
59
+ closeButtonSvg.appendChild(closeButtonPath);
60
+ closeButton.appendChild(closeButtonSvg);
61
+ }
62
+ }
63
+ else {
64
+ closeButton.textContent = closingButtonText;
65
+ }
66
+ }
67
+ closeButton.addEventListener('click', () => {
68
+ adSticky.addEventListener('transitionend', function () {
69
+ adSticky.remove();
70
+ const slot = window.googletag
71
+ ?.pubads()
72
+ .getSlots()
73
+ .find(slot => slot.getSlotElementId() === footerStickyDomId);
74
+ if (slot) {
75
+ window.googletag.destroySlots([slot]);
76
+ }
77
+ }, { once: true });
78
+ hideAdSlot(adSticky);
79
+ });
80
+ const onRenderResult = (renderResult) => {
81
+ log.debug(stickyAd, `result ${renderResult}`);
82
+ if (renderResult === 'disallowed' || renderResult === 'empty') {
83
+ log.debug(stickyAd, 'stickyFooter container');
84
+ hideAdSlot(adSticky);
85
+ return Promise.resolve();
86
+ }
87
+ else if (renderResult === 'standard') {
88
+ showAdSlot(adSticky);
89
+ const stickyOnLoadEventPromise = stickyOnLoadEvent(footerStickyDomId, window);
90
+ return stickyRenderedEvent(footerStickyDomId, disallowedAdvertiserIds, window)
91
+ .then(result => result === 'empty' || result === 'disallowed'
92
+ ? Promise.resolve(result)
93
+ : stickyOnLoadEventPromise.then(() => result))
94
+ .then(onRenderResult);
95
+ }
96
+ return Promise.resolve();
97
+ };
98
+ if (env === 'production') {
99
+ if (footerStickyDomId) {
100
+ const stickyOnLoadEventPromise = stickyOnLoadEvent(footerStickyDomId, window);
101
+ stickyRenderedEvent(footerStickyDomId, disallowedAdvertiserIds, window)
102
+ .then(result => result === 'empty' || result === 'disallowed'
103
+ ? Promise.resolve(result)
104
+ : stickyOnLoadEventPromise.then(() => result))
105
+ .then(onRenderResult);
106
+ }
107
+ else {
108
+ log.warn('[sticky-footer-ad]', `Could not find adSticky container ${adStickyContainerDataRef} or closeButton ${adStickyCloseButtonDataRef}`, adSticky, closeButton);
109
+ }
110
+ }
111
+ else {
112
+ onRenderResult('standard');
113
+ }
114
+ }
115
+ else {
116
+ log.warn('[sticky-footer-ad]', `Could not find adSticky container ${adStickyContainerDataRef} or closeButton ${adStickyCloseButtonDataRef}`, adSticky, closeButton);
117
+ }
118
+ };
@@ -0,0 +1,43 @@
1
+ import { LOW_PRIORITY, mkPrepareRequestAdsStep } from 'ad-tag/ads/adPipeline';
2
+ import { initAdSticky } from './footerStickyAd';
3
+ export class StickyFooterAdsV2 {
4
+ constructor() {
5
+ this.name = 'sticky-footer-ads-v2';
6
+ this.description = 'sticky footer ad creatives';
7
+ this.moduleType = 'creatives';
8
+ this.stickyFooterAdConfig = null;
9
+ }
10
+ config__() {
11
+ return this.stickyFooterAdConfig;
12
+ }
13
+ configure__(moduleConfig) {
14
+ if (moduleConfig?.stickyFooterAdV2 && moduleConfig.stickyFooterAdV2.enabled) {
15
+ this.stickyFooterAdConfig = moduleConfig.stickyFooterAdV2;
16
+ }
17
+ }
18
+ prepareRequestAdsSteps__() {
19
+ const config = this.stickyFooterAdConfig;
20
+ return config
21
+ ? [
22
+ mkPrepareRequestAdsStep(this.name, LOW_PRIORITY, (ctx, slots) => {
23
+ const desktopSlot = slots.find(slot => slot.moliSlot.domId === config.stickyFooterDomIds.desktop);
24
+ const mobileSlot = slots.find(slot => slot.moliSlot.domId === config.stickyFooterDomIds.mobile);
25
+ const footerAdSlot = mobileSlot ? mobileSlot : desktopSlot;
26
+ if (mobileSlot && desktopSlot) {
27
+ ctx.logger__.warn(this.name, 'mobile and desktop sticky footer are called!');
28
+ }
29
+ if (footerAdSlot) {
30
+ initAdSticky(ctx.window__, ctx.env__, ctx.logger__, footerAdSlot.moliSlot.domId, config.disallowedAdvertiserIds, config.closingButtonText);
31
+ }
32
+ return Promise.resolve();
33
+ })
34
+ ]
35
+ : [];
36
+ }
37
+ configureSteps__() {
38
+ return [];
39
+ }
40
+ initSteps__() {
41
+ return [];
42
+ }
43
+ }
@@ -0,0 +1,25 @@
1
+ export const intersectionObserverFadeOutCallback = (container, target, adRenderResult, navbar, navbarHiddenClass, fadeOutClassName) => entries => {
2
+ entries.forEach(entry => {
3
+ if (target && entry.target === target) {
4
+ adRenderResult.then(result => {
5
+ if (entry.isIntersecting ||
6
+ (!entry.isIntersecting && entry.boundingClientRect.y < 0) ||
7
+ result === 'empty' ||
8
+ result === 'disallowed') {
9
+ container.classList.add(fadeOutClassName);
10
+ }
11
+ else if (entry.boundingClientRect.y >= 0 && result === 'standard') {
12
+ container.classList.remove(fadeOutClassName);
13
+ }
14
+ });
15
+ }
16
+ else if (entry.target === navbar && navbarHiddenClass) {
17
+ if (entry.isIntersecting) {
18
+ container.classList.remove(navbarHiddenClass);
19
+ }
20
+ else {
21
+ container.classList.add(navbarHiddenClass);
22
+ }
23
+ }
24
+ });
25
+ };
@@ -0,0 +1,103 @@
1
+ import { intersectionObserverFadeOutCallback } from './fadeOutCallback';
2
+ import { adRenderResult } from './renderResult';
3
+ import { LOW_PRIORITY, mkConfigureStepOncePerRequestAdsCycle, mkPrepareRequestAdsStep } from 'ad-tag/ads/adPipeline';
4
+ export class StickyHeaderAd {
5
+ constructor() {
6
+ this.name = 'sticky-header-ads';
7
+ this.description = 'sticky header ad creatives';
8
+ this.moduleType = 'creatives';
9
+ this.containerSelector = '[data-ref="header-ad"]';
10
+ this.buttonSelector = '[data-ref="header-ad-close-button"]';
11
+ this.navbarHiddenClassName = 'h5v-header-ad--navbarHidden';
12
+ this.observer = null;
13
+ this.stickyHeaderAdConfig = null;
14
+ }
15
+ configure__(moduleConfig) {
16
+ if (moduleConfig?.stickyHeaderAd?.enabled) {
17
+ this.stickyHeaderAdConfig = moduleConfig.stickyHeaderAd;
18
+ }
19
+ }
20
+ config__() {
21
+ return this.stickyHeaderAdConfig;
22
+ }
23
+ initSteps__() {
24
+ return [];
25
+ }
26
+ configureSteps__() {
27
+ const config = this.stickyHeaderAdConfig;
28
+ return config
29
+ ? [
30
+ mkConfigureStepOncePerRequestAdsCycle('sticky-header-ads:cleanup', () => {
31
+ if (this.observer) {
32
+ this.observer.disconnect();
33
+ this.observer = null;
34
+ }
35
+ return Promise.resolve();
36
+ })
37
+ ]
38
+ : [];
39
+ }
40
+ prepareRequestAdsSteps__() {
41
+ const config = this.stickyHeaderAdConfig;
42
+ return config
43
+ ? [
44
+ mkPrepareRequestAdsStep(this.name, LOW_PRIORITY, (ctx, slots) => {
45
+ if (this.observer) {
46
+ return Promise.resolve();
47
+ }
48
+ const headerSlot = slots.find(slot => slot.moliSlot.domId === config.headerAdDomId);
49
+ if (!headerSlot) {
50
+ return Promise.resolve();
51
+ }
52
+ const container = ctx.window__.document.querySelector(this.containerSelector);
53
+ if (!container) {
54
+ ctx.logger__.warn(this.name, `Could not find sticky header container with selector '${this.containerSelector}'`);
55
+ return Promise.resolve();
56
+ }
57
+ const minVisibleDuration = config.minVisibleDurationMs ?? 0;
58
+ if (config.fadeOutTrigger !== false) {
59
+ const options = config.fadeOutTrigger.options ?? {
60
+ rootMargin: '0px'
61
+ };
62
+ const targets = ctx.window__.document.querySelectorAll(config.fadeOutTrigger.selector);
63
+ const target = targets.length > 0 ? targets.item(0) : null;
64
+ const navbarConfig = config.navbarConfig;
65
+ const navbarHiddenClass = navbarConfig?.navbarHiddenClassName ?? this.navbarHiddenClassName;
66
+ const navbar = navbarConfig
67
+ ? ctx.window__.document.querySelector(navbarConfig.selector)
68
+ : null;
69
+ if (target) {
70
+ const adRenderResultPromise = adRenderResult(ctx, headerSlot.moliSlot, config.disallowedAdvertiserIds, minVisibleDuration);
71
+ this.observer = new IntersectionObserver(intersectionObserverFadeOutCallback(container, target, adRenderResultPromise, navbar, navbarHiddenClass, config.fadeOutClassName), options);
72
+ this.observer.observe(target);
73
+ if (navbar) {
74
+ this.observer.observe(navbar);
75
+ }
76
+ }
77
+ else {
78
+ ctx.logger__.error(this.name, `No DOM element found for selector ${config.fadeOutTrigger.selector}. Sticky header may never fade out`);
79
+ }
80
+ }
81
+ const closeButton = ctx.window__.document.querySelector(this.buttonSelector);
82
+ if (closeButton) {
83
+ closeButton.addEventListener('click', () => {
84
+ container.classList.add(config.fadeOutClassName);
85
+ if (this.observer) {
86
+ this.observer.disconnect();
87
+ }
88
+ if (ctx.env__ === 'production') {
89
+ ctx.window__.googletag.destroySlots([headerSlot.adSlot]);
90
+ }
91
+ if (config.destroySlot) {
92
+ if (container) {
93
+ container.remove();
94
+ }
95
+ }
96
+ });
97
+ }
98
+ return Promise.resolve();
99
+ })
100
+ ]
101
+ : [];
102
+ }
103
+ }
@@ -0,0 +1,24 @@
1
+ export const adRenderResult = (ctx, headerSlot, disallowedAdvertiserIds, minVisibleDuration) => new Promise(resolve => {
2
+ if (ctx.env__ === 'test') {
3
+ resolve('standard');
4
+ return;
5
+ }
6
+ const listener = event => {
7
+ if (event.slot.getSlotElementId() !== headerSlot.domId) {
8
+ return;
9
+ }
10
+ if (event.advertiserId && disallowedAdvertiserIds.includes(event.advertiserId)) {
11
+ resolve('disallowed');
12
+ }
13
+ else if (event.isEmpty) {
14
+ resolve('empty');
15
+ }
16
+ else {
17
+ minVisibleDuration > 0
18
+ ? ctx.window__.setTimeout(() => resolve('standard'), minVisibleDuration)
19
+ : resolve('standard');
20
+ }
21
+ ctx.window__.googletag.pubads().removeEventListener('slotRenderEnded', listener);
22
+ };
23
+ ctx.window__.googletag.pubads().addEventListener('slotRenderEnded', listener);
24
+ });
@@ -0,0 +1,79 @@
1
+ import { tcfapi } from 'ad-tag/types/tcfapi';
2
+ import { mkInitStep } from 'ad-tag/ads/adPipeline';
3
+ import { AssetLoadMethod } from 'ad-tag/util/assetLoaderService';
4
+ const requiredPurposeIds = [
5
+ tcfapi.responses.TCPurpose.STORE_INFORMATION_ON_DEVICE,
6
+ tcfapi.responses.TCPurpose.SELECT_BASIC_ADS,
7
+ tcfapi.responses.TCPurpose.CREATE_PERSONALISED_ADS_PROFILE,
8
+ tcfapi.responses.TCPurpose.SELECT_PERSONALISED_ADS,
9
+ tcfapi.responses.TCPurpose.CREATE_PERSONALISED_CONTENT_PROFILE,
10
+ tcfapi.responses.TCPurpose.SELECT_PERSONALISED_CONTENT,
11
+ tcfapi.responses.TCPurpose.MEASURE_AD_PERFORMANCE,
12
+ tcfapi.responses.TCPurpose.MEASURE_CONTENT_PERFORMANCE,
13
+ tcfapi.responses.TCPurpose.APPLY_MARKET_RESEARCH,
14
+ tcfapi.responses.TCPurpose.DEVELOP_IMPROVE_PRODUCTS,
15
+ tcfapi.responses.TCPurpose.USE_LIMITED_DATA_TO_SElECT_CONTENT
16
+ ];
17
+ export const createUtiq = () => {
18
+ let utiqConfig = null;
19
+ let loaded = false;
20
+ const loadUtiq = (config, context) => {
21
+ if (context.env__ === 'test') {
22
+ return Promise.resolve();
23
+ }
24
+ if (loaded) {
25
+ return Promise.resolve();
26
+ }
27
+ loaded = true;
28
+ const utiqWindow = context.window__;
29
+ utiqWindow.Utiq = utiqWindow.Utiq
30
+ ? { ...utiqWindow.Utiq, config: { ...utiqWindow.Utiq.config, ...config.options } }
31
+ : { queue: [], config: config.options };
32
+ utiqWindow.Utiq.queue = utiqWindow.Utiq.queue || [];
33
+ if (context.tcData__.gdprApplies &&
34
+ requiredPurposeIds.some(purposeId => context.tcData__.gdprApplies && !context.tcData__.purpose.consents[purposeId])) {
35
+ return Promise.resolve();
36
+ }
37
+ const minAdRequests = config.delay?.enabled && config.delay.minAdRequests ? config.delay.minAdRequests : 0;
38
+ if (context.requestAdsCalls__ < minAdRequests) {
39
+ context.logger__.info('Utiq', `not enough ad requests (${context.requestAdsCalls__}) to load Utiq. ${minAdRequests} required.`);
40
+ return Promise.resolve();
41
+ }
42
+ return context.assetLoaderService__
43
+ .loadScript({
44
+ name: 'utiq',
45
+ loadMethod: AssetLoadMethod.TAG,
46
+ assetUrl: config.assetUrl
47
+ })
48
+ .catch(error => context.logger__.error('failed to load utiq', error));
49
+ };
50
+ const hasDelayEnabled = (config) => {
51
+ return config?.delay?.enabled ?? false;
52
+ };
53
+ return {
54
+ name: 'utiq',
55
+ description: 'user module',
56
+ moduleType: 'identity',
57
+ config__() {
58
+ return utiqConfig;
59
+ },
60
+ configure__(moduleConfig) {
61
+ if (moduleConfig?.utiq && moduleConfig.utiq.enabled) {
62
+ utiqConfig = moduleConfig.utiq;
63
+ }
64
+ },
65
+ initSteps__() {
66
+ return utiqConfig?.enabled && !hasDelayEnabled(utiqConfig)
67
+ ? [mkInitStep('utiq', ctx => loadUtiq(utiqConfig, ctx))]
68
+ : [];
69
+ },
70
+ configureSteps__() {
71
+ return utiqConfig?.enabled && hasDelayEnabled(utiqConfig)
72
+ ? [mkInitStep('utiq', ctx => loadUtiq(utiqConfig, ctx))]
73
+ : [];
74
+ },
75
+ prepareRequestAdsSteps__() {
76
+ return [];
77
+ }
78
+ };
79
+ };
@@ -0,0 +1,86 @@
1
+ const previousMaxCpm = (cpms) => {
2
+ if (cpms.length === 0) {
3
+ return null;
4
+ }
5
+ else {
6
+ const maxBid = Math.max(...cpms);
7
+ return Math.round(maxBid * 100);
8
+ }
9
+ };
10
+ const previousMinCpm = (cpms) => {
11
+ if (cpms.length === 0) {
12
+ return null;
13
+ }
14
+ else {
15
+ const minBid = Math.min(...cpms);
16
+ return Math.round(minBid * 100);
17
+ }
18
+ };
19
+ const previousSecondHighestCpm = (cpms) => {
20
+ if (!cpms || cpms.length === 0) {
21
+ return null;
22
+ }
23
+ else if (cpms.length === 1) {
24
+ return Math.round(cpms[0] * 100);
25
+ }
26
+ else {
27
+ const secondHighestBid = cpms.sort()[cpms.length - 2];
28
+ return Math.round(secondHighestBid * 100);
29
+ }
30
+ };
31
+ const calculateDynamicFloorPrice = (strategy, previousCpms) => {
32
+ switch (strategy) {
33
+ case 'max':
34
+ return previousMaxCpm(previousCpms);
35
+ case 'min':
36
+ return previousMinCpm(previousCpms);
37
+ case 'second-highest':
38
+ return previousSecondHighestCpm(previousCpms);
39
+ default:
40
+ return null;
41
+ }
42
+ };
43
+ const roundDownToInteger = (num) => {
44
+ return Math.floor(num);
45
+ };
46
+ const roundDownPrice = (args) => {
47
+ const priceInCents = roundDownToInteger(args.priceInCents);
48
+ const roundingStepsInCents = roundDownToInteger(args.roundingStepsInCents);
49
+ const maxPriceRuleInCents = args.maxPriceRuleInCents !== undefined
50
+ ? roundDownToInteger(args.maxPriceRuleInCents)
51
+ : undefined;
52
+ const minPriceRuleInCents = args.minPriceRuleInCents !== undefined
53
+ ? roundDownToInteger(args.minPriceRuleInCents)
54
+ : undefined;
55
+ const roundedPriceInCents = Math.floor(priceInCents / roundingStepsInCents) * roundingStepsInCents;
56
+ if (maxPriceRuleInCents !== undefined && roundedPriceInCents > maxPriceRuleInCents) {
57
+ return maxPriceRuleInCents;
58
+ }
59
+ if (minPriceRuleInCents !== undefined && roundedPriceInCents < minPriceRuleInCents) {
60
+ return minPriceRuleInCents;
61
+ }
62
+ return roundedPriceInCents;
63
+ };
64
+ export const calculateDynamicPriceRule = ({ strategy, previousCpms, standardRule, roundingStepsInCents = 5, maxPriceRuleInCents = 500, minPriceRuleInCents = 5 }) => {
65
+ const validPreviousCpms = Array.isArray(previousCpms)
66
+ ? previousCpms?.filter(cpm => cpm > 0 && !isNaN(cpm))
67
+ : [];
68
+ if (!validPreviousCpms || validPreviousCpms.length === 0 || !strategy) {
69
+ return standardRule;
70
+ }
71
+ const calculatedFloorPrice = calculateDynamicFloorPrice(strategy, validPreviousCpms);
72
+ if (calculatedFloorPrice === null) {
73
+ return standardRule;
74
+ }
75
+ const roundedPriceRuleIdInCents = roundDownPrice({
76
+ priceInCents: calculatedFloorPrice,
77
+ roundingStepsInCents,
78
+ maxPriceRuleInCents,
79
+ minPriceRuleInCents
80
+ });
81
+ return {
82
+ ...standardRule,
83
+ floorprice: roundedPriceRuleIdInCents / 100,
84
+ priceRuleId: roundedPriceRuleIdInCents
85
+ };
86
+ };
@@ -0,0 +1,57 @@
1
+ import { createYieldOptimizationService } from './yieldOptimizationService';
2
+ import { HIGH_PRIORITY, mkInitStep, mkPrepareRequestAdsStep } from 'ad-tag/ads/adPipeline';
3
+ import { uniquePrimitiveFilter } from 'ad-tag/util/arrayUtils';
4
+ export const YieldOptimization = (testYieldOptimizationService) => {
5
+ const name = 'YieldOptimization';
6
+ const description = 'Provides floors and UPR ids';
7
+ const moduleType = 'yield';
8
+ let yieldModuleConfig = null;
9
+ const _initSteps = [];
10
+ const _prepareRequestAdsSteps = [];
11
+ const config__ = () => yieldModuleConfig;
12
+ const configure__ = (moduleConfig) => {
13
+ if (moduleConfig?.yieldOptimization?.enabled) {
14
+ yieldModuleConfig = moduleConfig.yieldOptimization;
15
+ const yieldOptimizationService = testYieldOptimizationService ??
16
+ createYieldOptimizationService(moduleConfig.yieldOptimization);
17
+ _initSteps.push(yieldOptimizationInit(yieldOptimizationService));
18
+ _prepareRequestAdsSteps.push(yieldOptimizationPrepareRequestAds(yieldOptimizationService));
19
+ }
20
+ };
21
+ const initSteps__ = () => _initSteps;
22
+ const configureSteps__ = () => [];
23
+ const prepareRequestAdsSteps__ = () => _prepareRequestAdsSteps;
24
+ const yieldOptimizationInit = (yieldOptimizationService) => mkInitStep('yield-optimization-init', context => {
25
+ const adUnitPaths = context.config__.slots
26
+ .filter(slot => context.labelConfigService__.filterSlot(slot))
27
+ .map(slot => slot.adUnitPath)
28
+ .filter(uniquePrimitiveFilter);
29
+ return yieldOptimizationService.init(context.labelConfigService__.getDeviceLabel(), context.adUnitPathVariables__, adUnitPaths, context.window__.fetch, context.logger__);
30
+ });
31
+ const yieldOptimizationPrepareRequestAds = (yieldOptimizationService) => mkPrepareRequestAdsStep('yield-optimization', HIGH_PRIORITY, (context, slots) => {
32
+ context.logger__.debug('YieldOptimizationService', context.requestId__, 'applying price rules');
33
+ const adServer = context.config__.adServer || 'gam';
34
+ const slotsWithPriceRule = slots.map(slot => {
35
+ return yieldOptimizationService
36
+ .setTargeting(slot.adSlot, adServer, context.logger__, yieldModuleConfig, context.auction__)
37
+ .then(priceRule => (slot.priceRule = priceRule));
38
+ });
39
+ return Promise.all(slotsWithPriceRule)
40
+ .then(() => yieldOptimizationService.getBrowser())
41
+ .then(browser => {
42
+ if (context.env__ === 'production' && adServer === 'gam') {
43
+ context.window__.googletag.pubads().setTargeting('upr_browser', browser);
44
+ }
45
+ });
46
+ });
47
+ return {
48
+ name,
49
+ description,
50
+ moduleType,
51
+ config__,
52
+ configure__,
53
+ initSteps__,
54
+ configureSteps__,
55
+ prepareRequestAdsSteps__
56
+ };
57
+ };
@@ -0,0 +1,6 @@
1
+ export const isYieldConfigDynamic = (config) => {
2
+ return config
3
+ ? config.dynamicFloorPrices
4
+ ?.strategy !== undefined
5
+ : false;
6
+ };