@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,111 @@
|
|
|
1
|
+
import { getBrowserStorageValue } from 'ad-tag/util/localStorage';
|
|
2
|
+
const interstitialContainerSelector = '[data-ref="h5v-interstitial"]';
|
|
3
|
+
const interstitialCloseButtonSelector = '[data-ref="h5v-interstitial-close"]';
|
|
4
|
+
const interstitialHidingClass = 'h5v-interstitial--hidden';
|
|
5
|
+
const interstitialRenderedEvent = (interstitialDomId, disallowedAdvertiserIds, window) => new Promise(resolve => {
|
|
6
|
+
const listener = (event) => {
|
|
7
|
+
if (event.slot.getSlotElementId() !== interstitialDomId) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (event.isEmpty) {
|
|
11
|
+
resolve('empty');
|
|
12
|
+
}
|
|
13
|
+
else if (event.advertiserId && disallowedAdvertiserIds.includes(event.advertiserId)) {
|
|
14
|
+
resolve('disallowed');
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
event.slot.setConfig({ safeFrame: { forceSafeFrame: true } });
|
|
18
|
+
resolve('standard');
|
|
19
|
+
}
|
|
20
|
+
window.googletag.pubads().removeEventListener('slotRenderEnded', listener);
|
|
21
|
+
};
|
|
22
|
+
window.googletag.cmd.push(() => {
|
|
23
|
+
window.googletag.pubads().addEventListener('slotRenderEnded', listener);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
const interstitialOnLoadEvent = (interstitialDomId, window) => new Promise(resolve => {
|
|
27
|
+
const listener = (event) => {
|
|
28
|
+
if (event.slot.getSlotElementId() !== interstitialDomId) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
resolve();
|
|
32
|
+
window.googletag.pubads().removeEventListener('slotOnload', listener);
|
|
33
|
+
};
|
|
34
|
+
window.googletag.pubads().addEventListener('slotOnload', listener);
|
|
35
|
+
});
|
|
36
|
+
const hideAdSlot = (element) => {
|
|
37
|
+
element.classList.add(interstitialHidingClass);
|
|
38
|
+
};
|
|
39
|
+
const showAdSlot = (element) => {
|
|
40
|
+
element.classList.remove(interstitialHidingClass);
|
|
41
|
+
};
|
|
42
|
+
let timeoutToBeClearedWhenModuleIsInitialized;
|
|
43
|
+
export const initInterstitialModule = (window, env, log, interstitialDomId, disallowedAdvertiserIds, closeAutomaticallyAfterMs) => {
|
|
44
|
+
clearTimeout(timeoutToBeClearedWhenModuleIsInitialized);
|
|
45
|
+
const interstitial = 'interstitial-module';
|
|
46
|
+
const interstitialAdContainer = window.document.querySelector(interstitialContainerSelector);
|
|
47
|
+
const closeButton = window.document.querySelector(interstitialCloseButtonSelector);
|
|
48
|
+
if (closeButton && interstitialAdContainer) {
|
|
49
|
+
log.debug(interstitial, 'Running interstitial module with defined container and close button');
|
|
50
|
+
const closeInterstitial = () => {
|
|
51
|
+
hideAdSlot(interstitialAdContainer);
|
|
52
|
+
const slot = window.googletag
|
|
53
|
+
?.pubads()
|
|
54
|
+
.getSlots()
|
|
55
|
+
.find(slot => slot.getSlotElementId() === interstitialDomId);
|
|
56
|
+
if (slot) {
|
|
57
|
+
window.googletag.destroySlots([slot]);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
closeButton.addEventListener('click', () => {
|
|
61
|
+
closeInterstitial();
|
|
62
|
+
});
|
|
63
|
+
const onRenderResult = (renderResult) => {
|
|
64
|
+
log.debug(interstitial, `result ${renderResult}`);
|
|
65
|
+
if (renderResult === 'disallowed' || renderResult === 'empty') {
|
|
66
|
+
log.debug(interstitial, 'hide interstitial container');
|
|
67
|
+
hideAdSlot(interstitialAdContainer);
|
|
68
|
+
return Promise.resolve();
|
|
69
|
+
}
|
|
70
|
+
else if (renderResult === 'standard') {
|
|
71
|
+
showAdSlot(interstitialAdContainer);
|
|
72
|
+
if (closeAutomaticallyAfterMs) {
|
|
73
|
+
timeoutToBeClearedWhenModuleIsInitialized = window.setTimeout(() => {
|
|
74
|
+
closeInterstitial();
|
|
75
|
+
}, closeAutomaticallyAfterMs);
|
|
76
|
+
}
|
|
77
|
+
const interstitialOnLoadEventPromise = interstitialOnLoadEvent(interstitialDomId, window);
|
|
78
|
+
return interstitialRenderedEvent(interstitialDomId, disallowedAdvertiserIds, window)
|
|
79
|
+
.then(result => result === 'empty' || result === 'disallowed'
|
|
80
|
+
? Promise.resolve(result)
|
|
81
|
+
: interstitialOnLoadEventPromise.then(() => result))
|
|
82
|
+
.then(onRenderResult);
|
|
83
|
+
}
|
|
84
|
+
return Promise.resolve();
|
|
85
|
+
};
|
|
86
|
+
switch (env) {
|
|
87
|
+
case 'production':
|
|
88
|
+
const interstitialOnLoadEventPromise = interstitialOnLoadEvent(interstitialDomId, window);
|
|
89
|
+
interstitialRenderedEvent(interstitialDomId, disallowedAdvertiserIds, window)
|
|
90
|
+
.then(result => result === 'empty' || result === 'disallowed'
|
|
91
|
+
? Promise.resolve(result)
|
|
92
|
+
: interstitialOnLoadEventPromise.then(() => result))
|
|
93
|
+
.then(onRenderResult);
|
|
94
|
+
break;
|
|
95
|
+
case 'test':
|
|
96
|
+
if (!!getBrowserStorageValue('test-interstitial', localStorage)) {
|
|
97
|
+
onRenderResult('standard');
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
onRenderResult('empty');
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
log.warn(interstitial, `Unknown environment: ${env}`);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
log.warn('[interstitial-module]', `Could not find interstitial container ${interstitialContainerSelector} or closeButton ${interstitialCloseButtonSelector}`, interstitial, closeButton);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { LOW_PRIORITY, mkPrepareRequestAdsStep } from 'ad-tag/ads/adPipeline';
|
|
2
|
+
import { mkConfigureStepOncePerRequestAdsCycle } from 'ad-tag/ads/adPipeline';
|
|
3
|
+
import { selectInfiniteSlot } from './selectInfiniteSlot';
|
|
4
|
+
export const createLazyLoad = () => {
|
|
5
|
+
let initialized = false;
|
|
6
|
+
const observers = [];
|
|
7
|
+
let lazyloadConfig = null;
|
|
8
|
+
const name = 'moli-lazy-load';
|
|
9
|
+
const description = 'Moli implementation of an ad lazy load module.';
|
|
10
|
+
const moduleType = 'lazy-load';
|
|
11
|
+
const config__ = () => lazyloadConfig;
|
|
12
|
+
const configure__ = (moduleConfig) => {
|
|
13
|
+
if (moduleConfig?.lazyload && moduleConfig.lazyload.enabled) {
|
|
14
|
+
lazyloadConfig = moduleConfig.lazyload;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const initSteps__ = () => {
|
|
18
|
+
return [];
|
|
19
|
+
};
|
|
20
|
+
const configureSteps__ = () => {
|
|
21
|
+
const config = lazyloadConfig;
|
|
22
|
+
return config
|
|
23
|
+
? [
|
|
24
|
+
mkConfigureStepOncePerRequestAdsCycle('lazy-module-configuration', context => {
|
|
25
|
+
initialized = false;
|
|
26
|
+
observers.forEach(observer => observer.disconnect());
|
|
27
|
+
observers.length = 0;
|
|
28
|
+
registerIntersectionObservers(context, config);
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
})
|
|
31
|
+
]
|
|
32
|
+
: [];
|
|
33
|
+
};
|
|
34
|
+
const prepareRequestAdsSteps__ = () => {
|
|
35
|
+
return [
|
|
36
|
+
mkPrepareRequestAdsStep('lazy-module-delay', LOW_PRIORITY, (context, slots) => {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const delay = context.options__?.options?.delay;
|
|
39
|
+
if (delay) {
|
|
40
|
+
context.logger__?.debug(name, 'delaying slots', slots);
|
|
41
|
+
const delayTrigger = new Promise(resolve => {
|
|
42
|
+
context.window__.addEventListener('h5v.trigger-delay', () => resolve(true), {
|
|
43
|
+
once: true
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
const timeout = new Promise(resolve => {
|
|
47
|
+
context.window__.setTimeout(() => resolve(false), delay.timeoutMs ?? 30000);
|
|
48
|
+
});
|
|
49
|
+
return Promise.race([delayTrigger, timeout]).then(triggered => {
|
|
50
|
+
return triggered ? resolve() : reject(new Error('Delay timeout exceeded'));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
resolve();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
})
|
|
58
|
+
];
|
|
59
|
+
};
|
|
60
|
+
const registerIntersectionObservers = (context, moduleConfig) => {
|
|
61
|
+
if (initialized) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
initialized = true;
|
|
65
|
+
context.logger__?.debug(name, 'initialize moli lazy load module');
|
|
66
|
+
const slotsConfig = moduleConfig.slots;
|
|
67
|
+
const bucketsConfig = moduleConfig.buckets;
|
|
68
|
+
const infiniteSlotsConfig = moduleConfig.infiniteSlots;
|
|
69
|
+
const window = context.window__;
|
|
70
|
+
slotsConfig.forEach(config => {
|
|
71
|
+
const observer = new window.IntersectionObserver(entries => {
|
|
72
|
+
context.logger__?.debug(name, 'lazy-load slots called with', entries);
|
|
73
|
+
entries.forEach((entry) => {
|
|
74
|
+
if (entry.isIntersecting) {
|
|
75
|
+
context.logger__?.debug(name, `Trigger ad slot with DOM ID ${entry.target.id}`);
|
|
76
|
+
window.moli.refreshAdSlot(entry.target.id);
|
|
77
|
+
observer.unobserve(entry.target);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}, {
|
|
81
|
+
root: config.options.rootId
|
|
82
|
+
? window.document.getElementById(config.options.rootId)
|
|
83
|
+
: null,
|
|
84
|
+
threshold: config.options.threshold,
|
|
85
|
+
rootMargin: config.options.rootMargin
|
|
86
|
+
});
|
|
87
|
+
observers.push(observer);
|
|
88
|
+
config.domIds.forEach(domId => {
|
|
89
|
+
const slot = context.config__.slots.find(slot => slot.domId === domId);
|
|
90
|
+
if (!slot) {
|
|
91
|
+
context.logger__?.warn(name, `Lazy-load non-existing slot with domID ${domId}`);
|
|
92
|
+
}
|
|
93
|
+
else if (slot.behaviour.loaded !== 'manual') {
|
|
94
|
+
context.logger__?.warn(name, `Lazy-load configured for slot without manual loading behaviour. ${domId}`);
|
|
95
|
+
}
|
|
96
|
+
else if (slot.behaviour.loaded === 'manual') {
|
|
97
|
+
const elementToObserve = window.document.getElementById(domId);
|
|
98
|
+
elementToObserve && observer.observe(elementToObserve);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
bucketsConfig.forEach(config => {
|
|
103
|
+
const observer = new window.IntersectionObserver(entries => {
|
|
104
|
+
entries.forEach((entry) => {
|
|
105
|
+
if (entry.isIntersecting && entry.target.id === config.observedDomId) {
|
|
106
|
+
const correspondingBucket = context.config__.slots.find(slot => slot.domId === config.observedDomId)?.behaviour.bucket;
|
|
107
|
+
if (correspondingBucket !== config.bucket) {
|
|
108
|
+
context.logger__?.warn(name, `${config.observedDomId} doesn't belong to ${config.bucket}`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const domIdsInCorrespondingBucket = context.config__.slots
|
|
112
|
+
.filter(slot => slot.behaviour.bucket === config.bucket)
|
|
113
|
+
.map(slot => slot.domId);
|
|
114
|
+
context.logger__?.debug(`Refresh ${config.bucket}`, `Trigger ad slots with DOM IDs [${domIdsInCorrespondingBucket.join(', ')}]`);
|
|
115
|
+
window.moli.refreshBucket(config.bucket);
|
|
116
|
+
observer.unobserve(entry.target);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}, {
|
|
121
|
+
root: config.options.rootId
|
|
122
|
+
? window.document.getElementById(config.options.rootId)
|
|
123
|
+
: null,
|
|
124
|
+
threshold: config.options.threshold,
|
|
125
|
+
rootMargin: config.options.rootMargin
|
|
126
|
+
});
|
|
127
|
+
observers.push(observer);
|
|
128
|
+
if (!context.config__.buckets?.enabled) {
|
|
129
|
+
context.logger__?.warn(name, "GlobalBucket config isn't enabled");
|
|
130
|
+
}
|
|
131
|
+
if (!(context.config__.buckets?.bucket && context.config__.buckets.bucket[config.bucket])) {
|
|
132
|
+
context.logger__?.error(name, `Lazy-load non-existing bucket with name ${config.bucket}`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const elementToObserve = window.document.getElementById(config.observedDomId);
|
|
136
|
+
elementToObserve && observer.observe(elementToObserve);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const { configuredInfiniteSlots, findSlot } = selectInfiniteSlot(context.config__.slots);
|
|
140
|
+
(infiniteSlotsConfig || []).forEach(config => {
|
|
141
|
+
const serialNumberLabel = 'data-h5-serial-number';
|
|
142
|
+
if (configuredInfiniteSlots.length > 0) {
|
|
143
|
+
const observer = new window.IntersectionObserver(entries => {
|
|
144
|
+
entries.forEach((entry) => {
|
|
145
|
+
if (entry.isIntersecting) {
|
|
146
|
+
const { configuredInfiniteSlot, configSlotDomId } = findSlot(entry.target);
|
|
147
|
+
if (configuredInfiniteSlot) {
|
|
148
|
+
const serialNumber = entry.target.attributes?.getNamedItem(serialNumberLabel)?.value;
|
|
149
|
+
const createdDomId = `${configuredInfiniteSlot.domId}-${serialNumber}`;
|
|
150
|
+
entry.target.setAttribute('id', createdDomId);
|
|
151
|
+
context.logger__?.debug(name, `Trigger ad slot with newly created DOM ID ${createdDomId}`);
|
|
152
|
+
window.moli.refreshInfiniteAdSlot(createdDomId, configuredInfiniteSlot.domId);
|
|
153
|
+
observer.unobserve(entry.target);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
context.logger__?.error(name, `No infinite-scrolling slot configured for ${configSlotDomId}`);
|
|
157
|
+
observer.unobserve(entry.target);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}, {
|
|
162
|
+
root: config.options.rootId
|
|
163
|
+
? window.document.getElementById(config.options.rootId)
|
|
164
|
+
: null,
|
|
165
|
+
threshold: config.options.threshold,
|
|
166
|
+
rootMargin: config.options.rootMargin
|
|
167
|
+
});
|
|
168
|
+
observers.push(observer);
|
|
169
|
+
const infiniteElements = window.document.querySelectorAll(config.selector);
|
|
170
|
+
infiniteElements.forEach((element, index) => {
|
|
171
|
+
element.setAttribute(serialNumberLabel, `${index + 1}`);
|
|
172
|
+
element && observer.observe(element);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
context.logger__?.warn(name, `No infinite-scrolling slots configured!`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
name,
|
|
182
|
+
description,
|
|
183
|
+
moduleType,
|
|
184
|
+
config__,
|
|
185
|
+
configure__,
|
|
186
|
+
initSteps__,
|
|
187
|
+
configureSteps__,
|
|
188
|
+
prepareRequestAdsSteps__,
|
|
189
|
+
registerIntersectionObservers
|
|
190
|
+
};
|
|
191
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const selectInfiniteSlot = (slots) => {
|
|
2
|
+
const configuredInfiniteSlots = slots.filter(slot => slot.behaviour.loaded === 'infinite');
|
|
3
|
+
const findSlot = (target) => {
|
|
4
|
+
const configSlotDomId = target.getAttribute('data-h5-slot-dom-id');
|
|
5
|
+
const configuredInfiniteSlot = configSlotDomId
|
|
6
|
+
? configuredInfiniteSlots.find(slot => slot.domId === configSlotDomId)
|
|
7
|
+
: configuredInfiniteSlots[0];
|
|
8
|
+
return { configuredInfiniteSlot, configSlotDomId };
|
|
9
|
+
};
|
|
10
|
+
return { configuredInfiniteSlots, findSlot };
|
|
11
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { mkConfigureStep } from 'ad-tag/ads/adPipeline';
|
|
2
|
+
import { uniquePrimitiveFilter } from 'ad-tag/util/arrayUtils';
|
|
3
|
+
import { mergeDeep } from 'ad-tag/util/objectUtils';
|
|
4
|
+
import { extractTopPrivateDomainFromHostname } from 'ad-tag/util/extractTopPrivateDomainFromHostname';
|
|
5
|
+
export class PrebidFirstPartyDataModule {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.description = 'Module for passing first party data to prebid auctions';
|
|
8
|
+
this.moduleType = 'prebid';
|
|
9
|
+
this.name = 'prebid-first-party-data';
|
|
10
|
+
this.moduleConfig = null;
|
|
11
|
+
this._configureSteps = [];
|
|
12
|
+
}
|
|
13
|
+
config__() {
|
|
14
|
+
return this.moduleConfig;
|
|
15
|
+
}
|
|
16
|
+
configure__(moduleConfig) {
|
|
17
|
+
if (moduleConfig?.prebidFirstPartyData?.enabled) {
|
|
18
|
+
const config = moduleConfig.prebidFirstPartyData;
|
|
19
|
+
this._configureSteps = [
|
|
20
|
+
mkConfigureStep('prebid-fpd-module-configure', context => PrebidFirstPartyDataModule.setPrebidFpdConfig(context, config, context.logger__))
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
initSteps__() {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
configureSteps__() {
|
|
28
|
+
return this._configureSteps;
|
|
29
|
+
}
|
|
30
|
+
prepareRequestAdsSteps__() {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
static setPrebidFpdConfig(context, config, log) {
|
|
34
|
+
if (context.config__.prebid) {
|
|
35
|
+
const keyValues = context.config__.targeting?.keyValues || {};
|
|
36
|
+
const gptTargeting = config.gptTargetingMappings;
|
|
37
|
+
context.window__.pbjs.que.push(() => {
|
|
38
|
+
const existingFpd = context.window__.pbjs.readConfig().ortb2 || {};
|
|
39
|
+
const ortb2FromKeyValues = mergeDeep({}, config.staticPrebidFirstPartyData, existingFpd);
|
|
40
|
+
if (gptTargeting) {
|
|
41
|
+
const site = {
|
|
42
|
+
cat: [],
|
|
43
|
+
sectioncat: [],
|
|
44
|
+
pagecat: [],
|
|
45
|
+
...ortb2FromKeyValues.site
|
|
46
|
+
};
|
|
47
|
+
if (gptTargeting.cat) {
|
|
48
|
+
const keyValueData = PrebidFirstPartyDataModule.extractKeyValueArray(gptTargeting.cat, keyValues);
|
|
49
|
+
site.cat?.push(...keyValueData);
|
|
50
|
+
site.pagecat?.push(...keyValueData);
|
|
51
|
+
site.sectioncat?.push(...keyValueData);
|
|
52
|
+
}
|
|
53
|
+
if (gptTargeting.sectionCat) {
|
|
54
|
+
site.sectioncat = PrebidFirstPartyDataModule.extractKeyValueArray(gptTargeting.sectionCat, keyValues);
|
|
55
|
+
}
|
|
56
|
+
if (gptTargeting.pageCat) {
|
|
57
|
+
site.pagecat = PrebidFirstPartyDataModule.extractKeyValueArray(gptTargeting.pageCat, keyValues);
|
|
58
|
+
}
|
|
59
|
+
if (site.cat) {
|
|
60
|
+
site.cat = site.cat.filter(uniquePrimitiveFilter);
|
|
61
|
+
}
|
|
62
|
+
if (site.sectioncat) {
|
|
63
|
+
site.sectioncat = site.sectioncat.filter(uniquePrimitiveFilter);
|
|
64
|
+
}
|
|
65
|
+
if (site.pagecat) {
|
|
66
|
+
site.pagecat = site.pagecat.filter(uniquePrimitiveFilter);
|
|
67
|
+
}
|
|
68
|
+
const provider = config.iabDataProviderName ??
|
|
69
|
+
extractTopPrivateDomainFromHostname(context.window__.location.hostname);
|
|
70
|
+
if (!config.iabDataProviderName && (gptTargeting.iabV2 || gptTargeting.iabV3)) {
|
|
71
|
+
log.debug('PrebidFirstPartyDataModule', 'Targeting for iabV2 or iabV3 was defined, but iabDataProviderName was not configured. Use tld as fallback', provider);
|
|
72
|
+
}
|
|
73
|
+
site.content = {
|
|
74
|
+
...site.content,
|
|
75
|
+
data: site.content?.data?.filter(data => data.name !== provider) ?? []
|
|
76
|
+
};
|
|
77
|
+
if (gptTargeting.iabV2 && provider) {
|
|
78
|
+
const iabV2Ids = PrebidFirstPartyDataModule.extractKeyValueArray(gptTargeting.iabV2, keyValues);
|
|
79
|
+
const publisherContentData = {
|
|
80
|
+
name: provider,
|
|
81
|
+
ext: {
|
|
82
|
+
segtax: 6
|
|
83
|
+
},
|
|
84
|
+
segment: iabV2Ids.map(iabV2Id => ({ id: iabV2Id })).filter(uniquePrimitiveFilter)
|
|
85
|
+
};
|
|
86
|
+
site.content.data?.push(publisherContentData);
|
|
87
|
+
}
|
|
88
|
+
if (gptTargeting.iabV3 && provider) {
|
|
89
|
+
const iabV3Ids = PrebidFirstPartyDataModule.extractKeyValueArray(gptTargeting.iabV3, keyValues);
|
|
90
|
+
const publisherContentData = {
|
|
91
|
+
name: provider,
|
|
92
|
+
ext: {
|
|
93
|
+
segtax: 7
|
|
94
|
+
},
|
|
95
|
+
segment: iabV3Ids.map(iabV3Id => ({ id: iabV3Id })).filter(uniquePrimitiveFilter)
|
|
96
|
+
};
|
|
97
|
+
site.content.data?.push(publisherContentData);
|
|
98
|
+
}
|
|
99
|
+
ortb2FromKeyValues.site = site;
|
|
100
|
+
}
|
|
101
|
+
context.window__.pbjs.setConfig({ ortb2: ortb2FromKeyValues });
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return Promise.resolve();
|
|
105
|
+
}
|
|
106
|
+
static extractKeyValueArray(key, keyValues) {
|
|
107
|
+
const value = keyValues[key];
|
|
108
|
+
if (value) {
|
|
109
|
+
return typeof value === 'string' ? [value] : value;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { AssetLoadMethod } from 'ad-tag/util/assetLoaderService';
|
|
2
|
+
import { mkConfigureStep, mkInitStep } from '../../adPipeline';
|
|
3
|
+
export class Pubstack {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.name = 'pubstack';
|
|
6
|
+
this.description = 'prebid analytics integration';
|
|
7
|
+
this.moduleType = 'reporting';
|
|
8
|
+
this.pubstackConfig = null;
|
|
9
|
+
}
|
|
10
|
+
config__() {
|
|
11
|
+
return this.pubstackConfig;
|
|
12
|
+
}
|
|
13
|
+
configure__(moduleConfig) {
|
|
14
|
+
if (moduleConfig?.pubstack && moduleConfig.pubstack.enabled) {
|
|
15
|
+
this.pubstackConfig = moduleConfig.pubstack;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
initSteps__() {
|
|
19
|
+
const config = this.pubstackConfig;
|
|
20
|
+
return config
|
|
21
|
+
? [
|
|
22
|
+
mkInitStep('pubstack-init', ctx => {
|
|
23
|
+
if (ctx.env__ === 'test') {
|
|
24
|
+
return Promise.resolve();
|
|
25
|
+
}
|
|
26
|
+
ctx.assetLoaderService__
|
|
27
|
+
.loadScript({
|
|
28
|
+
name: 'pubstack',
|
|
29
|
+
loadMethod: AssetLoadMethod.TAG,
|
|
30
|
+
assetUrl: `https://boot.pbstck.com/v1/tag/${config.tagId}`
|
|
31
|
+
})
|
|
32
|
+
.catch(error => ctx.logger__.error('failed to load pubstack', error));
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
: [];
|
|
37
|
+
}
|
|
38
|
+
configureSteps__() {
|
|
39
|
+
const config = this.pubstackConfig;
|
|
40
|
+
return config
|
|
41
|
+
? [
|
|
42
|
+
mkConfigureStep('pubstack-configure', ctx => {
|
|
43
|
+
if (ctx.env__ === 'test') {
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
}
|
|
46
|
+
const validABTestValues = ['0', '1', '2', '3'];
|
|
47
|
+
const meta = ctx.window__.document.head.querySelector('meta[name="pbstck_context:pbstck_ab_test"]');
|
|
48
|
+
if (meta && meta.content && validABTestValues.includes(meta.content)) {
|
|
49
|
+
ctx.window__.googletag.pubads().setTargeting('pbstck_ab_test', meta.content);
|
|
50
|
+
}
|
|
51
|
+
return Promise.resolve();
|
|
52
|
+
})
|
|
53
|
+
]
|
|
54
|
+
: [];
|
|
55
|
+
}
|
|
56
|
+
prepareRequestAdsSteps__() {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const closeButtonDataRef = 'footer-ad-close-button';
|
|
2
|
+
const containerDataRefSelector = '[data-ref=h5-footer-ad-container]';
|
|
3
|
+
const closeButtonDataRefSelector = `[data-ref=${closeButtonDataRef}]`;
|
|
4
|
+
const css = {
|
|
5
|
+
adSlot: 'h5-footer-ad',
|
|
6
|
+
container: 'h5-footer-ad-container',
|
|
7
|
+
footerAdClose: 'h5-footer-ad-close',
|
|
8
|
+
isShiftedBottom: 'is-shifted-bottom'
|
|
9
|
+
};
|
|
10
|
+
const renderFooterAd = (window, floorAdDomId, disallowedAdvertiserIds, log) => (event) => {
|
|
11
|
+
const slot = event.slot;
|
|
12
|
+
const footerAdContainerElement = window.document.body.querySelector(containerDataRefSelector);
|
|
13
|
+
const footerAdElement = window.document.getElementById(floorAdDomId);
|
|
14
|
+
const removeFooterAd = () => {
|
|
15
|
+
if (slot.getSlotElementId() === floorAdDomId && footerAdContainerElement) {
|
|
16
|
+
window.googletag?.destroySlots([slot]);
|
|
17
|
+
footerAdContainerElement.remove();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
if (!footerAdContainerElement ||
|
|
21
|
+
!footerAdElement ||
|
|
22
|
+
slot.getSlotElementId() !== floorAdDomId ||
|
|
23
|
+
event.isEmpty ||
|
|
24
|
+
(!!event.advertiserId && disallowedAdvertiserIds.includes(event.advertiserId)) ||
|
|
25
|
+
window.matchMedia('(max-width: 767px)').matches) {
|
|
26
|
+
log.debug('[footer-ad]', 'remove footer ad container');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (window.document.body.querySelector(closeButtonDataRefSelector)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const footerAdElementClose = document.createElement('button');
|
|
33
|
+
footerAdElementClose.classList.add(css.footerAdClose);
|
|
34
|
+
footerAdElementClose.setAttribute('aria-label', 'Anzeige entfernen');
|
|
35
|
+
footerAdElementClose.setAttribute('data-ref', closeButtonDataRef);
|
|
36
|
+
footerAdElement.classList.add(css.adSlot);
|
|
37
|
+
if (Array.isArray(event.size) &&
|
|
38
|
+
event.size[1] > 200 &&
|
|
39
|
+
window.matchMedia('(max-height: 800px)').matches) {
|
|
40
|
+
footerAdElement.classList.add(css.isShiftedBottom);
|
|
41
|
+
}
|
|
42
|
+
footerAdContainerElement.classList.add(css.container);
|
|
43
|
+
footerAdContainerElement.style.setProperty('display', 'block');
|
|
44
|
+
footerAdContainerElement.appendChild(footerAdElementClose);
|
|
45
|
+
footerAdElementClose.addEventListener('click', () => removeFooterAd());
|
|
46
|
+
};
|
|
47
|
+
export const setupFooterAdListener = (window, env, log, floorAdDomId, disallowedAdvertiserIds) => {
|
|
48
|
+
if (env === 'production') {
|
|
49
|
+
window.googletag
|
|
50
|
+
.pubads()
|
|
51
|
+
.addEventListener('slotRenderEnded', renderFooterAd(window, floorAdDomId, disallowedAdvertiserIds, log));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
renderFooterAd(window, floorAdDomId, disallowedAdvertiserIds, log)({
|
|
55
|
+
advertiserId: 1,
|
|
56
|
+
size: [728, 90],
|
|
57
|
+
isEmpty: false,
|
|
58
|
+
slot: {
|
|
59
|
+
getSlotElementId: () => floorAdDomId
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { LOW_PRIORITY, mkPrepareRequestAdsStep } from 'ad-tag/ads/adPipeline';
|
|
2
|
+
import { setupFooterAdListener } from './desktopFloorAd';
|
|
3
|
+
import { initMobileAdSticky } from './mobileSticky';
|
|
4
|
+
export class StickyFooterAd {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'sticky-footer-ads';
|
|
7
|
+
this.description = 'sticky footer ad creatives';
|
|
8
|
+
this.moduleType = 'creatives';
|
|
9
|
+
this.stickyFooterAdConfig = null;
|
|
10
|
+
}
|
|
11
|
+
config__() {
|
|
12
|
+
return this.stickyFooterAdConfig;
|
|
13
|
+
}
|
|
14
|
+
configure__(moduleConfig) {
|
|
15
|
+
if (moduleConfig?.stickyFooterAd?.enabled) {
|
|
16
|
+
this.stickyFooterAdConfig = moduleConfig.stickyFooterAd;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
prepareRequestAdsSteps__() {
|
|
20
|
+
const config = this.stickyFooterAdConfig;
|
|
21
|
+
return config
|
|
22
|
+
? [
|
|
23
|
+
mkPrepareRequestAdsStep(this.name, LOW_PRIORITY, (ctx, slots) => {
|
|
24
|
+
if (config.mobileStickyDomId &&
|
|
25
|
+
slots.some(slot => slot.moliSlot.domId === this.stickyFooterAdConfig?.mobileStickyDomId)) {
|
|
26
|
+
initMobileAdSticky(ctx.window__, ctx.env__, ctx.logger__, config.mobileStickyDomId, config.disallowedAdvertiserIds, config.initiallyHidden ?? false);
|
|
27
|
+
}
|
|
28
|
+
if (config.desktopFloorAdDomId &&
|
|
29
|
+
slots.some(slot => slot.moliSlot.domId === config.desktopFloorAdDomId)) {
|
|
30
|
+
setupFooterAdListener(ctx.window__, ctx.env__, ctx.logger__, config.desktopFloorAdDomId, config.disallowedAdvertiserIds);
|
|
31
|
+
}
|
|
32
|
+
return Promise.resolve();
|
|
33
|
+
})
|
|
34
|
+
]
|
|
35
|
+
: [];
|
|
36
|
+
}
|
|
37
|
+
configureSteps__() {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
initSteps__() {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|