@highfivve/ad-tag 5.11.3 → 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.
@@ -1,9 +1,6 @@
1
1
  import { mkInitStep } from 'ad-tag/ads/adPipeline';
2
2
  import { isPlainObject } from 'ad-tag/util/objectUtils';
3
- const findAdSlot = (message, googletag) => googletag
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 = findAdSlot(message, context.window__.googletag);
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 = findAdSlot(message, context.window__.googletag);
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
- const observedSlot = this.observedDomElementForSlot(slot);
17
- if (this.intersectionObserver && observedSlot) {
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?.variant === 'css')) &&
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 viewabilityOverride = this.viewabilityOverrides[slotDomId];
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 bucketOverride = config.viewabilityOverrides?.[slotId]?.refreshBucket;
67
- if (bucketOverride === true) {
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 => hasBidderWonLastAuction(context, 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).map(([domId, override]) => (React.createElement(Row, { key: domId, label: domId, subEntry: true },
74
- override?.variant === 'css' && React.createElement(Tag, { variant: "grey" },
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?.variant === 'disabled' && React.createElement(Tag, { variant: "yellow" }, "checks disabled"),
78
- override?.refreshBucket && React.createElement(Tag, { variant: "yellow" }, "refreshes bucket"))))))));
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,
@@ -1,3 +1,3 @@
1
1
  export const packageJson = {
2
- version: '5.11.3'
2
+ version: '5.11.5'
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highfivve/ad-tag",
3
- "version": "5.11.3",
3
+ "version": "5.11.5",
4
4
  "license": "Apache-2.0",
5
5
  "description": "An ad tag implementation called moli",
6
6
  "main": "./lib/index.js",