@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,380 @@
1
+ import { LOW_PRIORITY, mkConfigureStep, mkConfigureStepOncePerRequestAdsCycle, mkInitStep, mkPrepareRequestAdsStep } from './adPipeline';
2
+ import { SizeConfigService } from './sizeConfigService';
3
+ import { isNotNull } from '../util/arrayUtils';
4
+ import { AssetLoadMethod } from '../util/assetLoaderService';
5
+ import { tcfapi } from '../types/tcfapi';
6
+ import { createTestSlots } from '../util/test-slots';
7
+ import { resolveAdUnitPath } from './adUnitPath';
8
+ import { formatKey } from './keyValues';
9
+ const testAdSlot = (domId, adUnitPath) => ({
10
+ setCollapseEmptyDiv() {
11
+ return;
12
+ },
13
+ addService(_service) {
14
+ return;
15
+ },
16
+ getSlotElementId() {
17
+ return domId;
18
+ },
19
+ getAdUnitPath() {
20
+ return adUnitPath;
21
+ },
22
+ setTargeting(_key, _value) {
23
+ return this;
24
+ },
25
+ getTargeting(_key) {
26
+ return [];
27
+ },
28
+ getTargetingKeys() {
29
+ return [];
30
+ },
31
+ clearTargeting(_key) {
32
+ return;
33
+ },
34
+ getResponseInformation() {
35
+ return null;
36
+ },
37
+ setConfig(_config) {
38
+ return;
39
+ }
40
+ });
41
+ const configureTargeting = (window, runtimeKeyValues, serverSideTargeting) => {
42
+ const staticKeyValues = serverSideTargeting ? serverSideTargeting.keyValues : {};
43
+ const excludes = serverSideTargeting?.adManagerExcludes ?? [];
44
+ [staticKeyValues, runtimeKeyValues].forEach(keyValues => {
45
+ Object.keys(keyValues)
46
+ .filter(key => !excludes.includes(key))
47
+ .forEach(key => {
48
+ const value = keyValues[key];
49
+ if (value) {
50
+ window.googletag.pubads().setTargeting(key, value);
51
+ }
52
+ });
53
+ });
54
+ };
55
+ const useStandardGpt = (tcData, consentConfig) => {
56
+ if (consentConfig?.useLimitedAds === false) {
57
+ return true;
58
+ }
59
+ return (!tcData.gdprApplies ||
60
+ (tcData.vendor.consents[755] &&
61
+ tcData.purpose.consents[tcfapi.responses.TCPurpose.STORE_INFORMATION_ON_DEVICE] &&
62
+ [
63
+ tcfapi.responses.TCPurpose.SELECT_BASIC_ADS,
64
+ tcfapi.responses.TCPurpose.MEASURE_AD_PERFORMANCE,
65
+ tcfapi.responses.TCPurpose.APPLY_MARKET_RESEARCH,
66
+ tcfapi.responses.TCPurpose.DEVELOP_IMPROVE_PRODUCTS
67
+ ].every(purposeId => tcData.purpose.consents[purposeId] || tcData.purpose.legitimateInterests[purposeId])));
68
+ };
69
+ export const gptInit = () => {
70
+ let result;
71
+ return mkInitStep('gpt-init', (context) => {
72
+ if (context.env__ === 'test') {
73
+ return Promise.resolve();
74
+ }
75
+ if (!result) {
76
+ result = new Promise(resolve => {
77
+ context.logger__.debug('GAM', 'init googletag stub');
78
+ context.window__.googletag = context.window__.googletag || {};
79
+ context.window__.googletag.cmd = context.window__.googletag.cmd || [];
80
+ context.window__.googletag.cmd.push(resolve);
81
+ context.assetLoaderService__
82
+ .loadScript({
83
+ name: 'gpt',
84
+ loadMethod: AssetLoadMethod.TAG,
85
+ assetUrl: useStandardGpt(context.tcData__, context.config__.consent)
86
+ ? 'https://securepubads.g.doubleclick.net/tag/js/gpt.js'
87
+ : 'https://pagead2.googlesyndication.com/tag/js/gpt.js'
88
+ })
89
+ .catch(error => context.logger__.error('failed to load gpt.js', error));
90
+ });
91
+ }
92
+ return result;
93
+ });
94
+ };
95
+ export const gptDestroyAdSlots = () => {
96
+ let currentRequestAdsCalls = 0;
97
+ return mkConfigureStep('gpt-destroy-ad-slots', (context, slots) => {
98
+ if (context.env__ === 'test') {
99
+ return Promise.resolve();
100
+ }
101
+ const cleanup = context.config__.spa?.cleanup ?? { slots: 'all' };
102
+ const destroySelectedSlots = (slots) => {
103
+ if (slots.length === 0) {
104
+ context.logger__.debug('GAM', 'no ad slots to destroy');
105
+ return Promise.resolve();
106
+ }
107
+ context.logger__.debug('GAM', `destroy ${slots.length} ad slots`, slots);
108
+ context.window__.googletag.destroySlots(slots);
109
+ return Promise.resolve();
110
+ };
111
+ const isNextRequestAdsCall = currentRequestAdsCalls !== context.requestAdsCalls__;
112
+ currentRequestAdsCalls = context.requestAdsCalls__;
113
+ context.logger__.debug('GAM', `destroy ${cleanup.slots} ad slots`);
114
+ switch (cleanup.slots) {
115
+ case 'all':
116
+ if (isNextRequestAdsCall) {
117
+ context.window__.googletag.destroySlots();
118
+ }
119
+ return Promise.resolve();
120
+ case 'requested':
121
+ const allGptSlots = context.window__.googletag.pubads().getSlots();
122
+ const gptSlots = slots
123
+ .map(slot => allGptSlots.find(s => s.getSlotElementId() === slot.domId))
124
+ .filter(isNotNull);
125
+ return destroySelectedSlots(gptSlots);
126
+ case 'excluded':
127
+ if (isNextRequestAdsCall) {
128
+ const destroyableSlots = context.window__.googletag
129
+ .pubads()
130
+ .getSlots()
131
+ .filter(slot => !cleanup.slotIds.includes(slot.getSlotElementId()));
132
+ return destroySelectedSlots(destroyableSlots);
133
+ }
134
+ return Promise.resolve();
135
+ default:
136
+ return Promise.resolve();
137
+ }
138
+ });
139
+ };
140
+ export const gptResetTargeting = () => mkConfigureStepOncePerRequestAdsCycle('gpt-reset-targeting', (context) => new Promise(resolve => {
141
+ if (context.env__ === 'production') {
142
+ context.logger__.debug('GAM', 'reset top level targeting');
143
+ context.window__.googletag.pubads().clearTargeting();
144
+ configureTargeting(context.window__, context.runtimeConfig__.keyValues, context.config__.targeting);
145
+ }
146
+ resolve();
147
+ }));
148
+ export const gptConfigure = () => {
149
+ let result;
150
+ return mkConfigureStep('gpt-configure', (context, _slots) => {
151
+ if (!result) {
152
+ result = new Promise(resolve => {
153
+ const env = context.runtimeConfig__.environment || 'production';
154
+ context.logger__.debug('GAM', 'configure googletag');
155
+ switch (env) {
156
+ case 'production':
157
+ configureTargeting(context.window__, context.runtimeConfig__.keyValues, context.config__.targeting);
158
+ context.window__.googletag.pubads().enableAsyncRendering();
159
+ context.window__.googletag.pubads().disableInitialLoad();
160
+ context.window__.googletag.pubads().enableSingleRequest();
161
+ if (context.config__.gpt?.pageSettingsConfig) {
162
+ context.window__.googletag.setConfig(context.config__.gpt.pageSettingsConfig);
163
+ }
164
+ const limitedAds = !useStandardGpt(context.tcData__, context.config__.consent);
165
+ context.logger__.debug('GAM', `use limited ads`, limitedAds);
166
+ context.window__.googletag.pubads().setPrivacySettings({
167
+ limitedAds
168
+ });
169
+ context.window__.googletag.enableServices();
170
+ resolve();
171
+ return;
172
+ case 'test':
173
+ resolve();
174
+ return;
175
+ }
176
+ });
177
+ }
178
+ return result;
179
+ });
180
+ };
181
+ export const gptLDeviceLabelKeyValue = () => mkPrepareRequestAdsStep('gpt-device-label-keyValue', LOW_PRIORITY, ctx => {
182
+ if (ctx.env__ === 'test') {
183
+ return Promise.resolve();
184
+ }
185
+ return new Promise(resolve => {
186
+ const deviceLabel = ctx.labelConfigService__.getDeviceLabel();
187
+ ctx.logger__.debug('GAM', 'adding "device_label" key-value with values', deviceLabel);
188
+ ctx.window__.googletag.pubads().setTargeting('device_label', deviceLabel);
189
+ resolve();
190
+ });
191
+ });
192
+ export const gptConsentKeyValue = () => mkPrepareRequestAdsStep('gpt-consent-keyValue', LOW_PRIORITY, ctx => {
193
+ if (ctx.env__ === 'test') {
194
+ return Promise.resolve();
195
+ }
196
+ return new Promise(resolve => {
197
+ const tcData = ctx.tcData__;
198
+ const fullConsent = !tcData.gdprApplies ||
199
+ [
200
+ tcfapi.responses.TCPurpose.STORE_INFORMATION_ON_DEVICE,
201
+ tcfapi.responses.TCPurpose.SELECT_BASIC_ADS,
202
+ tcfapi.responses.TCPurpose.CREATE_PERSONALISED_ADS_PROFILE,
203
+ tcfapi.responses.TCPurpose.SELECT_PERSONALISED_ADS,
204
+ tcfapi.responses.TCPurpose.CREATE_PERSONALISED_CONTENT_PROFILE,
205
+ tcfapi.responses.TCPurpose.SELECT_PERSONALISED_CONTENT,
206
+ tcfapi.responses.TCPurpose.MEASURE_AD_PERFORMANCE,
207
+ tcfapi.responses.TCPurpose.MEASURE_CONTENT_PERFORMANCE,
208
+ tcfapi.responses.TCPurpose.APPLY_MARKET_RESEARCH,
209
+ tcfapi.responses.TCPurpose.DEVELOP_IMPROVE_PRODUCTS
210
+ ].every(purpose => tcData.purpose.consents[purpose]);
211
+ ctx.window__.googletag.pubads().setTargeting('consent', fullConsent ? 'full' : 'none');
212
+ resolve();
213
+ });
214
+ });
215
+ export const gptDefineSlots = () => (context, slots) => {
216
+ const slotDefinitions = slots.map(moliSlot => {
217
+ const sizeConfigService = new SizeConfigService(moliSlot.sizeConfig, context.labelConfigService__.getSupportedLabels(), context.window__);
218
+ const filterSupportedSizes = sizeConfigService.filterSupportedSizes;
219
+ if (!(sizeConfigService.filterSlot(moliSlot) &&
220
+ context.labelConfigService__.filterSlot(moliSlot))) {
221
+ return Promise.resolve(null);
222
+ }
223
+ const sizes = filterSupportedSizes(moliSlot.sizes);
224
+ const resolvedAdUnitPath = resolveAdUnitPath(moliSlot.adUnitPath, context.adUnitPathVariables__);
225
+ const createDivIfMissing = (domId) => {
226
+ if (!context.window__.document.getElementById(domId)) {
227
+ const slot = context.window__.document.createElement('div');
228
+ slot.id = domId;
229
+ slot.setAttribute('data-h5v-position', moliSlot.position);
230
+ slot.style.setProperty('display', 'none');
231
+ context.window__.document.body.appendChild(slot);
232
+ }
233
+ };
234
+ const defineAdSlot = () => {
235
+ switch (moliSlot.position) {
236
+ case 'in-page':
237
+ return [
238
+ context.window__.googletag.defineSlot(resolvedAdUnitPath, sizes, moliSlot.domId),
239
+ null
240
+ ];
241
+ case 'interstitial':
242
+ createDivIfMissing(moliSlot.domId);
243
+ switch (context.auction__.interstitialChannel()) {
244
+ case 'gam':
245
+ return [
246
+ context.window__.googletag.defineOutOfPageSlot(resolvedAdUnitPath, context.window__.googletag.enums.OutOfPageFormat.INTERSTITIAL),
247
+ context.window__.googletag.enums.OutOfPageFormat.INTERSTITIAL
248
+ ];
249
+ case 'c':
250
+ default:
251
+ return [
252
+ context.window__.googletag.defineSlot(resolvedAdUnitPath, sizes, moliSlot.domId),
253
+ null
254
+ ];
255
+ }
256
+ case 'out-of-page':
257
+ createDivIfMissing(moliSlot.domId);
258
+ return [
259
+ context.window__.googletag.defineOutOfPageSlot(resolvedAdUnitPath, moliSlot.domId),
260
+ null
261
+ ];
262
+ case 'out-of-page-interstitial':
263
+ return [
264
+ context.window__.googletag.defineOutOfPageSlot(resolvedAdUnitPath, context.window__.googletag.enums.OutOfPageFormat.INTERSTITIAL),
265
+ context.window__.googletag.enums.OutOfPageFormat.INTERSTITIAL
266
+ ];
267
+ case 'out-of-page-bottom-anchor':
268
+ return [
269
+ context.window__.googletag.defineOutOfPageSlot(resolvedAdUnitPath, context.window__.googletag.enums.OutOfPageFormat.BOTTOM_ANCHOR),
270
+ context.window__.googletag.enums.OutOfPageFormat.BOTTOM_ANCHOR
271
+ ];
272
+ case 'out-of-page-top-anchor':
273
+ return [
274
+ context.window__.googletag.defineOutOfPageSlot(resolvedAdUnitPath, context.window__.googletag.enums.OutOfPageFormat.TOP_ANCHOR),
275
+ context.window__.googletag.enums.OutOfPageFormat.TOP_ANCHOR
276
+ ];
277
+ }
278
+ };
279
+ const defineAndDisplayAdSlot = () => {
280
+ if (context.env__ === 'test') {
281
+ return null;
282
+ }
283
+ const [adSlot, format] = defineAdSlot();
284
+ if (adSlot) {
285
+ if (format) {
286
+ adSlot.setTargeting(formatKey, format.toString());
287
+ }
288
+ else {
289
+ adSlot.clearTargeting(formatKey);
290
+ }
291
+ context.window__.googletag.display(adSlot);
292
+ }
293
+ return adSlot;
294
+ };
295
+ const allSlots = context.env__ === 'test' ? [] : context.window__.googletag.pubads().getSlots();
296
+ const existingSlot = allSlots.find(s => s.getSlotElementId() === moliSlot.domId);
297
+ const adSlot = existingSlot
298
+ ? existingSlot
299
+ : defineAndDisplayAdSlot();
300
+ switch (context.env__) {
301
+ case 'production':
302
+ if (adSlot) {
303
+ if (moliSlot.gpt) {
304
+ adSlot.setConfig(moliSlot.gpt);
305
+ context.logger__.debug('GAM', `Add slot settings: [AdSlot] ${adSlot} [Settings] ${moliSlot.gpt}`);
306
+ }
307
+ adSlot.setCollapseEmptyDiv(moliSlot.gpt?.collapseEmptyDiv !== false);
308
+ adSlot.addService(context.window__.googletag.pubads());
309
+ context.logger__.debug('GAM', `Register slot: [DomID] ${moliSlot.domId} [AdUnitPath] ${moliSlot.adUnitPath}`);
310
+ return Promise.resolve({
311
+ moliSlot,
312
+ adSlot,
313
+ filterSupportedSizes
314
+ });
315
+ }
316
+ else if (moliSlot.position === 'out-of-page-interstitial' ||
317
+ moliSlot.position === 'out-of-page-top-anchor' ||
318
+ moliSlot.position === 'out-of-page-bottom-anchor') {
319
+ context.logger__.warn('GAM', `${moliSlot.position} is not supported`);
320
+ return Promise.resolve(null);
321
+ }
322
+ else {
323
+ const error = `Slot: [DomID] ${moliSlot.domId} [AdUnitPath] ${moliSlot.adUnitPath} is already defined. You may have called requestAds() multiple times`;
324
+ context.logger__.error('GAM', error);
325
+ return Promise.reject(new Error(error));
326
+ }
327
+ case 'test':
328
+ return Promise.resolve({
329
+ moliSlot,
330
+ adSlot: testAdSlot(moliSlot.domId, moliSlot.adUnitPath),
331
+ filterSupportedSizes
332
+ });
333
+ default:
334
+ return Promise.reject(`invalid environment: ${context.runtimeConfig__.environment}`);
335
+ }
336
+ });
337
+ return Promise.all(slotDefinitions).then(slots => slots.filter(isNotNull));
338
+ };
339
+ const checkAndSwitchToWebInterstitial = (slotsToRefresh, context) => {
340
+ const interstitialSlot = slotsToRefresh.find(({ moliSlot }) => moliSlot.position === 'interstitial');
341
+ if (interstitialSlot && context.auction__.interstitialChannel() === 'gam') {
342
+ context.window__.googletag.destroySlots([interstitialSlot.adSlot]);
343
+ const gamWebInterstitial = context.window__.googletag.defineOutOfPageSlot(resolveAdUnitPath(interstitialSlot.moliSlot.adUnitPath, context.adUnitPathVariables__), context.window__.googletag.enums.OutOfPageFormat.INTERSTITIAL);
344
+ if (gamWebInterstitial) {
345
+ context.logger__.debug('GAM', 'Display out-of-page-interstitial slot');
346
+ gamWebInterstitial.addService(context.window__.googletag.pubads());
347
+ gamWebInterstitial.setTargeting(formatKey, context.window__.googletag.enums.OutOfPageFormat.INTERSTITIAL.toString());
348
+ context.window__.googletag.display(gamWebInterstitial);
349
+ return [
350
+ ...slotsToRefresh.filter(({ moliSlot }) => moliSlot.position !== 'interstitial'),
351
+ { ...interstitialSlot, adSlot: gamWebInterstitial }
352
+ ];
353
+ }
354
+ else {
355
+ context.logger__.error('GAM', 'Failed to define out-of-page-interstitial slot');
356
+ }
357
+ }
358
+ return slotsToRefresh;
359
+ };
360
+ export const gptRequestAds = () => (context, slots) => new Promise(resolve => {
361
+ context.logger__.debug('GAM', 'requestAds');
362
+ switch (context.env__) {
363
+ case 'test':
364
+ createTestSlots(context, slots);
365
+ break;
366
+ case 'production':
367
+ const slotsToRefresh = slots.filter(({ adSlot }) => !context.auction__.isSlotThrottled(adSlot));
368
+ if (slotsToRefresh.length === 0) {
369
+ break;
370
+ }
371
+ const updatedSlots = checkAndSwitchToWebInterstitial(slotsToRefresh, context);
372
+ context.window__.googletag.pubads().refresh(updatedSlots.map(({ adSlot }) => adSlot));
373
+ const debugMessage = updatedSlots
374
+ .map(({ moliSlot }) => `[DomID] ${moliSlot.domId} [AdUnitPath] ${moliSlot.adUnitPath}`)
375
+ .join('\n');
376
+ context.logger__.debug('GAM', `Refresh ${slots.length} slot(s):\n${debugMessage}`);
377
+ break;
378
+ }
379
+ resolve();
380
+ });
@@ -0,0 +1 @@
1
+ export const formatKey = 'f';
@@ -0,0 +1,42 @@
1
+ import { flatten, uniquePrimitiveFilter } from '../util/arrayUtils';
2
+ const possibleDevices = ['mobile', 'desktop', 'android', 'ios'];
3
+ export const createLabelConfigService = (labelSizeConfig, extraLabels, window) => {
4
+ const isPublisherDeviceDefined = extraLabels.some((label) => possibleDevices.indexOf(label) > -1);
5
+ const supportedLabelSizeConfigs = labelSizeConfig.filter(conf => window.matchMedia(conf.mediaQuery).matches);
6
+ const supportedLabels = [
7
+ ...(isPublisherDeviceDefined
8
+ ? []
9
+ : flatten(supportedLabelSizeConfigs.map(conf => conf.labelsSupported))),
10
+ ...extraLabels
11
+ ].filter(uniquePrimitiveFilter);
12
+ const filterSlot = (slot) => {
13
+ let labelsMatching = true;
14
+ if (supportedLabels.length && slot.labelAll?.length) {
15
+ labelsMatching = slot.labelAll.every(label => supportedLabels.indexOf(label) > -1);
16
+ }
17
+ if (supportedLabels.length && slot.labelAny && !slot.labelAll?.length) {
18
+ labelsMatching = slot.labelAny.some(label => supportedLabels.indexOf(label) > -1);
19
+ }
20
+ return labelsMatching;
21
+ };
22
+ const getSupportedLabels = () => {
23
+ return supportedLabels;
24
+ };
25
+ const getDeviceLabel = () => {
26
+ return (supportedLabels.find((label) => possibleDevices.indexOf(label) > -1) || 'mobile');
27
+ };
28
+ const addLabel = (label) => {
29
+ if (!possibleDevices.includes(label)) {
30
+ supportedLabels.push(label);
31
+ }
32
+ };
33
+ return {
34
+ filterSlot,
35
+ getSupportedLabels,
36
+ getDeviceLabel,
37
+ addLabel
38
+ };
39
+ };
40
+ export const getDeviceLabel = (window, runtimeConfig, labelSizeConfig, targeting) => {
41
+ return createLabelConfigService(labelSizeConfig ?? [], [...(targeting?.labels || []), ...runtimeConfig.labels], window).getDeviceLabel();
42
+ };
@@ -0,0 +1,158 @@
1
+ export class AdVisibilityService {
2
+ constructor(userActivityService, refreshInterval, refreshIntervalOverrides, useIntersectionObserver, disableAdVisibilityChecks, viewabilityOverrides, window, logger) {
3
+ this.userActivityService = userActivityService;
4
+ this.refreshInterval = refreshInterval;
5
+ this.refreshIntervalOverrides = refreshIntervalOverrides;
6
+ this.useIntersectionObserver = useIntersectionObserver;
7
+ this.disableAdVisibilityChecks = disableAdVisibilityChecks;
8
+ this.viewabilityOverrides = viewabilityOverrides;
9
+ this.window = window;
10
+ this.logger = logger;
11
+ this.isSlotTracked = (domId) => this.visibilityRecords.has(domId);
12
+ this.removeSlotTracking = (slot) => {
13
+ this.logger?.debug('AdVisibilityService', `removing slot visibility tracking for ${slot.getSlotElementId()}`, slot);
14
+ this.visibilityRecords.delete(slot.getSlotElementId());
15
+ const observedSlot = this.observedDomElementForSlot(slot);
16
+ if (this.intersectionObserver && observedSlot) {
17
+ this.intersectionObserver.unobserve(observedSlot.target);
18
+ }
19
+ };
20
+ this.handleGoogletagAdVisibilityChanged = (event) => {
21
+ const slot = event.slot;
22
+ const visibilityRecord = this.visibilityRecordForGoogletagEvent(event);
23
+ if (visibilityRecord) {
24
+ this.logger?.debug('AdVisibilityService', `Visibility of slot ${slot.getSlotElementId()} changed. Visible area: ${event.inViewPercentage}%`);
25
+ this.updateVisibilityRecord(visibilityRecord, event.inViewPercentage / 100);
26
+ }
27
+ };
28
+ this.updateVisibilityRecord = (visibilityRecord, adVisibilityRatio) => {
29
+ if (visibilityRecord.latestStartVisible) {
30
+ const addedDuration = this.window.performance.now() - visibilityRecord.latestStartVisible;
31
+ this.logger?.debug('AdVisibilityService', `added ${Math.round(addedDuration)}ms visibility to ${visibilityRecord.slot.getSlotElementId()}`);
32
+ visibilityRecord.durationVisibleSum += addedDuration;
33
+ }
34
+ if (adVisibilityRatio >= this.minimalAdVisibilityRatio) {
35
+ visibilityRecord.latestStartVisible = this.window.performance.now();
36
+ this.logger?.debug('AdVisibilityService', `ad ${visibilityRecord.slot.getSlotElementId()} visible`);
37
+ }
38
+ else {
39
+ visibilityRecord.latestStartVisible = undefined;
40
+ this.logger?.debug('AdVisibilityService', `ad ${visibilityRecord.slot.getSlotElementId()} not visible`);
41
+ }
42
+ };
43
+ this.visibilityRecordForGoogletagEvent = (event) => this.visibilityRecords.get(event.slot.getSlotElementId());
44
+ this.minimalAdVisibilityRatio = disableAdVisibilityChecks ? 0 : 0.5;
45
+ this.visibilityRecords = new Map();
46
+ const requiredIntersectionObserver = (useIntersectionObserver ||
47
+ Object.values(viewabilityOverrides).some(entry => entry?.variant === 'css')) &&
48
+ 'IntersectionObserver' in this.window;
49
+ if (requiredIntersectionObserver) {
50
+ this.intersectionObserver = new this.window.IntersectionObserver(entries => this.handleObservedAdVisibilityChanged(entries), { threshold: this.minimalAdVisibilityRatio });
51
+ }
52
+ if (!useIntersectionObserver) {
53
+ this.window.googletag.cmd.push(() => {
54
+ this.window.googletag
55
+ .pubads()
56
+ .addEventListener('slotVisibilityChanged', this.handleGoogletagAdVisibilityChanged);
57
+ });
58
+ }
59
+ this.userActivityService.addUserActivityChangedListener(state => this.handleUserActivityChanged(state));
60
+ this.setUpdateTimer(true);
61
+ this.logger?.debug('AdVisibilityService', 'initialized');
62
+ }
63
+ trackSlot(slot, refreshCallback, advertiserId) {
64
+ const slotDomId = slot.getSlotElementId();
65
+ const domElement = this.observedDomElementForSlot(slot);
66
+ if (domElement) {
67
+ this.logger?.debug('AdVisibilityService', `tracking slot ${slot.getSlotElementId()}`, slot);
68
+ if (this.isSlotTracked(slotDomId)) {
69
+ this.removeSlotTracking(slot);
70
+ }
71
+ const override = domElement.viewabilityOverride;
72
+ this.visibilityRecords.set(slot.getSlotElementId(), {
73
+ slot: slot,
74
+ latestStartVisible: this.disableAdVisibilityChecks ||
75
+ (override?.variant === 'disabled' &&
76
+ (override.disableAllAdVisibilityChecks ||
77
+ !(override.disabledAdVisibilityCheckAdvertiserIds &&
78
+ override.disabledAdVisibilityCheckAdvertiserIds.length > 0 &&
79
+ advertiserId) ||
80
+ override.disabledAdVisibilityCheckAdvertiserIds.includes(advertiserId)))
81
+ ? this.window.performance.now()
82
+ : undefined,
83
+ durationVisibleSum: 0,
84
+ refreshCallback: refreshCallback
85
+ });
86
+ if (this.intersectionObserver &&
87
+ (domElement.targetOverride || this.useIntersectionObserver)) {
88
+ this.intersectionObserver.observe(domElement.target);
89
+ }
90
+ }
91
+ }
92
+ setUpdateTimer(state) {
93
+ if (state) {
94
+ this.visibilityUpdateTimer = this.window.setInterval(() => this.updateAdVisibilityDuration(), AdVisibilityService.updateVisibilityInterval);
95
+ }
96
+ else {
97
+ this.window.clearInterval(this.visibilityUpdateTimer);
98
+ this.visibilityUpdateTimer = undefined;
99
+ }
100
+ }
101
+ updateAdVisibilityDuration() {
102
+ this.visibilityRecords.forEach(record => {
103
+ if (record.latestStartVisible) {
104
+ const now = this.window.performance.now();
105
+ const addedDuration = Math.round(now - record.latestStartVisible);
106
+ record.durationVisibleSum += addedDuration;
107
+ record.latestStartVisible = now;
108
+ this.logger?.debug('AdVisibilityService', `added ${addedDuration}ms visibility to ${record.slot.getSlotElementId()}, now totalling at ${record.durationVisibleSum}ms`);
109
+ }
110
+ });
111
+ Array.from(this.visibilityRecords.values())
112
+ .filter(record => {
113
+ const interval = this.refreshIntervalOverrides[record.slot.getSlotElementId()] || this.refreshInterval;
114
+ return record.durationVisibleSum > interval;
115
+ })
116
+ .forEach(record => {
117
+ this.logger?.debug('AdVisibilityService', `refreshing ad ${record.slot.getSlotElementId()} after ${record.durationVisibleSum}ms visibility`);
118
+ record.latestStartVisible = this.window.performance.now();
119
+ record.durationVisibleSum = -AdVisibilityService.consecutiveDurationToRefresh;
120
+ record.refreshCallback(record.slot);
121
+ });
122
+ }
123
+ handleObservedAdVisibilityChanged(entries) {
124
+ entries.forEach(entry => {
125
+ const visibilityRecord = this.visibilityRecordForEntry(entry);
126
+ if (visibilityRecord) {
127
+ this.logger?.debug('AdVisibilityService', `Visibility of slot ${visibilityRecord.slot.getSlotElementId()} changed. Visible area: ${entry.intersectionRatio * 100}%`);
128
+ this.updateVisibilityRecord(visibilityRecord, entry.intersectionRatio);
129
+ }
130
+ });
131
+ }
132
+ handleUserActivityChanged(userActivityState) {
133
+ this.visibilityRecords.forEach(record => {
134
+ if (record.latestStartVisible) {
135
+ record.latestStartVisible = this.window.performance.now();
136
+ }
137
+ });
138
+ this.setUpdateTimer(userActivityState);
139
+ }
140
+ visibilityRecordForEntry(entry) {
141
+ return this.visibilityRecords.get(entry.target.id);
142
+ }
143
+ observedDomElementForSlot(slot) {
144
+ const slotDomId = slot.getSlotElementId();
145
+ const viewabilityOverride = this.viewabilityOverrides[slotDomId];
146
+ const adSlotElement = this.window.document.getElementById(slotDomId);
147
+ const overrideElement = viewabilityOverride && viewabilityOverride.variant === 'css'
148
+ ? this.window.document.querySelector(viewabilityOverride.cssSelector)
149
+ : null;
150
+ return overrideElement
151
+ ? { target: overrideElement, targetOverride: true, viewabilityOverride }
152
+ : adSlotElement
153
+ ? { target: adSlotElement, targetOverride: false, viewabilityOverride }
154
+ : null;
155
+ }
156
+ }
157
+ AdVisibilityService.updateVisibilityInterval = 1000;
158
+ AdVisibilityService.consecutiveDurationToRefresh = 1500;