@highfivve/ad-tag 5.5.4

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.
Files changed (132) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +149 -0
  3. package/lib/ads/a9.js +190 -0
  4. package/lib/ads/adPipeline.js +159 -0
  5. package/lib/ads/adService.js +251 -0
  6. package/lib/ads/adUnitPath.js +37 -0
  7. package/lib/ads/auctions/adRequestThrottling.js +22 -0
  8. package/lib/ads/auctions/biddersDisabling.js +90 -0
  9. package/lib/ads/auctions/frequencyCapping.js +189 -0
  10. package/lib/ads/auctions/interstitialContext.js +92 -0
  11. package/lib/ads/auctions/previousBidCpms.js +33 -0
  12. package/lib/ads/auctions/resume.js +3 -0
  13. package/lib/ads/bridge/bridge.js +62 -0
  14. package/lib/ads/consent.js +63 -0
  15. package/lib/ads/eventService.js +44 -0
  16. package/lib/ads/globalAuctionContext.js +98 -0
  17. package/lib/ads/googleAdManager.js +380 -0
  18. package/lib/ads/keyValues.js +1 -0
  19. package/lib/ads/labelConfigService.js +42 -0
  20. package/lib/ads/modules/ad-reload/adVisibilityService.js +158 -0
  21. package/lib/ads/modules/ad-reload/index.js +163 -0
  22. package/lib/ads/modules/ad-reload/userActivityService.js +70 -0
  23. package/lib/ads/modules/adex/adex-mapping.js +77 -0
  24. package/lib/ads/modules/adex/adexUtiq.js +15 -0
  25. package/lib/ads/modules/adex/index.js +142 -0
  26. package/lib/ads/modules/adex/sendAdvertisingId.js +20 -0
  27. package/lib/ads/modules/blocklist-url/index.js +118 -0
  28. package/lib/ads/modules/cleanup/index.js +93 -0
  29. package/lib/ads/modules/confiant/index.js +47 -0
  30. package/lib/ads/modules/emetriq/index.js +154 -0
  31. package/lib/ads/modules/emetriq/trackInApp.js +34 -0
  32. package/lib/ads/modules/emetriq/trackLoginEvent.js +59 -0
  33. package/lib/ads/modules/generic-skin/index.js +150 -0
  34. package/lib/ads/modules/identitylink/index.js +58 -0
  35. package/lib/ads/modules/interstitial/index.js +38 -0
  36. package/lib/ads/modules/interstitial/interstitialAd.js +111 -0
  37. package/lib/ads/modules/lazy-load/index.js +191 -0
  38. package/lib/ads/modules/lazy-load/selectInfiniteSlot.js +11 -0
  39. package/lib/ads/modules/prebid-first-party-data/index.js +115 -0
  40. package/lib/ads/modules/pubstack/index.js +59 -0
  41. package/lib/ads/modules/sticky-footer-ad/desktopFloorAd.js +63 -0
  42. package/lib/ads/modules/sticky-footer-ad/index.js +43 -0
  43. package/lib/ads/modules/sticky-footer-ad/mobileSticky.js +93 -0
  44. package/lib/ads/modules/sticky-footer-ad-v2/footerStickyAd.js +118 -0
  45. package/lib/ads/modules/sticky-footer-ad-v2/index.js +43 -0
  46. package/lib/ads/modules/sticky-header-ad/fadeOutCallback.js +25 -0
  47. package/lib/ads/modules/sticky-header-ad/index.js +103 -0
  48. package/lib/ads/modules/sticky-header-ad/renderResult.js +24 -0
  49. package/lib/ads/modules/utiq/index.js +79 -0
  50. package/lib/ads/modules/yield-optimization/dynamicFloorPrice.js +86 -0
  51. package/lib/ads/modules/yield-optimization/index.js +57 -0
  52. package/lib/ads/modules/yield-optimization/isYieldOptimizationConfigDynamic.js +6 -0
  53. package/lib/ads/modules/yield-optimization/yieldOptimizationService.js +169 -0
  54. package/lib/ads/modules/zeotap/index.js +111 -0
  55. package/lib/ads/moli.js +645 -0
  56. package/lib/ads/moliGlobal.js +11 -0
  57. package/lib/ads/prebid-outstream.js +13 -0
  58. package/lib/ads/prebid.js +406 -0
  59. package/lib/ads/sizeConfigService.js +49 -0
  60. package/lib/ads/spa.js +32 -0
  61. package/lib/bundle/adReload.js +2 -0
  62. package/lib/bundle/adex.js +2 -0
  63. package/lib/bundle/blocklistUrls.js +2 -0
  64. package/lib/bundle/cleanup.js +2 -0
  65. package/lib/bundle/confiant.js +2 -0
  66. package/lib/bundle/configureFromEndpoint.js +40 -0
  67. package/lib/bundle/emetriq.js +2 -0
  68. package/lib/bundle/identityLink.js +2 -0
  69. package/lib/bundle/init.js +2 -0
  70. package/lib/bundle/interstitialModule.js +2 -0
  71. package/lib/bundle/lazyLoad.js +2 -0
  72. package/lib/bundle/prebidFirstPartyData.js +2 -0
  73. package/lib/bundle/pubstack.js +2 -0
  74. package/lib/bundle/skin.js +2 -0
  75. package/lib/bundle/stickyFooterAd.js +2 -0
  76. package/lib/bundle/stickyFooterAds2.js +2 -0
  77. package/lib/bundle/stickyHeaderAd.js +2 -0
  78. package/lib/bundle/utiq.js +2 -0
  79. package/lib/bundle/yieldOptimization.js +2 -0
  80. package/lib/bundle/zeotap.js +2 -0
  81. package/lib/console/components/adSlotConfig.js +225 -0
  82. package/lib/console/components/consentConfig.js +138 -0
  83. package/lib/console/components/globalConfig.js +634 -0
  84. package/lib/console/components/labelConfigDebug.js +19 -0
  85. package/lib/console/components/sizeConfigDebug.js +34 -0
  86. package/lib/console/components/tag.js +4 -0
  87. package/lib/console/debug.js +38 -0
  88. package/lib/console/util/array.js +1 -0
  89. package/lib/console/util/calculateAdDensity.js +36 -0
  90. package/lib/console/util/debounce.js +12 -0
  91. package/lib/console/util/debugLogger.js +6 -0
  92. package/lib/console/util/extractPositionFromPath.js +8 -0
  93. package/lib/console/util/prebid.js +3 -0
  94. package/lib/console/util/stringUtils.js +23 -0
  95. package/lib/console/util/themingService.js +31 -0
  96. package/lib/console/util/windowResizeService.js +22 -0
  97. package/lib/console/validations/adReloadValidations.js +47 -0
  98. package/lib/console/validations/bucketValidations.js +78 -0
  99. package/lib/console/validations/sizesConfigValidations.js +97 -0
  100. package/lib/gen/packageJson.js +3 -0
  101. package/lib/index.js +3 -0
  102. package/lib/types/apstag.js +1 -0
  103. package/lib/types/dom.js +1 -0
  104. package/lib/types/emetriq.js +1 -0
  105. package/lib/types/googletag.js +1 -0
  106. package/lib/types/identitylink.js +1 -0
  107. package/lib/types/module.js +1 -0
  108. package/lib/types/moliConfig.js +1 -0
  109. package/lib/types/moliRuntime.js +1 -0
  110. package/lib/types/prebidjs.js +45 -0
  111. package/lib/types/supplyChainObject.js +1 -0
  112. package/lib/types/tcfapi.js +48 -0
  113. package/lib/util/addNewInfiniteSlotToConfig.js +11 -0
  114. package/lib/util/arrayUtils.js +9 -0
  115. package/lib/util/assetLoaderService.js +91 -0
  116. package/lib/util/browserStorageKeys.js +7 -0
  117. package/lib/util/debugDelay.js +12 -0
  118. package/lib/util/domready.js +15 -0
  119. package/lib/util/environmentOverride.js +16 -0
  120. package/lib/util/extractAdTagVersion.js +16 -0
  121. package/lib/util/extractTopPrivateDomainFromHostname.js +17 -0
  122. package/lib/util/localStorage.js +26 -0
  123. package/lib/util/logging.js +106 -0
  124. package/lib/util/objectUtils.js +29 -0
  125. package/lib/util/query.js +40 -0
  126. package/lib/util/queryParameters.js +5 -0
  127. package/lib/util/resolveOverrides.js +21 -0
  128. package/lib/util/resolveStoredRequestIdInOrtb2Object.js +12 -0
  129. package/lib/util/sizes.js +3 -0
  130. package/lib/util/test-slots.js +150 -0
  131. package/lib/util/uuid.js +10 -0
  132. package/package.json +127 -0
