@highfivve/ad-tag 5.10.4 → 5.11.0
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/lib/ads/auctions/anchorContext.js +50 -0
- package/lib/ads/auctions/waterfallContext.js +77 -0
- package/lib/ads/globalAuctionContext.js +26 -2
- package/lib/ads/googleAdManager.js +31 -1
- package/lib/ads/keyValues.js +2 -0
- package/lib/ads/prebid.js +13 -1
- package/lib/gen/packageJson.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { formatKey } from 'ad-tag/ads/keyValues';
|
|
2
|
+
import { createWaterfallContext, createOnEmptyBidRotationTrigger } from 'ad-tag/ads/auctions/waterfallContext';
|
|
3
|
+
export const isGamAnchor = (slot, window) => {
|
|
4
|
+
const [value] = slot.getTargeting(formatKey);
|
|
5
|
+
return (!!value &&
|
|
6
|
+
(value === window.googletag.enums.OutOfPageFormat.BOTTOM_ANCHOR.toString() ||
|
|
7
|
+
value === window.googletag.enums.OutOfPageFormat.TOP_ANCHOR.toString()));
|
|
8
|
+
};
|
|
9
|
+
const sessionStorageKeys = {
|
|
10
|
+
bottomMobile: 'h5v_anchor_bm',
|
|
11
|
+
bottomDesktop: 'h5v_anchor_bd',
|
|
12
|
+
top: 'h5v_anchor_t'
|
|
13
|
+
};
|
|
14
|
+
export const createAnchorContext = (configs, window__, now, logger) => {
|
|
15
|
+
const bottomMobile = configs.bottomMobile
|
|
16
|
+
? createWaterfallContext(sessionStorageKeys.bottomMobile, configs.bottomMobile, createOnEmptyBidRotationTrigger(), window__, now, logger, 'anchor-bottom-mobile')
|
|
17
|
+
: undefined;
|
|
18
|
+
const bottomDesktop = configs.bottomDesktop
|
|
19
|
+
? createWaterfallContext(sessionStorageKeys.bottomDesktop, configs.bottomDesktop, createOnEmptyBidRotationTrigger(), window__, now, logger, 'anchor-bottom-desktop')
|
|
20
|
+
: undefined;
|
|
21
|
+
const top = configs.top
|
|
22
|
+
? createWaterfallContext(sessionStorageKeys.top, configs.top, createOnEmptyBidRotationTrigger(), window__, now, logger, 'anchor-top')
|
|
23
|
+
: undefined;
|
|
24
|
+
const bottomInstances = [bottomMobile, bottomDesktop].filter((instance) => !!instance);
|
|
25
|
+
const bottomInstanceForDomId = (domId) => {
|
|
26
|
+
if (configs.bottomMobile?.domId === domId) {
|
|
27
|
+
return bottomMobile;
|
|
28
|
+
}
|
|
29
|
+
if (configs.bottomDesktop?.domId === domId) {
|
|
30
|
+
return bottomDesktop;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
anchorBottomChannel: (domId) => bottomInstanceForDomId(domId)?.channel(),
|
|
36
|
+
anchorTopChannel: () => top?.channel(),
|
|
37
|
+
updateAdUnitPaths: (variables) => {
|
|
38
|
+
bottomInstances.forEach(instance => instance.updateAdUnitPaths(variables));
|
|
39
|
+
top?.updateAdUnitPaths(variables);
|
|
40
|
+
},
|
|
41
|
+
onSlotRenderEnded: (event) => {
|
|
42
|
+
bottomInstances.forEach(instance => instance.onSlotRenderEnded(event));
|
|
43
|
+
top?.onSlotRenderEnded(event);
|
|
44
|
+
},
|
|
45
|
+
onAuctionEnd: (event) => {
|
|
46
|
+
bottomInstances.forEach(instance => instance.onAuctionEnd(event));
|
|
47
|
+
top?.onAuctionEnd(event);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { resolveAdUnitPath } from 'ad-tag/ads/adUnitPath';
|
|
2
|
+
export const createOnEmptyBidRotationTrigger = () => ({
|
|
3
|
+
shouldShiftOnSlotRenderEnded: event => event.isEmpty,
|
|
4
|
+
shouldShiftOnAuctionEnd: (event, domId) => {
|
|
5
|
+
const bids = event.bidsReceived?.filter(bid => bid.adUnitCode === domId);
|
|
6
|
+
return (bids?.length ?? 0) === 0;
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
export const createWaterfallContext = (sessionStorageKey, config, rotationTrigger, window__, now, logger, logLabel) => {
|
|
10
|
+
const sessionStorageTimeToLive = config.ttlStorage ?? 30 * 60 * 1000;
|
|
11
|
+
const currentTime = now();
|
|
12
|
+
let resolvedAdUnitPath = config.adUnitPath;
|
|
13
|
+
let currentState = {
|
|
14
|
+
priority: config.priority,
|
|
15
|
+
updatedAt: currentTime
|
|
16
|
+
};
|
|
17
|
+
try {
|
|
18
|
+
const sessionState = window__.sessionStorage.getItem(sessionStorageKey);
|
|
19
|
+
if (sessionState) {
|
|
20
|
+
const parsedState = JSON.parse(sessionState);
|
|
21
|
+
if (currentTime - parsedState.updatedAt < sessionStorageTimeToLive) {
|
|
22
|
+
currentState = parsedState;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
currentState.updatedAt = currentTime;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
logger.error(logLabel, 'failed to load waterfall state from session storage', e);
|
|
31
|
+
}
|
|
32
|
+
if (config.priority.length === 0) {
|
|
33
|
+
logger.error(logLabel, 'no waterfall priority configured');
|
|
34
|
+
}
|
|
35
|
+
const persistState = () => {
|
|
36
|
+
currentState.updatedAt = now();
|
|
37
|
+
try {
|
|
38
|
+
window__.sessionStorage.setItem(sessionStorageKey, JSON.stringify(currentState));
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
logger.error(logLabel, 'failed to persist waterfall state to session storage', e);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const shiftPriority = (arr) => {
|
|
45
|
+
if (arr.length === 0) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return [...arr.slice(1), arr[0]];
|
|
49
|
+
};
|
|
50
|
+
const onSlotRenderEnded = (event) => {
|
|
51
|
+
if (event.slot.getAdUnitPath() !== resolvedAdUnitPath) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (rotationTrigger.shouldShiftOnSlotRenderEnded(event)) {
|
|
55
|
+
currentState.priority = shiftPriority(currentState.priority);
|
|
56
|
+
persistState();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const onAuctionEnd = (event) => {
|
|
60
|
+
if (!event.adUnitCodes.includes(config.domId)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (rotationTrigger.shouldShiftOnAuctionEnd(event, config.domId)) {
|
|
64
|
+
currentState.priority = shiftPriority(currentState.priority);
|
|
65
|
+
persistState();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
state: () => currentState,
|
|
70
|
+
channel: () => currentState.priority[0],
|
|
71
|
+
updateAdUnitPaths: (variables) => {
|
|
72
|
+
resolvedAdUnitPath = resolveAdUnitPath(config.adUnitPath, variables);
|
|
73
|
+
},
|
|
74
|
+
onSlotRenderEnded,
|
|
75
|
+
onAuctionEnd
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -4,6 +4,7 @@ import { createFrequencyCapping } from './auctions/frequencyCapping';
|
|
|
4
4
|
import { createPreviousBidCpms } from './auctions/previousBidCpms';
|
|
5
5
|
import { mkConfigureStep } from './adPipeline';
|
|
6
6
|
import { createInterstitialContext } from 'ad-tag/ads/auctions/interstitialContext';
|
|
7
|
+
import { createAnchorContext } from 'ad-tag/ads/auctions/anchorContext';
|
|
7
8
|
import { createRewardedAdContext } from 'ad-tag/ads/auctions/rewardedAdContext';
|
|
8
9
|
import { createTrackWinningBidder } from 'ad-tag/ads/auctions/trackWinningBidder';
|
|
9
10
|
import { resolveOverridableConfig } from 'ad-tag/ads/configOverrides';
|
|
@@ -25,6 +26,9 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
|
|
|
25
26
|
const frequencyCapConfig = resolveFeature(config.frequencyCap, 'frequencyCap');
|
|
26
27
|
const previousBidCpmsConfig = resolveFeature(config.previousBidCpms, 'previousBidCpms');
|
|
27
28
|
const interstitialConfig = resolveFeature(config.interstitial, 'interstitial');
|
|
29
|
+
const anchorBottomMobileConfig = resolveFeature(config.anchorBottomMobile, 'anchorBottomMobile');
|
|
30
|
+
const anchorBottomDesktopConfig = resolveFeature(config.anchorBottomDesktop, 'anchorBottomDesktop');
|
|
31
|
+
const anchorTopConfig = resolveFeature(config.anchorTop, 'anchorTop');
|
|
28
32
|
const rewardedAdConfig = resolveFeature(config.rewardedAd, 'rewardedAd');
|
|
29
33
|
const trackWinningBidder = trackWinningBidderConfig?.enabled
|
|
30
34
|
? createTrackWinningBidder()
|
|
@@ -42,6 +46,16 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
|
|
|
42
46
|
const interstitial = interstitialConfig?.enabled
|
|
43
47
|
? createInterstitialContext(interstitialConfig, window, window.Date.now, logger)
|
|
44
48
|
: undefined;
|
|
49
|
+
const anchorEnabled = !!anchorBottomMobileConfig?.enabled ||
|
|
50
|
+
!!anchorBottomDesktopConfig?.enabled ||
|
|
51
|
+
!!anchorTopConfig?.enabled;
|
|
52
|
+
const anchor = anchorEnabled
|
|
53
|
+
? createAnchorContext({
|
|
54
|
+
bottomMobile: anchorBottomMobileConfig?.enabled ? anchorBottomMobileConfig : undefined,
|
|
55
|
+
bottomDesktop: anchorBottomDesktopConfig?.enabled ? anchorBottomDesktopConfig : undefined,
|
|
56
|
+
top: anchorTopConfig?.enabled ? anchorTopConfig : undefined
|
|
57
|
+
}, window, window.Date.now, logger)
|
|
58
|
+
: undefined;
|
|
45
59
|
const rewardedAd = rewardedAdConfig?.enabled
|
|
46
60
|
? createRewardedAdContext(rewardedAdConfig, window, logger, assetLoaderService)
|
|
47
61
|
: undefined;
|
|
@@ -51,11 +65,13 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
|
|
|
51
65
|
if (biddersDisablingConfig?.enabled ||
|
|
52
66
|
previousBidCpmsConfig?.enabled ||
|
|
53
67
|
frequencyCapConfig?.enabled ||
|
|
54
|
-
interstitialConfig?.enabled
|
|
68
|
+
interstitialConfig?.enabled ||
|
|
69
|
+
anchorEnabled) {
|
|
55
70
|
window.pbjs.que.push(() => {
|
|
56
71
|
window.pbjs.onEvent('auctionEnd', auction => {
|
|
57
72
|
biddersDisabling?.onAuctionEnd(auction);
|
|
58
73
|
interstitial?.onAuctionEnd(auction);
|
|
74
|
+
anchor?.onAuctionEnd(auction);
|
|
59
75
|
if (previousBidCpmsConfig?.enabled && auction.bidsReceived) {
|
|
60
76
|
previousBidCpms?.onAuctionEnd(auction.bidsReceived);
|
|
61
77
|
}
|
|
@@ -91,11 +107,12 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
|
|
|
91
107
|
frequencyCapping?.afterRequestAds();
|
|
92
108
|
});
|
|
93
109
|
}
|
|
94
|
-
if (frequencyCapConfig?.enabled || interstitialConfig?.enabled) {
|
|
110
|
+
if (frequencyCapConfig?.enabled || interstitialConfig?.enabled || anchorEnabled) {
|
|
95
111
|
window.googletag.cmd.push(() => {
|
|
96
112
|
window.googletag.pubads().addEventListener('slotRenderEnded', event => {
|
|
97
113
|
frequencyCapping?.onSlotRenderEnded(event);
|
|
98
114
|
interstitial?.onSlotRenderEnded(event);
|
|
115
|
+
anchor?.onSlotRenderEnded(event);
|
|
99
116
|
});
|
|
100
117
|
window.googletag.pubads().addEventListener('impressionViewable', event => {
|
|
101
118
|
frequencyCapping?.onImpressionViewable(event);
|
|
@@ -105,6 +122,7 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
|
|
|
105
122
|
const configureStep = mkConfigureStep('GlobalAuctionContext', context => {
|
|
106
123
|
frequencyCapping?.updateAdUnitPaths(context.adUnitPathVariables__);
|
|
107
124
|
interstitial?.updateAdUnitPaths(context.adUnitPathVariables__);
|
|
125
|
+
anchor?.updateAdUnitPaths(context.adUnitPathVariables__);
|
|
108
126
|
rewardedAd?.updateAdUnitPaths(context.adUnitPathVariables__);
|
|
109
127
|
return Promise.resolve();
|
|
110
128
|
});
|
|
@@ -128,6 +146,12 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
|
|
|
128
146
|
interstitialChannel: () => {
|
|
129
147
|
return interstitial?.interstitialChannel();
|
|
130
148
|
},
|
|
149
|
+
anchorBottomChannel: (domId) => {
|
|
150
|
+
return anchor?.anchorBottomChannel(domId);
|
|
151
|
+
},
|
|
152
|
+
anchorTopChannel: () => {
|
|
153
|
+
return anchor?.anchorTopChannel();
|
|
154
|
+
},
|
|
131
155
|
rewardedAd: () => {
|
|
132
156
|
return rewardedAd?.requestRewardedAd() ?? Promise.resolve({ state: 'empty' });
|
|
133
157
|
},
|
|
@@ -5,7 +5,7 @@ import { AssetLoadMethod } from '../util/assetLoaderService';
|
|
|
5
5
|
import { tcfapi } from '../types/tcfapi';
|
|
6
6
|
import { createTestSlots } from '../util/test-slots';
|
|
7
7
|
import { resolveAdUnitPath } from './adUnitPath';
|
|
8
|
-
import { formatKey, CUSTOM_INTERSTITIAL_FORMAT } from './keyValues';
|
|
8
|
+
import { formatKey, CUSTOM_INTERSTITIAL_FORMAT, CUSTOM_ANCHOR_BOTTOM_FORMAT, CUSTOM_ANCHOR_TOP_FORMAT } from './keyValues';
|
|
9
9
|
const testAdSlot = (domId, adUnitPath) => ({
|
|
10
10
|
setCollapseEmptyDiv() {
|
|
11
11
|
return;
|
|
@@ -38,6 +38,11 @@ const testAdSlot = (domId, adUnitPath) => ({
|
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
|
+
const setCustomAnchorFormatTargeting = (adSlot, channel, customFormat) => {
|
|
42
|
+
if (channel === 'c') {
|
|
43
|
+
adSlot.setTargeting(formatKey, customFormat);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
41
46
|
const configureTargeting = (window, runtimeKeyValues, serverSideTargeting) => {
|
|
42
47
|
const staticKeyValues = serverSideTargeting ? serverSideTargeting.keyValues : {};
|
|
43
48
|
const excludes = serverSideTargeting?.adManagerExcludes ?? [];
|
|
@@ -231,6 +236,21 @@ export const gptDefineSlots = () => (context, slots) => {
|
|
|
231
236
|
context.window__.document.body.appendChild(slot);
|
|
232
237
|
}
|
|
233
238
|
};
|
|
239
|
+
const defineAnchorSlot = (channel, outOfPageFormat) => {
|
|
240
|
+
switch (channel) {
|
|
241
|
+
case 'gam':
|
|
242
|
+
return [
|
|
243
|
+
context.window__.googletag.defineOutOfPageSlot(resolvedAdUnitPath, outOfPageFormat),
|
|
244
|
+
outOfPageFormat
|
|
245
|
+
];
|
|
246
|
+
case 'c':
|
|
247
|
+
default:
|
|
248
|
+
return [
|
|
249
|
+
context.window__.googletag.defineSlot(resolvedAdUnitPath, sizes, moliSlot.domId),
|
|
250
|
+
null
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
};
|
|
234
254
|
const defineAdSlot = () => {
|
|
235
255
|
switch (moliSlot.position) {
|
|
236
256
|
case 'in-page':
|
|
@@ -276,6 +296,10 @@ export const gptDefineSlots = () => (context, slots) => {
|
|
|
276
296
|
context.window__.googletag.defineOutOfPageSlot(resolvedAdUnitPath, context.window__.googletag.enums.OutOfPageFormat.TOP_ANCHOR),
|
|
277
297
|
context.window__.googletag.enums.OutOfPageFormat.TOP_ANCHOR
|
|
278
298
|
];
|
|
299
|
+
case 'anchor-bottom':
|
|
300
|
+
return defineAnchorSlot(context.auction__.anchorBottomChannel(moliSlot.domId), context.window__.googletag.enums.OutOfPageFormat.BOTTOM_ANCHOR);
|
|
301
|
+
case 'anchor-top':
|
|
302
|
+
return defineAnchorSlot(context.auction__.anchorTopChannel(), context.window__.googletag.enums.OutOfPageFormat.TOP_ANCHOR);
|
|
279
303
|
}
|
|
280
304
|
};
|
|
281
305
|
const defineAndDisplayAdSlot = () => {
|
|
@@ -292,6 +316,12 @@ export const gptDefineSlots = () => (context, slots) => {
|
|
|
292
316
|
adSlot.setTargeting(formatKey, CUSTOM_INTERSTITIAL_FORMAT);
|
|
293
317
|
}
|
|
294
318
|
}
|
|
319
|
+
else if (moliSlot.position === 'anchor-bottom') {
|
|
320
|
+
setCustomAnchorFormatTargeting(adSlot, context.auction__.anchorBottomChannel(moliSlot.domId), CUSTOM_ANCHOR_BOTTOM_FORMAT);
|
|
321
|
+
}
|
|
322
|
+
else if (moliSlot.position === 'anchor-top') {
|
|
323
|
+
setCustomAnchorFormatTargeting(adSlot, context.auction__.anchorTopChannel(), CUSTOM_ANCHOR_TOP_FORMAT);
|
|
324
|
+
}
|
|
295
325
|
else {
|
|
296
326
|
adSlot.setConfig({ targeting: { [formatKey]: null } });
|
|
297
327
|
}
|
package/lib/ads/keyValues.js
CHANGED
package/lib/ads/prebid.js
CHANGED
|
@@ -8,6 +8,7 @@ import { AssetLoadMethod } from '../util/assetLoaderService';
|
|
|
8
8
|
import { packageJson } from 'ad-tag/gen/packageJson';
|
|
9
9
|
import { prebidOutstreamRenderer } from 'ad-tag/ads/prebid-outstream';
|
|
10
10
|
import { isGamInterstitial } from 'ad-tag/ads/auctions/interstitialContext';
|
|
11
|
+
import { isGamAnchor } from 'ad-tag/ads/auctions/anchorContext';
|
|
11
12
|
import { criteoEnrichWithFpd } from 'ad-tag/ads/criteo';
|
|
12
13
|
import { enrichId5WithFpd } from 'ad-tag/ads/id5';
|
|
13
14
|
const prebidTimeout = (context) => {
|
|
@@ -257,13 +258,24 @@ export const prebidPrepareRequestAds = (prebidConfig) => mkPrepareRequestAdsStep
|
|
|
257
258
|
resolve();
|
|
258
259
|
}
|
|
259
260
|
}));
|
|
261
|
+
const isGamAnchorChannel = ({ moliSlot }, context) => {
|
|
262
|
+
switch (moliSlot.position) {
|
|
263
|
+
case 'anchor-bottom':
|
|
264
|
+
return context.auction__.anchorBottomChannel(moliSlot.domId) === 'gam';
|
|
265
|
+
case 'anchor-top':
|
|
266
|
+
return context.auction__.anchorTopChannel() === 'gam';
|
|
267
|
+
default:
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
260
271
|
export const prebidRequestBids = (prebidConfig, adServer) => mkRequestBidsStep('prebid-request-bids', (context, slots) => {
|
|
261
272
|
const failsafeTimeout = Math.max((prebidConfig.config.bidderTimeout ?? 2000) + 3000, prebidConfig.failsafeTimeout ?? 0);
|
|
262
273
|
const failsafe = new Promise(resolve => context.window__.setTimeout(resolve, failsafeTimeout));
|
|
263
274
|
const auction = new Promise(resolve => {
|
|
264
275
|
const slotsToRefresh = slots.filter(slot => !context.auction__.isSlotThrottled(slot.adSlot) &&
|
|
265
276
|
(!isGamInterstitial(slot.adSlot, context.window__) ||
|
|
266
|
-
context.auction__.interstitialChannel() !== 'gam')
|
|
277
|
+
context.auction__.interstitialChannel() !== 'gam') &&
|
|
278
|
+
(!isGamAnchor(slot.adSlot, context.window__) || !isGamAnchorChannel(slot, context)));
|
|
267
279
|
const requestObject = prebidConfig.ephemeralAdUnits
|
|
268
280
|
? {
|
|
269
281
|
adUnits: createdAdUnits(context, prebidConfig, slotsToRefresh)
|
package/lib/gen/packageJson.js
CHANGED