prebid.js 9.52.0 → 9.53.2

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 (235) hide show
  1. package/.circleci/config.yml +27 -4
  2. package/creative/crossDomain.js +4 -2
  3. package/dist/33acrossAnalyticsAdapter.js +1 -1
  4. package/dist/33acrossBidAdapter.js +1 -1
  5. package/dist/33acrossIdSystem.js +1 -1
  6. package/dist/BTBidAdapter.js +1 -1
  7. package/dist/adagioAnalyticsAdapter.js +1 -1
  8. package/dist/adagioBidAdapter.js +1 -1
  9. package/dist/adagioUtils.js +1 -1
  10. package/dist/addefendBidAdapter.js +1 -1
  11. package/dist/adgenerationBidAdapter.js +1 -1
  12. package/dist/adlooxRtdProvider.js +1 -1
  13. package/dist/adqueryBidAdapter.js +1 -1
  14. package/dist/adrelevantisBidAdapter.js +1 -1
  15. package/dist/adstirBidAdapter.js +1 -1
  16. package/dist/adtrgtmeBidAdapter.js +1 -1
  17. package/dist/adxcgAnalyticsAdapter.js +1 -1
  18. package/dist/adxcgBidAdapter.js +1 -1
  19. package/dist/adyoulikeBidAdapter.js +1 -1
  20. package/dist/agmaAnalyticsAdapter.js +1 -1
  21. package/dist/ajaBidAdapter.js +1 -1
  22. package/dist/amxBidAdapter.js +1 -1
  23. package/dist/amxIdSystem.js +1 -1
  24. package/dist/aniviewBidAdapter.js +1 -1
  25. package/dist/appierAnalyticsAdapter.js +1 -1
  26. package/dist/appnexusBidAdapter.js +1 -1
  27. package/dist/asoBidAdapter.js +1 -1
  28. package/dist/axonixBidAdapter.js +1 -1
  29. package/dist/beopBidAdapter.js +1 -1
  30. package/dist/bidglassBidAdapter.js +1 -1
  31. package/dist/big-richmediaBidAdapter.js +1 -1
  32. package/dist/bitmediaBidAdapter.js +1 -1
  33. package/dist/bridBidAdapter.js +1 -1
  34. package/dist/bridgeuppBidAdapter.js +1 -1
  35. package/dist/bridgewellBidAdapter.js +1 -1
  36. package/dist/brightMountainMediaBidAdapter.js +1 -1
  37. package/dist/carodaBidAdapter.js +1 -1
  38. package/dist/chromeAiRtdProvider.js +1 -0
  39. package/dist/chtnwBidAdapter.js +1 -1
  40. package/dist/chunk-core.js +1 -1
  41. package/dist/concertBidAdapter.js +1 -1
  42. package/dist/connectadBidAdapter.js +1 -1
  43. package/dist/consumableBidAdapter.js +1 -1
  44. package/dist/contxtfulBidAdapter.js +1 -1
  45. package/dist/conversantAnalyticsAdapter.js +1 -1
  46. package/dist/conversantBidAdapter.js +1 -1
  47. package/dist/craftBidAdapter.js +1 -1
  48. package/dist/criteoBidAdapter.js +1 -1
  49. package/dist/cwireBidAdapter.js +1 -1
  50. package/dist/dailymotionBidAdapter.js +1 -1
  51. package/dist/debugging-standalone.js +1 -1
  52. package/dist/dependencies.json +10 -2
  53. package/dist/dspxBidAdapter.js +1 -1
  54. package/dist/dxkultureBidAdapter.js +1 -1
  55. package/dist/eplanningBidAdapter.js +1 -1
  56. package/dist/equativBidAdapter.js +1 -1
  57. package/dist/eskimiBidAdapter.js +1 -1
  58. package/dist/euidIdSystem.js +1 -1
  59. package/dist/exadsBidAdapter.js +1 -1
  60. package/dist/excoBidAdapter.js +1 -1
  61. package/dist/fanAdapter.js +1 -1
  62. package/dist/feedadBidAdapter.js +1 -1
  63. package/dist/finativeBidAdapter.js +1 -1
  64. package/dist/freewheel-sspBidAdapter.js +1 -1
  65. package/dist/gmosspBidAdapter.js +1 -1
  66. package/dist/greenbidsAnalyticsAdapter.js +1 -1
  67. package/dist/greenbidsBidAdapter.js +1 -1
  68. package/dist/greenbidsRtdProvider.js +1 -1
  69. package/dist/gridBidAdapter.js +1 -1
  70. package/dist/gumgumBidAdapter.js +1 -1
  71. package/dist/h12mediaBidAdapter.js +1 -1
  72. package/dist/hypelabBidAdapter.js +1 -1
  73. package/dist/id5AnalyticsAdapter.js +1 -1
  74. package/dist/id5IdSystem.js +1 -1
  75. package/dist/imdsBidAdapter.js +1 -1
  76. package/dist/improvedigitalBidAdapter.js +1 -1
  77. package/dist/inmobiBidAdapter.js +1 -1
  78. package/dist/insticatorBidAdapter.js +1 -1
  79. package/dist/intentIqAnalyticsAdapter.js +1 -1
  80. package/dist/ixBidAdapter.js +1 -1
  81. package/dist/jixieBidAdapter.js +1 -1
  82. package/dist/jixieIdSystem.js +1 -0
  83. package/dist/justpremiumBidAdapter.js +1 -1
  84. package/dist/kargoBidAdapter.js +1 -1
  85. package/dist/kimberliteBidAdapter.js +1 -1
  86. package/dist/konduitAnalyticsAdapter.js +1 -1
  87. package/dist/kueezBidAdapter.js +1 -1
  88. package/dist/lassoBidAdapter.js +1 -1
  89. package/dist/lifestreetBidAdapter.js +1 -1
  90. package/dist/liveIntentId.js +1 -1
  91. package/dist/logicadBidAdapter.js +1 -1
  92. package/dist/loglyliftBidAdapter.js +1 -1
  93. package/dist/luceadBidAdapter.js +1 -1
  94. package/dist/mabidderBidAdapter.js +1 -1
  95. package/dist/madsenseBidAdapter.js +1 -1
  96. package/dist/magniteAnalyticsAdapter.js +1 -1
  97. package/dist/malltvAnalyticsAdapter.js +1 -1
  98. package/dist/marsmediaBidAdapter.js +1 -1
  99. package/dist/mediafuseBidAdapter.js +1 -1
  100. package/dist/medianetBidAdapter.js +1 -1
  101. package/dist/medianetUtils.js +1 -1
  102. package/dist/mediasquareBidAdapter.js +1 -1
  103. package/dist/mgidBidAdapter.js +1 -1
  104. package/dist/missenaBidAdapter.js +1 -1
  105. package/dist/mobilefuseBidAdapter.js +1 -1
  106. package/dist/nextMillenniumBidAdapter.js +1 -1
  107. package/dist/nexx360Utils.js +1 -1
  108. package/dist/nobidAnalyticsAdapter.js +1 -1
  109. package/dist/nobidBidAdapter.js +1 -1
  110. package/dist/nodalsAiRtdProvider.js +1 -1
  111. package/dist/not-for-prod/prebid.js +175 -173
  112. package/dist/oguryBidAdapter.js +1 -1
  113. package/dist/onetagBidAdapter.js +1 -1
  114. package/dist/ooloAnalyticsAdapter.js +1 -1
  115. package/dist/openxBidAdapter.js +1 -1
  116. package/dist/optidigitalBidAdapter.js +1 -1
  117. package/dist/orbidderBidAdapter.js +1 -1
  118. package/dist/ortb2.5Translator.js +1 -1
  119. package/dist/outbrainBidAdapter.js +1 -1
  120. package/dist/ozoneBidAdapter.js +1 -1
  121. package/dist/pixfutureBidAdapter.js +1 -1
  122. package/dist/prebidServerBidAdapter.js +1 -1
  123. package/dist/publinkIdSystem.js +1 -1
  124. package/dist/pubmaticAnalyticsAdapter.js +1 -1
  125. package/dist/pubmaticBidAdapter.js +1 -1
  126. package/dist/pubmaticRtdProvider.js +1 -1
  127. package/dist/pubwiseAnalyticsAdapter.js +1 -1
  128. package/dist/pubxaiAnalyticsAdapter.js +1 -1
  129. package/dist/pxyzBidAdapter.js +1 -1
  130. package/dist/quantcastBidAdapter.js +1 -1
  131. package/dist/readpeakBidAdapter.js +1 -1
  132. package/dist/relaidoBidAdapter.js +1 -1
  133. package/dist/relevatehealthBidAdapter.js +1 -1
  134. package/dist/retailspotBidAdapter.js +1 -1
  135. package/dist/rhythmoneBidAdapter.js +1 -1
  136. package/dist/riseUtils.js +1 -1
  137. package/dist/rubiconBidAdapter.js +1 -1
  138. package/dist/seedingAllianceBidAdapter.js +1 -1
  139. package/dist/seedtagBidAdapter.js +1 -1
  140. package/dist/sharethroughAnalyticsAdapter.js +1 -1
  141. package/dist/sharethroughBidAdapter.js +1 -1
  142. package/dist/showheroes-bsBidAdapter.js +1 -1
  143. package/dist/smaatoBidAdapter.js +1 -1
  144. package/dist/smartadserverBidAdapter.js +1 -1
  145. package/dist/smartxBidAdapter.js +1 -1
  146. package/dist/smilewantedBidAdapter.js +1 -1
  147. package/dist/snigelBidAdapter.js +1 -1
  148. package/dist/sonobiBidAdapter.js +1 -1
  149. package/dist/sovrnBidAdapter.js +1 -1
  150. package/dist/sparteoBidAdapter.js +1 -1
  151. package/dist/sspBCBidAdapter.js +1 -1
  152. package/dist/stvBidAdapter.js +1 -1
  153. package/dist/sublimeBidAdapter.js +1 -1
  154. package/dist/taboolaBidAdapter.js +1 -1
  155. package/dist/tappxBidAdapter.js +1 -1
  156. package/dist/targetVideoBidAdapter.js +1 -1
  157. package/dist/teadsBidAdapter.js +1 -1
  158. package/dist/terceptAnalyticsAdapter.js +1 -1
  159. package/dist/themoneytizerBidAdapter.js +1 -1
  160. package/dist/trionBidAdapter.js +1 -1
  161. package/dist/tripleliftBidAdapter.js +1 -1
  162. package/dist/ttdBidAdapter.js +1 -1
  163. package/dist/ucfunnelAnalyticsAdapter.js +1 -1
  164. package/dist/uid2IdSystem.js +1 -1
  165. package/dist/underdogmediaBidAdapter.js +1 -1
  166. package/dist/undertoneBidAdapter.js +1 -1
  167. package/dist/unrulyBidAdapter.js +1 -1
  168. package/dist/userId.js +1 -1
  169. package/dist/vidazooUtils.js +1 -1
  170. package/dist/videobyteBidAdapter.js +1 -1
  171. package/dist/visxBidAdapter.js +1 -1
  172. package/dist/vuukleBidAdapter.js +1 -1
  173. package/dist/widespaceBidAdapter.js +1 -1
  174. package/dist/winrBidAdapter.js +1 -1
  175. package/dist/yahooAdsBidAdapter.js +1 -1
  176. package/dist/yandexBidAdapter.js +1 -1
  177. package/dist/yieldmoBidAdapter.js +1 -1
  178. package/dist/yieldoneAnalyticsAdapter.js +1 -1
  179. package/gulpfile.js +12 -6
  180. package/integrationExamples/chromeai/japanese.html +224 -0
  181. package/integrationExamples/gpt/x-domain/creative.html +1 -1
  182. package/karma.conf.maker.js +7 -7
  183. package/karmaRunner.js +3 -3
  184. package/libraries/liveIntentId/shared.js +16 -0
  185. package/modules/.submodules.json +1 -0
  186. package/modules/aniviewBidAdapter.js +32 -23
  187. package/modules/chromeAiRtdProvider.js +421 -0
  188. package/modules/chromeAiRtdProvider.md +230 -0
  189. package/modules/fanAdapter.js +318 -124
  190. package/modules/ixBidAdapter.js +5 -0
  191. package/modules/jixieIdSystem.js +186 -0
  192. package/modules/ozoneBidAdapter.js +214 -336
  193. package/modules/prebidServerBidAdapter/index.js +59 -35
  194. package/modules/pubmaticRtdProvider.js +418 -4
  195. package/modules/pubmaticRtdProvider.md +12 -2
  196. package/modules/relevatehealthBidAdapter.js +20 -130
  197. package/modules/relevatehealthBidAdapter.md +1 -2
  198. package/modules/sovrnBidAdapter.js +4 -4
  199. package/modules/teadsBidAdapter.js +5 -0
  200. package/modules/ttdBidAdapter.js +5 -4
  201. package/modules/userId/index.js +30 -31
  202. package/package.json +5 -4
  203. package/src/adapterManager.js +3 -0
  204. package/test/spec/libraries/cmUtils_spec.js +17 -12
  205. package/test/spec/modules/aniviewBidAdapter_spec.js +15 -2
  206. package/test/spec/modules/chromeAiRtdProvider_spec.js +393 -0
  207. package/test/spec/modules/dgkeywordRtdProvider_spec.js +5 -2
  208. package/test/spec/modules/euidIdSystem_spec.js +9 -3
  209. package/test/spec/modules/fanAdapter_spec.js +264 -268
  210. package/test/spec/modules/id5IdSystem_spec.js +57 -101
  211. package/test/spec/modules/identityLinkIdSystem_spec.js +1 -0
  212. package/test/spec/modules/idxIdSystem_spec.js +2 -75
  213. package/test/spec/modules/instreamTracking_spec.js +15 -18
  214. package/test/spec/modules/ixBidAdapter_spec.js +26 -0
  215. package/test/spec/modules/jixieIdSystem_spec.js +303 -0
  216. package/test/spec/modules/liveIntentExternalIdSystem_spec.js +5 -0
  217. package/test/spec/modules/liveIntentIdMinimalSystem_spec.js +5 -0
  218. package/test/spec/modules/liveIntentIdSystem_spec.js +38 -0
  219. package/test/spec/modules/lmpIdSystem_spec.js +2 -81
  220. package/test/spec/modules/ozoneBidAdapter_spec.js +511 -658
  221. package/test/spec/modules/prebidServerBidAdapter_spec.js +72 -2
  222. package/test/spec/modules/pubmaticBidAdapter_spec.js +411 -126
  223. package/test/spec/modules/pubmaticRtdProvider_spec.js +946 -2
  224. package/test/spec/modules/raynRtdProvider_spec.js +18 -22
  225. package/test/spec/modules/relevatehealthBidAdapter_spec.js +9 -39
  226. package/test/spec/modules/sharedIdSystem_spec.js +3 -3
  227. package/test/spec/modules/sovrnBidAdapter_spec.js +71 -3
  228. package/test/spec/modules/teadsBidAdapter_spec.js +9 -4
  229. package/test/spec/modules/ttdBidAdapter_spec.js +24 -2
  230. package/test/spec/modules/uid2IdSystem_helpers.js +5 -2
  231. package/test/spec/modules/userId_spec.js +142 -387
  232. package/test/spec/modules/yieldoneAnalyticsAdapter_spec.js +13 -12
  233. package/test/spec/modules/zeotapIdPlusIdSystem_spec.js +3 -69
  234. package/wdio.shared.conf.js +2 -2
  235. package/CLAUDE.md +0 -1