@@ -0,0 +1,163 @@
1
+ import { AdVisibilityService } from './adVisibilityService';
2
+ import { UserActivityService } from './userActivityService';
3
+ import { mkConfigureStep } from '../../adPipeline';
4
+ import { isNotNull } from 'ad-tag/util/arrayUtils';
5
+ export class AdReload {
6
+ constructor() {
7
+ this.name = 'moli-ad-reload';
8
+ this.description = 'Moli implementation of an ad reload module.';
9
+ this.moduleType = 'ad-reload';
10
+ this.moduleConfig = null;
11
+ this.refreshIntervalMs = 20000;
12
+ this.reloadKeyValue = 'native-ad-reload';
13
+ this.initialized = false;
14
+ this.initialize = (context, config, slotsToMonitor, reloadAdSlotCallback) => {
15
+ if (context.env__ === 'test') {
16
+ context.logger__.info('AdReload', 'disabled in environment test');
17
+ return;
18
+ }
19
+ if (this.initialized) {
20
+ return;
21
+ }
22
+ context.logger__.debug('AdReload', 'initialize moli ad reload module');
23
+ this.setupAdVisibilityService(config, context.window__, context.logger__);
24
+ this.setupSlotRenderListener(config, slotsToMonitor, reloadAdSlotCallback, context.window__, context.logger__);
25
+ this.initialized = true;
26
+ };
27
+ this.setupAdVisibilityService = (config, window, logger) => {
28
+ this.adVisibilityService = new AdVisibilityService(new UserActivityService(window, config.userActivityLevelControl, logger), this.refreshIntervalMs, config.refreshIntervalMsOverrides ?? {}, false, !!config.disableAdVisibilityChecks, config.viewabilityOverrides ?? {}, window, logger);
29
+ };
30
+ this.setupSlotRenderListener = (config, slotsToMonitor, reloadAdSlotCallback, window, logger) => window.googletag.pubads().addEventListener('slotRenderEnded', renderEndedEvent => {
31
+ const { slot: googleTagSlot, campaignId, advertiserId, yieldGroupIds, isEmpty: slotIsEmpty } = renderEndedEvent;
32
+ const slotDomId = googleTagSlot.getSlotElementId();
33
+ const slotIsMonitored = slotsToMonitor.indexOf(slotDomId) > -1;
34
+ const orderIdNotExcluded = !campaignId || config.excludeOrderIds.indexOf(campaignId) === -1;
35
+ const orderIdIncluded = !!campaignId && config.includeOrderIds.indexOf(campaignId) > -1;
36
+ const advertiserIdIncluded = !!advertiserId && config.includeAdvertiserIds.indexOf(advertiserId) > -1;
37
+ const yieldGroupIdIncluded = !!yieldGroupIds && config.includeYieldGroupIds.some(id => yieldGroupIds.indexOf(id) > -1);
38
+ const trackingSlotAllowed = !slotIsEmpty &&
39
+ slotIsMonitored &&
40
+ orderIdNotExcluded &&
41
+ (orderIdIncluded || advertiserIdIncluded || yieldGroupIdIncluded);
42
+ if (!trackingSlotAllowed) {
43
+ this.logTrackingDisallowedReason(slotDomId, {
44
+ slotIsEmpty,
45
+ slotIsMonitored,
46
+ orderIdNotExcluded,
47
+ orderIdIncluded,
48
+ advertiserIdIncluded,
49
+ yieldGroupIdIncluded
50
+ }, logger, yieldGroupIds, campaignId, advertiserId);
51
+ }
52
+ const slotAlreadyTracked = !!this.adVisibilityService?.isSlotTracked(slotDomId);
53
+ if (trackingSlotAllowed) {
54
+ this.adVisibilityService.trackSlot(googleTagSlot, reloadAdSlotCallback, advertiserId);
55
+ }
56
+ else if (slotAlreadyTracked) {
57
+ this.adVisibilityService.removeSlotTracking(googleTagSlot);
58
+ }
59
+ });
60
+ this.reloadAdSlot = (config, ctx) => (googleTagSlot) => {
61
+ const slotId = googleTagSlot.getSlotElementId();
62
+ const moliSlot = ctx.config__.slots.find(moliSlot => moliSlot.domId === slotId);
63
+ if (moliSlot && moliSlot.behaviour.loaded !== 'infinite') {
64
+ ctx.logger__.debug('AdReload', 'fired slot reload', moliSlot.domId);
65
+ const sizesOverride = this.maybeOptimizeSlotForCls(config, moliSlot, googleTagSlot, ctx.logger__, ctx.window__);
66
+ googleTagSlot.setTargeting(this.reloadKeyValue, 'true');
67
+ const getBucketAndLoadingBehaviour = () => {
68
+ const bucketOverride = config.viewabilityOverrides?.[slotId]?.refreshBucket;
69
+ if (bucketOverride === true) {
70
+ const loaded = moliSlot.behaviour.loaded;
71
+ const bucket = moliSlot.behaviour.bucket;
72
+ const bucketName = typeof bucket === 'string'
73
+ ? bucket
74
+ : bucket?.[ctx.labelConfigService__.getDeviceLabel()];
75
+ return bucketName && loaded !== 'infinite' ? { name: bucketName, loaded } : undefined;
76
+ }
77
+ };
78
+ const bucket = getBucketAndLoadingBehaviour();
79
+ if (bucket) {
80
+ ctx.window__.moli
81
+ .refreshBucket(bucket.name, { loaded: bucket.loaded })
82
+ .then(result => ctx.logger__.debug('AdReload', `refreshBucket '${bucket.name}' result`, result))
83
+ .catch(error => ctx.logger__.error('AdReload', `refreshBucket '${bucket.name}' failed`, error));
84
+ }
85
+ else {
86
+ ctx.window__.moli
87
+ .refreshAdSlot(slotId, {
88
+ loaded: moliSlot.behaviour.loaded,
89
+ ...(sizesOverride && { sizesOverride: sizesOverride })
90
+ })
91
+ .catch(error => ctx.logger__.error('AdReload', `refreshing ${slotId} failed`, error));
92
+ }
93
+ }
94
+ };
95
+ this.maybeOptimizeSlotForCls = (config, moliSlot, googleTagSlot, logger, _window) => {
96
+ const slotDomId = moliSlot.domId;
97
+ if (config.optimizeClsScoreDomIds.indexOf(slotDomId) > -1) {
98
+ const slotDomElement = _window.document.getElementById(slotDomId);
99
+ if (slotDomElement && !!slotDomElement.style) {
100
+ const slotHeight = slotDomElement.scrollHeight;
101
+ slotDomElement.style.setProperty('height', `${slotHeight}px`);
102
+ const newSlotSizes = moliSlot.sizes.filter(size => size !== 'fluid' && size[1] <= slotHeight);
103
+ logger.debug('AdReload', `CLS optimization: slot ${slotDomId} received fixed height ${slotHeight}px`, 'new sizes:', newSlotSizes);
104
+ if (newSlotSizes.length < moliSlot.sizes.filter(size => size !== 'fluid').length) {
105
+ _window.googletag.destroySlots([googleTagSlot]);
106
+ }
107
+ return newSlotSizes;
108
+ }
109
+ logger.warn('AdReload', `CLS optimization: slot ${slotDomId} to be optimized but not found in DOM.`);
110
+ }
111
+ return moliSlot.sizes;
112
+ };
113
+ this.logTrackingDisallowedReason = (slotDomId, reasons, logger, yieldGroupIds, campaignId, advertiserId) => {
114
+ const { slotIsEmpty, slotIsMonitored, orderIdNotExcluded, orderIdIncluded, advertiserIdIncluded, yieldGroupIdIncluded } = reasons;
115
+ if (slotIsEmpty) {
116
+ logger.debug('AdReload', slotDomId, 'slot not tracked: reported empty');
117
+ }
118
+ if (!slotIsMonitored) {
119
+ logger.debug('AdReload', slotDomId, 'slot not tracked: excluded by DOM id');
120
+ }
121
+ if (!orderIdNotExcluded) {
122
+ logger.debug('AdReload', slotDomId, 'slot not tracked: excluded by order id', campaignId);
123
+ }
124
+ if (!(orderIdIncluded || advertiserIdIncluded || yieldGroupIdIncluded)) {
125
+ logger.debug('AdReload', slotDomId, 'slot not tracked: neither order id', campaignId, 'nor advertiser id', advertiserId, 'nor yieldGroup id', yieldGroupIds, 'included');
126
+ }
127
+ };
128
+ }
129
+ config__() {
130
+ return this.moduleConfig;
131
+ }
132
+ isInitialized() {
133
+ return this.initialized;
134
+ }
135
+ configure__(moduleConfig) {
136
+ if (moduleConfig?.adReload?.enabled) {
137
+ this.moduleConfig = moduleConfig.adReload;
138
+ }
139
+ }
140
+ initSteps__() {
141
+ return [];
142
+ }
143
+ configureSteps__() {
144
+ const config = this.moduleConfig;
145
+ return config
146
+ ? [
147
+ mkConfigureStep(this.name, context => {
148
+ const slotsToMonitor = context.config__.slots
149
+ .filter(slot => config.excludeAdSlotDomIds.indexOf(slot.domId) === -1)
150
+ .map(slot => slot.domId)
151
+ .filter(isNotNull);
152
+ const reloadAdSlotCallback = this.reloadAdSlot(config, context);
153
+ context.logger__.debug('AdReload', 'monitoring slots', slotsToMonitor);
154
+ this.initialize(context, config, slotsToMonitor, reloadAdSlotCallback);
155
+ return Promise.resolve();
156
+ })
157
+ ]
158
+ : [];
159
+ }
160
+ prepareRequestAdsSteps__() {
161
+ return [];
162
+ }
163
+ }
@@ -0,0 +1,70 @@
1
+ export const userActivityParametersForLevel = new Map([
2
+ ['strict', { userActivityDuration: 10 * 1000, userBecomingInactiveDuration: 5 * 1000 }],
3
+ ['moderate', { userActivityDuration: 12 * 1000, userBecomingInactiveDuration: 8 * 1000 }],
4
+ ['lax', { userActivityDuration: 15 * 1000, userBecomingInactiveDuration: 12 * 1000 }]
5
+ ]);
6
+ export class UserActivityService {
7
+ constructor(window, userActivityLevelControl = {
8
+ level: 'strict'
9
+ }, logger) {
10
+ this.window = window;
11
+ this.userActivityLevelControl = userActivityLevelControl;
12
+ this.logger = logger;
13
+ this.isActive = true;
14
+ this.handleUserInteraction = () => this.userActive();
15
+ this.handleUserIdle = () => this.userInactive();
16
+ this.handleUserBecomingIdle = () => this.userBecomingInactive();
17
+ this.handlePageVisibilityChanged = () => {
18
+ if (this.window.document.hidden) {
19
+ this.userInactive();
20
+ }
21
+ else {
22
+ this.userActive();
23
+ }
24
+ };
25
+ this.listener = [];
26
+ this.window.document.addEventListener('visibilitychange', this.handlePageVisibilityChanged);
27
+ this.logger?.debug('UserActivityService', 'initialized');
28
+ switch (userActivityLevelControl.level) {
29
+ case 'custom':
30
+ this.userActivityDuration = userActivityLevelControl.userActivityDuration;
31
+ this.userBecomingInactiveDuration = userActivityLevelControl.userBecomingInactiveDuration;
32
+ break;
33
+ default:
34
+ const { userActivityDuration, userBecomingInactiveDuration } = userActivityParametersForLevel.get(userActivityLevelControl.level);
35
+ this.userActivityDuration = userActivityDuration;
36
+ this.userBecomingInactiveDuration = userBecomingInactiveDuration;
37
+ }
38
+ this.userActive();
39
+ }
40
+ addUserActivityChangedListener(listener) {
41
+ this.listener.push(listener);
42
+ }
43
+ userActive() {
44
+ if (!this.isActive) {
45
+ this.isActive = true;
46
+ this.listener.forEach(listener => listener(this.isActive));
47
+ this.logger?.debug('UserActivityService', 'user has become active');
48
+ }
49
+ UserActivityService.observedEvents.forEach(event => this.window.removeEventListener(event, this.handleUserInteraction));
50
+ if (this.userInactiveTimer) {
51
+ this.window.clearTimeout(this.userInactiveTimer);
52
+ }
53
+ if (this.userBecomingIdleTimer) {
54
+ this.window.clearTimeout(this.userBecomingIdleTimer);
55
+ }
56
+ this.userInactiveTimer = this.window.setTimeout(this.handleUserIdle, this.userActivityDuration);
57
+ this.userBecomingIdleTimer = this.window.setTimeout(this.handleUserBecomingIdle, this.userBecomingInactiveDuration);
58
+ }
59
+ userInactive() {
60
+ this.isActive = false;
61
+ UserActivityService.observedEvents.forEach(event => this.window.addEventListener(event, this.handleUserInteraction));
62
+ this.listener.forEach(listener => listener(this.isActive));
63
+ this.logger?.debug('UserActivityService', 'user has become inactive');
64
+ }
65
+ userBecomingInactive() {
66
+ UserActivityService.observedEvents.forEach(event => this.window.addEventListener(event, this.handleUserInteraction));
67
+ this.logger?.debug('UserActivityService', 'user will shortly become inactive');
68
+ }
69
+ }
70
+ UserActivityService.observedEvents = ['mousemove', 'touchstart', 'scroll', 'keypress'];
@@ -0,0 +1,77 @@
1
+ export const toAdexMapType = (keyValueMap, mappingDefinition, logger) => {
2
+ const mapKey = keyValueMap[mappingDefinition.key];
3
+ const mapValue = extractStringOrNumber(keyValueMap, mappingDefinition.valueType, mappingDefinition.valueKey);
4
+ if (!mapKey ||
5
+ Array.isArray(mapKey) ||
6
+ (mapValue === undefined && mappingDefinition.defaultValue === undefined) ||
7
+ (mappingDefinition.valueType === 'number' &&
8
+ Number.isNaN(mapValue) &&
9
+ mappingDefinition.defaultValue === undefined)) {
10
+ logger.warn('Adex DMP', `values for key ${mappingDefinition.key}/valueKey ${mappingDefinition.valueKey} were empty or key was an array. Key/Value:`, mapKey, mapValue);
11
+ return undefined;
12
+ }
13
+ const adexTargetValue = convertToAdexTargetValue(mapValue, mappingDefinition, logger);
14
+ return {
15
+ [mappingDefinition.attribute]: {
16
+ [mapKey]: adexTargetValue
17
+ }
18
+ };
19
+ };
20
+ export const toAdexStringOrNumberType = (keyValueMap, mappingDefinition, logger) => {
21
+ const value = extractStringOrNumber(keyValueMap, mappingDefinition.adexValueType, mappingDefinition.key);
22
+ if ((value === undefined && mappingDefinition.defaultValue === undefined) ||
23
+ (mappingDefinition.adexValueType === 'number' &&
24
+ ((Number.isNaN(value) && mappingDefinition.defaultValue === undefined) ||
25
+ Array.isArray(value)))) {
26
+ logger.warn('Adex DMP', `value for key ${mappingDefinition.key} was empty or key was an array. Value:`, value);
27
+ return undefined;
28
+ }
29
+ const adexTargetValue = convertToAdexTargetValue(value, mappingDefinition, logger);
30
+ return {
31
+ [mappingDefinition.attribute]: adexTargetValue
32
+ };
33
+ };
34
+ export const toAdexListType = (keyValueMap, mappingDefinition, logger) => {
35
+ const value = extractStringOrNumber(keyValueMap, 'string', mappingDefinition.key);
36
+ if ((value === undefined &&
37
+ (mappingDefinition.defaultValue === undefined ||
38
+ mappingDefinition.defaultValue.length === 0)) ||
39
+ typeof value === 'number') {
40
+ logger.warn('Adex DMP', `value for key "${mappingDefinition.key}" was empty or number. Value:`, value);
41
+ return undefined;
42
+ }
43
+ const adexTargetValue = convertToAdexListValue(value, mappingDefinition, logger);
44
+ return {
45
+ [mappingDefinition.attribute]: adexTargetValue
46
+ };
47
+ };
48
+ const sortAndToListObject = (arr) => Object.fromEntries(arr.sort().map(listValue => [listValue, 1]));
49
+ const sortAndJoin = (arr) => arr.sort().join(',');
50
+ const extractStringOrNumber = (keyValueMap, valueType, keyToExtract) => (valueType === 'number' ? Number(keyValueMap[keyToExtract]) : keyValueMap[keyToExtract]);
51
+ const convertToAdexTargetValue = (value, mappingDefinition, logger) => value !== undefined && !Number.isNaN(value)
52
+ ?
53
+ Array.isArray(value)
54
+ ?
55
+ sortAndJoin(value)
56
+ :
57
+ value
58
+ :
59
+ logAndUseDefaultValue(mappingDefinition, logger);
60
+ const convertToAdexListValue = (value, mappingDefinition, logger) => {
61
+ if (value === undefined) {
62
+ return logAndUseDefaultValue(mappingDefinition, logger);
63
+ }
64
+ return Array.isArray(value)
65
+ ?
66
+ sortAndToListObject(value)
67
+ :
68
+ { [value]: 1 };
69
+ };
70
+ const logAndUseDefaultValue = (mappingDefinition, logger) => {
71
+ logger.warn('Adex DMP', 'using defaultValue', mappingDefinition.defaultValue, 'as fallback for key', mappingDefinition.key);
72
+ return mappingDefinition.adexValueType === 'list'
73
+ ? mappingDefinition.defaultValue
74
+ ? sortAndToListObject(mappingDefinition.defaultValue)
75
+ : undefined
76
+ : mappingDefinition.defaultValue;
77
+ };
@@ -0,0 +1,15 @@
1
+ export const trackUtiqId = (config, window__) => {
2
+ var _a;
3
+ window__.Utiq || (window__.Utiq = { queue: [] });
4
+ (_a = window__.Utiq).queue || (_a.queue = []);
5
+ window__.Utiq.queue.push(() => {
6
+ window__.Utiq?.API?.addEventListener('onIdsAvailable', ({ mtid }) => {
7
+ window__._adexc.push([
8
+ `/${config.adexCustomerId}/${config.adexTagId}/`,
9
+ 'cm',
10
+ '_cm',
11
+ [308, mtid]
12
+ ]);
13
+ });
14
+ });
15
+ };
@@ -0,0 +1,142 @@
1
+ import { mkConfigureStep, mkInitStep } from 'ad-tag/ads/adPipeline';
2
+ import { AssetLoadMethod } from 'ad-tag/util/assetLoaderService';
3
+ import { isNotNull } from 'ad-tag/util/arrayUtils';
4
+ import { sendAdvertisingID } from 'ad-tag/ads/modules/adex/sendAdvertisingId';
5
+ import { tcfapi } from 'ad-tag/types/tcfapi';
6
+ var TCPurpose = tcfapi.responses.TCPurpose;
7
+ import { toAdexListType, toAdexMapType, toAdexStringOrNumberType } from 'ad-tag/ads/modules/adex/adex-mapping';
8
+ import { trackUtiqId } from 'ad-tag/ads/modules/adex/adexUtiq';
9
+ export class AdexModule {
10
+ constructor() {
11
+ this.name = 'the-adex-dmp';
12
+ this.description = 'Moli DMP module for The Adex.';
13
+ this.moduleType = 'dmp';
14
+ this.adexConfig = null;
15
+ this.isLoaded = false;
16
+ this.hasRequiredConsent = (tcData) => !tcData.gdprApplies ||
17
+ (tcData.vendor.consents['44'] &&
18
+ [
19
+ TCPurpose.STORE_INFORMATION_ON_DEVICE,
20
+ TCPurpose.SELECT_BASIC_ADS,
21
+ TCPurpose.CREATE_PERSONALISED_ADS_PROFILE,
22
+ TCPurpose.SELECT_PERSONALISED_ADS,
23
+ TCPurpose.CREATE_PERSONALISED_CONTENT_PROFILE,
24
+ TCPurpose.SELECT_PERSONALISED_CONTENT,
25
+ TCPurpose.MEASURE_AD_PERFORMANCE,
26
+ TCPurpose.MEASURE_CONTENT_PERFORMANCE,
27
+ TCPurpose.APPLY_MARKET_RESEARCH,
28
+ TCPurpose.DEVELOP_IMPROVE_PRODUCTS
29
+ ].every(purpose => tcData.purpose.consents[purpose]));
30
+ this.getOrInitAdexQueue = (_window) => {
31
+ const adexWindow = _window;
32
+ adexWindow._adexc = adexWindow._adexc || [];
33
+ return adexWindow;
34
+ };
35
+ this.getAdexKeyValues = (context, config) => {
36
+ const gamKeyValues = {
37
+ ...context.config__.targeting?.keyValues,
38
+ ...context.runtimeConfig__.keyValues
39
+ };
40
+ if (Object.keys(gamKeyValues).length > 0) {
41
+ return config.mappingDefinitions
42
+ .map(def => {
43
+ switch (def.adexValueType) {
44
+ case 'map':
45
+ return toAdexMapType(gamKeyValues, def, context.logger__);
46
+ case 'list':
47
+ return toAdexListType(gamKeyValues, def, context.logger__);
48
+ default:
49
+ return toAdexStringOrNumberType(gamKeyValues, def, context.logger__);
50
+ }
51
+ })
52
+ .filter(isNotNull);
53
+ }
54
+ };
55
+ this.configureAdexC = (context, config) => {
56
+ const adexWindow = this.getOrInitAdexQueue(context.window__);
57
+ const adexKeyValues = this.getAdexKeyValues(context, config);
58
+ if (!adexKeyValues) {
59
+ context.logger__.warn('Adex DMP', 'targeting key/values are empty');
60
+ }
61
+ else {
62
+ adexWindow._adexc.push([
63
+ `/${config.adexCustomerId}/${config.adexTagId}/`,
64
+ 'ut',
65
+ '_kv',
66
+ [
67
+ adexKeyValues.reduce((aggregator, additionalKeyValue) => ({ ...aggregator, ...additionalKeyValue }), {}),
68
+ config.spaMode ? 1 : 0
69
+ ]
70
+ ]);
71
+ }
72
+ if (config.enabledPartners?.includes('utiq')) {
73
+ trackUtiqId(config, context.window__);
74
+ }
75
+ };
76
+ }
77
+ config__() {
78
+ return this.adexConfig;
79
+ }
80
+ configure__(moduleConfig) {
81
+ if (moduleConfig?.adex && moduleConfig.adex.enabled) {
82
+ this.adexConfig = moduleConfig.adex;
83
+ }
84
+ }
85
+ initSteps__() {
86
+ const config = this.adexConfig;
87
+ if (config) {
88
+ return [mkInitStep('DMP module setup', context => this.track(context, config))];
89
+ }
90
+ return [];
91
+ }
92
+ configureSteps__() {
93
+ const config = this.adexConfig;
94
+ if (config?.spaMode) {
95
+ return [
96
+ mkConfigureStep('DMP module setup', context => {
97
+ this.configureAdexC(context, config);
98
+ return Promise.resolve();
99
+ })
100
+ ];
101
+ }
102
+ return [];
103
+ }
104
+ track(context, adexConfig) {
105
+ const { adexCustomerId, adexTagId, appConfig } = adexConfig;
106
+ this.configureAdexC(context, adexConfig);
107
+ const adexKeyValues = this.getAdexKeyValues(context, adexConfig);
108
+ const gamKeyValues = {
109
+ ...context.config__.targeting?.keyValues,
110
+ ...context.runtimeConfig__.keyValues
111
+ };
112
+ if (this.hasRequiredConsent(context.tcData__) &&
113
+ !this.isLoaded &&
114
+ Object.keys(gamKeyValues).length > 0) {
115
+ this.isLoaded = true;
116
+ const hasValidMobileKeyValues = isNotNull(appConfig) &&
117
+ isNotNull(gamKeyValues[appConfig.advertiserIdKey]) &&
118
+ (gamKeyValues[appConfig.clientTypeKey] === 'android' ||
119
+ gamKeyValues[appConfig.clientTypeKey] === 'ios');
120
+ if (appConfig?.advertiserIdKey && hasValidMobileKeyValues) {
121
+ const consentString = context.tcData__.gdprApplies ? context.tcData__.tcString : undefined;
122
+ const advertisingIdValue = gamKeyValues[appConfig.advertiserIdKey];
123
+ typeof advertisingIdValue === 'string' &&
124
+ adexKeyValues &&
125
+ sendAdvertisingID(adexCustomerId, appConfig.adexMobileTagId ? appConfig.adexMobileTagId : adexTagId, advertisingIdValue, adexKeyValues, gamKeyValues[appConfig.clientTypeKey] ?? '', context.window__.fetch, context.logger__, consentString);
126
+ }
127
+ else {
128
+ context.assetLoaderService__
129
+ .loadScript({
130
+ name: this.name,
131
+ assetUrl: `https://dmp.theadex.com/d/${adexCustomerId}/${adexTagId}/s/adex.js`,
132
+ loadMethod: AssetLoadMethod.TAG
133
+ })
134
+ .catch(error => context.logger__.error('failed to load adex', error));
135
+ }
136
+ }
137
+ return Promise.resolve();
138
+ }
139
+ prepareRequestAdsSteps__() {
140
+ return [];
141
+ }
142
+ }
@@ -0,0 +1,20 @@
1
+ const getAdvertisingIdIFAType = (clientType) => {
2
+ if (clientType === 'android') {
3
+ return 'aaid';
4
+ }
5
+ else if (clientType === 'ios') {
6
+ return 'idfa';
7
+ }
8
+ else {
9
+ return;
10
+ }
11
+ };
12
+ export const sendAdvertisingID = (adexCustomerId, adexTagId, advertisingId, adexAttributes, clientType, fetch, logger, consentString) => {
13
+ const ifaType = getAdvertisingIdIFAType(typeof clientType === 'string' ? clientType : clientType[0]);
14
+ const keyValuesMap = adexAttributes.reduce((acc, currentValue) => {
15
+ return { ...acc, ...currentValue };
16
+ }, {});
17
+ const keyValuesParameter = !!adexAttributes.length ? `&kv=${JSON.stringify(keyValuesMap)}` : '';
18
+ const consentParameter = consentString ? `&gdpr_consent=${consentString}` : '';
19
+ fetch(`https://api.theadex.com/collector/v1/ifa/c/${adexCustomerId}/t/${adexTagId}/request?&ifa=${advertisingId}&ifa_type=${ifaType}${keyValuesParameter}${consentParameter}`).catch(error => logger.error(error));
20
+ };
@@ -0,0 +1,118 @@
1
+ import { mkConfigureStep, mkInitStep } from '../../adPipeline';
2
+ const matches = (href, log) => (entry) => {
3
+ switch (entry.matchType) {
4
+ case 'exact':
5
+ return href === entry.pattern;
6
+ case 'contains':
7
+ return href.indexOf(entry.pattern) > -1;
8
+ case 'regex':
9
+ try {
10
+ return RegExp(entry.pattern).test(href);
11
+ }
12
+ catch (e) {
13
+ log.error(`Invalid regex pattern: ${entry.pattern}`, e);
14
+ return false;
15
+ }
16
+ default:
17
+ return false;
18
+ }
19
+ };
20
+ export const isBlocklisted = (blocklist, href, log) => blocklist.urls.some(matches(href, log));
21
+ export const createBlocklistedUrls = () => {
22
+ let blocklistConfig = null;
23
+ let blocklistCache = null;
24
+ const config__ = () => blocklistConfig;
25
+ const configure__ = (moduleConfig) => {
26
+ if (moduleConfig?.blocklist && moduleConfig.blocklist.enabled) {
27
+ blocklistConfig = moduleConfig.blocklist;
28
+ }
29
+ };
30
+ const getBlocklist = (blocklist, assetLoaderService, log) => {
31
+ switch (blocklist.provider) {
32
+ case 'static':
33
+ return () => Promise.resolve(blocklist.blocklist);
34
+ case 'dynamic':
35
+ let cachedResult;
36
+ return () => {
37
+ if (!cachedResult) {
38
+ cachedResult = loadConfigWithRetry(assetLoaderService, blocklist.endpoint, 3).catch(e => {
39
+ log.error('Blocklist URLs', 'Fetching blocklisted urls failed', e);
40
+ return { urls: [] };
41
+ });
42
+ }
43
+ return cachedResult;
44
+ };
45
+ }
46
+ };
47
+ const loadConfigWithRetry = (assetLoaderService, endpoint, retriesLeft, lastError = null) => {
48
+ if (retriesLeft <= 0) {
49
+ return Promise.reject(lastError);
50
+ }
51
+ if (!blocklistCache) {
52
+ blocklistCache = assetLoaderService
53
+ .loadJson('blocklist-urls.json', endpoint)
54
+ .catch(error => {
55
+ const exponentialBackoff = new Promise(resolve => setTimeout(resolve, 100 / retriesLeft));
56
+ return exponentialBackoff.then(() => loadConfigWithRetry(assetLoaderService, endpoint, retriesLeft - 1, error));
57
+ });
58
+ }
59
+ return blocklistCache;
60
+ };
61
+ const initSteps__ = () => {
62
+ return blocklistConfig
63
+ ? [
64
+ mkInitStep('blocklist-urls-init', ctx => {
65
+ switch (blocklistConfig?.mode) {
66
+ case 'key-value':
67
+ const key = blocklistConfig.key;
68
+ const value = blocklistConfig.isBlocklistedValue || 'true';
69
+ return getBlocklist(blocklistConfig.blocklist, ctx.assetLoaderService__, ctx.logger__)().then(blocklist => {
70
+ if (isBlocklisted(blocklist, ctx.window__.location.href, ctx.logger__)) {
71
+ ctx.window__.googletag
72
+ .pubads()
73
+ .setTargeting(key, value);
74
+ }
75
+ });
76
+ case 'block':
77
+ return getBlocklist(blocklistConfig.blocklist, ctx.assetLoaderService__, ctx.logger__)().then(blocklist => {
78
+ ctx.logger__.debug('Blocklist URLs', 'using blocklist', blocklist);
79
+ if (isBlocklisted(blocklist, ctx.window__.location.href, ctx.logger__)) {
80
+ return Promise.reject('blocklisted url found. Abort ad pipeline run');
81
+ }
82
+ });
83
+ default:
84
+ return Promise.resolve();
85
+ }
86
+ })
87
+ ]
88
+ : [];
89
+ };
90
+ const configureSteps__ = () => {
91
+ const config = blocklistConfig;
92
+ return config
93
+ ? [
94
+ mkConfigureStep('blocklist-labels', async (ctx) => {
95
+ const loadedBlocklist = await getBlocklist(config.blocklist, ctx.assetLoaderService__, ctx.logger__)();
96
+ const matcher = matches(ctx.window__.location.href, ctx.logger__);
97
+ loadedBlocklist.labels?.forEach(entry => {
98
+ const result = entry.reverseMatch === true ? !matcher(entry) : matcher(entry);
99
+ if (result) {
100
+ ctx.labelConfigService__.addLabel(entry.label);
101
+ }
102
+ });
103
+ })
104
+ ]
105
+ : [];
106
+ };
107
+ const prepareRequestAdsSteps__ = () => [];
108
+ return {
109
+ name: 'Blocklist URLs',
110
+ description: '',
111
+ moduleType: 'policy',
112
+ config__,
113
+ configure__,
114
+ initSteps__,
115
+ configureSteps__,
116
+ prepareRequestAdsSteps__
117
+ };
118
+ };