@highfivve/ad-tag 5.12.0 → 5.13.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.
@@ -6,6 +6,7 @@ import { generateAdUnitPathVariables } from './adUnitPath';
6
6
  import { createAssetLoaderService } from '../util/assetLoaderService';
7
7
  import { uuidV4 } from '../util/uuid';
8
8
  import { getMoliLabelsFromQueryParam, getMoliLabelsFromStorage } from '../util/debugLabels';
9
+ import { adVolumeToLabels } from '../util/adVolume';
9
10
  export const HIGH_PRIORITY = 100;
10
11
  export const LOW_PRIORITY = 10;
11
12
  export const mkInitStep = (name, fn) => {
@@ -86,6 +87,7 @@ export class AdPipeline {
86
87
  return this.tcData.then(consentData => {
87
88
  const extraLabels = [
88
89
  ...(config.targeting?.labels || []),
90
+ ...adVolumeToLabels(runtimeConfig.adVolume ?? config.targeting?.adVolume),
89
91
  ...runtimeConfig.labels,
90
92
  ...getMoliLabelsFromQueryParam(this.window),
91
93
  ...getMoliLabelsFromStorage(this.window)
@@ -5,6 +5,7 @@ import domready from '../util/domready';
5
5
  import { prebidClearAuction, prebidConfigure, prebidDefineSlots, prebidInit, prebidPrepareRequestAds, prebidRemoveAdUnits, prebidRenderAds, prebidRequestBids } from './prebid';
6
6
  import { a9Configure, a9Init, a9RequestBids, a9ClearTargetingStep, a9PublisherAudiences } from './a9';
7
7
  import { flatten, isNotNull } from '../util/arrayUtils';
8
+ import { mkInfiniteSlot } from '../util/mkInfiniteSlot';
8
9
  import { executeDebugDelay, getDebugDelayFromLocalStorage } from '../util/debugDelay';
9
10
  import { createGlobalAuctionContext } from './globalAuctionContext';
10
11
  import { getDeviceLabel } from 'ad-tag/ads/labelConfigService';
@@ -137,6 +138,7 @@ export class AdService {
137
138
  this.logger.info('AdService', `RequestAds[${this.requestAdsCalls}]`, refreshSlots, refreshBuckets);
138
139
  this.eventService.emit('beforeRequestAds', { runtimeConfig: runtimeConfig });
139
140
  try {
141
+ const queuedInfiniteSlots = this.resolveInfiniteSlots(refreshInfiniteSlots, config);
140
142
  const immediatelyLoadedSlots = config.slots
141
143
  .map(slot => {
142
144
  if (isManualSlot(slot)) {
@@ -145,9 +147,7 @@ export class AdService {
145
147
  : null;
146
148
  }
147
149
  else if (isInfiniteSlot(slot)) {
148
- return refreshInfiniteSlots.some(infiniteSlot => infiniteSlot.artificialDomId === slot.domId)
149
- ? slot
150
- : null;
150
+ return null;
151
151
  }
152
152
  else if (isBackfillSlot(slot)) {
153
153
  return null;
@@ -157,6 +157,7 @@ export class AdService {
157
157
  }
158
158
  })
159
159
  .filter(isNotNull)
160
+ .concat(queuedInfiniteSlots)
160
161
  .filter(isSlotAvailable(this.window));
161
162
  if (config.buckets?.enabled) {
162
163
  const buckets = new Map();
@@ -193,6 +194,16 @@ export class AdService {
193
194
  return Promise.reject(e);
194
195
  }
195
196
  };
197
+ this.resolveInfiniteSlots = (infiniteSlots, config) => infiniteSlots
198
+ .map(({ idOfConfiguredSlot, artificialDomId }) => {
199
+ const configuredSlot = config.slots.find(slot => slot.domId === idOfConfiguredSlot && isInfiniteSlot(slot));
200
+ if (!configuredSlot) {
201
+ this.logger.error('AdService', `no ad slot with an 'infinite' loading behaviour configured for domId ${idOfConfiguredSlot}`);
202
+ return null;
203
+ }
204
+ return mkInfiniteSlot(configuredSlot, artificialDomId);
205
+ })
206
+ .filter(isNotNull);
196
207
  this.rewardedAd = () => {
197
208
  return this.globalAuctionContext.rewardedAd();
198
209
  };
@@ -208,6 +219,14 @@ export class AdService {
208
219
  this.adPipeline = new AdPipeline(adPipelineConfig, this.logger, window, this.globalAuctionContext);
209
220
  }
210
221
  }
222
+ refreshInfiniteAdSlots(infiniteSlots, config, runtimeConfig) {
223
+ if (infiniteSlots.length === 0) {
224
+ return Promise.resolve();
225
+ }
226
+ const availableSlots = this.resolveInfiniteSlots(infiniteSlots, config).filter(isSlotAvailable(this.window));
227
+ this.logger.debug('AdService', 'refresh infinite ad slots', availableSlots);
228
+ return this.adPipeline.run(availableSlots, config, runtimeConfig, this.requestAdsCalls);
229
+ }
211
230
  refreshAdSlots(domIds, config, runtimeConfig, options) {
212
231
  if (domIds.length === 0) {
213
232
  return Promise.resolve();
@@ -6,7 +6,7 @@ const hasConsent = (context, scriptConfig) => {
6
6
  }
7
7
  switch (scriptConfig.consent.cmpApi) {
8
8
  case 'tcf':
9
- return (!context.tcData__.gdprApplies ||
9
+ return Boolean(!context.tcData__.gdprApplies ||
10
10
  context.tcData__.vendor.consents[scriptConfig.consent.vendorId]);
11
11
  }
12
12
  };
@@ -32,15 +32,17 @@ export const customModule = () => {
32
32
  .forEach(scriptConfig => {
33
33
  try {
34
34
  const script = context.window__.document.createElement('script');
35
- script.type = 'text/javascript';
36
- script.src = scriptConfig.src;
35
+ if (scriptConfig.src) {
36
+ script.type = 'text/javascript';
37
+ script.src = scriptConfig.src;
38
+ }
37
39
  if (scriptConfig.attributes) {
38
40
  Object.entries(scriptConfig.attributes).forEach(([key, value]) => {
39
41
  script.setAttribute(key, value);
40
42
  });
41
43
  }
42
44
  context.window__.document.head.appendChild(script);
43
- context.logger__?.info(name, `Injected script from URL: ${scriptConfig.src}`);
45
+ context.logger__?.info(name, `Injected script from URL: ${scriptConfig.src ?? '(attribute-driven)'}`);
44
46
  }
45
47
  catch (e) {
46
48
  context.logger__?.error(name, 'Failed to inject script from config', scriptConfig, e);
package/lib/ads/moli.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { AssetLoadMethod, createAssetLoaderService } from '../util/assetLoaderService';
2
2
  import { getLogger } from '../util/logging';
3
- import { addNewInfiniteSlotToConfig } from '../util/addNewInfiniteSlotToConfig';
4
3
  import { AdService } from './adService';
5
4
  import { createEventService } from './eventService';
6
5
  import { getAbTestValues, getActiveEnvironmentOverride, setEnvironmentOverrideInStorage } from '../util/environmentOverride';
@@ -25,58 +24,13 @@ export const createMoliTag = (window) => {
25
24
  modules: []
26
25
  };
27
26
  function setTargeting(key, value) {
28
- switch (state.state) {
29
- case 'configurable':
30
- case 'configured': {
31
- state.runtimeConfig.keyValues[key] = value;
32
- break;
33
- }
34
- case 'spa-finished':
35
- case 'spa-requestAds': {
36
- state.nextRuntimeConfig.keyValues[key] = value;
37
- break;
38
- }
39
- default: {
40
- getLogger(state.runtimeConfig, window).error('MoliGlobal', `Setting key-value after configuration: ${key} : ${value}`);
41
- break;
42
- }
43
- }
27
+ setConfig({ targeting: { [key]: value } });
44
28
  }
45
29
  function addLabel(label) {
46
- switch (state.state) {
47
- case 'configurable':
48
- case 'configured': {
49
- state.runtimeConfig.labels.push(label);
50
- break;
51
- }
52
- case 'spa-requestAds':
53
- case 'spa-finished': {
54
- state.nextRuntimeConfig.labels.push(label);
55
- break;
56
- }
57
- default: {
58
- getLogger(state.runtimeConfig, window).error('MoliGlobal', `Adding label after configure: ${label}`);
59
- break;
60
- }
61
- }
30
+ setConfig({ labels: [label] });
62
31
  }
63
32
  function setAdUnitPathVariables(variables) {
64
- switch (state.state) {
65
- case 'configurable':
66
- case 'configured': {
67
- state.runtimeConfig.adUnitPathVariables = variables;
68
- break;
69
- }
70
- case 'spa-requestAds':
71
- case 'spa-finished': {
72
- state.nextRuntimeConfig.adUnitPathVariables = variables;
73
- break;
74
- }
75
- default: {
76
- getLogger(state.runtimeConfig, window).error('MoliGlobal', `Setting unit path variables after configuration: ${variables}`);
77
- break;
78
- }
79
- }
33
+ setConfig({ adUnitPathVariables: variables });
80
34
  }
81
35
  function getAdUnitPathVariables() {
82
36
  const domain = extractTopPrivateDomainFromHostname(window.location.hostname) || 'unknown';
@@ -231,16 +185,6 @@ export const createMoliTag = (window) => {
231
185
  }
232
186
  return Promise.resolve(null);
233
187
  }
234
- function refreshQueuedInfiniteSlots(config, runtimeConfig) {
235
- const { refreshInfiniteSlots } = runtimeConfig;
236
- if (refreshInfiniteSlots.length === 0) {
237
- return config;
238
- }
239
- const log = getLogger(runtimeConfig, window);
240
- const configWithInfiniteSlots = refreshInfiniteSlots.reduce((currentConfig, slot) => addNewInfiniteSlotToConfig(currentConfig, slot.idOfConfiguredSlot, slot.artificialDomId, log), config);
241
- adService.refreshAdSlots(refreshInfiniteSlots.map(slot => slot.artificialDomId), configWithInfiniteSlots, runtimeConfig);
242
- return configWithInfiniteSlots;
243
- }
244
188
  function requestAds() {
245
189
  switch (state.state) {
246
190
  case 'configurable': {
@@ -296,13 +240,7 @@ export const createMoliTag = (window) => {
296
240
  getLogger(state.runtimeConfig, window).error('MoliGlobal', `failed to configure ${module.moduleType} module ${module.name}`, e);
297
241
  }
298
242
  });
299
- const { refreshInfiniteSlots } = state.runtimeConfig;
300
- let config = state.config;
301
- if (refreshInfiniteSlots.length > 0) {
302
- refreshInfiniteSlots.forEach(slot => {
303
- config = addNewInfiniteSlotToConfig(config, slot.idOfConfiguredSlot, slot.artificialDomId, getLogger(state.runtimeConfig, window));
304
- });
305
- }
243
+ const config = state.config;
306
244
  const log = getLogger(state.runtimeConfig, window);
307
245
  if (state.runtimeConfig.hooks && state.runtimeConfig.hooks.beforeRequestAds) {
308
246
  state.runtimeConfig.hooks.beforeRequestAds.forEach(hook => {
@@ -334,8 +272,8 @@ export const createMoliTag = (window) => {
334
272
  const validateLocation = config.spa?.validateLocation ?? 'href';
335
273
  if (state.state === 'spa-requestAds' &&
336
274
  allowRefreshAdSlot(validateLocation, state.href, window.location)) {
337
- const { runtimeConfig } = state;
338
- const config = refreshQueuedInfiniteSlots(state.config, runtimeConfig);
275
+ const { runtimeConfig, config } = state;
276
+ adService.refreshInfiniteAdSlots(runtimeConfig.refreshInfiniteSlots, config, runtimeConfig);
339
277
  if (state.runtimeConfig.refreshSlots.length > 0) {
340
278
  adService.refreshAdSlots(runtimeConfig.refreshSlots, config, runtimeConfig);
341
279
  }
@@ -446,7 +384,8 @@ export const createMoliTag = (window) => {
446
384
  })
447
385
  .then(requestedConfig => {
448
386
  const runtimeConfig = state.runtimeConfig;
449
- const config = refreshQueuedInfiniteSlots(requestedConfig, runtimeConfig);
387
+ const config = requestedConfig;
388
+ adService.refreshInfiniteAdSlots(runtimeConfig.refreshInfiniteSlots, config, runtimeConfig);
450
389
  if (state.runtimeConfig.refreshSlots.length > 0) {
451
390
  adService.refreshAdSlots(state.runtimeConfig.refreshSlots, config, state.runtimeConfig);
452
391
  }
@@ -535,16 +474,12 @@ export const createMoliTag = (window) => {
535
474
  case 'spa-finished':
536
475
  const validateLocation = state.config.spa?.validateLocation ?? 'href';
537
476
  if (allowRefreshAdSlot(validateLocation, state.href, window.location)) {
538
- state = {
539
- ...state,
540
- config: addNewInfiniteSlotToConfig(state.config, idOfConfiguredSlot, domId, getLogger(state.runtimeConfig, window))
541
- };
542
477
  return adService
543
- .refreshAdSlots([domId], state.config, state.runtimeConfig)
478
+ .refreshInfiniteAdSlots([{ artificialDomId: domId, idOfConfiguredSlot: idOfConfiguredSlot }], state.config, state.runtimeConfig)
544
479
  .then(() => 'refreshed');
545
480
  }
546
481
  else {
547
- state.runtimeConfig.refreshInfiniteSlots.push({
482
+ state.nextRuntimeConfig.refreshInfiniteSlots.push({
548
483
  artificialDomId: domId,
549
484
  idOfConfiguredSlot: idOfConfiguredSlot
550
485
  });
@@ -552,12 +487,8 @@ export const createMoliTag = (window) => {
552
487
  }
553
488
  case 'finished':
554
489
  case 'requestAds': {
555
- state = {
556
- ...state,
557
- config: addNewInfiniteSlotToConfig(state.config, idOfConfiguredSlot, domId, getLogger(state.runtimeConfig, window))
558
- };
559
490
  return adService
560
- .refreshAdSlots([domId], state.config, state.runtimeConfig)
491
+ .refreshInfiniteAdSlots([{ artificialDomId: domId, idOfConfiguredSlot: idOfConfiguredSlot }], state.config, state.runtimeConfig)
561
492
  .then(() => 'refreshed');
562
493
  }
563
494
  default: {
@@ -664,37 +595,73 @@ export const createMoliTag = (window) => {
664
595
  function setABtestTargeting() {
665
596
  const abTestValues = getAbTestValues(window, QueryParameters.abTest, BrowserStorageKeys.abTest);
666
597
  const abTestValue = abTestValues.length > 0 ? Number(abTestValues[0].value) : Math.floor(Math.random() * 100) + 1;
667
- setTargeting(QueryParameters.abTest, abTestValue.toString());
598
+ setConfig({ targeting: { [QueryParameters.abTest]: abTestValue.toString() } });
668
599
  }
669
600
  function addDomainLabel(domainFromConfig) {
670
601
  const domain = domainFromConfig || extractTopPrivateDomainFromHostname(window.location.hostname);
671
602
  if (domain) {
672
- addLabel(domain);
603
+ setConfig({ labels: [domain] });
673
604
  }
674
605
  }
675
606
  function addGeoLabels(geoFromConfig) {
676
607
  const { country, continent } = geoFromConfig ?? detectGeoFromBrowser();
677
- if (country) {
678
- addLabel(country);
679
- }
680
- if (continent) {
681
- addLabel(continent);
608
+ const labels = [country, continent].filter((label) => !!label);
609
+ if (labels.length > 0) {
610
+ setConfig({ labels });
682
611
  }
683
612
  }
684
613
  function setAudience(audience) {
614
+ setConfig({ audience: audience });
615
+ }
616
+ function validateConfigOverrides(partial) {
617
+ const validated = {
618
+ labels: partial.labels,
619
+ targeting: partial.targeting,
620
+ audience: partial.audience,
621
+ adUnitPathVariables: partial.adUnitPathVariables
622
+ };
623
+ if (partial.adVolume !== undefined) {
624
+ if (Number.isInteger(partial.adVolume) && partial.adVolume >= 1 && partial.adVolume <= 10) {
625
+ validated.adVolume = partial.adVolume;
626
+ }
627
+ else {
628
+ getLogger(state.runtimeConfig, window).warn('MoliGlobal', `Ignoring invalid adVolume in setConfig(): ${partial.adVolume}. Must be an integer between 1 and 10.`);
629
+ }
630
+ }
631
+ return validated;
632
+ }
633
+ function applyConfigOverrides(target, validated) {
634
+ if (validated.adVolume !== undefined) {
635
+ target.adVolume = validated.adVolume;
636
+ }
637
+ if (validated.labels !== undefined) {
638
+ target.labels.push(...validated.labels);
639
+ }
640
+ if (validated.targeting !== undefined) {
641
+ Object.assign(target.keyValues, validated.targeting);
642
+ }
643
+ if (validated.audience !== undefined) {
644
+ target.audience = validated.audience;
645
+ }
646
+ if (validated.adUnitPathVariables !== undefined) {
647
+ target.adUnitPathVariables = validated.adUnitPathVariables;
648
+ }
649
+ }
650
+ function setConfig(partial) {
651
+ const validated = validateConfigOverrides(partial);
685
652
  switch (state.state) {
686
653
  case 'configurable':
687
654
  case 'configured': {
688
- state.runtimeConfig.audience = audience;
655
+ applyConfigOverrides(state.runtimeConfig, validated);
689
656
  break;
690
657
  }
691
658
  case 'spa-finished':
692
659
  case 'spa-requestAds': {
693
- state.nextRuntimeConfig.audience = audience;
660
+ applyConfigOverrides(state.nextRuntimeConfig, validated);
694
661
  break;
695
662
  }
696
663
  default: {
697
- getLogger(state.runtimeConfig, window).error('MoliGlobal', `Setting audience targeting after configuration: ${JSON.stringify(audience)}`);
664
+ getLogger(state.runtimeConfig, window).error('MoliGlobal', `Setting config after configuration: ${JSON.stringify(partial)}`);
698
665
  break;
699
666
  }
700
667
  }
@@ -751,6 +718,7 @@ export const createMoliTag = (window) => {
751
718
  getState: getState,
752
719
  openConsole: openConsole,
753
720
  setAudience: setAudience,
721
+ setConfig: setConfig,
754
722
  addEventListener: eventService.addEventListener,
755
723
  removeEventListener: eventService.removeEventListener
756
724
  };
@@ -13,9 +13,14 @@ if (currentScript) {
13
13
  .filter(l => l.length > 0);
14
14
  if (labels.length > 0) {
15
15
  window.moli.que.push(moli => {
16
- moli.beforeRequestAds(() => labels.forEach(label => moli.addLabel(label)));
16
+ moli.beforeRequestAds(() => moli.setConfig({ labels }));
17
17
  });
18
18
  }
19
+ const adVolumeAttr = currentScript.getAttribute('data-ad-volume');
20
+ if (adVolumeAttr !== null) {
21
+ const adVolume = parseInt(adVolumeAttr, 10);
22
+ window.moli.que.push(moli => moli.setConfig({ adVolume }));
23
+ }
19
24
  window.moli.configLabel = version;
20
25
  const endpoint = currentScript.getAttribute('data-endpoint') ?? 'api.h5v.eu/publishers';
21
26
  const fallback = currentScript.getAttribute('data-endpoint-fallback') ?? 'cdn.h5v.eu/publishers';
@@ -1,3 +1,3 @@
1
1
  export const packageJson = {
2
- version: '5.12.0'
2
+ version: '5.13.0'
3
3
  };
@@ -0,0 +1,6 @@
1
+ export const adVolumeToLabels = (adVolume) => {
2
+ if (adVolume === undefined) {
3
+ return [];
4
+ }
5
+ return Array.from({ length: adVolume }, (_, index) => `av${index + 1}`);
6
+ };
@@ -0,0 +1,4 @@
1
+ export const mkInfiniteSlot = (configuredSlot, artificialDomId) => ({
2
+ ...configuredSlot,
3
+ domId: artificialDomId
4
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highfivve/ad-tag",
3
- "version": "5.12.0",
3
+ "version": "5.13.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "An ad tag implementation called moli",
6
6
  "main": "./lib/index.js",
@@ -1,11 +0,0 @@
1
- export const addNewInfiniteSlotToConfig = (config, idOfConfiguredSlot, artificialIdOfNewSlot, logger) => {
2
- const configuredInfiniteAdSlot = config.slots.find(configSlot => configSlot.domId === idOfConfiguredSlot);
3
- if (configuredInfiniteAdSlot) {
4
- const newAdSlot = { ...configuredInfiniteAdSlot, domId: artificialIdOfNewSlot };
5
- return { ...config, slots: [...config.slots, newAdSlot] };
6
- }
7
- else {
8
- logger.error('MoliGlobal', `no infinite ad slot configured!`, config);
9
- return config;
10
- }
11
- };