@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.
- package/LICENSE +201 -0
- package/README.md +149 -0
- package/lib/ads/a9.js +190 -0
- package/lib/ads/adPipeline.js +159 -0
- package/lib/ads/adService.js +251 -0
- package/lib/ads/adUnitPath.js +37 -0
- package/lib/ads/auctions/adRequestThrottling.js +22 -0
- package/lib/ads/auctions/biddersDisabling.js +90 -0
- package/lib/ads/auctions/frequencyCapping.js +189 -0
- package/lib/ads/auctions/interstitialContext.js +92 -0
- package/lib/ads/auctions/previousBidCpms.js +33 -0
- package/lib/ads/auctions/resume.js +3 -0
- package/lib/ads/bridge/bridge.js +62 -0
- package/lib/ads/consent.js +63 -0
- package/lib/ads/eventService.js +44 -0
- package/lib/ads/globalAuctionContext.js +98 -0
- package/lib/ads/googleAdManager.js +380 -0
- package/lib/ads/keyValues.js +1 -0
- package/lib/ads/labelConfigService.js +42 -0
- package/lib/ads/modules/ad-reload/adVisibilityService.js +158 -0
- package/lib/ads/modules/ad-reload/index.js +163 -0
- package/lib/ads/modules/ad-reload/userActivityService.js +70 -0
- package/lib/ads/modules/adex/adex-mapping.js +77 -0
- package/lib/ads/modules/adex/adexUtiq.js +15 -0
- package/lib/ads/modules/adex/index.js +142 -0
- package/lib/ads/modules/adex/sendAdvertisingId.js +20 -0
- package/lib/ads/modules/blocklist-url/index.js +118 -0
- package/lib/ads/modules/cleanup/index.js +93 -0
- package/lib/ads/modules/confiant/index.js +47 -0
- package/lib/ads/modules/emetriq/index.js +154 -0
- package/lib/ads/modules/emetriq/trackInApp.js +34 -0
- package/lib/ads/modules/emetriq/trackLoginEvent.js +59 -0
- package/lib/ads/modules/generic-skin/index.js +150 -0
- package/lib/ads/modules/identitylink/index.js +58 -0
- package/lib/ads/modules/interstitial/index.js +38 -0
- package/lib/ads/modules/interstitial/interstitialAd.js +111 -0
- package/lib/ads/modules/lazy-load/index.js +191 -0
- package/lib/ads/modules/lazy-load/selectInfiniteSlot.js +11 -0
- package/lib/ads/modules/prebid-first-party-data/index.js +115 -0
- package/lib/ads/modules/pubstack/index.js +59 -0
- package/lib/ads/modules/sticky-footer-ad/desktopFloorAd.js +63 -0
- package/lib/ads/modules/sticky-footer-ad/index.js +43 -0
- package/lib/ads/modules/sticky-footer-ad/mobileSticky.js +93 -0
- package/lib/ads/modules/sticky-footer-ad-v2/footerStickyAd.js +118 -0
- package/lib/ads/modules/sticky-footer-ad-v2/index.js +43 -0
- package/lib/ads/modules/sticky-header-ad/fadeOutCallback.js +25 -0
- package/lib/ads/modules/sticky-header-ad/index.js +103 -0
- package/lib/ads/modules/sticky-header-ad/renderResult.js +24 -0
- package/lib/ads/modules/utiq/index.js +79 -0
- package/lib/ads/modules/yield-optimization/dynamicFloorPrice.js +86 -0
- package/lib/ads/modules/yield-optimization/index.js +57 -0
- package/lib/ads/modules/yield-optimization/isYieldOptimizationConfigDynamic.js +6 -0
- package/lib/ads/modules/yield-optimization/yieldOptimizationService.js +169 -0
- package/lib/ads/modules/zeotap/index.js +111 -0
- package/lib/ads/moli.js +645 -0
- package/lib/ads/moliGlobal.js +11 -0
- package/lib/ads/prebid-outstream.js +13 -0
- package/lib/ads/prebid.js +406 -0
- package/lib/ads/sizeConfigService.js +49 -0
- package/lib/ads/spa.js +32 -0
- package/lib/bundle/adReload.js +2 -0
- package/lib/bundle/adex.js +2 -0
- package/lib/bundle/blocklistUrls.js +2 -0
- package/lib/bundle/cleanup.js +2 -0
- package/lib/bundle/confiant.js +2 -0
- package/lib/bundle/configureFromEndpoint.js +40 -0
- package/lib/bundle/emetriq.js +2 -0
- package/lib/bundle/identityLink.js +2 -0
- package/lib/bundle/init.js +2 -0
- package/lib/bundle/interstitialModule.js +2 -0
- package/lib/bundle/lazyLoad.js +2 -0
- package/lib/bundle/prebidFirstPartyData.js +2 -0
- package/lib/bundle/pubstack.js +2 -0
- package/lib/bundle/skin.js +2 -0
- package/lib/bundle/stickyFooterAd.js +2 -0
- package/lib/bundle/stickyFooterAds2.js +2 -0
- package/lib/bundle/stickyHeaderAd.js +2 -0
- package/lib/bundle/utiq.js +2 -0
- package/lib/bundle/yieldOptimization.js +2 -0
- package/lib/bundle/zeotap.js +2 -0
- package/lib/console/components/adSlotConfig.js +225 -0
- package/lib/console/components/consentConfig.js +138 -0
- package/lib/console/components/globalConfig.js +634 -0
- package/lib/console/components/labelConfigDebug.js +19 -0
- package/lib/console/components/sizeConfigDebug.js +34 -0
- package/lib/console/components/tag.js +4 -0
- package/lib/console/debug.js +38 -0
- package/lib/console/util/array.js +1 -0
- package/lib/console/util/calculateAdDensity.js +36 -0
- package/lib/console/util/debounce.js +12 -0
- package/lib/console/util/debugLogger.js +6 -0
- package/lib/console/util/extractPositionFromPath.js +8 -0
- package/lib/console/util/prebid.js +3 -0
- package/lib/console/util/stringUtils.js +23 -0
- package/lib/console/util/themingService.js +31 -0
- package/lib/console/util/windowResizeService.js +22 -0
- package/lib/console/validations/adReloadValidations.js +47 -0
- package/lib/console/validations/bucketValidations.js +78 -0
- package/lib/console/validations/sizesConfigValidations.js +97 -0
- package/lib/gen/packageJson.js +3 -0
- package/lib/index.js +3 -0
- package/lib/types/apstag.js +1 -0
- package/lib/types/dom.js +1 -0
- package/lib/types/emetriq.js +1 -0
- package/lib/types/googletag.js +1 -0
- package/lib/types/identitylink.js +1 -0
- package/lib/types/module.js +1 -0
- package/lib/types/moliConfig.js +1 -0
- package/lib/types/moliRuntime.js +1 -0
- package/lib/types/prebidjs.js +45 -0
- package/lib/types/supplyChainObject.js +1 -0
- package/lib/types/tcfapi.js +48 -0
- package/lib/util/addNewInfiniteSlotToConfig.js +11 -0
- package/lib/util/arrayUtils.js +9 -0
- package/lib/util/assetLoaderService.js +91 -0
- package/lib/util/browserStorageKeys.js +7 -0
- package/lib/util/debugDelay.js +12 -0
- package/lib/util/domready.js +15 -0
- package/lib/util/environmentOverride.js +16 -0
- package/lib/util/extractAdTagVersion.js +16 -0
- package/lib/util/extractTopPrivateDomainFromHostname.js +17 -0
- package/lib/util/localStorage.js +26 -0
- package/lib/util/logging.js +106 -0
- package/lib/util/objectUtils.js +29 -0
- package/lib/util/query.js +40 -0
- package/lib/util/queryParameters.js +5 -0
- package/lib/util/resolveOverrides.js +21 -0
- package/lib/util/resolveStoredRequestIdInOrtb2Object.js +12 -0
- package/lib/util/sizes.js +3 -0
- package/lib/util/test-slots.js +150 -0
- package/lib/util/uuid.js +10 -0
- 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
|
+
};
|