@highfivve/ad-tag 5.11.4 → 5.11.5
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/bridge/bridge.js +3 -6
- package/lib/ads/findGoogletagSlot.js +9 -0
- package/lib/ads/modules/ad-reload/adVisibilityService.js +9 -5
- package/lib/ads/modules/ad-reload/index.js +5 -2
- package/lib/ads/modules/ad-reload/viewabilityOverride.js +3 -0
- package/lib/ads/modules/cleanup/index.js +37 -3
- package/lib/console/components/moduleConfigs.js +13 -7
- package/lib/gen/packageJson.js +1 -1
- package/package.json +1 -1
package/lib/ads/bridge/bridge.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { mkInitStep } from 'ad-tag/ads/adPipeline';
|
|
2
2
|
import { isPlainObject } from 'ad-tag/util/objectUtils';
|
|
3
|
-
|
|
4
|
-
.pubads()
|
|
5
|
-
.getSlots()
|
|
6
|
-
.find(slot => slot.getSlotElementId() === message.domId || slot.getAdUnitPath() === message.adUnitPath);
|
|
3
|
+
import { findGoogletagSlot } from 'ad-tag/ads/findGoogletagSlot';
|
|
7
4
|
const parseMessageData = (data) => {
|
|
8
5
|
try {
|
|
9
6
|
const message = typeof data === 'string' ? JSON.parse(data) : isPlainObject(data) ? data : null;
|
|
@@ -22,7 +19,7 @@ const parseMessageData = (data) => {
|
|
|
22
19
|
};
|
|
23
20
|
const handleRefresh = (message, context) => {
|
|
24
21
|
const backfillMoliSlot = context.config__.slots.find(slot => slot.domId === message.domId && slot.behaviour.loaded === 'backfill');
|
|
25
|
-
const adSlot =
|
|
22
|
+
const adSlot = findGoogletagSlot(message, context.window__.googletag);
|
|
26
23
|
if (backfillMoliSlot && adSlot) {
|
|
27
24
|
context.logger__.debug('bridge', `Refresh ad slot ${message.domId}`);
|
|
28
25
|
context.window__.googletag.destroySlots([adSlot]);
|
|
@@ -32,7 +29,7 @@ const handleRefresh = (message, context) => {
|
|
|
32
29
|
}
|
|
33
30
|
};
|
|
34
31
|
const handlePassback = (message, context) => {
|
|
35
|
-
const adSlot =
|
|
32
|
+
const adSlot = findGoogletagSlot(message, context.window__.googletag);
|
|
36
33
|
const passbackKey = 'passback';
|
|
37
34
|
if (adSlot && adSlot.getTargeting(passbackKey).length === 0) {
|
|
38
35
|
context.logger__.debug('passback', `Process passback for ad slot ${adSlot.getAdUnitPath()}`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const findGoogletagSlot = (reference, googletag) => googletag
|
|
2
|
+
.pubads()
|
|
3
|
+
.getSlots()
|
|
4
|
+
.find(slot => {
|
|
5
|
+
const slotDomId = slot.getSlotElementId();
|
|
6
|
+
const slotAdUnitPath = slot.getAdUnitPath();
|
|
7
|
+
return ((!!reference.domId && !!slotDomId && slotDomId === reference.domId) ||
|
|
8
|
+
(!!reference.adUnitPath && !!slotAdUnitPath && slotAdUnitPath === reference.adUnitPath));
|
|
9
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isAdvertiserIncluded } from 'ad-tag/ads/isAdvertiserIncluded';
|
|
2
|
+
import { formatKey } from 'ad-tag/ads/keyValues';
|
|
3
|
+
import { asViewabilityOverrideEntryList, resolveViewabilityOverride } from './viewabilityOverride';
|
|
2
4
|
export class AdVisibilityService {
|
|
3
5
|
constructor(userActivityService, refreshInterval, refreshIntervalOverrides, useIntersectionObserver, disableAdVisibilityChecks, viewabilityOverrides, window, logger) {
|
|
4
6
|
this.userActivityService = userActivityService;
|
|
@@ -12,10 +14,10 @@ export class AdVisibilityService {
|
|
|
12
14
|
this.isSlotTracked = (domId) => this.visibilityRecords.has(domId);
|
|
13
15
|
this.removeSlotTracking = (slot) => {
|
|
14
16
|
this.logger?.debug('AdVisibilityService', `removing slot visibility tracking for ${slot.getSlotElementId()}`, slot);
|
|
17
|
+
const record = this.visibilityRecords.get(slot.getSlotElementId());
|
|
15
18
|
this.visibilityRecords.delete(slot.getSlotElementId());
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
this.intersectionObserver.unobserve(observedSlot.target);
|
|
19
|
+
if (this.intersectionObserver && record) {
|
|
20
|
+
this.intersectionObserver.unobserve(record.target);
|
|
19
21
|
}
|
|
20
22
|
};
|
|
21
23
|
this.handleGoogletagAdVisibilityChanged = (event) => {
|
|
@@ -45,7 +47,7 @@ export class AdVisibilityService {
|
|
|
45
47
|
this.minimalAdVisibilityRatio = disableAdVisibilityChecks ? 0 : 0.5;
|
|
46
48
|
this.visibilityRecords = new Map();
|
|
47
49
|
const requiredIntersectionObserver = (useIntersectionObserver ||
|
|
48
|
-
Object.values(viewabilityOverrides).some(entry => entry
|
|
50
|
+
Object.values(viewabilityOverrides).some(entryOrList => asViewabilityOverrideEntryList(entryOrList).some(entry => entry.variant === 'css'))) &&
|
|
49
51
|
'IntersectionObserver' in this.window;
|
|
50
52
|
if (requiredIntersectionObserver) {
|
|
51
53
|
this.intersectionObserver = new this.window.IntersectionObserver(entries => this.handleObservedAdVisibilityChanged(entries), { threshold: this.minimalAdVisibilityRatio });
|
|
@@ -72,6 +74,7 @@ export class AdVisibilityService {
|
|
|
72
74
|
const override = domElement.viewabilityOverride;
|
|
73
75
|
this.visibilityRecords.set(slot.getSlotElementId(), {
|
|
74
76
|
slot: slot,
|
|
77
|
+
target: domElement.target,
|
|
75
78
|
latestStartVisible: this.disableAdVisibilityChecks ||
|
|
76
79
|
(override?.variant === 'disabled' &&
|
|
77
80
|
(override.disableAllAdVisibilityChecks ||
|
|
@@ -153,7 +156,8 @@ export class AdVisibilityService {
|
|
|
153
156
|
}
|
|
154
157
|
observedDomElementForSlot(slot) {
|
|
155
158
|
const slotDomId = slot.getSlotElementId();
|
|
156
|
-
const
|
|
159
|
+
const [liveFormat] = slot.getTargeting(formatKey);
|
|
160
|
+
const viewabilityOverride = resolveViewabilityOverride(asViewabilityOverrideEntryList(this.viewabilityOverrides[slotDomId]), liveFormat);
|
|
157
161
|
const adSlotElement = this.window.document.getElementById(slotDomId);
|
|
158
162
|
const overrideElement = viewabilityOverride && viewabilityOverride.variant === 'css'
|
|
159
163
|
? this.window.document.querySelector(viewabilityOverride.cssSelector)
|
|
@@ -3,6 +3,8 @@ import { UserActivityService } from './userActivityService';
|
|
|
3
3
|
import { mkConfigureStep } from '../../adPipeline';
|
|
4
4
|
import { isNotNull } from 'ad-tag/util/arrayUtils';
|
|
5
5
|
import { isAdvertiserIncluded } from 'ad-tag/ads/isAdvertiserIncluded';
|
|
6
|
+
import { formatKey } from 'ad-tag/ads/keyValues';
|
|
7
|
+
import { asViewabilityOverrideEntryList, resolveViewabilityOverride } from './viewabilityOverride';
|
|
6
8
|
export const createAdReload = () => {
|
|
7
9
|
const name = 'moli-ad-reload';
|
|
8
10
|
const defaultRefreshIntervalMs = 20000;
|
|
@@ -63,8 +65,9 @@ export const createAdReload = () => {
|
|
|
63
65
|
const sizesOverride = maybeOptimizeSlotForCls(config, moliSlot, googleTagSlot, ctx.logger__, ctx.window__);
|
|
64
66
|
googleTagSlot.setTargeting(reloadKeyValue, 'true');
|
|
65
67
|
const getBucketAndLoadingBehaviour = () => {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
+
const [liveFormat] = googleTagSlot.getTargeting(formatKey);
|
|
69
|
+
const matchedOverride = resolveViewabilityOverride(asViewabilityOverrideEntryList(config.viewabilityOverrides?.[slotId]), liveFormat);
|
|
70
|
+
if (matchedOverride?.refreshBucket === true) {
|
|
68
71
|
const loaded = moliSlot.behaviour.loaded;
|
|
69
72
|
const bucket = moliSlot.behaviour.bucket;
|
|
70
73
|
const bucketName = typeof bucket === 'string'
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const asViewabilityOverrideEntryList = (entryOrList) => entryOrList === undefined ? [] : Array.isArray(entryOrList) ? entryOrList : [entryOrList];
|
|
2
|
+
export const isViewabilityOverrideConditionMatch = (conditions, liveFormat) => !conditions || conditions.format === undefined || conditions.format === liveFormat;
|
|
3
|
+
export const resolveViewabilityOverride = (entries, liveFormat) => entries.find(entry => isViewabilityOverrideConditionMatch(entry.conditions, liveFormat));
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { mkPrepareRequestAdsStep, HIGH_PRIORITY, mkConfigureStepOncePerRequestAdsCycle } from 'ad-tag/ads/adPipeline';
|
|
1
|
+
import { mkPrepareRequestAdsStep, HIGH_PRIORITY, mkConfigureStep, mkConfigureStepOncePerRequestAdsCycle } from 'ad-tag/ads/adPipeline';
|
|
2
|
+
import { resolveAdUnitPath } from 'ad-tag/ads/adUnitPath';
|
|
3
|
+
import { findGoogletagSlot } from 'ad-tag/ads/findGoogletagSlot';
|
|
2
4
|
export const createCleanup = () => {
|
|
3
5
|
const name = 'cleanup';
|
|
4
6
|
let cleanupConfig = null;
|
|
@@ -25,7 +27,7 @@ export const createCleanup = () => {
|
|
|
25
27
|
});
|
|
26
28
|
});
|
|
27
29
|
}
|
|
28
|
-
else {
|
|
30
|
+
else if ('jsAsString' in config.deleteMethod) {
|
|
29
31
|
config.deleteMethod.jsAsString.forEach(jsLineAsString => {
|
|
30
32
|
try {
|
|
31
33
|
context.logger__.debug('Cleanup Module', `Try to execute string as JS: '${jsLineAsString}'`);
|
|
@@ -37,9 +39,30 @@ export const createCleanup = () => {
|
|
|
37
39
|
}
|
|
38
40
|
});
|
|
39
41
|
}
|
|
42
|
+
else if ('destroySlot' in config.deleteMethod) {
|
|
43
|
+
let resolvedAdUnitPath;
|
|
44
|
+
try {
|
|
45
|
+
resolvedAdUnitPath = resolveAdUnitPath(config.deleteMethod.adUnitPath, context.adUnitPathVariables__);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
context.logger__.error('Cleanup Module', `failed to resolve adUnitPath '${config.deleteMethod.adUnitPath}' for domId ${config.domId}, skipping`, e);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const googleTagSlot = findGoogletagSlot({ domId: config.domId, adUnitPath: resolvedAdUnitPath }, context.window__.googletag);
|
|
52
|
+
if (googleTagSlot) {
|
|
53
|
+
context.logger__.debug('Cleanup Module', `destroying stale gam slot for domId ${config.domId}`, googleTagSlot);
|
|
54
|
+
context.window__.googletag.destroySlots([googleTagSlot]);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
context.logger__.debug('Cleanup Module', `no gam slot found for domId ${config.domId} / adUnitPath ${resolvedAdUnitPath}, nothing to destroy`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
40
60
|
});
|
|
41
61
|
};
|
|
42
62
|
const hasBidderWonLastAuction = (context, config) => {
|
|
63
|
+
if (!config.bidder) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
43
66
|
const prebidWinningBids = context.window__.pbjs.getAllWinningBids();
|
|
44
67
|
const bidderThatWonLastAuctionOnSlot = prebidWinningBids
|
|
45
68
|
.filter(bid => bid.adUnitCode === config.domId)
|
|
@@ -55,10 +78,20 @@ export const createCleanup = () => {
|
|
|
55
78
|
return Promise.resolve();
|
|
56
79
|
}
|
|
57
80
|
context.window__.pbjs.que.push(() => {
|
|
58
|
-
const configsOfDomIdsThatNeedToBeCleaned = config.configs.filter(config =>
|
|
81
|
+
const configsOfDomIdsThatNeedToBeCleaned = config.configs.filter(config => !('destroySlot' in config.deleteMethod) &&
|
|
82
|
+
hasBidderWonLastAuction(context, config));
|
|
59
83
|
cleanUp(context, configsOfDomIdsThatNeedToBeCleaned);
|
|
60
84
|
});
|
|
61
85
|
return Promise.resolve();
|
|
86
|
+
}),
|
|
87
|
+
mkConfigureStep('destroy-stale-gam-slot-before-redefine', (context, slots) => {
|
|
88
|
+
if (context.runtimeConfig__.environment === 'test') {
|
|
89
|
+
return Promise.resolve();
|
|
90
|
+
}
|
|
91
|
+
const domIdsThisCycle = slots.map(slot => slot.domId);
|
|
92
|
+
const configsToDestroy = config.configs.filter(config => 'destroySlot' in config.deleteMethod && domIdsThisCycle.includes(config.domId));
|
|
93
|
+
cleanUp(context, configsToDestroy);
|
|
94
|
+
return Promise.resolve();
|
|
62
95
|
})
|
|
63
96
|
]
|
|
64
97
|
: [];
|
|
@@ -74,6 +107,7 @@ export const createCleanup = () => {
|
|
|
74
107
|
context.window__.pbjs.que.push(() => {
|
|
75
108
|
const configsOfDomIdsThatNeedToBeCleaned = config.configs
|
|
76
109
|
.filter(config => slots.map(slot => slot.moliSlot.domId).includes(config.domId))
|
|
110
|
+
.filter(config => !('destroySlot' in config.deleteMethod))
|
|
77
111
|
.filter(config => hasBidderWonLastAuction(context, config));
|
|
78
112
|
cleanUp(context, configsOfDomIdsThatNeedToBeCleaned);
|
|
79
113
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { Fragment } from 'react';
|
|
2
2
|
import { Tag, TagLabel } from './tag';
|
|
3
3
|
import { SubHeadline, TagContainer } from './ui';
|
|
4
|
+
import { asViewabilityOverrideEntryList } from 'ad-tag/ads/modules/ad-reload/viewabilityOverride';
|
|
4
5
|
const BoolTag = ({ value, trueLabel, falseLabel }) => (React.createElement(Tag, { variant: value ? 'green' : 'grey' }, value ? (trueLabel ?? 'yes') : (falseLabel ?? 'no')));
|
|
5
6
|
const Row = ({ label, subEntry, children }) => (React.createElement(TagContainer, { subEntry: subEntry },
|
|
6
7
|
React.createElement(TagLabel, null, label),
|
|
@@ -70,12 +71,15 @@ const AdReloadModule = ({ config }) => (React.createElement(React.Fragment, null
|
|
|
70
71
|
"ms")))))))))),
|
|
71
72
|
config.viewabilityOverrides && Object.keys(config.viewabilityOverrides).length > 0 && (React.createElement(React.Fragment, null,
|
|
72
73
|
React.createElement(SubHeadline, null, "Viewability overrides"),
|
|
73
|
-
Object.entries(config.viewabilityOverrides).
|
|
74
|
-
override?.
|
|
74
|
+
Object.entries(config.viewabilityOverrides).flatMap(([domId, entryOrList]) => asViewabilityOverrideEntryList(entryOrList).map((override, index) => (React.createElement(Row, { key: `${domId}-${index}`, label: index === 0 ? domId : '', subEntry: true },
|
|
75
|
+
override.conditions?.format && (React.createElement(Tag, { variant: "blue" },
|
|
76
|
+
"format: ",
|
|
77
|
+
override.conditions.format)),
|
|
78
|
+
override.variant === 'css' && React.createElement(Tag, { variant: "grey" },
|
|
75
79
|
"css: ",
|
|
76
80
|
override.cssSelector),
|
|
77
|
-
override
|
|
78
|
-
override
|
|
81
|
+
override.variant === 'disabled' && React.createElement(Tag, { variant: "yellow" }, "checks disabled"),
|
|
82
|
+
override.refreshBucket && React.createElement(Tag, { variant: "yellow" }, "refreshes bucket")))))))));
|
|
79
83
|
const BlocklistModule = ({ config }) => (React.createElement(React.Fragment, null,
|
|
80
84
|
React.createElement(Row, { label: "Mode" },
|
|
81
85
|
React.createElement(Tag, { variant: config.mode === 'block' ? 'red' : 'yellow' }, config.mode)),
|
|
@@ -95,13 +99,15 @@ const BlocklistModule = ({ config }) => (React.createElement(React.Fragment, nul
|
|
|
95
99
|
const CleanupModule = ({ config }) => (React.createElement(React.Fragment, null,
|
|
96
100
|
config.configs.length === 0 && React.createElement("i", null, "No cleanup configs"),
|
|
97
101
|
config.configs.map((cleanupConfig, index) => (React.createElement(Row, { key: index, label: cleanupConfig.domId },
|
|
98
|
-
React.createElement(Tag, { variant: "blue" }, cleanupConfig.bidder),
|
|
102
|
+
cleanupConfig.bidder && React.createElement(Tag, { variant: "blue" }, cleanupConfig.bidder),
|
|
99
103
|
'cssSelectors' in cleanupConfig.deleteMethod ? (React.createElement(Tag, { variant: "grey" },
|
|
100
104
|
"css: ",
|
|
101
|
-
cleanupConfig.deleteMethod.cssSelectors.join(', '))) : (React.createElement(Tag, { variant: "yellow" },
|
|
105
|
+
cleanupConfig.deleteMethod.cssSelectors.join(', '))) : 'jsAsString' in cleanupConfig.deleteMethod ? (React.createElement(Tag, { variant: "yellow" },
|
|
102
106
|
"JS snippet (",
|
|
103
107
|
cleanupConfig.deleteMethod.jsAsString.length,
|
|
104
|
-
")"))
|
|
108
|
+
")")) : (React.createElement(Tag, { variant: "yellow" },
|
|
109
|
+
"destroy GAM slot: ",
|
|
110
|
+
cleanupConfig.deleteMethod.adUnitPath)))))));
|
|
105
111
|
const CustomModule = ({ config }) => (React.createElement(React.Fragment, null,
|
|
106
112
|
React.createElement(Row, { label: "Inline JS" }, config.inlineJs ? (React.createElement(Tag, { variant: "yellow" },
|
|
107
113
|
config.inlineJs.code.length,
|
package/lib/gen/packageJson.js
CHANGED