@highfivve/ad-tag 5.9.2 → 5.10.1

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.
@@ -56,6 +56,7 @@ export class AdService {
56
56
  this.window = window;
57
57
  this.adPipelineConfig = adPipelineConfig;
58
58
  this.requestAdsCalls = 0;
59
+ this.globalAuctionContext = createGlobalAuctionContext(this.window, getDefaultLogger(), this.eventService);
59
60
  this.adPipeline = new AdPipeline({
60
61
  init: [() => Promise.reject('AdPipeline not initialized yet')],
61
62
  configure: [],
@@ -63,7 +64,7 @@ export class AdService {
63
64
  prepareRequestAds: [],
64
65
  requestBids: [],
65
66
  requestAds: () => Promise.resolve()
66
- }, getDefaultLogger(), this.window, createGlobalAuctionContext(this.window, getDefaultLogger(), this.eventService));
67
+ }, getDefaultLogger(), this.window, this.globalAuctionContext);
67
68
  this.initialize = (config, runtimeConfig, isLabelConditionMet = () => false) => {
68
69
  const env = AdService.getEnvironment(runtimeConfig);
69
70
  const adServer = config.adServer || 'gam';
@@ -100,7 +101,8 @@ export class AdService {
100
101
  prepareRequestAds.push(a9ClearTargetingStep());
101
102
  requestBids.push(a9RequestBids(config.a9));
102
103
  }
103
- const globalAuctionContext = createGlobalAuctionContext(this.window, this.logger, this.eventService, config.globalAuctionContext, isLabelConditionMet);
104
+ const globalAuctionContext = createGlobalAuctionContext(this.window, this.logger, this.eventService, config.globalAuctionContext, isLabelConditionMet, this.assetService);
105
+ this.globalAuctionContext = globalAuctionContext;
104
106
  configure.push(globalAuctionContext.configureStep());
105
107
  init.push(...runtimeConfig.adPipelineConfig.initSteps);
106
108
  configure.push(...runtimeConfig.adPipelineConfig.configureSteps);
@@ -191,6 +193,9 @@ export class AdService {
191
193
  return Promise.reject(e);
192
194
  }
193
195
  };
196
+ this.rewardedAd = () => {
197
+ return this.globalAuctionContext.rewardedAd();
198
+ };
194
199
  this.getAdPipeline = () => {
195
200
  return this.adPipeline;
196
201
  };
@@ -199,7 +204,8 @@ export class AdService {
199
204
  };
200
205
  this.logger = new ProxyLogger(getDefaultLogger());
201
206
  if (adPipelineConfig) {
202
- this.adPipeline = new AdPipeline(adPipelineConfig, this.logger, window, createGlobalAuctionContext(window, this.logger, this.eventService));
207
+ this.globalAuctionContext = createGlobalAuctionContext(window, this.logger, this.eventService);
208
+ this.adPipeline = new AdPipeline(adPipelineConfig, this.logger, window, this.globalAuctionContext);
203
209
  }
204
210
  }