@@ -1,9 +1,13 @@
1
1
  import Adapter from '../../src/adapter.js';
2
2
  import {
3
+ compressDataWithGZip,
4
+ debugTurnedOn,
3
5
  deepClone,
4
6
  flatten,
5
7
  generateUUID,
8
+ getParameterByName,
6
9
  insertUserSyncIframe,
10
+ isGzipCompressionSupported,
7
11
  isNumber,
8
12
  isPlainObject,
9
13
  isStr,
@@ -14,7 +18,7 @@ import {
14
18
  triggerPixel,
15
19
  uniques,
16
20
  } from '../../src/utils.js';
17
- import {EVENTS, REJECTION_REASON, S2S} from '../../src/constants.js';
21
+ import {DEBUG_MODE, EVENTS, REJECTION_REASON, S2S} from '../../src/constants.js';
18
22
  import adapterManager, {s2sActivityParams} from '../../src/adapterManager.js';
19
23
  import {config} from '../../src/config.js';
20
24
  import {addPaapiConfig, isValid} from '../../src/adapters/bidderFactory.js';
@@ -66,6 +70,7 @@ let _s2sConfigs;
66
70
  * @property {number} [maxBids=1]
67
71
  * @property {AdapterOptions} [adapterOptions] adds arguments to resulting OpenRTB payload to Prebid Server
68
72
  * @property {Object} [syncUrlModifier]
73
+ * @property {boolean} [filterBidderlessCalls=false] filter out ad units without bidders or storedrequest before sending to PBS
69
74
  */
70
75
 
71
76
  /**
@@ -97,7 +102,8 @@ export const s2sDefaultConfig = {
97
102
  {event: 1, methods: [1, 2]}
98
103
  ],
99
104
  },
100
- maxTimeout: 1500
105
+ maxTimeout: 1500,
106
+ filterBidderlessCalls: false
101
107
  };
102
108
 
103
109
  config.setDefaults({
@@ -511,43 +517,61 @@ export const processPBSRequest = hook('async', function (s2sBidRequest, bidReque
511
517
  events.emit(EVENTS.BEFORE_PBS_HTTP, requestData)
512
518
  logInfo('BidRequest: ' + requestData);
513
519
  if (request && requestData.requestJson && requestData.endpointUrl) {
514
- const networkDone = s2sBidRequest.metrics.startTiming('net');
515
- ajax(
516
- requestData.endpointUrl,
517
- {
518
- success: function (response) {
519
- networkDone();
520
- let result;
521
- try {
522
- result = JSON.parse(response);
523
- const {bids, paapi} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request));
524
- bids.forEach(onBid);
525
- if (paapi) {
526
- paapi.forEach(onFledge);
520
+ const callAjax = (payload, endpointUrl) => {
521
+ const networkDone = s2sBidRequest.metrics.startTiming('net');
522
+ ajax(
523
+ endpointUrl,
524
+ {
525
+ success: function (response) {
526
+ networkDone();
527
+ let result;
528
+ try {
529
+ result = JSON.parse(response);
530
+ const {bids, paapi} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request));
531
+ bids.forEach(onBid);
532
+ if (paapi) {
533
+ paapi.forEach(onFledge);
534
+ }
535
+ } catch (error) {
536
+ logError(error);
527
537
  }
528
- } catch (error) {
529
- logError(error);
530
- }
531
- if (!result || (result.status && result.status.includes('Error'))) {
532
- logError('error parsing response: ', result ? result.status : 'not valid JSON');
533
- onResponse(false, requestedBidders);
534
- } else {
535
- onResponse(true, requestedBidders, result);
538
+ if (!result || (result.status && result.status.includes('Error'))) {
539
+ logError('error parsing response: ', result ? result.status : 'not valid JSON');
540
+ onResponse(false, requestedBidders);
541
+ } else {
542
+ onResponse(true, requestedBidders, result);
543
+ }
544
+ },
545
+ error: function () {
546
+ networkDone();
547
+ onError.apply(this, arguments);
536
548
  }
537
549
  },
538
- error: function () {
539
- networkDone();
540
- onError.apply(this, arguments);
550
+ payload,
551
+ {
552
+ contentType: 'text/plain',
553
+ withCredentials: true,
554
+ browsingTopics: isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, s2sActivityParams(s2sBidRequest.s2sConfig)),
555
+ customHeaders: requestData.customHeaders
541
556
  }
542
- },
543
- requestData.requestJson,
544
- {
545
- contentType: 'text/plain',
546
- withCredentials: true,
547
- browsingTopics: isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, s2sActivityParams(s2sBidRequest.s2sConfig)),
548
- customHeaders: requestData.customHeaders
549
- }
550
- );
557
+ );
558
+ }
559
+
560
+ const enableGZipCompression = s2sBidRequest.s2sConfig.endpointCompression && !requestData.customHeaders['Content-Encoding'];
561
+ const debugMode = getParameterByName(DEBUG_MODE).toUpperCase() === 'TRUE' || debugTurnedOn();
562
+ if (enableGZipCompression && debugMode) {
563
+ logWarn('Skipping GZIP compression for PBS as debug mode is enabled');
564
+ }
565
+
566
+ if (enableGZipCompression && !debugMode && isGzipCompressionSupported()) {
567
+ compressDataWithGZip(requestData.requestJson).then(compressedPayload => {
568
+ const url = new URL(requestData.endpointUrl);
569
+ url.searchParams.set('gzip', '1');
570
+ callAjax(compressedPayload, url.href);
571
+ });
572
+ } else {
573
+ callAjax(requestData.requestJson, requestData.endpointUrl);
574
+ }
551
575
  } else {
552
576
  logError('PBS request not made. Check endpoints.');
553
577
  }
@@ -1,8 +1,9 @@
1
1
  import { submodule } from '../src/hook.js';
2
- import { logError, isStr, isPlainObject, isEmpty, isFn, mergeDeep } from '../src/utils.js';
2
+ import { logError, logInfo, isStr, isPlainObject, isEmpty, isFn, mergeDeep } from '../src/utils.js';
3
3
  import { config as conf } from '../src/config.js';
4
4
  import { getDeviceType as fetchDeviceType, getOS } from '../libraries/userAgentUtils/index.js';
5
5
  import { getLowEntropySUA } from '../src/fpd/sua.js';
6
+ import { getGlobal } from '../src/prebidGlobal.js';
6
7
 
7
8
  /**
8
9
  * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
@@ -14,7 +15,7 @@ import { getLowEntropySUA } from '../src/fpd/sua.js';
14
15
  */
15
16
  import { continueAuction } from './priceFloors.js'; // eslint-disable-line prebid/validate-imports
16
17
 
17
- const CONSTANTS = Object.freeze({
18
+ export const CONSTANTS = Object.freeze({
18
19
  SUBMODULE_NAME: 'pubmatic',
19
20
  REAL_TIME_MODULE: 'realTimeData',
20
21
  LOG_PRE_FIX: 'PubMatic-Rtd-Provider: ',
@@ -33,6 +34,21 @@ const CONSTANTS = Object.freeze({
33
34
  BASEURL: 'https://ads.pubmatic.com/AdServer/js/pwt',
34
35
  FLOORS: 'floors.json',
35
36
  CONFIGS: 'config.json'
37
+ },
38
+ BID_STATUS: {
39
+ NOBID: 0,
40
+ WON: 1,
41
+ FLOORED: 2
42
+ },
43
+ MULTIPLIERS: {
44
+ WIN: 1.0,
45
+ FLOORED: 0.8,
46
+ NOBID: 1.2
47
+ },
48
+ TARGETING_KEYS: {
49
+ PM_YM_FLRS: 'pm_ym_flrs', // Whether RTD floor was applied
50
+ PM_YM_FLRV: 'pm_ym_flrv', // Final floor value (after applying multiplier)
51
+ PM_YM_BID_S: 'pm_ym_bid_s' // Bid status (0: No bid, 1: Won, 2: Floored)
36
52
  }
37
53
  });
38
54
 
@@ -65,6 +81,14 @@ export let configMerged;
65
81
  // configMerged is a reference to the function that can resolve configMergedPromise whenever we want
66
82
  let configMergedPromise = new Promise((resolve) => { configMerged = resolve; });
67
83
  export let _country;
84
+ // Store multipliers from floors.json, will use default values from CONSTANTS if not available
85
+ export let _multipliers = null;
86
+
87
+ // Use a private variable for profile configs
88
+ let _profileConfigs;
89
+ // Export getter and setter functions for _profileConfigs
90
+ export const getProfileConfigs = () => _profileConfigs;
91
+ export const setProfileConfigs = (configs) => { _profileConfigs = configs; };
68
92
 
69
93
  // Waits for a given promise to resolve within a timeout
70
94
  export function withTimeout(promise, ms) {
@@ -104,6 +128,303 @@ export const getBrowserType = () => {
104
128
  return browserIndex.toString();
105
129
  }
106
130
 
131
+ // Find all bids for a specific ad unit
132
+ function findBidsForAdUnit(auction, code) {
133
+ return auction?.bidsReceived?.filter(bid => bid.adUnitCode === code) || [];
134
+ }
135
+
136
+ // Find rejected bids for a specific ad unit
137
+ function findRejectedBidsForAdUnit(auction, code) {
138
+ if (!auction?.bidsRejected) return [];
139
+
140
+ // If bidsRejected is an array
141
+ if (Array.isArray(auction.bidsRejected)) {
142
+ return auction.bidsRejected.filter(bid => bid.adUnitCode === code);
143
+ }
144
+
145
+ // If bidsRejected is an object mapping bidders to their rejected bids
146
+ if (typeof auction.bidsRejected === 'object') {
147
+ return Object.values(auction.bidsRejected)
148
+ .filter(Array.isArray)
149
+ .flatMap(bidderBids => bidderBids.filter(bid => bid.adUnitCode === code));
150
+ }
151
+
152
+ return [];
153
+ }
154
+
155
+ // Find a rejected bid due to price floor
156
+ function findRejectedFloorBid(rejectedBids) {
157
+ return rejectedBids.find(bid => {
158
+ const errorMessage = bid.statusMessage || bid.status || '';
159
+ return errorMessage.includes('price floor') ||
160
+ (bid.floorData?.floorValue && bid.cpm < bid.floorData.floorValue);
161
+ });
162
+ }
163
+
164
+ // Find the winning or highest bid for an ad unit
165
+ function findWinningBid(adUnitCode) {
166
+ try {
167
+ const pbjs = getGlobal();
168
+ if (!pbjs?.getHighestCpmBids) return null;
169
+
170
+ const highestCpmBids = pbjs.getHighestCpmBids(adUnitCode);
171
+ if (!highestCpmBids?.length) {
172
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No highest CPM bids found for ad unit: ${adUnitCode}`);
173
+ return null;
174
+ }
175
+
176
+ const highestCpmBid = highestCpmBids[0];
177
+ logInfo(CONSTANTS.LOG_PRE_FIX, `Found highest CPM bid using pbjs.getHighestCpmBids() for ad unit: ${adUnitCode}, CPM: ${highestCpmBid.cpm}`);
178
+ return highestCpmBid;
179
+ } catch (error) {
180
+ logError(CONSTANTS.LOG_PRE_FIX, `Error finding highest CPM bid: ${error}`);
181
+ return null;
182
+ }
183
+ }
184
+
185
+ // Find a bid with the minimum floor value
186
+ function findBidWithFloor(bids) {
187
+ let bidWithMinFloor = null;
188
+ let minFloorValue = Infinity;
189
+
190
+ if (!bids || !bids.length) return null;
191
+
192
+ for (const bid of bids) {
193
+ if (bid.floorData?.floorValue &&
194
+ !isNaN(parseFloat(bid.floorData.floorValue)) &&
195
+ parseFloat(bid.floorData.floorValue) < minFloorValue) {
196
+ minFloorValue = parseFloat(bid.floorData.floorValue);
197
+ bidWithMinFloor = bid;
198
+ }
199
+ }
200
+
201
+ // Log the result for debugging
202
+ if (bidWithMinFloor) {
203
+ logInfo(CONSTANTS.LOG_PRE_FIX, `Found bid with minimum floor value: ${minFloorValue}`);
204
+ }
205
+
206
+ return bidWithMinFloor;
207
+ }
208
+
209
+ // Find floor value from bidder requests
210
+ function findFloorValueFromBidderRequests(auction, code) {
211
+ if (!auction?.bidderRequests?.length) return 0;
212
+
213
+ // Find all bids in bidder requests for this ad unit
214
+ const bidsFromRequests = auction.bidderRequests
215
+ .flatMap(request => request.bids || [])
216
+ .filter(bid => bid.adUnitCode === code);
217
+
218
+ if (!bidsFromRequests.length) {
219
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No bids found for ad unit: ${code}`);
220
+ return 0;
221
+ }
222
+
223
+ const bidWithGetFloor = bidsFromRequests.find(bid => bid.getFloor);
224
+ if (!bidWithGetFloor) {
225
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No bid with getFloor method found for ad unit: ${code}`);
226
+ return 0;
227
+ }
228
+
229
+ // Helper function to extract sizes with their media types from a source object
230
+ const extractSizes = (source) => {
231
+ if (!source) return null;
232
+
233
+ const result = [];
234
+
235
+ // Extract banner sizes
236
+ if (source.mediaTypes?.banner?.sizes) {
237
+ source.mediaTypes.banner.sizes.forEach(size => {
238
+ result.push({
239
+ size,
240
+ mediaType: 'banner'
241
+ });
242
+ });
243
+ }
244
+
245
+ // Extract video sizes
246
+ if (source.mediaTypes?.video?.playerSize) {
247
+ const playerSize = source.mediaTypes.video.playerSize;
248
+ // Handle both formats: [[w, h]] and [w, h]
249
+ const videoSizes = Array.isArray(playerSize[0]) ? playerSize : [playerSize];
250
+
251
+ videoSizes.forEach(size => {
252
+ result.push({
253
+ size,
254
+ mediaType: 'video'
255
+ });
256
+ });
257
+ }
258
+
259
+ // Use general sizes as fallback if no specific media types found
260
+ if (result.length === 0 && source.sizes) {
261
+ source.sizes.forEach(size => {
262
+ result.push({
263
+ size,
264
+ mediaType: 'banner' // Default to banner for general sizes
265
+ });
266
+ });
267
+ }
268
+
269
+ return result.length > 0 ? result : null;
270
+ };
271
+
272
+ // Try to get sizes from different sources in order of preference
273
+ const adUnit = auction.adUnits?.find(unit => unit.code === code);
274
+ let sizes = extractSizes(adUnit) || extractSizes(bidWithGetFloor);
275
+
276
+ // Handle fallback to wildcard size if no sizes found
277
+ if (!sizes) {
278
+ sizes = [{ size: ['*', '*'], mediaType: 'banner' }];
279
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No sizes found, using wildcard size for ad unit: ${code}`);
280
+ }
281
+
282
+ // Try to get floor values for each size
283
+ let minFloor = -1;
284
+
285
+ for (const sizeObj of sizes) {
286
+ // Extract size and mediaType from the object
287
+ const { size, mediaType } = sizeObj;
288
+
289
+ // Call getFloor with the appropriate media type
290
+ const floorInfo = bidWithGetFloor.getFloor({
291
+ currency: 'USD', // Default currency
292
+ mediaType: mediaType, // Use the media type we extracted
293
+ size: size
294
+ });
295
+
296
+ if (floorInfo?.floor && !isNaN(parseFloat(floorInfo.floor))) {
297
+ const floorValue = parseFloat(floorInfo.floor);
298
+ logInfo(CONSTANTS.LOG_PRE_FIX, `Floor value for ${mediaType} size ${size}: ${floorValue}`);
299
+
300
+ // Update minimum floor value
301
+ minFloor = minFloor === -1 ? floorValue : Math.min(minFloor, floorValue);
302
+ }
303
+ }
304
+
305
+ if (minFloor !== -1) {
306
+ logInfo(CONSTANTS.LOG_PRE_FIX, `Calculated minimum floor value ${minFloor} for ad unit: ${code}`);
307
+ return minFloor;
308
+ }
309
+
310
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No floor data found for ad unit: ${code}`);
311
+ return 0;
312
+ }
313
+
314
+ // Select multiplier based on priority order: floors.json → config.json → default
315
+ function selectMultiplier(multiplierKey, profileConfigs) {
316
+ // Define sources in priority order
317
+ const multiplierSources = [
318
+ {
319
+ name: 'config.json',
320
+ getValue: () => {
321
+ const configPath = profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.multiplier;
322
+ const lowerKey = multiplierKey.toLowerCase();
323
+ return configPath && lowerKey in configPath ? configPath[lowerKey] : null;
324
+ }
325
+ },
326
+ {
327
+ name: 'floor.json',
328
+ getValue: () => _multipliers && multiplierKey in _multipliers ? _multipliers[multiplierKey] : null
329
+ },
330
+ {
331
+ name: 'default',
332
+ getValue: () => CONSTANTS.MULTIPLIERS[multiplierKey]
333
+ }
334
+ ];
335
+
336
+ // Find the first source with a non-null value
337
+ for (const source of multiplierSources) {
338
+ const value = source.getValue();
339
+ if (value != null) {
340
+ return { value, source: source.name };
341
+ }
342
+ }
343
+
344
+ // Fallback (shouldn't happen due to default source)
345
+ return { value: CONSTANTS.MULTIPLIERS[multiplierKey], source: 'default' };
346
+ }
347
+
348
+ // Identify winning bid scenario and return scenario data
349
+ function handleWinningBidScenario(winningBid, code) {
350
+ return {
351
+ scenario: 'winning',
352
+ bidStatus: CONSTANTS.BID_STATUS.WON,
353
+ baseValue: winningBid.cpm,
354
+ multiplierKey: 'WIN',
355
+ logMessage: `Bid won for ad unit: ${code}, CPM: ${winningBid.cpm}`
356
+ };
357
+ }
358
+
359
+ // Identify rejected floor bid scenario and return scenario data
360
+ function handleRejectedFloorBidScenario(rejectedFloorBid, code) {
361
+ const baseValue = rejectedFloorBid.floorData?.floorValue || 0;
362
+ return {
363
+ scenario: 'rejected',
364
+ bidStatus: CONSTANTS.BID_STATUS.FLOORED,
365
+ baseValue,
366
+ multiplierKey: 'FLOORED',
367
+ logMessage: `Bid rejected due to price floor for ad unit: ${code}, Floor value: ${baseValue}, Bid CPM: ${rejectedFloorBid.cpm}`
368
+ };
369
+ }
370
+
371
+ // Identify floored bid scenario and return scenario data
372
+ function handleFlooredBidScenario(bidWithFloor, code) {
373
+ const baseValue = bidWithFloor.floorData.floorValue;
374
+ return {
375
+ scenario: 'floored',
376
+ bidStatus: CONSTANTS.BID_STATUS.FLOORED,
377
+ baseValue,
378
+ multiplierKey: 'FLOORED',
379
+ logMessage: `Floored bid for ad unit: ${code}, Floor value: ${baseValue}`
380
+ };
381
+ }
382
+
383
+ // Identify no bid scenario and return scenario data
384
+ function handleNoBidScenario(auction, code) {
385
+ const baseValue = findFloorValueFromBidderRequests(auction, code);
386
+ return {
387
+ scenario: 'nobid',
388
+ bidStatus: CONSTANTS.BID_STATUS.NOBID,
389
+ baseValue,
390
+ multiplierKey: 'NOBID',
391
+ logMessage: `No bids for ad unit: ${code}, Floor value: ${baseValue}`
392
+ };
393
+ }
394
+
395
+ // Determine which scenario applies based on bid conditions
396
+ function determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) {
397
+ if (winningBid) {
398
+ return handleWinningBidScenario(winningBid, code);
399
+ }
400
+
401
+ if (rejectedFloorBid) {
402
+ return handleRejectedFloorBidScenario(rejectedFloorBid, code);
403
+ }
404
+
405
+ const bidWithFloor = findBidWithFloor(bidsForAdUnit);
406
+ if (bidWithFloor?.floorData?.floorValue) {
407
+ return handleFlooredBidScenario(bidWithFloor, code);
408
+ }
409
+
410
+ return handleNoBidScenario(auction, code);
411
+ }
412
+
413
+ // Main function that determines bid status and calculates values
414
+ function determineBidStatusAndValues(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) {
415
+ const profileConfigs = getProfileConfigs();
416
+
417
+ // Determine the scenario based on bid conditions
418
+ const { bidStatus, baseValue, multiplierKey, logMessage } =
419
+ determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code);
420
+
421
+ // Select the appropriate multiplier
422
+ const { value: multiplier, source } = selectMultiplier(multiplierKey, profileConfigs);
423
+ logInfo(CONSTANTS.LOG_PRE_FIX, logMessage + ` (Using ${source} multiplier: ${multiplier})`);
424
+
425
+ return { bidStatus, baseValue, multiplier };
426
+ }
427
+
107
428
  // Getter Functions
108
429
  export const getOs = () => getOS().toString();
109
430
  export const getDeviceType = () => fetchDeviceType().toString();
@@ -181,7 +502,30 @@ export const fetchData = async (publisherId, profileId, type) => {
181
502
  _country = cc ? cc.split(',')?.map(code => code.trim())[0] : undefined;
182
503
  }
183
504
 
184
- return await response.json();
505
+ const data = await response.json();
506
+
507
+ // Extract multipliers from floors.json if available
508
+ if (type === "FLOORS" && data && data.multiplier) {
509
+ // Map of source keys to destination keys
510
+ const multiplierKeys = {
511
+ 'win': 'WIN',
512
+ 'floored': 'FLOORED',
513
+ 'nobid': 'NOBID'
514
+ };
515
+
516
+ // Initialize _multipliers and only add keys that exist in data.multiplier
517
+ _multipliers = Object.entries(multiplierKeys)
518
+ .reduce((acc, [srcKey, destKey]) => {
519
+ if (srcKey in data.multiplier) {
520
+ acc[destKey] = data.multiplier[srcKey];
521
+ }
522
+ return acc;
523
+ }, {});
524
+
525
+ logInfo(CONSTANTS.LOG_PRE_FIX, `Using multipliers from floors.json: ${JSON.stringify(_multipliers)}`);
526
+ }
527
+
528
+ return data;
185
529
  } catch (error) {
186
530
  logError(`${CONSTANTS.LOG_PRE_FIX} Error while fetching ${type}: ${error}`);
187
531
  }
@@ -224,6 +568,9 @@ const init = (config, _userConsent) => {
224
568
  const remainingTime = Math.max(maxWaitTime - elapsedTime, 0);
225
569
  const floorsData = await withTimeout(_fetchFloorRulesPromise, remainingTime);
226
570
 
571
+ // Store the profile configs globally
572
+ setProfileConfigs(profileConfigs);
573
+
227
574
  const floorsConfig = getFloorsConfig(floorsData, profileConfigs);
228
575
  floorsConfig && conf?.setConfig(floorsConfig);
229
576
  configMerged();
@@ -266,7 +613,73 @@ const getBidRequestData = (reqBidsConfigObj, callback) => {
266
613
  });
267
614
  }
268
615
 
269
- /** @type {RtdSubmodule} */
616
+ /**
617
+ * Returns targeting data for ad units
618
+ * @param {string[]} adUnitCodes - Ad unit codes
619
+ * @param {Object} config - Module configuration
620
+ * @param {Object} userConsent - User consent data
621
+ * @param {Object} auction - Auction object
622
+ * @return {Object} - Targeting data for ad units
623
+ */
624
+ export const getTargetingData = (adUnitCodes, config, userConsent, auction) => {
625
+ // Access the profile configs stored globally
626
+ const profileConfigs = getProfileConfigs();
627
+
628
+ // Return empty object if profileConfigs is undefined or pmTargetingKeys.enabled is explicitly set to false
629
+ if (!profileConfigs || profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.enabled === false) {
630
+ logInfo(`${CONSTANTS.LOG_PRE_FIX} pmTargetingKeys is disabled or profileConfigs is undefined`);
631
+ return {};
632
+ }
633
+
634
+ // Helper to check if RTD floor is applied to a bid
635
+ const isRtdFloorApplied = bid => bid.floorData?.floorProvider === "PM" && !bid.floorData.skipped;
636
+
637
+ // Check if any bid has RTD floor applied
638
+ const hasRtdFloorAppliedBid =
639
+ auction?.adUnits?.some(adUnit => adUnit.bids?.some(isRtdFloorApplied)) ||
640
+ auction?.bidsReceived?.some(isRtdFloorApplied);
641
+
642
+ // Only log when RTD floor is applied
643
+ if (hasRtdFloorAppliedBid) {
644
+ logInfo(CONSTANTS.LOG_PRE_FIX, 'Setting targeting via getTargetingData:');
645
+ }
646
+
647
+ // Process each ad unit code
648
+ const targeting = {};
649
+
650
+ adUnitCodes.forEach(code => {
651
+ targeting[code] = {};
652
+
653
+ // For non-RTD floor applied cases, only set pm_ym_flrs to 0
654
+ if (!hasRtdFloorAppliedBid) {
655
+ targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 0;
656
+ return;
657
+ }
658
+
659
+ // Find bids and determine status for RTD floor applied cases
660
+ const bidsForAdUnit = findBidsForAdUnit(auction, code);
661
+ const rejectedBidsForAdUnit = findRejectedBidsForAdUnit(auction, code);
662
+ const rejectedFloorBid = findRejectedFloorBid(rejectedBidsForAdUnit);
663
+ const winningBid = findWinningBid(code);
664
+
665
+ // Determine bid status and values
666
+ const { bidStatus, baseValue, multiplier } = determineBidStatusAndValues(
667
+ winningBid,
668
+ rejectedFloorBid,
669
+ bidsForAdUnit,
670
+ auction,
671
+ code
672
+ );
673
+
674
+ // Set all targeting keys
675
+ targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 1;
676
+ targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRV] = (baseValue * multiplier).toFixed(2);
677
+ targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_BID_S] = bidStatus;
678
+ });
679
+
680
+ return targeting;
681
+ };
682
+
270
683
  export const pubmaticSubmodule = {
271
684
  /**
272
685
  * used to link submodule with realTimeData
@@ -275,6 +688,7 @@ export const pubmaticSubmodule = {
275
688
  name: CONSTANTS.SUBMODULE_NAME,
276
689
  init,
277
690
  getBidRequestData,
691
+ getTargetingData
278
692
  };
279
693
 
280
694
  export const registerSubModule = () => {
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## Description
8
8
 
9
- The PubMatic RTD module fetches pricing floor data and updates the Price Floors Module based on user's context in real-time as per Price Floors Modules Floor Data Provider Interface guidelines [Dynamic Floor Data Provider](https://docs.prebid.org/dev-docs/modules/floors.html#floor-data-provider-interface).
9
+ The PubMatic RTD module provides dynamic yield optimization by fetching real-time pricing floor data and generating targeting data for ad server integration and reporting. The module integrates with Prebid's Price Floors system as per [Dynamic Floor Data Provider](https://docs.prebid.org/dev-docs/modules/floors.html#floor-data-provider-interface) guidelines.
10
10
 
11
11
  ## Usage
12
12
 
@@ -66,7 +66,17 @@ pbjs.setConfig({
66
66
  | params.publisherId | String | Publisher ID | |
67
67
  | params.profileId | String | Profile ID | |
68
68
 
69
+ ## Targeting Keys
70
+
71
+ The module sets the following targeting keys for ad server integration and reporting:
72
+
73
+ | Key | Description | Values |
74
+ | :-- | :---------- | :----- |
75
+ | pm_ym_flrs | Whether RTD floor was applied to the auction | 0 (not applied)/1 (applied) |
76
+ | pm_ym_flrv | Floor value after applying dynamic multipliers | Decimal value (e.g., "1.25") |
77
+ | pm_ym_bid_s | Bid outcome status | 0 (no bid), 1 (won), 2 (floored) |
78
+
69
79
 
70
80
  ## What Should Change in the Bid Request?
71
81
 
72
- There are no direct changes in the bid request due to our RTD module, but floor configuration will be set using the price floors module. These changes will be reflected in adunit bids or bidder requests as floor data.
82
+ The RTD module applies dynamic floor configuration through the Price Floors module, which affects floor values in bid requests. Additionally, the module generates targeting data that is made available to the ad server.