@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,159 @@
|
|
|
1
|
+
import { createLabelConfigService } from './labelConfigService';
|
|
2
|
+
import { tcfapi } from '../types/tcfapi';
|
|
3
|
+
import { consentReady } from './consent';
|
|
4
|
+
var TCPurpose = tcfapi.responses.TCPurpose;
|
|
5
|
+
import { generateAdUnitPathVariables } from './adUnitPath';
|
|
6
|
+
import { createAssetLoaderService } from '../util/assetLoaderService';
|
|
7
|
+
import { uuidV4 } from '../util/uuid';
|
|
8
|
+
export const HIGH_PRIORITY = 100;
|
|
9
|
+
export const LOW_PRIORITY = 10;
|
|
10
|
+
export const mkInitStep = (name, fn) => {
|
|
11
|
+
Object.defineProperty(fn, 'name', { value: name });
|
|
12
|
+
return fn;
|
|
13
|
+
};
|
|
14
|
+
export const mkConfigureStep = (name, fn) => {
|
|
15
|
+
Object.defineProperty(fn, 'name', { value: name });
|
|
16
|
+
return fn;
|
|
17
|
+
};
|
|
18
|
+
export const mkConfigureStepOncePerRequestAdsCycle = (name, fn) => {
|
|
19
|
+
Object.defineProperty(fn, 'name', { value: name });
|
|
20
|
+
let currentRequestAdsCalls = 0;
|
|
21
|
+
return mkConfigureStep(name, (context, slots) => {
|
|
22
|
+
if (currentRequestAdsCalls !== context.requestAdsCalls__) {
|
|
23
|
+
currentRequestAdsCalls = context.requestAdsCalls__;
|
|
24
|
+
return fn(context, slots);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return Promise.resolve();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
export const mkConfigureStepOnce = (name, fn) => {
|
|
32
|
+
Object.defineProperty(fn, 'name', { value: name });
|
|
33
|
+
return mkConfigureStep(name, (context, slots) => context.requestAdsCalls__ === 1 && context.requestId__ === 1
|
|
34
|
+
? fn(context, slots)
|
|
35
|
+
: Promise.resolve());
|
|
36
|
+
};
|
|
37
|
+
export const mkPrepareRequestAdsStep = (name, priority, fn) => {
|
|
38
|
+
const step = Object.assign(fn, { priority: priority });
|
|
39
|
+
Object.defineProperty(fn, 'name', { value: name });
|
|
40
|
+
return step;
|
|
41
|
+
};
|
|
42
|
+
export const mkRequestBidsStep = (name, fn) => {
|
|
43
|
+
Object.defineProperty(fn, 'name', { value: name });
|
|
44
|
+
return fn;
|
|
45
|
+
};
|
|
46
|
+
export class AdPipeline {
|
|
47
|
+
constructor(config, logger, window, auction) {
|
|
48
|
+
this.config = config;
|
|
49
|
+
this.logger = logger;
|
|
50
|
+
this.window = window;
|
|
51
|
+
this.auction = auction;
|
|
52
|
+
this.init = null;
|
|
53
|
+
this.tcData = null;
|
|
54
|
+
this.requestId = 0;
|
|
55
|
+
this.runPrepareRequestAds = (context, definedSlots) => {
|
|
56
|
+
const byPriority = new Map();
|
|
57
|
+
this.config.prepareRequestAds.forEach(step => {
|
|
58
|
+
const steps = byPriority.get(step.priority);
|
|
59
|
+
if (steps) {
|
|
60
|
+
steps.push(step);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
byPriority.set(step.priority, [step]);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return (Array.from(byPriority.entries())
|
|
67
|
+
.sort(([prio1], [prio2]) => (prio1 > prio2 ? -1 : 1))
|
|
68
|
+
.reduce((prevSteps, [priority, steps]) => {
|
|
69
|
+
return prevSteps.then(() => {
|
|
70
|
+
context.logger__.debug('AdPipeline', context.requestId__, `run prepareRequestAds with priority ${priority}`);
|
|
71
|
+
return Promise.all(steps.map(step => step(context, definedSlots)));
|
|
72
|
+
});
|
|
73
|
+
}, Promise.resolve(undefined)));
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
run(slots, config, runtimeConfig, requestAdsCalls, options) {
|
|
77
|
+
this.requestId = this.requestId + 1;
|
|
78
|
+
const currentRequestId = this.requestId;
|
|
79
|
+
const auctionId = uuidV4(this.window);
|
|
80
|
+
this.logger.debug('AdPipeline', `starting run with requestId ${currentRequestId} on ${requestAdsCalls}. call. AuctionId ${auctionId}`, slots);
|
|
81
|
+
const consentConfig = config.consent || {};
|
|
82
|
+
this.tcData = this.tcData
|
|
83
|
+
? this.tcData
|
|
84
|
+
: consentReady(consentConfig, this.window, this.logger, runtimeConfig.environment);
|
|
85
|
+
return this.tcData.then(consentData => {
|
|
86
|
+
const extraLabels = [...(config.targeting?.labels || []), ...runtimeConfig.labels];
|
|
87
|
+
if (consentData.gdprApplies &&
|
|
88
|
+
consentData.purpose.consents[TCPurpose.STORE_INFORMATION_ON_DEVICE]) {
|
|
89
|
+
extraLabels.push('purpose-1');
|
|
90
|
+
}
|
|
91
|
+
else if (!consentData.gdprApplies) {
|
|
92
|
+
extraLabels.push('purpose-1');
|
|
93
|
+
}
|
|
94
|
+
const labelConfigService = createLabelConfigService(config.labelSizeConfig || [], extraLabels, this.window);
|
|
95
|
+
const bucketName = options?.bucketName;
|
|
96
|
+
const bucketConfig = bucketName && config.buckets?.bucket && config.buckets.bucket[bucketName]
|
|
97
|
+
? config.buckets.bucket[bucketName]
|
|
98
|
+
: null;
|
|
99
|
+
const defaultAdUnitPathVariables = generateAdUnitPathVariables(this.window.location.hostname, labelConfigService.getDeviceLabel(), config.targeting?.adUnitPathVariables, config.domain);
|
|
100
|
+
const adUnitPathVariables = {
|
|
101
|
+
...defaultAdUnitPathVariables,
|
|
102
|
+
...runtimeConfig.adUnitPathVariables
|
|
103
|
+
};
|
|
104
|
+
const context = {
|
|
105
|
+
auctionId__: auctionId,
|
|
106
|
+
requestId__: currentRequestId,
|
|
107
|
+
requestAdsCalls__: requestAdsCalls,
|
|
108
|
+
logger__: this.logger,
|
|
109
|
+
env__: runtimeConfig.environment || 'production',
|
|
110
|
+
config__: config,
|
|
111
|
+
runtimeConfig__: runtimeConfig,
|
|
112
|
+
labelConfigService__: labelConfigService,
|
|
113
|
+
window__: this.window,
|
|
114
|
+
tcData__: consentData,
|
|
115
|
+
bucket__: bucketConfig,
|
|
116
|
+
adUnitPathVariables__: adUnitPathVariables,
|
|
117
|
+
auction__: this.auction,
|
|
118
|
+
assetLoaderService__: createAssetLoaderService(this.window),
|
|
119
|
+
options__: options
|
|
120
|
+
};
|
|
121
|
+
this.init = this.init
|
|
122
|
+
? this.init
|
|
123
|
+
: this.logStage('init', currentRequestId).then(() => Promise.all(this.config.init.map(step => step(context))));
|
|
124
|
+
const REJECTED_NO_SLOTS_AFTER_FILTERING = 'rejected-no-slots-after-filtering';
|
|
125
|
+
return this.init
|
|
126
|
+
.then(() => this.logStage('configure', currentRequestId).then(() => Promise.all(this.config.configure.map(step => step(context, slots)))))
|
|
127
|
+
.then(() => this.logStage('defineSlots', currentRequestId).then(() => this.config.defineSlots(context, slots)))
|
|
128
|
+
.then(definedSlots => {
|
|
129
|
+
if (!definedSlots.length) {
|
|
130
|
+
return Promise.reject(REJECTED_NO_SLOTS_AFTER_FILTERING);
|
|
131
|
+
}
|
|
132
|
+
return (this.logStage('prepareRequestAds', currentRequestId)
|
|
133
|
+
.then(() => this.runPrepareRequestAds(context, definedSlots))
|
|
134
|
+
.then(() => this.logStage('requestBids', currentRequestId))
|
|
135
|
+
.then(() => Promise.all(this.config.requestBids.map(step => step(context, definedSlots))))
|
|
136
|
+
.then(() => this.logStage('requestAds', currentRequestId))
|
|
137
|
+
.then(() => this.config.requestAds(context, definedSlots)));
|
|
138
|
+
})
|
|
139
|
+
.catch(error => {
|
|
140
|
+
switch (error) {
|
|
141
|
+
case REJECTED_NO_SLOTS_AFTER_FILTERING:
|
|
142
|
+
return Promise.resolve();
|
|
143
|
+
default:
|
|
144
|
+
this.logger.error('AdPipeline', 'running ad pipeline failed with error', error);
|
|
145
|
+
return Promise.reject(error);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
getAuction() {
|
|
151
|
+
return this.auction;
|
|
152
|
+
}
|
|
153
|
+
logStage(stageName, requestId) {
|
|
154
|
+
return new Promise(resolve => {
|
|
155
|
+
this.logger.debug('AdPipeline', requestId, `stage: ${stageName}`);
|
|
156
|
+
resolve();
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { getDefaultLogger, getLogger, ProxyLogger } from '../util/logging';
|
|
2
|
+
import { AdPipeline } from './adPipeline';
|
|
3
|
+
import { gptConfigure, gptConsentKeyValue, gptDefineSlots, gptDestroyAdSlots, gptInit, gptLDeviceLabelKeyValue, gptRequestAds, gptResetTargeting } from './googleAdManager';
|
|
4
|
+
import domready from '../util/domready';
|
|
5
|
+
import { prebidClearAuction, prebidConfigure, prebidDefineSlots, prebidInit, prebidPrepareRequestAds, prebidRemoveAdUnits, prebidRenderAds, prebidRequestBids } from './prebid';
|
|
6
|
+
import { a9Configure, a9Init, a9RequestBids, a9ClearTargetingStep, a9PublisherAudiences } from './a9';
|
|
7
|
+
import { flatten, isNotNull } from '../util/arrayUtils';
|
|
8
|
+
import { executeDebugDelay, getDebugDelayFromLocalStorage } from '../util/debugDelay';
|
|
9
|
+
import { createGlobalAuctionContext } from './globalAuctionContext';
|
|
10
|
+
import { getDeviceLabel } from 'ad-tag/ads/labelConfigService';
|
|
11
|
+
import { bridgeInitStep } from 'ad-tag/ads/bridge/bridge';
|
|
12
|
+
const isManualSlot = (slot) => {
|
|
13
|
+
return slot.behaviour.loaded === 'manual';
|
|
14
|
+
};
|
|
15
|
+
const isInfiniteSlot = (slot) => {
|
|
16
|
+
return slot.behaviour.loaded === 'infinite';
|
|
17
|
+
};
|
|
18
|
+
const isBackfillSlot = (slot) => {
|
|
19
|
+
return slot.behaviour.loaded === 'backfill';
|
|
20
|
+
};
|
|
21
|
+
const isSlotAvailable = (_window) => (slot) => {
|
|
22
|
+
return (!!_window.document.getElementById(slot.domId) ||
|
|
23
|
+
slot.position === 'interstitial' ||
|
|
24
|
+
slot.position === 'out-of-page' ||
|
|
25
|
+
slot.position === 'out-of-page-interstitial' ||
|
|
26
|
+
slot.position === 'out-of-page-top-anchor' ||
|
|
27
|
+
slot.position === 'out-of-page-bottom-anchor');
|
|
28
|
+
};
|
|
29
|
+
const getBucketName = (bucket, device) => {
|
|
30
|
+
if (!bucket) {
|
|
31
|
+
return 'default';
|
|
32
|
+
}
|
|
33
|
+
if (typeof bucket === 'string') {
|
|
34
|
+
return bucket;
|
|
35
|
+
}
|
|
36
|
+
return bucket[device] || 'default';
|
|
37
|
+
};
|
|
38
|
+
const slotsInBucket = (_window, config, device, bucket, options) => {
|
|
39
|
+
const { loaded } = { ...{ loaded: 'manual' }, ...options };
|
|
40
|
+
return (config.slots
|
|
41
|
+
.filter(isSlotAvailable(_window))
|
|
42
|
+
.filter(slot => {
|
|
43
|
+
const slotBucket = getBucketName(slot.behaviour.bucket, device);
|
|
44
|
+
return (slotBucket === bucket &&
|
|
45
|
+
(slot.behaviour.loaded === loaded || slot.behaviour.loaded === 'infinite'));
|
|
46
|
+
})
|
|
47
|
+
.map(slot => (options?.sizesOverride ? { ...slot, sizes: options.sizesOverride } : slot)));
|
|
48
|
+
};
|
|
49
|
+
export class AdService {
|
|
50
|
+
static getEnvironment(config) {
|
|
51
|
+
return config.environment || 'production';
|
|
52
|
+
}
|
|
53
|
+
constructor(assetService, eventService, window, adPipelineConfig) {
|
|
54
|
+
this.assetService = assetService;
|
|
55
|
+
this.eventService = eventService;
|
|
56
|
+
this.window = window;
|
|
57
|
+
this.adPipelineConfig = adPipelineConfig;
|
|
58
|
+
this.requestAdsCalls = 0;
|
|
59
|
+
this.adPipeline = new AdPipeline({
|
|
60
|
+
init: [() => Promise.reject('AdPipeline not initialized yet')],
|
|
61
|
+
configure: [],
|
|
62
|
+
defineSlots: () => Promise.resolve([]),
|
|
63
|
+
prepareRequestAds: [],
|
|
64
|
+
requestBids: [],
|
|
65
|
+
requestAds: () => Promise.resolve()
|
|
66
|
+
}, getDefaultLogger(), this.window, createGlobalAuctionContext(this.window, getDefaultLogger(), this.eventService));
|
|
67
|
+
this.initialize = (config, runtimeConfig) => {
|
|
68
|
+
const env = AdService.getEnvironment(runtimeConfig);
|
|
69
|
+
const adServer = config.adServer || 'gam';
|
|
70
|
+
const isGam = adServer === 'gam';
|
|
71
|
+
const isSinglePageApp = config.spa?.enabled === true;
|
|
72
|
+
this.logger.setLogger(getLogger(runtimeConfig, this.window));
|
|
73
|
+
this.logger.debug('AdService', `Initializing with environment ${env} and ad server ${adServer}`);
|
|
74
|
+
const init = isGam ? [gptInit(), bridgeInitStep()] : [];
|
|
75
|
+
const configure = isGam ? [gptConfigure()] : [];
|
|
76
|
+
if (isGam && isSinglePageApp) {
|
|
77
|
+
configure.push(gptDestroyAdSlots(), gptResetTargeting());
|
|
78
|
+
}
|
|
79
|
+
const prepareRequestAds = [];
|
|
80
|
+
if (isGam) {
|
|
81
|
+
prepareRequestAds.push(gptLDeviceLabelKeyValue(), gptConsentKeyValue());
|
|
82
|
+
}
|
|
83
|
+
const requestBids = [];
|
|
84
|
+
if (config.prebid && env === 'production') {
|
|
85
|
+
init.push(prebidInit(this.assetService));
|
|
86
|
+
configure.push(prebidConfigure(config.prebid, config.schain));
|
|
87
|
+
if (isSinglePageApp) {
|
|
88
|
+
configure.push(prebidRemoveAdUnits(config.prebid));
|
|
89
|
+
if (config.prebid.clearAllAuctions) {
|
|
90
|
+
configure.push(prebidClearAuction());
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
prepareRequestAds.push(prebidPrepareRequestAds(config.prebid));
|
|
94
|
+
requestBids.push(prebidRequestBids(config.prebid, adServer));
|
|
95
|
+
}
|
|
96
|
+
if (config.a9 && env === 'production' && isGam) {
|
|
97
|
+
init.push(a9Init(config.a9, this.assetService));
|
|
98
|
+
configure.push(a9Configure(config.a9, config.schain));
|
|
99
|
+
configure.push(a9PublisherAudiences(config.a9));
|
|
100
|
+
prepareRequestAds.push(a9ClearTargetingStep());
|
|
101
|
+
requestBids.push(a9RequestBids(config.a9));
|
|
102
|
+
}
|
|
103
|
+
const globalAuctionContext = createGlobalAuctionContext(this.window, this.logger, this.eventService, config.globalAuctionContext);
|
|
104
|
+
configure.push(globalAuctionContext.configureStep());
|
|
105
|
+
init.push(...runtimeConfig.adPipelineConfig.initSteps);
|
|
106
|
+
configure.push(...runtimeConfig.adPipelineConfig.configureSteps);
|
|
107
|
+
prepareRequestAds.push(...runtimeConfig.adPipelineConfig.prepareRequestAdsSteps);
|
|
108
|
+
requestBids.push(...runtimeConfig.adPipelineConfig.requestBidsSteps);
|
|
109
|
+
if (env === 'test') {
|
|
110
|
+
const debugDelay = getDebugDelayFromLocalStorage(this.window);
|
|
111
|
+
if (debugDelay) {
|
|
112
|
+
configure.push(() => executeDebugDelay(this.window, debugDelay));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
this.adPipeline = new AdPipeline({
|
|
116
|
+
init,
|
|
117
|
+
configure,
|
|
118
|
+
defineSlots: isGam ? gptDefineSlots() : prebidDefineSlots(),
|
|
119
|
+
prepareRequestAds,
|
|
120
|
+
requestBids,
|
|
121
|
+
requestAds: isGam ? gptRequestAds() : prebidRenderAds()
|
|
122
|
+
}, this.logger, this.window, globalAuctionContext);
|
|
123
|
+
return new Promise(resolve => {
|
|
124
|
+
domready(this.window, () => {
|
|
125
|
+
this.logger.debug('DOM', 'dom ready');
|
|
126
|
+
resolve(config);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
this.requestAds = async (config, runtimeConfig) => {
|
|
131
|
+
this.requestAdsCalls = this.requestAdsCalls + 1;
|
|
132
|
+
const { refreshSlots, refreshInfiniteSlots, refreshBuckets } = runtimeConfig;
|
|
133
|
+
const device = getDeviceLabel(this.window, runtimeConfig, config.labelSizeConfig, config.targeting);
|
|
134
|
+
const refreshSlotsFromBuckets = refreshBuckets.flatMap(bucket => slotsInBucket(this.window, config, device, bucket.bucket, bucket.options).map(slot => slot.domId));
|
|
135
|
+
this.logger.info('AdService', `RequestAds[${this.requestAdsCalls}]`, refreshSlots, refreshBuckets);
|
|
136
|
+
this.eventService.emit('beforeRequestAds', { runtimeConfig: runtimeConfig });
|
|
137
|
+
try {
|
|
138
|
+
const immediatelyLoadedSlots = config.slots
|
|
139
|
+
.map(slot => {
|
|
140
|
+
if (isManualSlot(slot)) {
|
|
141
|
+
return refreshSlots.includes(slot.domId) || refreshSlotsFromBuckets.includes(slot.domId)
|
|
142
|
+
? slot
|
|
143
|
+
: null;
|
|
144
|
+
}
|
|
145
|
+
else if (isInfiniteSlot(slot)) {
|
|
146
|
+
return refreshInfiniteSlots.some(infiniteSlot => infiniteSlot.artificialDomId === slot.domId)
|
|
147
|
+
? slot
|
|
148
|
+
: null;
|
|
149
|
+
}
|
|
150
|
+
else if (isBackfillSlot(slot)) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
return slot;
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
.filter(isNotNull)
|
|
158
|
+
.filter(isSlotAvailable(this.window));
|
|
159
|
+
if (config.buckets?.enabled) {
|
|
160
|
+
const buckets = new Map();
|
|
161
|
+
immediatelyLoadedSlots.forEach(slot => {
|
|
162
|
+
const bucket = getBucketName(slot.behaviour.bucket, device);
|
|
163
|
+
const slots = buckets.get(bucket);
|
|
164
|
+
if (slots) {
|
|
165
|
+
slots.push(slot);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
buckets.set(bucket, [slot]);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
const result = buckets.size === 0
|
|
172
|
+
? this.adPipeline.run([], config, runtimeConfig, this.requestAdsCalls).then(() => [])
|
|
173
|
+
: Promise.all(Array.from(buckets.entries()).map(([bucketId, bucketSlots]) => {
|
|
174
|
+
this.logger.debug('AdPipeline', `running bucket ${bucketId}, slots:`, bucketSlots);
|
|
175
|
+
return this.adPipeline
|
|
176
|
+
.run(bucketSlots, config, runtimeConfig, this.requestAdsCalls)
|
|
177
|
+
.then(() => bucketSlots);
|
|
178
|
+
}));
|
|
179
|
+
this.eventService.emit('afterRequestAds', { state: 'finished' });
|
|
180
|
+
return result.then(slots => flatten(slots));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
await this.adPipeline.run(immediatelyLoadedSlots, config, runtimeConfig, this.requestAdsCalls);
|
|
184
|
+
this.eventService.emit('afterRequestAds', { state: 'finished' });
|
|
185
|
+
return immediatelyLoadedSlots;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
this.logger.error('AdPipeline', 'slot filtering failed', e);
|
|
190
|
+
this.eventService.emit('afterRequestAds', { state: 'error' });
|
|
191
|
+
return Promise.reject(e);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
this.getAdPipeline = () => {
|
|
195
|
+
return this.adPipeline;
|
|
196
|
+
};
|
|
197
|
+
this.setLogger = (logger) => {
|
|
198
|
+
this.logger.setLogger(logger);
|
|
199
|
+
};
|
|
200
|
+
this.logger = new ProxyLogger(getDefaultLogger());
|
|
201
|
+
if (adPipelineConfig) {
|
|
202
|
+
this.adPipeline = new AdPipeline(adPipelineConfig, this.logger, window, createGlobalAuctionContext(window, this.logger, this.eventService));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
refreshAdSlots(domIds, config, runtimeConfig, options) {
|
|
206
|
+
if (domIds.length === 0) {
|
|
207
|
+
return Promise.resolve();
|
|
208
|
+
}
|
|
209
|
+
const { loaded } = { ...{ loaded: 'manual' }, ...options };
|
|
210
|
+
const allowedLoadingBehaviours = new Set(['infinite']);
|
|
211
|
+
if (loaded === 'eager') {
|
|
212
|
+
allowedLoadingBehaviours.add('manual');
|
|
213
|
+
allowedLoadingBehaviours.add('eager');
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
allowedLoadingBehaviours.add(loaded);
|
|
217
|
+
}
|
|
218
|
+
const availableSlots = config.slots
|
|
219
|
+
.filter(slot => domIds.some(domId => domId === slot.domId) &&
|
|
220
|
+
allowedLoadingBehaviours.has(slot.behaviour.loaded))
|
|
221
|
+
.filter(isSlotAvailable(this.window))
|
|
222
|
+
.map(slot => (options?.sizesOverride ? { ...slot, sizes: options.sizesOverride } : slot));
|
|
223
|
+
if (domIds.length !== availableSlots.length) {
|
|
224
|
+
const slotsInConfigOnly = availableSlots.filter(slot => domIds.every(domId => domId !== slot.domId));
|
|
225
|
+
const slotsOnPageDomOnly = domIds.filter(domId => availableSlots.every(slot => slot.domId !== domId));
|
|
226
|
+
if (slotsInConfigOnly.length) {
|
|
227
|
+
this.logger.warn('AdService', 'The following slots do not exist on the page DOM.', slotsInConfigOnly);
|
|
228
|
+
}
|
|
229
|
+
if (slotsOnPageDomOnly.length) {
|
|
230
|
+
this.logger.warn('AdService', 'The following slots are not configured in the ad-tag config.', slotsInConfigOnly);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
this.logger.debug('AdService', 'refresh ad slots', availableSlots);
|
|
234
|
+
return this.adPipeline.run(availableSlots, config, runtimeConfig, this.requestAdsCalls, {
|
|
235
|
+
options
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
refreshBucket(bucket, config, runtimeConfig, options) {
|
|
239
|
+
if (!config.buckets?.enabled) {
|
|
240
|
+
return Promise.resolve();
|
|
241
|
+
}
|
|
242
|
+
const device = getDeviceLabel(this.window, runtimeConfig, config.labelSizeConfig, config.targeting);
|
|
243
|
+
const availableSlotsInBucket = slotsInBucket(this.window, config, device, bucket, options);
|
|
244
|
+
if (availableSlotsInBucket.length === 0) {
|
|
245
|
+
this.logger.warn('AdService', 'No slots found in bucket', bucket);
|
|
246
|
+
return Promise.resolve();
|
|
247
|
+
}
|
|
248
|
+
this.logger.debug('AdService', 'refresh ad buckets', availableSlotsInBucket, config.targeting);
|
|
249
|
+
return this.adPipeline.run(availableSlotsInBucket, config, runtimeConfig, this.requestAdsCalls, { bucketName: bucket, options: options });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { extractTopPrivateDomainFromHostname } from '../util/extractTopPrivateDomainFromHostname';
|
|
2
|
+
export const removeChildId = (adUnitPath) => {
|
|
3
|
+
const [_, networkIds, ...rest] = adUnitPath.split('/');
|
|
4
|
+
const [parentNetworkId, ...childIds] = networkIds.split(',');
|
|
5
|
+
return childIds.length === 0 ? adUnitPath : ['', parentNetworkId, ...rest].join('/');
|
|
6
|
+
};
|
|
7
|
+
export const resolveAdUnitPath = (adUnitPath, adUnitPathVariables) => {
|
|
8
|
+
const paramsPattern = /[^{]+(?=})/g;
|
|
9
|
+
const validCharactersPattern = /([A-Za-z0-9\_]+)/g;
|
|
10
|
+
const extractedParams = adUnitPath.match(paramsPattern);
|
|
11
|
+
if (!adUnitPathVariables || !extractedParams) {
|
|
12
|
+
return adUnitPath;
|
|
13
|
+
}
|
|
14
|
+
extractedParams.forEach(param => {
|
|
15
|
+
const validParam = param.match(validCharactersPattern);
|
|
16
|
+
if (!validParam) {
|
|
17
|
+
throw new SyntaxError(`invalid variable "${param}" in path`);
|
|
18
|
+
}
|
|
19
|
+
if (!adUnitPathVariables[param]) {
|
|
20
|
+
throw new ReferenceError(`path variable "${param}" is not defined`);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return adUnitPath.replace(new RegExp(Object.keys(adUnitPathVariables)
|
|
24
|
+
.map(key => `{${key}}`)
|
|
25
|
+
.join('|'), 'g'), match => adUnitPathVariables[match.substr(1, match.length - 2)]);
|
|
26
|
+
};
|
|
27
|
+
export const withDepth = (adUnitPath, depth) => {
|
|
28
|
+
const adUnitPathSegments = adUnitPath.split('/');
|
|
29
|
+
return adUnitPathSegments.slice(0, depth + 1).join('/');
|
|
30
|
+
};
|
|
31
|
+
export const generateAdUnitPathVariables = (hostname, device, varsFromConfig, domainFromConfig) => ({
|
|
32
|
+
...{
|
|
33
|
+
device: device,
|
|
34
|
+
domain: domainFromConfig || extractTopPrivateDomainFromHostname(hostname) || 'unknown'
|
|
35
|
+
},
|
|
36
|
+
...varsFromConfig
|
|
37
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const createAdRequestThrottling = (config, _window) => {
|
|
2
|
+
const slotsRequested = new Set();
|
|
3
|
+
function onSlotRequested(event) {
|
|
4
|
+
const slotId = event.slot.getSlotElementId();
|
|
5
|
+
const shouldAddSlot = !config.includedDomIds ||
|
|
6
|
+
config.includedDomIds.length === 0 ||
|
|
7
|
+
config.includedDomIds.includes(slotId);
|
|
8
|
+
if (shouldAddSlot) {
|
|
9
|
+
slotsRequested.add(slotId);
|
|
10
|
+
_window.setTimeout(() => {
|
|
11
|
+
slotsRequested.delete(slotId);
|
|
12
|
+
}, config.throttle * 1000);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function isThrottled(slotId) {
|
|
16
|
+
return slotsRequested.has(slotId);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
onSlotRequested,
|
|
20
|
+
isThrottled
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const createBiddersDisabling = (config, window, logger) => {
|
|
2
|
+
const participationInfo = new Map();
|
|
3
|
+
logger?.info(`Bidders disabling feature is ${config.enabled ? 'enabled' : 'disabled'}`);
|
|
4
|
+
const isBidderDisabled = (domId, bidderCode) => {
|
|
5
|
+
if (config.excludedPositions?.includes(domId)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
return participationInfo.get(domId)?.get(bidderCode)?.disabled ?? false;
|
|
9
|
+
};
|
|
10
|
+
const onAuctionEnd = (auction) => {
|
|
11
|
+
auction.bidderRequests?.forEach(bidderRequest => {
|
|
12
|
+
bidderRequest?.bids?.forEach(bid => {
|
|
13
|
+
const bidderCode = bid.bidder;
|
|
14
|
+
const adUnitCode = bid.adUnitCode;
|
|
15
|
+
if (!participationInfo.get(adUnitCode)) {
|
|
16
|
+
participationInfo.set(adUnitCode, new Map());
|
|
17
|
+
}
|
|
18
|
+
const bidderState = participationInfo.get(adUnitCode)?.get(bidderCode);
|
|
19
|
+
if (bidderState) {
|
|
20
|
+
const newBidRequestCount = bidderState.bidRequestCount + 1;
|
|
21
|
+
participationInfo.get(adUnitCode)?.set(bidderCode, {
|
|
22
|
+
...bidderState,
|
|
23
|
+
bidRequestCount: newBidRequestCount
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
participationInfo.get(adUnitCode)?.set(bidderCode, {
|
|
28
|
+
disabled: false,
|
|
29
|
+
bidRequestCount: 1,
|
|
30
|
+
bidReceivedCount: 0
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
auction.bidsReceived?.forEach(bidReceived => {
|
|
36
|
+
const bidderForPosition = bidReceived.bidderCode;
|
|
37
|
+
const position = bidReceived.adUnitCode;
|
|
38
|
+
const bidderState = participationInfo.get(position)?.get(bidderForPosition);
|
|
39
|
+
if (bidderState) {
|
|
40
|
+
participationInfo.get(position)?.set(bidderForPosition, {
|
|
41
|
+
...bidderState,
|
|
42
|
+
bidReceivedCount: bidderState.bidReceivedCount + 1
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
participationInfo.get(position)?.set(bidderForPosition, {
|
|
47
|
+
disabled: false,
|
|
48
|
+
bidRequestCount: 1,
|
|
49
|
+
bidReceivedCount: 1
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
deactivateBidderForTTL();
|
|
54
|
+
};
|
|
55
|
+
const deactivateBidderForTTL = () => {
|
|
56
|
+
participationInfo.forEach((bidders, position) => {
|
|
57
|
+
bidders.forEach((bidderState, bidderCode) => {
|
|
58
|
+
if (shouldDisableBidder(bidderState)) {
|
|
59
|
+
disableBidder(position, bidderCode);
|
|
60
|
+
window.setTimeout(() => {
|
|
61
|
+
enableBidder(position, bidderCode);
|
|
62
|
+
}, config.reactivationPeriod);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
const shouldDisableBidder = (bidderState) => {
|
|
68
|
+
return (bidderState.bidRequestCount > config.minBidRequests &&
|
|
69
|
+
bidderState.bidReceivedCount / bidderState.bidRequestCount < config.minRate &&
|
|
70
|
+
!bidderState.disabled);
|
|
71
|
+
};
|
|
72
|
+
const disableBidder = (position, bidderCode) => {
|
|
73
|
+
const bidderState = participationInfo.get(position)?.get(bidderCode);
|
|
74
|
+
if (bidderState) {
|
|
75
|
+
bidderState.disabled = true;
|
|
76
|
+
logger?.info(`Bidder ${bidderCode} for position ${position} is now disabled.`);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const enableBidder = (position, bidderCode) => {
|
|
80
|
+
const bidderState = participationInfo.get(position)?.get(bidderCode);
|
|
81
|
+
if (bidderState) {
|
|
82
|
+
bidderState.disabled = false;
|
|
83
|
+
logger?.info(`Bidder ${bidderCode} for position ${position} is now enabled.`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
isBidderDisabled,
|
|
88
|
+
onAuctionEnd
|
|
89
|
+
};
|
|
90
|
+
};
|