205
211
  refreshAdSlots(domIds, config, runtimeConfig, options) {
@@ -0,0 +1,250 @@
1
+ import { resolveAdUnitPath } from 'ad-tag/ads/adUnitPath';
2
+ import { AssetLoadMethod } from 'ad-tag/util/assetLoaderService';
3
+ const defaultRewardPayload = { amount: 1, type: 'reward' };
4
+ export const createRewardedAdContext = (config, window__, logger, assetLoaderService) => {
5
+ let gamAdUnitPath = config.gam?.adUnitPath;
6
+ let inFlight = false;
7
+ let welectBundle;
8
+ const loadWelectSdk = (welectConfig) => {
9
+ const preloadedApi = window__.Welect;
10
+ if (preloadedApi) {
11
+ return Promise.resolve(preloadedApi);
12
+ }
13
+ welectBundle =
14
+ welectBundle ??
15
+ assetLoaderService
16
+ .loadScript({
17
+ name: 'welect',
18
+ assetUrl: welectConfig.bundleUrl,
19
+ loadMethod: AssetLoadMethod.TAG
20
+ })
21
+ .then(() => {
22
+ const welectApi = window__.Welect;
23
+ return welectApi
24
+ ? Promise.resolve(welectApi)
25
+ : Promise.reject(new Error('welect bundle loaded, but window.Welect is not defined'));
26
+ })
27
+ .catch(error => {
28
+ welectBundle = undefined;
29
+ return Promise.reject(error);
30
+ });
31
+ return welectBundle;
32
+ };
33
+ const attemptGam = (gamConfig) => new Promise(resolve => {
34
+ window__.googletag.cmd.push(() => {
35
+ const googletagRef = window__.googletag;
36
+ const pubads = googletagRef.pubads();
37
+ const adUnitPath = gamAdUnitPath ?? gamConfig.adUnitPath;
38
+ const slot = googletagRef.defineOutOfPageSlot(adUnitPath, googletagRef.enums.OutOfPageFormat.REWARDED);
39
+ if (!slot) {
40
+ logger.error('rewardedAd', `failed to define rewarded slot for ${adUnitPath}`);
41
+ resolve({ outcome: 'no-fill' });
42
+ return;
43
+ }
44
+ let settled = false;
45
+ let timeoutId;
46
+ const destroySlot = () => googletagRef.destroySlots([slot]);
47
+ const removeListeners = () => {
48
+ pubads.removeEventListener('slotRenderEnded', onSlotRenderEnded);
49
+ pubads.removeEventListener('rewardedSlotReady', onRewardedSlotReady);
50
+ pubads.removeEventListener('rewardedSlotGranted', onRewardedSlotGranted);
51
+ pubads.removeEventListener('rewardedSlotClosed', onRewardedSlotClosed);
52
+ };
53
+ const settle = (attempt) => {
54
+ if (settled) {
55
+ return;
56
+ }
57
+ settled = true;
58
+ window__.clearTimeout(timeoutId);
59
+ resolve(attempt);
60
+ };
61
+ const onSlotRenderEnded = (event) => {
62
+ if (event.slot !== slot) {
63
+ return;
64
+ }
65
+ if (event.isEmpty) {
66
+ logger.debug('rewardedAd', 'gam attempt has no fill');
67
+ removeListeners();
68
+ destroySlot();
69
+ settle({ outcome: 'no-fill' });
70
+ }
71
+ };
72
+ const onRewardedSlotReady = (event) => {
73
+ if (event.slot !== slot) {
74
+ return;
75
+ }
76
+ window__.clearTimeout(timeoutId);
77
+ logger.debug('rewardedAd', 'gam rewarded slot ready');
78
+ event.makeRewardedVisible();
79
+ };
80
+ const onRewardedSlotGranted = (event) => {
81
+ if (event.slot !== slot) {
82
+ return;
83
+ }
84
+ const payload = event.payload ?? defaultRewardPayload;
85
+ logger.debug('rewardedAd', 'gam granted reward', payload);
86
+ settle({ outcome: 'granted', payload });
87
+ };
88
+ const onRewardedSlotClosed = (event) => {
89
+ if (event.slot !== slot) {
90
+ return;
91
+ }
92
+ logger.debug('rewardedAd', 'gam rewarded slot closed');
93
+ removeListeners();
94
+ destroySlot();
95
+ settle({ outcome: 'canceled' });
96
+ };
97
+ pubads.addEventListener('slotRenderEnded', onSlotRenderEnded);
98
+ pubads.addEventListener('rewardedSlotReady', onRewardedSlotReady);
99
+ pubads.addEventListener('rewardedSlotGranted', onRewardedSlotGranted);
100
+ pubads.addEventListener('rewardedSlotClosed', onRewardedSlotClosed);
101
+ timeoutId = window__.setTimeout(() => {
102
+ logger.debug('rewardedAd', `gam attempt timed out after ${config.timeoutMs}ms without a rewardedSlotReady event`);
103
+ removeListeners();
104
+ destroySlot();
105
+ settle({ outcome: 'no-fill' });
106
+ }, config.timeoutMs);
107
+ slot.addService(pubads);
108
+ googletagRef.display(slot);
109
+ });
110
+ });
111
+ const hasValidWelectToken = (welectConfig) => new Promise(resolve => {
112
+ let settled = false;
113
+ const settle = (validToken) => {
114
+ if (settled) {
115
+ return;
116
+ }
117
+ settled = true;
118
+ window__.clearTimeout(timeoutId);
119
+ resolve(validToken);
120
+ };
121
+ const timeoutId = window__.setTimeout(() => {
122
+ logger.debug('rewardedAd', `welect token preflight timed out after ${config.timeoutMs}ms`);
123
+ settle(false);
124
+ }, config.timeoutMs);
125
+ loadWelectSdk(welectConfig)
126
+ .then(welectApi => {
127
+ const checkToken = welectApi.checkToken;
128
+ if (!checkToken) {
129
+ logger.error('rewardedAd', 'welect bundle does not provide checkToken');
130
+ settle(false);
131
+ return;
132
+ }
133
+ checkToken({
134
+ onValid: () => settle(true),
135
+ onInvalid: () => settle(false)
136
+ });
137
+ })
138
+ .catch(error => {
139
+ logger.error('rewardedAd', 'welect token preflight failed', error);
140
+ settle(false);
141
+ });
142
+ });
143
+ const attemptWelect = (welectConfig) => new Promise(resolve => {
144
+ let settled = false;
145
+ const settle = (attempt) => {
146
+ if (settled) {
147
+ return;
148
+ }
149
+ settled = true;
150
+ window__.clearTimeout(timeoutId);
151
+ resolve(attempt);
152
+ };
153
+ const timeoutId = window__.setTimeout(() => {
154
+ logger.debug('rewardedAd', `welect attempt timed out after ${config.timeoutMs}ms without an availability result`);
155
+ settle({ outcome: 'no-fill' });
156
+ }, config.timeoutMs);
157
+ loadWelectSdk(welectConfig)
158
+ .then(welectApi => {
159
+ if (settled) {
160
+ return;
161
+ }
162
+ const checkAvailability = welectApi.checkAvailability;
163
+ const runSession = welectApi.runSession;
164
+ if (!checkAvailability || !runSession) {
165
+ logger.error('rewardedAd', 'welect bundle does not provide checkAvailability/runSession');
166
+ settle({ outcome: 'no-fill' });
167
+ return;
168
+ }
169
+ checkAvailability({
170
+ onAvailable: () => {
171
+ if (settled) {
172
+ return;
173
+ }
174
+ window__.clearTimeout(timeoutId);
175
+ logger.debug('rewardedAd', 'welect session available');
176
+ runSession({
177
+ onSuccess: () => {
178
+ logger.debug('rewardedAd', 'welect granted reward', welectConfig.payload);
179
+ settle({ outcome: 'granted', payload: welectConfig.payload });
180
+ },
181
+ onAbort: () => {
182
+ logger.debug('rewardedAd', 'welect session aborted');
183
+ settle({ outcome: 'canceled' });
184
+ }
185
+ });
186
+ },
187
+ onUnavailable: () => {
188
+ logger.debug('rewardedAd', 'welect attempt has no fill');
189
+ settle({ outcome: 'no-fill' });
190
+ }
191
+ });
192
+ })
193
+ .catch(error => {
194
+ logger.error('rewardedAd', 'failed to load the welect bundle', error);
195
+ settle({ outcome: 'no-fill' });
196
+ });
197
+ });
198
+ const attemptChannel = (channel) => {
199
+ switch (channel) {
200
+ case 'gam':
201
+ return config.gam ? attemptGam(config.gam) : Promise.resolve({ outcome: 'no-fill' });
202
+ case 'welect':
203
+ return config.welect
204
+ ? attemptWelect(config.welect)
205
+ : Promise.resolve({ outcome: 'no-fill' });
206
+ }
207
+ };
208
+ const requestRewardedAd = async () => {
209
+ if (inFlight) {
210
+ return { state: 'error', reason: 'already-in-progress' };
211
+ }
212
+ if (!config.enabled || config.priority.length === 0) {
213
+ return { state: 'empty' };
214
+ }
215
+ inFlight = true;
216
+ try {
217
+ const welectConfig = config.welect;
218
+ if (welectConfig &&
219
+ config.priority.includes('welect') &&
220
+ welectConfig.checkToken !== false &&
221
+ (await hasValidWelectToken(welectConfig))) {
222
+ logger.debug('rewardedAd', 'valid welect token - granting without an ad');
223
+ return { state: 'granted', channel: 'welect', payload: welectConfig.payload };
224
+ }
225
+ for (const channel of config.priority) {
226
+ const attempt = await attemptChannel(channel);
227
+ switch (attempt.outcome) {
228
+ case 'granted':
229
+ return { state: 'granted', channel, payload: attempt.payload };
230
+ case 'canceled':
231
+ return { state: 'canceled', channel };
232
+ case 'no-fill':
233
+ break;
234
+ }
235
+ }
236
+ return { state: 'empty' };
237
+ }
238
+ finally {
239
+ inFlight = false;
240
+ }
241
+ };
242
+ return {
243
+ requestRewardedAd,
244
+ updateAdUnitPaths: (variables) => {
245
+ if (config.gam) {
246
+ gamAdUnitPath = resolveAdUnitPath(config.gam.adUnitPath, variables);
247
+ }
248
+ }
249
+ };
250
+ };
@@ -4,9 +4,11 @@ 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 { createRewardedAdContext } from 'ad-tag/ads/auctions/rewardedAdContext';
7
8
  import { createTrackWinningBidder } from 'ad-tag/ads/auctions/trackWinningBidder';
8
9
  import { resolveOverridableConfig } from 'ad-tag/ads/configOverrides';
9
- export const createGlobalAuctionContext = (window, logger, eventService, config = {}, isLabelConditionMet = () => false) => {
10
+ import { createAssetLoaderService } from 'ad-tag/util/assetLoaderService';
11
+ export const createGlobalAuctionContext = (window, logger, eventService, config = {}, isLabelConditionMet = () => false, assetLoaderService = createAssetLoaderService(window)) => {
10
12
  const resolveFeature = (base, feature) => {
11
13
  if (!base) {
12
14
  return undefined;
@@ -23,6 +25,7 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
23
25
  const frequencyCapConfig = resolveFeature(config.frequencyCap, 'frequencyCap');
24
26
  const previousBidCpmsConfig = resolveFeature(config.previousBidCpms, 'previousBidCpms');
25
27
  const interstitialConfig = resolveFeature(config.interstitial, 'interstitial');
28
+ const rewardedAdConfig = resolveFeature(config.rewardedAd, 'rewardedAd');
26
29
  const trackWinningBidder = trackWinningBidderConfig?.enabled
27
30
  ? createTrackWinningBidder()
28
31
  : undefined;
@@ -39,6 +42,9 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
39
42
  const interstitial = interstitialConfig?.enabled
40
43
  ? createInterstitialContext(interstitialConfig, window, window.Date.now, logger)
41
44
  : undefined;
45
+ const rewardedAd = rewardedAdConfig?.enabled
46
+ ? createRewardedAdContext(rewardedAdConfig, window, logger, assetLoaderService)
47
+ : undefined;
42
48
  window.pbjs = window.pbjs || { que: [] };
43
49
  window.googletag = window.googletag || {};
44
50
  window.googletag.cmd = window.googletag.cmd || [];
@@ -99,6 +105,7 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
99
105
  const configureStep = mkConfigureStep('GlobalAuctionContext', context => {
100
106
  frequencyCapping?.updateAdUnitPaths(context.adUnitPathVariables__);
101
107
  interstitial?.updateAdUnitPaths(context.adUnitPathVariables__);
108
+ rewardedAd?.updateAdUnitPaths(context.adUnitPathVariables__);
102
109
  return Promise.resolve();
103
110
  });
104
111
  return {
@@ -121,6 +128,9 @@ export const createGlobalAuctionContext = (window, logger, eventService, config
121
128
  interstitialChannel: () => {
122
129
  return interstitial?.interstitialChannel();
123
130
  },
131
+ rewardedAd: () => {
132
+ return rewardedAd?.requestRewardedAd() ?? Promise.resolve({ state: 'empty' });
133
+ },
124
134
  hasMinimumRequestAds(minRequestAds) {
125
135
  if (!frequencyCapping) {
126
136
  return true;
@@ -155,6 +155,7 @@ export const createAdReload = () => {
155
155
  const prepareRequestAdsSteps__ = () => [];
156
156
  return {
157
157
  name,
158
+ configKey: 'adReload',
158
159
  description: 'Moli implementation of an ad reload module.',
159
160
  moduleType: 'ad-reload',
160
161
  config__,
@@ -131,6 +131,7 @@ export const createAdexModule = () => {
131
131
  const prepareRequestAdsSteps__ = () => [];
132
132
  return {
133
133
  name,
134
+ configKey: 'adex',
134
135
  description: 'Moli DMP module for The Adex.',
135
136
  moduleType: 'dmp',
136
137
  config__,
@@ -98,6 +98,7 @@ export const createBlocklistedUrls = () => {
98
98
  const result = entry.reverseMatch === true ? !matcher(entry) : matcher(entry);
99
99
  if (result) {
100
100
  ctx.labelConfigService__.addLabel(entry.label);
101
+ ctx.logger__.debug('Blocklist URLs', `Added label ${entry.label} for url ${ctx.window__.location.href}`);
101
102
  }
102
103
  });
103
104
  })
@@ -107,6 +108,7 @@ export const createBlocklistedUrls = () => {
107
108
  const prepareRequestAdsSteps__ = () => [];
108
109
  return {
109
110
  name: 'Blocklist URLs',
111
+ configKey: 'blocklist',
110
112
  description: '',
111
113
  moduleType: 'policy',
112
114
  config__,
@@ -84,6 +84,7 @@ export const createCleanup = () => {
84
84
  };
85
85
  return {
86
86
  name,
87
+ configKey: 'cleanup',
87
88
  description: 'cleanup out-of-page formats on navigation or ad-reload',
88
89
  moduleType: 'creatives',
89
90
  config__,
@@ -36,6 +36,7 @@ export const createConfiant = () => {
36
36
  const prepareRequestAdsSteps__ = () => [];
37
37
  return {
38
38
  name,
39
+ configKey: 'confiant',
39
40
  description: 'ad fraud detection and protection module',
40
41
  moduleType: 'ad-fraud',
41
42
  config__,
@@ -51,6 +51,7 @@ export const customModule = () => {
51
51
  };
52
52
  return {
53
53
  name,
54
+ configKey: 'custom',
54
55
  description: 'Injects custom inline JavaScript code',
55
56
  moduleType: 'custom',
56
57
  config__: () => null,
@@ -146,6 +146,7 @@ export const createEmetriq = () => {
146
146
  const prepareRequestAdsSteps__ = () => [];
147
147
  return {
148
148
  name,
149
+ configKey: 'emetriq',
149
150
  description: 'Provides Emetriq data collection functionality to Moli.',
150
151
  moduleType: 'dmp',
151
152
  config__,
@@ -16,32 +16,6 @@ export const createSkin = () => {
16
16
  let skinModuleConfig = null;
17
17
  let bidsBackHandler = [];
18
18
  const config__ = () => skinModuleConfig;
19
- const getSkinBids = (config, bidResponses) => {
20
- const skinBidResponse = bidResponses[config.skinAdSlotDomId];
21
- const isSkinBid = (bid) => {
22
- const oneFilterApplied = config.formatFilter.some(filter => {
23
- switch (filter.bidder) {
24
- case '*':
25
- return true;
26
- case 'gumgum':
27
- return (bid.bidder === prebidjs.GumGum &&
28
- (filter.auid === undefined ||
29
- (typeof bid.ad !== 'string' && bid.ad.auid === filter.auid)));
30
- default:
31
- return bid.bidder === filter.bidder;
32
- }
33
- });
34
- return bid.cpm > 0 && oneFilterApplied;
35
- };
36
- return skinBidResponse
37
- ?
38
- skinBidResponse.bids.filter(isSkinBid).sort((bid1, bid2) => bid2.cpm - bid1.cpm)
39
- : [];
40
- };
41
- const getHighestSkinBidCpm = (config, bidResponses) => {
42
- const skinBids = getSkinBids(config, bidResponses);
43
- return skinBids.length > 0 ? skinBids[0].cpm : null;
44
- };
45
19
  const getConfigEffect = (config, bidResponses, log) => {
46
20
  const skinBidResponse = bidResponses[config.skinAdSlotDomId];
47
21
  const isSkinBid = (bid) => {
@@ -80,29 +54,12 @@ export const createSkin = () => {
80
54
  }
81
55
  return skinBids.length > 0 ? "BlockOtherSlots" : "NoBlocking";
82
56
  };
83
- const selectConfig = (skinModuleConfig, bidResponses, log) => {
84
- const configEvaluations = skinModuleConfig.configs.map(config => ({
85
- skinConfig: config,
86
- configEffect: getConfigEffect(config, bidResponses, log),
87
- highestSkinCpm: getHighestSkinBidCpm(config, bidResponses)
88
- }));
89
- const matchingConfigs = configEvaluations.filter(({ configEffect }) => configEffect !== "NoBlocking");
90
- const compareWithOtherSkinsConfigs = matchingConfigs.filter(({ skinConfig }) => skinConfig.compareWithOtherSkins);
91
- if (compareWithOtherSkinsConfigs.length > 1) {
92
- return compareWithOtherSkinsConfigs.reduce((selectedConfig, currentConfig) => {
93
- if (selectedConfig.highestSkinCpm === null ||
94
- (currentConfig.highestSkinCpm !== null &&
95
- currentConfig.highestSkinCpm > selectedConfig.highestSkinCpm)) {
96
- return currentConfig;
97
- }
98
- return selectedConfig;
99
- });
100
- }
101
- const fallbackConfig = matchingConfigs[0];
102
- return fallbackConfig
103
- ? { skinConfig: fallbackConfig.skinConfig, configEffect: fallbackConfig.configEffect }
104
- : undefined;
105
- };
57
+ const selectConfig = (skinModuleConfig, bidResponses, log) => skinModuleConfig.configs
58
+ .map(config => ({
59
+ skinConfig: config,
60
+ configEffect: getConfigEffect(config, bidResponses, log)
61
+ }))
62
+ .find(({ configEffect }) => configEffect !== "NoBlocking");
106
63
  const destroyAdSlot = (slotDefinitions, gWindow) => (adSlotDomId) => {
107
64
  const adSlots = slotDefinitions
108
65
  .map(slot => slot.adSlot)
@@ -178,6 +135,7 @@ export const createSkin = () => {
178
135
  const prebidBidsBackHandler__ = () => bidsBackHandler;
179
136
  return {
180
137
  name,
138
+ configKey: 'skin',
181
139
  description: 'Block other ad slots if a wallpaper has won the auction',
182
140
  moduleType: 'prebid',
183
141
  config__,
@@ -29,6 +29,7 @@ export const geoEdge = () => {
29
29
  };
30
30
  return {
31
31
  name: name,
32
+ configKey: 'geoedge',
32
33
  description: 'ad fraud detection and protection module',
33
34
  moduleType: 'ad-fraud',
34
35
  config__: () => null,
@@ -60,6 +60,7 @@ export const createIdentityLink = () => {
60
60
  const prepareRequestAdsSteps__ = () => [];
61
61
  return {
62
62
  name,
63
+ configKey: 'identitylink',
63
64
  description: "Provides LiveRamp's ATS (authenticated traffic solution) functionality to Moli.",
64
65
  moduleType: 'identity',
65
66
  config__,
@@ -4,6 +4,7 @@ export const createInterstitialModule = () => {
4
4
  let interstitialModuleConfig = null;
5
5
  return {
6
6
  name: 'interstitial-module',
7
+ configKey: 'interstitial',
7
8
  description: 'interstitial ad creatives',
8
9
  moduleType: 'creatives',
9
10
  config__() {
@@ -179,6 +179,7 @@ export const createLazyLoad = () => {
179
179
  };
180
180
  return {
181
181
  name,
182
+ configKey: 'lazyload',
182
183
  description,
183
184
  moduleType,
184
185
  config__,
@@ -136,6 +136,7 @@ export const MoliAnalytics = () => {
136
136
  };
137
137
  return {
138
138
  name: 'moli-analytics',
139
+ configKey: 'moliAnalytics',
139
140
  description: 'ad events tracking and analytics module',
140
141
  moduleType: 'reporting',
141
142
  config__() {
@@ -102,6 +102,7 @@ export const createPrebidFirstPartyDataModule = () => {
102
102
  const prepareRequestAdsSteps__ = () => [];
103
103
  return {
104
104
  name,
105
+ configKey: 'prebidFirstPartyData',
105
106
  description: 'Module for passing first party data to prebid auctions',
106
107
  moduleType: 'prebid',
107
108
  config__,
@@ -52,6 +52,7 @@ export const createPubstack = () => {
52
52
  const prepareRequestAdsSteps__ = () => [];
53
53
  return {
54
54
  name,
55
+ configKey: 'pubstack',
55
56
  description: 'prebid analytics integration',
56
57
  moduleType: 'reporting',
57
58
  config__,
@@ -32,6 +32,7 @@ export const createStickyFooterAd = () => {
32
32
  const initSteps__ = () => [];
33
33
  return {
34
34
  name,
35
+ configKey: 'stickyFooterAd',
35
36
  description: 'sticky footer ad creatives',
36
37
  moduleType: 'creatives',
37
38
  config__,
@@ -32,6 +32,7 @@ export const createStickyFooterAdsV2 = () => {
32
32
  const initSteps__ = () => [];
33
33
  return {
34
34
  name,
35
+ configKey: 'stickyFooterAdV2',
35
36
  description: 'sticky footer ad creatives',
36
37
  moduleType: 'creatives',
37
38
  config__,
@@ -104,6 +104,7 @@ export const createStickyHeaderAd = () => {
104
104
  };
105
105
  return {
106
106
  name,
107
+ configKey: 'stickyHeaderAd',
107
108
  description: 'sticky header ad creatives',
108
109
  moduleType: 'creatives',
109
110
  config__,
@@ -97,6 +97,7 @@ export const createUtiq = () => {
97
97
  };
98
98
  return {
99
99
  name: 'utiq',
100
+ configKey: 'utiq',
100
101
  description: 'user module',
101
102
  moduleType: 'identity',
102
103
  config__() {
@@ -46,6 +46,7 @@ export const YieldOptimization = (testYieldOptimizationService) => {
46
46
  });
47
47
  return {
48
48
  name,
49
+ configKey: 'yieldOptimization',
49
50
  description,
50
51
  moduleType,
51
52
  config__,
@@ -100,6 +100,7 @@ export const createZeotap = () => {
100
100
  const prepareRequestAdsSteps__ = () => [];
101
101
  return {
102
102
  name,
103
+ configKey: 'zeotap',
103
104
  description: 'Provides Zeotap functionality (data collection and identity plus) to Moli.',
104
105
  moduleType: 'identity',
105
106
  config__,
package/lib/ads/moli.js CHANGED
@@ -247,7 +247,8 @@ export const createMoliTag = (window) => {
247
247
  modules
248
248
  .map(module => {
249
249
  const moduleName = module.name;
250
- const base = state.config?.modules?.[moduleName];
250
+ const configKey = module.configKey;
251
+ const base = state.config?.modules?.[configKey];
251
252
  if (!base) {
252
253
  return null;
253
254
  }
@@ -262,15 +263,15 @@ export const createMoliTag = (window) => {
262
263
  }
263
264
  const resolvedModulesConfig = {
264
265
  ...state.config?.modules,
265
- [moduleName]: effectiveConfig
266
+ [configKey]: effectiveConfig
266
267
  };
267
- return { module, resolvedModulesConfig };
268
+ return { module, resolvedModulesConfig, effectiveConfig };
268
269
  })
269
270
  .filter((entry) => entry !== null)
270
- .forEach(({ module, resolvedModulesConfig }) => {
271
+ .forEach(({ module, resolvedModulesConfig, effectiveConfig }) => {
271
272
  try {
272
273
  module.configure__(resolvedModulesConfig);
273
- getLogger(state.runtimeConfig, window).debug('MoliGlobal', `configure ${module.moduleType} module ${module.name}`, module.config__());
274
+ getLogger(state.runtimeConfig, window).debug('MoliGlobal', `configure ${module.moduleType} module ${module.name}`, `module is ${effectiveConfig.enabled ? 'enabled' : 'disabled'}`, module.config__());
274
275
  state.runtimeConfig.adPipelineConfig.initSteps.push(...module.initSteps__());
275
276
  state.runtimeConfig.adPipelineConfig.configureSteps.push(...module.configureSteps__());
276
277
  state.runtimeConfig.adPipelineConfig.prepareRequestAdsSteps.push(...module.prepareRequestAdsSteps__());
@@ -579,6 +580,38 @@ export const createMoliTag = (window) => {
579
580
  }
580
581
  }
581
582
  }
583
+ function rewardedAd() {
584
+ switch (state.state) {
585
+ case 'configurable':
586
+ case 'configured':
587
+ case 'requestAds':
588
+ case 'spa-requestAds': {
589
+ return new Promise(resolve => {
590
+ let queued = true;
591
+ state.runtimeConfig.hooks.afterRequestAds.push(afterState => {
592
+ if (!queued) {
593
+ return;
594
+ }
595
+ queued = false;
596
+ if (afterState === 'error') {
597
+ resolve({ state: 'error', reason: 'ad-tag-error' });
598
+ }
599
+ else {
600
+ adService.rewardedAd().then(resolve);
601
+ }
602
+ });
603
+ });
604
+ }
605
+ case 'finished':
606
+ case 'spa-finished': {
607
+ return adService.rewardedAd();
608
+ }
609
+ case 'error': {
610
+ getLogger(state.runtimeConfig, window).error('MoliGlobal', `rewardedAd is not allowed in state ${state.state}`);
611
+ return Promise.resolve({ state: 'error', reason: 'ad-tag-error' });
612
+ }
613
+ }
614
+ }
582
615
  function triggerDelay() {
583
616
  window.dispatchEvent(new CustomEvent('h5v.trigger-delay'));
584
617
  return;
@@ -689,6 +722,7 @@ export const createMoliTag = (window) => {
689
722
  refreshAdSlot: refreshAdSlot,
690
723
  refreshBucket: refreshBucket,
691
724
  refreshInfiniteAdSlot: refreshInfiniteAdSlot,
725
+ rewardedAd: rewardedAd,
692
726
  triggerDelay: triggerDelay,
693
727
  getState: getState,
694
728
  openConsole: openConsole,
@@ -32,8 +32,10 @@ const cmpVendors = {
32
32
  export const ConsentConfig = () => {
33
33
  const [consentState, setConsentState] = React.useState({ messages: [] });
34
34
  React.useEffect(() => {
35
+ let listenerId;
35
36
  if (window.__tcfapi) {
36
37
  window.__tcfapi('addEventListener', 2, event => {
38
+ listenerId = event.listenerId;
37
39
  const tcModel = event.gdprApplies ? TCString.decode(event.tcString) : undefined;
38
40
  setConsentState(prevState => ({
39
41
  ...prevState,
@@ -59,7 +61,12 @@ export const ConsentConfig = () => {
59
61
  messages: [errorMessage, ...prevState.messages]
60
62
  }));
61
63
  }
62
- });
64
+ return () => {
65
+ if (window.__tcfapi && listenerId != null) {
66
+ window.__tcfapi('removeEventListener', 2, () => undefined, listenerId);
67
+ }
68
+ };
69
+ }, []);
63
70
  const cmpVendorTag = () => {
64
71
  if (consentState.tcModel) {
65
72
  const cmpVendor = cmpVendors[consentState.tcModel.cmpId.toString()];
@@ -97,7 +104,7 @@ export const ConsentConfig = () => {
97
104
  React.createElement("span", { className: "inline-block min-w-36 max-w-96 pr-1 font-medium" }, "TCF Policy Version"),
98
105
  tcModel ? (React.createElement(Tag, null, tcModel.policyVersion.toString())) : (React.createElement(Tag, { variant: "red" }, "Unknown")))));
99
106
  };
100
- const isCmpFunctionAvailable = () => window.__tcfapi || typeof window.__tcfapi === 'function';
107
+ const isCmpFunctionAvailable = () => typeof window.__tcfapi === 'function';
101
108
  const consentData = () => {
102
109
  if (isCmpFunctionAvailable()) {
103
110
  return (React.createElement("div", null,
@@ -196,7 +196,10 @@ export default {
196
196
  info: '#7da6d7',
197
197
  success: '#42cbc9',
198
198
  warning: '#e8d06a',
199
- error: '#f88383'
199
+ error: '#f88383',
200
+ 'base-100': '#1f1f2e',
201
+ 'base-200': '#2d2d3d',
202
+ 'base-300': '#3a3a4d'
200
203
  }
201
204
  }
202
205
  ],
@@ -1,3 +1,3 @@
1
1
  export const packageJson = {
2
- version: '5.9.2'
2
+ version: '5.10.1'
3
3
  };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highfivve/ad-tag",
3
- "version": "5.9.2",
3
+ "version": "5.10.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "An ad tag implementation called moli",
6
6
  "main": "./lib/index.js",
@@ -67,7 +67,7 @@
67
67
  "@types/core-js": "^2.5.8",
68
68
  "@types/domready": "^1.0.0",
69
69
  "@types/jsdom": "^20.0.0",
70
- "@types/mocha": "8.2.2",
70
+ "@types/mocha": "10.0.10",
71
71
  "@types/node": "^20.10.4",
72
72
  "@types/react": "^18.3.3",
73
73
  "@types/react-dom": "^18.3.0",
@@ -89,8 +89,8 @@
89
89
  "eslint-plugin-jsdoc": "^34.8.1",
90
90
  "eslint-plugin-preact": "^0.1.0",
91
91
  "jsdom": "19.0.0",
92
- "mocha": "8.4.0",
93
- "mocha-junit-reporter": "2.0.0",
92
+ "mocha": "11.7.6",
93
+ "mocha-junit-reporter": "2.2.1",
94
94
  "np": "^10.0.5",
95
95
  "postcss": "^8.4.47",
96
96
  "postcss-cli": "^11.0.0",