prebid.js 9.53.2 → 9.53.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 (229) hide show
  1. package/dist/33acrossAnalyticsAdapter.js +1 -1
  2. package/dist/33acrossBidAdapter.js +1 -1
  3. package/dist/33acrossIdSystem.js +1 -1
  4. package/dist/BTBidAdapter.js +1 -1
  5. package/dist/adagioAnalyticsAdapter.js +1 -1
  6. package/dist/adagioBidAdapter.js +1 -1
  7. package/dist/adagioRtdProvider.js +1 -1
  8. package/dist/adagioUtils.js +1 -1
  9. package/dist/addefendBidAdapter.js +1 -1
  10. package/dist/adgenerationBidAdapter.js +1 -1
  11. package/dist/adlooxRtdProvider.js +1 -1
  12. package/dist/adqueryBidAdapter.js +1 -1
  13. package/dist/adrelevantisBidAdapter.js +1 -1
  14. package/dist/adstirBidAdapter.js +1 -1
  15. package/dist/adtrgtmeBidAdapter.js +1 -1
  16. package/dist/adxcgAnalyticsAdapter.js +1 -1
  17. package/dist/adxcgBidAdapter.js +1 -1
  18. package/dist/adyoulikeBidAdapter.js +1 -1
  19. package/dist/agmaAnalyticsAdapter.js +1 -1
  20. package/dist/ajaBidAdapter.js +1 -1
  21. package/dist/amxBidAdapter.js +1 -1
  22. package/dist/amxIdSystem.js +1 -1
  23. package/dist/aniviewBidAdapter.js +1 -1
  24. package/dist/appierAnalyticsAdapter.js +1 -1
  25. package/dist/appnexusBidAdapter.js +1 -1
  26. package/dist/asoBidAdapter.js +1 -1
  27. package/dist/axonixBidAdapter.js +1 -1
  28. package/dist/beopBidAdapter.js +1 -1
  29. package/dist/bidderTimeoutUtils.js +1 -0
  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/chtnwBidAdapter.js +1 -1
  39. package/dist/chunk-core.js +1 -1
  40. package/dist/concertBidAdapter.js +1 -1
  41. package/dist/connectadBidAdapter.js +1 -1
  42. package/dist/consumableBidAdapter.js +1 -1
  43. package/dist/contxtfulBidAdapter.js +1 -1
  44. package/dist/conversantAnalyticsAdapter.js +1 -1
  45. package/dist/conversantBidAdapter.js +1 -1
  46. package/dist/craftBidAdapter.js +1 -1
  47. package/dist/criteoBidAdapter.js +1 -1
  48. package/dist/cwireBidAdapter.js +1 -1
  49. package/dist/dailymotionBidAdapter.js +1 -1
  50. package/dist/debugging-standalone.js +1 -1
  51. package/dist/dependencies.json +10 -1
  52. package/dist/dspxBidAdapter.js +1 -1
  53. package/dist/dxkultureBidAdapter.js +1 -1
  54. package/dist/eplanningBidAdapter.js +1 -1
  55. package/dist/equativBidAdapter.js +1 -1
  56. package/dist/eskimiBidAdapter.js +1 -1
  57. package/dist/euidIdSystem.js +1 -1
  58. package/dist/exadsBidAdapter.js +1 -1
  59. package/dist/excoBidAdapter.js +1 -1
  60. package/dist/feedadBidAdapter.js +1 -1
  61. package/dist/finativeBidAdapter.js +1 -1
  62. package/dist/freewheel-sspBidAdapter.js +1 -1
  63. package/dist/fwsspBidAdapter.js +1 -1
  64. package/dist/gmosspBidAdapter.js +1 -1
  65. package/dist/greenbidsAnalyticsAdapter.js +1 -1
  66. package/dist/greenbidsBidAdapter.js +1 -1
  67. package/dist/greenbidsRtdProvider.js +1 -1
  68. package/dist/gridBidAdapter.js +1 -1
  69. package/dist/gumgumBidAdapter.js +1 -1
  70. package/dist/h12mediaBidAdapter.js +1 -1
  71. package/dist/hypelabBidAdapter.js +1 -1
  72. package/dist/id5AnalyticsAdapter.js +1 -1
  73. package/dist/id5IdSystem.js +1 -1
  74. package/dist/imdsBidAdapter.js +1 -1
  75. package/dist/improvedigitalBidAdapter.js +1 -1
  76. package/dist/inmobiBidAdapter.js +1 -1
  77. package/dist/insticatorBidAdapter.js +1 -1
  78. package/dist/intentIqAnalyticsAdapter.js +1 -1
  79. package/dist/ixBidAdapter.js +1 -1
  80. package/dist/jixieBidAdapter.js +1 -1
  81. package/dist/justpremiumBidAdapter.js +1 -1
  82. package/dist/kargoBidAdapter.js +1 -1
  83. package/dist/kimberliteBidAdapter.js +1 -1
  84. package/dist/konduitAnalyticsAdapter.js +1 -1
  85. package/dist/kueezBidAdapter.js +1 -1
  86. package/dist/lassoBidAdapter.js +1 -1
  87. package/dist/lifestreetBidAdapter.js +1 -1
  88. package/dist/liveIntentId.js +1 -1
  89. package/dist/logicadBidAdapter.js +1 -1
  90. package/dist/loglyliftBidAdapter.js +1 -1
  91. package/dist/luceadBidAdapter.js +1 -1
  92. package/dist/mabidderBidAdapter.js +1 -1
  93. package/dist/madsenseBidAdapter.js +1 -1
  94. package/dist/magniteAnalyticsAdapter.js +1 -1
  95. package/dist/malltvAnalyticsAdapter.js +1 -1
  96. package/dist/marsmediaBidAdapter.js +1 -1
  97. package/dist/mediafuseBidAdapter.js +1 -1
  98. package/dist/medianetBidAdapter.js +1 -1
  99. package/dist/medianetUtils.js +1 -1
  100. package/dist/mediasquareBidAdapter.js +1 -1
  101. package/dist/mgidBidAdapter.js +1 -1
  102. package/dist/missenaBidAdapter.js +1 -1
  103. package/dist/mobilefuseBidAdapter.js +1 -1
  104. package/dist/nextMillenniumBidAdapter.js +1 -1
  105. package/dist/nexx360Utils.js +1 -1
  106. package/dist/nobidAnalyticsAdapter.js +1 -1
  107. package/dist/nobidBidAdapter.js +1 -1
  108. package/dist/nodalsAiRtdProvider.js +1 -1
  109. package/dist/not-for-prod/prebid.js +178 -175
  110. package/dist/objectGuard.js +1 -1
  111. package/dist/oguryBidAdapter.js +1 -1
  112. package/dist/onetagBidAdapter.js +1 -1
  113. package/dist/ooloAnalyticsAdapter.js +1 -1
  114. package/dist/openxBidAdapter.js +1 -1
  115. package/dist/optableRtdProvider.js +1 -1
  116. package/dist/optidigitalBidAdapter.js +1 -1
  117. package/dist/orbidderBidAdapter.js +1 -1
  118. package/dist/outbrainBidAdapter.js +1 -1
  119. package/dist/pixfutureBidAdapter.js +1 -1
  120. package/dist/publinkIdSystem.js +1 -1
  121. package/dist/pubmaticAnalyticsAdapter.js +1 -1
  122. package/dist/pubmaticBidAdapter.js +1 -1
  123. package/dist/pubmaticIdSystem.js +1 -1
  124. package/dist/pubmaticRtdProvider.js +1 -1
  125. package/dist/pubmaticUtils.js +1 -0
  126. package/dist/pubwiseAnalyticsAdapter.js +1 -1
  127. package/dist/pubxaiAnalyticsAdapter.js +1 -1
  128. package/dist/pxyzBidAdapter.js +1 -1
  129. package/dist/quantcastBidAdapter.js +1 -1
  130. package/dist/readpeakBidAdapter.js +1 -1
  131. package/dist/relaidoBidAdapter.js +1 -1
  132. package/dist/retailspotBidAdapter.js +1 -1
  133. package/dist/rhythmoneBidAdapter.js +1 -1
  134. package/dist/riseUtils.js +1 -1
  135. package/dist/rtdModule.js +1 -1
  136. package/dist/rubiconBidAdapter.js +1 -1
  137. package/dist/seedingAllianceBidAdapter.js +1 -1
  138. package/dist/seedtagBidAdapter.js +1 -1
  139. package/dist/sevioBidAdapter.js +1 -0
  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/timeoutRtdProvider.js +1 -1
  161. package/dist/trionBidAdapter.js +1 -1
  162. package/dist/tripleliftBidAdapter.js +1 -1
  163. package/dist/ttdBidAdapter.js +1 -1
  164. package/dist/ucfunnelAnalyticsAdapter.js +1 -1
  165. package/dist/uid2IdSystem.js +1 -1
  166. package/dist/underdogmediaBidAdapter.js +1 -1
  167. package/dist/undertoneBidAdapter.js +1 -1
  168. package/dist/unrulyBidAdapter.js +1 -1
  169. package/dist/userId.js +1 -1
  170. package/dist/vidazooUtils.js +1 -1
  171. package/dist/videobyteBidAdapter.js +1 -1
  172. package/dist/visxBidAdapter.js +1 -1
  173. package/dist/vuukleBidAdapter.js +1 -1
  174. package/dist/widespaceBidAdapter.js +1 -1
  175. package/dist/winrBidAdapter.js +1 -1
  176. package/dist/yahooAdsBidAdapter.js +1 -1
  177. package/dist/yandexBidAdapter.js +1 -1
  178. package/dist/yieldmoBidAdapter.js +1 -1
  179. package/dist/yieldoneAnalyticsAdapter.js +1 -1
  180. package/integrationExamples/gpt/pubmaticRtdProvider_Example.html +161 -0
  181. package/libraries/bidderTimeoutUtils/bidderTimeoutUtils.js +119 -0
  182. package/libraries/objectGuard/objectGuard.js +170 -48
  183. package/libraries/objectGuard/ortbGuard.js +33 -43
  184. package/libraries/pubmaticUtils/plugins/dynamicTimeout.js +209 -0
  185. package/libraries/pubmaticUtils/plugins/floorProvider.js +168 -0
  186. package/libraries/pubmaticUtils/plugins/pluginManager.js +106 -0
  187. package/libraries/pubmaticUtils/plugins/unifiedPricingRule.js +375 -0
  188. package/libraries/pubmaticUtils/pubmaticUtils.js +76 -0
  189. package/modules/adagioAnalyticsAdapter.js +6 -1
  190. package/modules/adagioBidAdapter.js +12 -5
  191. package/modules/adagioRtdProvider.js +41 -35
  192. package/modules/fwsspBidAdapter.js +134 -69
  193. package/modules/fwsspBidAdapter.md +121 -26
  194. package/modules/optableRtdProvider.js +33 -12
  195. package/modules/pubmaticAnalyticsAdapter.js +315 -587
  196. package/modules/pubmaticBidAdapter.js +71 -8
  197. package/modules/pubmaticIdSystem.js +4 -4
  198. package/modules/pubmaticRtdProvider.js +105 -613
  199. package/modules/rtdModule/index.js +23 -6
  200. package/modules/sevioBidAdapter.js +413 -0
  201. package/modules/sevioBidAdapter.md +29 -0
  202. package/modules/sparteoBidAdapter.js +122 -10
  203. package/modules/timeoutRtdProvider.js +2 -105
  204. package/modules/ttdBidAdapter.js +0 -5
  205. package/modules/userId/eids.js +1 -1
  206. package/modules/userId/index.js +32 -1
  207. package/package.json +1 -1
  208. package/src/auction.js +3 -0
  209. package/test/spec/activities/objectGuard_spec.js +189 -32
  210. package/test/spec/activities/ortbGuard_spec.js +10 -15
  211. package/test/spec/libraries/bidderTimeoutUtils/bidderTimeoutUtils_spec.js +213 -0
  212. package/test/spec/libraries/pubmaticUtils/plugins/dynamicTimeout_spec.js +746 -0
  213. package/test/spec/libraries/pubmaticUtils/plugins/floorProvider_spec.js +184 -0
  214. package/test/spec/libraries/pubmaticUtils/plugins/pluginManager_spec.js +489 -0
  215. package/test/spec/libraries/pubmaticUtils/plugins/unifiedPricingRule_spec.js +359 -0
  216. package/test/spec/libraries/pubmaticUtils/pubmaticUtils_spec.js +236 -0
  217. package/test/spec/modules/adagioAnalyticsAdapter_spec.js +94 -24
  218. package/test/spec/modules/adagioRtdProvider_spec.js +17 -17
  219. package/test/spec/modules/fwsspBidAdapter_spec.js +513 -78
  220. package/test/spec/modules/optableRtdProvider_spec.js +55 -5
  221. package/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +634 -916
  222. package/test/spec/modules/pubmaticBidAdapter_spec.js +260 -1
  223. package/test/spec/modules/pubmaticRtdProvider_spec.js +252 -1505
  224. package/test/spec/modules/realTimeDataModule_spec.js +58 -8
  225. package/test/spec/modules/sevioBidAdapter_spec.js +513 -0
  226. package/test/spec/modules/sparteoBidAdapter_spec.js +528 -43
  227. package/test/spec/modules/timeoutRtdProvider_spec.js +1 -201
  228. package/test/spec/modules/ttdBidAdapter_spec.js +0 -33
  229. package/test/spec/modules/userId_spec.js +115 -1
@@ -0,0 +1,375 @@
1
+ // plugins/unifiedPricingRule.js
2
+ import { logError, logInfo } from '../../../src/utils.js';
3
+ import { getGlobal } from '../../../src/prebidGlobal.js';
4
+ import { REJECTION_REASON } from '../../../src/constants.js';
5
+
6
+ const CONSTANTS = Object.freeze({
7
+ LOG_PRE_FIX: 'PubMatic-Unified-Pricing-Rule: ',
8
+ BID_STATUS: {
9
+ NOBID: 0,
10
+ WON: 1,
11
+ FLOORED: 2
12
+ },
13
+ MULTIPLIERS: {
14
+ WIN: 1.0,
15
+ FLOORED: 1.0,
16
+ NOBID: 1.0
17
+ },
18
+ TARGETING_KEYS: {
19
+ PM_YM_FLRS: 'pm_ym_flrs', // Whether RTD floor was applied
20
+ PM_YM_FLRV: 'pm_ym_flrv', // Final floor value (after applying multiplier)
21
+ PM_YM_BID_S: 'pm_ym_bid_s' // Bid status (0: No bid, 1: Won, 2: Floored)
22
+ }
23
+ });
24
+ export const getProfileConfigs = () => getConfigJsonManager()?.getYMConfig();
25
+
26
+ let _configJsonManager = null;
27
+ export const getConfigJsonManager = () => _configJsonManager;
28
+ export const setConfigJsonManager = (configJsonManager) => { _configJsonManager = configJsonManager; }
29
+
30
+ /**
31
+ * Initialize the floor provider
32
+ * @param {Object} pluginName - Plugin name
33
+ * @param {Object} configJsonManager - Configuration JSON manager object
34
+ * @returns {Promise<boolean>} - Promise resolving to initialization status
35
+ */
36
+ export async function init(pluginName, configJsonManager) {
37
+ setConfigJsonManager(configJsonManager);
38
+ return true;
39
+ }
40
+
41
+ /**
42
+ * Process bid request
43
+ * @param {Object} reqBidsConfigObj - Bid request config object
44
+ * @returns {Object} - Updated bid request config object
45
+ */
46
+ export function processBidRequest(reqBidsConfigObj) {
47
+ return reqBidsConfigObj;
48
+ }
49
+
50
+ /**
51
+ * Get targeting data
52
+ * @param {Array} adUnitCodes - Ad unit codes
53
+ * @param {Object} config - Module configuration
54
+ * @param {Object} userConsent - User consent data
55
+ * @param {Object} auction - Auction object
56
+ * @returns {Object} - Targeting data
57
+ */
58
+ export function getTargeting(adUnitCodes, config, userConsent, auction) {
59
+ // Access the profile configs stored globally
60
+ const profileConfigs = getProfileConfigs();
61
+
62
+ // Return empty object if profileConfigs is undefined or pmTargetingKeys.enabled is explicitly set to false
63
+ if (!profileConfigs || profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.enabled === false) {
64
+ logInfo(`${CONSTANTS.LOG_PRE_FIX} pmTargetingKeys is disabled or profileConfigs is undefined`);
65
+ return {};
66
+ }
67
+
68
+ // Helper to check if RTD floor is applied to a bid
69
+ const isRtdFloorApplied = bid => bid.floorData?.floorProvider === "PM" && !bid.floorData.skipped;
70
+
71
+ // Check if any bid has RTD floor applied
72
+ const hasRtdFloorAppliedBid =
73
+ auction?.adUnits?.some(adUnit => adUnit.bids?.some(isRtdFloorApplied)) ||
74
+ auction?.bidsReceived?.some(isRtdFloorApplied);
75
+
76
+ // Only log when RTD floor is applied
77
+ if (hasRtdFloorAppliedBid) {
78
+ logInfo(CONSTANTS.LOG_PRE_FIX, 'Setting targeting via getTargetingData:');
79
+ }
80
+
81
+ // Process each ad unit code
82
+ const targeting = {};
83
+
84
+ adUnitCodes.forEach(code => {
85
+ targeting[code] = {};
86
+
87
+ // For non-RTD floor applied cases, only set pm_ym_flrs to 0
88
+ if (!hasRtdFloorAppliedBid) {
89
+ targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 0;
90
+ return;
91
+ }
92
+
93
+ // Find bids and determine status for RTD floor applied cases
94
+ const bidsForAdUnit = findBidsForAdUnit(auction, code);
95
+ const rejectedBidsForAdUnit = findRejectedBidsForAdUnit(auction, code);
96
+ const rejectedFloorBid = findRejectedFloorBid(rejectedBidsForAdUnit);
97
+ const winningBid = findWinningBid(code);
98
+
99
+ // Determine bid status and values
100
+ const { bidStatus, baseValue, multiplier } = determineBidStatusAndValues(
101
+ winningBid,
102
+ rejectedFloorBid,
103
+ bidsForAdUnit,
104
+ auction,
105
+ code
106
+ );
107
+
108
+ // Set all targeting keys
109
+ targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 1;
110
+ targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRV] = (baseValue * multiplier).toFixed(2);
111
+ targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_BID_S] = bidStatus;
112
+ });
113
+
114
+ return targeting;
115
+ }
116
+
117
+ // Export the floor provider functions
118
+ export const UnifiedPricingRule = {
119
+ init,
120
+ processBidRequest,
121
+ getTargeting
122
+ };
123
+
124
+ // Find all bids for a specific ad unit
125
+ function findBidsForAdUnit(auction, code) {
126
+ return auction?.bidsReceived?.filter(bid => bid.adUnitCode === code) || [];
127
+ }
128
+
129
+ // Find rejected bids for a specific ad unit
130
+ function findRejectedBidsForAdUnit(auction, code) {
131
+ if (!auction?.bidsRejected) return [];
132
+
133
+ // If bidsRejected is an array
134
+ if (Array.isArray(auction.bidsRejected)) {
135
+ return auction.bidsRejected.filter(bid => bid.adUnitCode === code);
136
+ }
137
+
138
+ // If bidsRejected is an object mapping bidders to their rejected bids
139
+ if (typeof auction.bidsRejected === 'object') {
140
+ return Object.values(auction.bidsRejected)
141
+ .filter(Array.isArray)
142
+ .flatMap(bidderBids => bidderBids.filter(bid => bid.adUnitCode === code));
143
+ }
144
+
145
+ return [];
146
+ }
147
+
148
+ // Find a rejected bid due to price floor
149
+ function findRejectedFloorBid(rejectedBids) {
150
+ return rejectedBids.find(bid => {
151
+ return bid.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET &&
152
+ (bid.floorData?.floorValue && bid.cpm < bid.floorData.floorValue);
153
+ });
154
+ }
155
+
156
+ // Find the winning or highest bid for an ad unit
157
+ function findWinningBid(adUnitCode) {
158
+ try {
159
+ const pbjs = getGlobal();
160
+ if (!pbjs?.getHighestCpmBids) return null;
161
+
162
+ const highestCpmBids = pbjs.getHighestCpmBids(adUnitCode);
163
+ if (!highestCpmBids?.length) {
164
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No highest CPM bids found for ad unit: ${adUnitCode}`);
165
+ return null;
166
+ }
167
+
168
+ const highestCpmBid = highestCpmBids[0];
169
+ logInfo(CONSTANTS.LOG_PRE_FIX, `Found highest CPM bid using pbjs.getHighestCpmBids() for ad unit: ${adUnitCode}, CPM: ${highestCpmBid.cpm}`);
170
+ return highestCpmBid;
171
+ } catch (error) {
172
+ logError(CONSTANTS.LOG_PRE_FIX, `Error finding highest CPM bid: ${error}`);
173
+ return null;
174
+ }
175
+ }
176
+
177
+ // Find floor value from bidder requests
178
+ function findFloorValueFromBidderRequests(auction, code) {
179
+ if (!auction?.bidderRequests?.length) return 0;
180
+
181
+ // Find all bids in bidder requests for this ad unit
182
+ const bidsFromRequests = auction.bidderRequests
183
+ .flatMap(request => request.bids || [])
184
+ .filter(bid => bid.adUnitCode === code);
185
+
186
+ if (!bidsFromRequests.length) {
187
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No bids found for ad unit: ${code}`);
188
+ return 0;
189
+ }
190
+
191
+ const bidWithGetFloor = bidsFromRequests.find(bid => bid.getFloor);
192
+ if (!bidWithGetFloor) {
193
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No bid with getFloor method found for ad unit: ${code}`);
194
+ return 0;
195
+ }
196
+
197
+ // Helper function to extract sizes with their media types from a source object
198
+ const extractSizes = (source) => {
199
+ if (!source) return null;
200
+
201
+ const result = [];
202
+
203
+ // Extract banner sizes
204
+ if (source.mediaTypes?.banner?.sizes) {
205
+ source.mediaTypes.banner.sizes.forEach(size => {
206
+ result.push({
207
+ size,
208
+ mediaType: 'banner'
209
+ });
210
+ });
211
+ }
212
+
213
+ // Extract video sizes
214
+ if (source.mediaTypes?.video?.playerSize) {
215
+ const playerSize = source.mediaTypes.video.playerSize;
216
+ // Handle both formats: [[w, h]] and [w, h]
217
+ const videoSizes = Array.isArray(playerSize[0]) ? playerSize : [playerSize];
218
+
219
+ videoSizes.forEach(size => {
220
+ result.push({
221
+ size,
222
+ mediaType: 'video'
223
+ });
224
+ });
225
+ }
226
+
227
+ // Use general sizes as fallback if no specific media types found
228
+ if (result.length === 0 && source.sizes) {
229
+ source.sizes.forEach(size => {
230
+ result.push({
231
+ size,
232
+ mediaType: 'banner' // Default to banner for general sizes
233
+ });
234
+ });
235
+ }
236
+
237
+ return result.length > 0 ? result : null;
238
+ };
239
+
240
+ // Try to get sizes from different sources in order of preference
241
+ const adUnit = auction.adUnits?.find(unit => unit.code === code);
242
+ let sizes = extractSizes(adUnit) || extractSizes(bidWithGetFloor);
243
+
244
+ // Handle fallback to wildcard size if no sizes found
245
+ if (!sizes) {
246
+ sizes = [{ size: ['*', '*'], mediaType: 'banner' }];
247
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No sizes found, using wildcard size for ad unit: ${code}`);
248
+ }
249
+
250
+ // Try to get floor values for each size
251
+ let minFloor = -1;
252
+
253
+ for (const sizeObj of sizes) {
254
+ // Extract size and mediaType from the object
255
+ const { size, mediaType } = sizeObj;
256
+
257
+ // Call getFloor with the appropriate media type
258
+ const floorInfo = bidWithGetFloor.getFloor({
259
+ currency: 'USD', // Default currency
260
+ mediaType: mediaType, // Use the media type we extracted
261
+ size: size
262
+ });
263
+
264
+ if (floorInfo?.floor && !isNaN(parseFloat(floorInfo.floor))) {
265
+ const floorValue = parseFloat(floorInfo.floor);
266
+ logInfo(CONSTANTS.LOG_PRE_FIX, `Floor value for ${mediaType} size ${size}: ${floorValue}`);
267
+
268
+ // Update minimum floor value
269
+ minFloor = minFloor === -1 ? floorValue : Math.min(minFloor, floorValue);
270
+ }
271
+ }
272
+
273
+ if (minFloor !== -1) {
274
+ logInfo(CONSTANTS.LOG_PRE_FIX, `Calculated minimum floor value ${minFloor} for ad unit: ${code}`);
275
+ return minFloor;
276
+ }
277
+
278
+ logInfo(CONSTANTS.LOG_PRE_FIX, `No floor data found for ad unit: ${code}`);
279
+ return 0;
280
+ }
281
+
282
+ // Select multiplier based on priority order: floors.json → config.json → default
283
+ function selectMultiplier(multiplierKey, profileConfigs) {
284
+ // Define sources in priority order
285
+ const multiplierSources = [
286
+ {
287
+ name: 'config.json',
288
+ getValue: () => {
289
+ const configPath = profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.multiplier;
290
+ const lowerKey = multiplierKey.toLowerCase();
291
+ return configPath && lowerKey in configPath ? configPath[lowerKey] : null;
292
+ }
293
+ },
294
+ {
295
+ name: 'floor.json',
296
+ getValue: () => {
297
+ const configPath = profileConfigs?.plugins?.dynamicFloors?.data?.multiplier;
298
+ const lowerKey = multiplierKey.toLowerCase();
299
+ return configPath && lowerKey in configPath ? configPath[lowerKey] : null;
300
+ }
301
+ },
302
+ {
303
+ name: 'default',
304
+ getValue: () => CONSTANTS.MULTIPLIERS[multiplierKey]
305
+ }
306
+ ];
307
+
308
+ // Find the first source with a non-null value
309
+ for (const source of multiplierSources) {
310
+ const value = source.getValue();
311
+ if (value != null) {
312
+ return { value, source: source.name };
313
+ }
314
+ }
315
+
316
+ // Fallback (shouldn't happen due to default source)
317
+ return { value: CONSTANTS.MULTIPLIERS[multiplierKey], source: 'default' };
318
+ }
319
+
320
+ // Identify winning bid scenario and return scenario data
321
+ function handleWinningBidScenario(winningBid, code) {
322
+ return {
323
+ scenario: 'winning',
324
+ bidStatus: CONSTANTS.BID_STATUS.WON,
325
+ baseValue: winningBid.cpm,
326
+ multiplierKey: 'WIN',
327
+ logMessage: `Bid won for ad unit: ${code}, CPM: ${winningBid.cpm}`
328
+ };
329
+ }
330
+
331
+ // Identify rejected floor bid scenario and return scenario data
332
+ function handleRejectedFloorBidScenario(rejectedFloorBid, code) {
333
+ const baseValue = rejectedFloorBid.floorData?.floorValue || 0;
334
+ return {
335
+ scenario: 'rejected',
336
+ bidStatus: CONSTANTS.BID_STATUS.FLOORED,
337
+ baseValue,
338
+ multiplierKey: 'FLOORED',
339
+ logMessage: `Bid rejected due to price floor for ad unit: ${code}, Floor value: ${baseValue}, Bid CPM: ${rejectedFloorBid.cpm}`
340
+ };
341
+ }
342
+
343
+ // Identify no bid scenario and return scenario data
344
+ function handleNoBidScenario(auction, code) {
345
+ const baseValue = findFloorValueFromBidderRequests(auction, code);
346
+ return {
347
+ scenario: 'nobid',
348
+ bidStatus: CONSTANTS.BID_STATUS.NOBID,
349
+ baseValue,
350
+ multiplierKey: 'NOBID',
351
+ logMessage: `No bids for ad unit: ${code}, Floor value: ${baseValue}`
352
+ };
353
+ }
354
+
355
+ // Determine which scenario applies based on bid conditions
356
+ function determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) {
357
+ return winningBid ? handleWinningBidScenario(winningBid, code)
358
+ : rejectedFloorBid ? handleRejectedFloorBidScenario(rejectedFloorBid, code)
359
+ : handleNoBidScenario(auction, code);
360
+ }
361
+
362
+ // Main function that determines bid status and calculates values
363
+ function determineBidStatusAndValues(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) {
364
+ const profileConfigs = getProfileConfigs();
365
+
366
+ // Determine the scenario based on bid conditions
367
+ const { bidStatus, baseValue, multiplierKey, logMessage } =
368
+ determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code);
369
+
370
+ // Select the appropriate multiplier
371
+ const { value: multiplier, source } = selectMultiplier(multiplierKey, profileConfigs);
372
+ logInfo(CONSTANTS.LOG_PRE_FIX, logMessage + ` (Using ${source} multiplier: ${multiplier})`);
373
+
374
+ return { bidStatus, baseValue, multiplier };
375
+ }
@@ -0,0 +1,76 @@
1
+ import { getLowEntropySUA } from '../../src/fpd/sua.js';
2
+
3
+ const CONSTANTS = Object.freeze({
4
+ TIME_OF_DAY_VALUES: {
5
+ MORNING: 'morning',
6
+ AFTERNOON: 'afternoon',
7
+ EVENING: 'evening',
8
+ NIGHT: 'night'
9
+ },
10
+ UTM: 'utm_',
11
+ UTM_VALUES: {
12
+ TRUE: '1',
13
+ FALSE: '0'
14
+ },
15
+ });
16
+
17
+ const BROWSER_REGEX_MAP = [
18
+ { regex: /\b(?:crios)\/([\w.]+)/i, id: 1 }, // Chrome for iOS
19
+ { regex: /(edg|edge)(?:e|ios|a)?(?:\/([\w.]+))?/i, id: 2 }, // Edge
20
+ { regex: /(opera|opr)(?:.+version\/|\/|\s+)([\w.]+)/i, id: 3 }, // Opera
21
+ { regex: /(?:ms|\()(ie) ([\w.]+)|(?:trident\/[\w.]+)/i, id: 4 }, // Internet Explorer
22
+ { regex: /fxios\/([-\w.]+)/i, id: 5 }, // Firefox for iOS
23
+ { regex: /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w.]+);)/i, id: 6 }, // Facebook In-App Browser
24
+ { regex: / wv\).+(chrome)\/([\w.]+)/i, id: 7 }, // Chrome WebView
25
+ { regex: /droid.+ version\/([\w.]+)\b.+(?:mobile safari|safari)/i, id: 8 }, // Android Browser
26
+ { regex: /(chrome|crios)(?:\/v?([\w.]+))?\b/i, id: 9 }, // Chrome
27
+ { regex: /version\/([\w.,]+) .*mobile\/\w+ (safari)/i, id: 10 }, // Safari Mobile
28
+ { regex: /version\/([\w.,]+) .*(mobile ?safari|safari)/i, id: 11 }, // Safari
29
+ { regex: /(firefox)\/([\w.]+)/i, id: 12 } // Firefox
30
+ ];
31
+
32
+ export const getBrowserType = () => {
33
+ const brandName = getLowEntropySUA()?.browsers
34
+ ?.map(b => b.brand.toLowerCase())
35
+ .join(' ') || '';
36
+ const browserMatch = brandName ? BROWSER_REGEX_MAP.find(({ regex }) => regex.test(brandName)) : -1;
37
+
38
+ if (browserMatch?.id) return browserMatch.id.toString();
39
+
40
+ const userAgent = navigator?.userAgent;
41
+ let browserIndex = userAgent == null ? -1 : 0;
42
+
43
+ if (userAgent) {
44
+ browserIndex = BROWSER_REGEX_MAP.find(({ regex }) => regex.test(userAgent))?.id || 0;
45
+ }
46
+ return browserIndex.toString();
47
+ }
48
+
49
+ export const getCurrentTimeOfDay = () => {
50
+ const currentHour = new Date().getHours();
51
+
52
+ return currentHour < 5 ? CONSTANTS.TIME_OF_DAY_VALUES.NIGHT
53
+ : currentHour < 12 ? CONSTANTS.TIME_OF_DAY_VALUES.MORNING
54
+ : currentHour < 17 ? CONSTANTS.TIME_OF_DAY_VALUES.AFTERNOON
55
+ : currentHour < 19 ? CONSTANTS.TIME_OF_DAY_VALUES.EVENING
56
+ : CONSTANTS.TIME_OF_DAY_VALUES.NIGHT;
57
+ }
58
+
59
+ export const getUtmValue = () => {
60
+ const url = new URL(window.location?.href);
61
+ const urlParams = new URLSearchParams(url?.search);
62
+ return urlParams && urlParams.toString().includes(CONSTANTS.UTM) ? CONSTANTS.UTM_VALUES.TRUE : CONSTANTS.UTM_VALUES.FALSE;
63
+ }
64
+
65
+ /**
66
+ * Determines whether an action should be throttled based on a given percentage.
67
+ *
68
+ * @param {number} skipRate - The percentage rate at which throttling will be applied (0-100).
69
+ * @param {number} maxRandomValue - The upper bound for generating a random number (default is 100).
70
+ * @returns {boolean} - Returns true if the action should be throttled, false otherwise.
71
+ */
72
+ export const shouldThrottle = (skipRate, maxRandomValue = 100) => {
73
+ // Determine throttling based on the throttle rate and a random value
74
+ const rate = skipRate ?? maxRandomValue;
75
+ return Math.floor(Math.random() * maxRandomValue) < rate;
76
+ };
@@ -267,7 +267,7 @@ function handlerAuctionInit(event) {
267
267
  ban_szs: bannerSizes.join(','),
268
268
  bdrs: sortedBidderNames.join(','),
269
269
  pgtyp: deepAccess(event.bidderRequests[0], 'ortb2.site.ext.data.pagetype', null),
270
- plcmt: deepAccess(adUnits[0], 'ortb2Imp.ext.data.placement', null),
270
+ plcmt: deepAccess(adUnits[0], 'ortb2Imp.ext.data.adg_rtd.placement', null), // adg_rtd.placement is set by AdagioRtdProvider.
271
271
  t_n: adgRtdSession.testName || null,
272
272
  t_v: adgRtdSession.testVersion || null,
273
273
  s_id: adgRtdSession.id || null,
@@ -289,6 +289,11 @@ function handlerAuctionInit(event) {
289
289
  // for backward compatibility: if we didn't find organizationId & site but we have a bid from adagio we might still find it in params
290
290
  qp.org_id = qp.org_id || adagioAdUnitBids[0].params.organizationId;
291
291
  qp.site = qp.site || adagioAdUnitBids[0].params.site;
292
+
293
+ // `qp.plcmt` uses the value set by the AdagioRtdProvider. If not present, we fallback on the value set at the adUnit.params level.
294
+ if (!qp.plcmt) {
295
+ qp.plcmt = deepAccess(adagioAdUnitBids[0], 'params.placement', null);
296
+ }
292
297
  }
293
298
  }
294
299
 
@@ -399,11 +399,18 @@ function autoFillParams(bid) {
399
399
  bid.params.site = adgGlobalConf.siteId.split(':')[1];
400
400
  }
401
401
 
402
- // `useAdUnitCodeAsPlacement` is an edge case. Useful when a Prebid Manager cannot handle properly params setting.
403
- // In Prebid.js 9, `placement` should be defined in ortb2Imp and the `useAdUnitCodeAsPlacement` param should be removed
404
- bid.params.placement = deepAccess(bid, 'ortb2Imp.ext.data.placement', bid.params.placement);
405
- if (!bid.params.placement && (adgGlobalConf.useAdUnitCodeAsPlacement === true || bid.params.useAdUnitCodeAsPlacement === true)) {
406
- bid.params.placement = bid.adUnitCode;
402
+ if (!bid.params.placement) {
403
+ let p = deepAccess(bid, 'ortb2Imp.ext.data.adg_rtd.placement', '');
404
+ if (!p) {
405
+ // Use ortb2Imp.ext.data.placement for backward compatibility.
406
+ p = deepAccess(bid, 'ortb2Imp.ext.data.placement', '');
407
+ }
408
+
409
+ // `useAdUnitCodeAsPlacement` is an edge case. Useful when a Prebid Manager cannot handle properly params setting.
410
+ if (!p && bid.params.useAdUnitCodeAsPlacement === true) {
411
+ p = bid.adUnitCode;
412
+ }
413
+ bid.params.placement = p;
407
414
  }
408
415
 
409
416
  bid.params.adUnitElementId = deepAccess(bid, 'ortb2Imp.ext.data.divId', bid.params.adUnitElementId);
@@ -47,7 +47,7 @@ export const PLACEMENT_SOURCES = {
47
47
  };
48
48
  export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME });
49
49
 
50
- const { logError, logWarn } = prefixLog('AdagioRtdProvider:');
50
+ const { logError, logInfo, logWarn } = prefixLog('AdagioRtdProvider:');
51
51
 
52
52
  // Guard to avoid storing the same bid data several times.
53
53
  const guard = new Set();
@@ -238,6 +238,25 @@ export const _internal = {
238
238
  return value;
239
239
  }
240
240
  });
241
+ },
242
+
243
+ // Compute the placement from the legacy RTD config params or ortb2Imp.ext.data.placement key.
244
+ computePlacementFromLegacy: function(rtdConfig, adUnit) {
245
+ const placementSource = deepAccess(rtdConfig, 'params.placementSource', '');
246
+ let placementFromSource = '';
247
+
248
+ switch (placementSource.toLowerCase()) {
249
+ case PLACEMENT_SOURCES.ADUNITCODE:
250
+ placementFromSource = adUnit.code;
251
+ break;
252
+ case PLACEMENT_SOURCES.GPID:
253
+ placementFromSource = deepAccess(adUnit, 'ortb2Imp.ext.gpid')
254
+ break;
255
+ }
256
+
257
+ const placementLegacy = deepAccess(adUnit, 'ortb2Imp.ext.data.placement', '');
258
+
259
+ return placementFromSource || placementLegacy;
241
260
  }
242
261
  };
243
262
 
@@ -317,7 +336,6 @@ function onBidRequest(bidderRequest, config, _userConsent) {
317
336
  * @param {*} config
318
337
  */
319
338
  function onGetBidRequestData(bidReqConfig, callback, config) {
320
- const configParams = deepAccess(config, 'params', {});
321
339
  const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global;
322
340
  const features = _internal.getFeatures().get();
323
341
  const ext = {
@@ -345,30 +363,11 @@ function onGetBidRequestData(bidReqConfig, callback, config) {
345
363
  const slotPosition = getSlotPosition(divId);
346
364
  deepSetValue(ortb2Imp, `ext.data.adg_rtd.adunit_position`, slotPosition);
347
365
 
348
- // It is expected that the publisher set a `adUnits[].ortb2Imp.ext.data.placement` value.
349
- // Btw, We allow fallback sources to programmatically set this value.
350
- // The source is defined in the `config.params.placementSource` and the possible values are `code` or `gpid`.
351
- // (Please note that this `placement` is not related to the oRTB video property.)
352
- if (!deepAccess(ortb2Imp, 'ext.data.placement')) {
353
- const { placementSource = '' } = configParams;
354
-
355
- switch (placementSource.toLowerCase()) {
356
- case PLACEMENT_SOURCES.ADUNITCODE:
357
- deepSetValue(ortb2Imp, 'ext.data.placement', adUnit.code);
358
- break;
359
- case PLACEMENT_SOURCES.GPID:
360
- deepSetValue(ortb2Imp, 'ext.data.placement', deepAccess(ortb2Imp, 'ext.gpid'));
361
- break;
362
- default:
363
- logWarn('`ortb2Imp.ext.data.placement` is missing and `params.definePlacement` is not set in the config.');
364
- }
365
- }
366
-
367
- // We expect that `pagetype`, `category`, `placement` are defined in FPD `ortb2.site.ext.data` and `adUnits[].ortb2Imp.ext.data` objects.
368
- // Btw, we have to ensure compatibility with publishers that use the "legacy" adagio params at the adUnit.params level.
369
366
  const adagioBid = adUnit.bids.find(bid => _internal.isAdagioBidder(bid.bidder));
370
367
  if (adagioBid) {
371
368
  // ortb2 level
369
+ // We expect that `pagetype`, `category` are defined in FPD `ortb2.site.ext.data` object.
370
+ // Btw, we still ensure compatibility with publishers that use the adagio params at the adUnit.params level.
372
371
  let mustWarnOrtb2 = false;
373
372
  if (!deepAccess(ortb2Site, 'ext.data.pagetype') && adagioBid.params.pagetype) {
374
373
  deepSetValue(ortb2Site, 'ext.data.pagetype', adagioBid.params.pagetype);
@@ -378,21 +377,28 @@ function onGetBidRequestData(bidReqConfig, callback, config) {
378
377
  deepSetValue(ortb2Site, 'ext.data.category', adagioBid.params.category);
379
378
  mustWarnOrtb2 = true;
380
379
  }
381
-
382
- // ortb2Imp level
383
- let mustWarnOrtb2Imp = false;
384
- if (!deepAccess(ortb2Imp, 'ext.data.placement')) {
385
- if (adagioBid.params.placement) {
386
- deepSetValue(ortb2Imp, 'ext.data.placement', adagioBid.params.placement);
387
- mustWarnOrtb2Imp = true;
388
- }
380
+ if (mustWarnOrtb2) {
381
+ logInfo('`pagetype` and/or `category` have been set in the FPD `ortb2.site.ext.data` object from `adUnits[].bids.adagio.params`.');
389
382
  }
390
383
 
391
- if (mustWarnOrtb2) {
392
- logWarn('`pagetype` and `category` must be defined in the FPD `ortb2.site.ext.data` object. Relying on `adUnits[].bids.adagio.params` is deprecated.');
384
+ // ortb2Imp level to handle legacy.
385
+ // The `placement` is finally set at the adUnit.params level (see https://github.com/prebid/Prebid.js/issues/12845)
386
+ // but we still need to set it at the ortb2Imp level for our internal use.
387
+ const placementParam = adagioBid.params.placement;
388
+ const adgRtdPlacement = deepAccess(ortb2Imp, 'ext.data.adg_rtd.placement', '');
389
+
390
+ if (placementParam) {
391
+ // Always overwrite the ortb2Imp value with the one from the adagio adUnit.params.placement if defined.
392
+ // This is the common case.
393
+ deepSetValue(ortb2Imp, 'ext.data.adg_rtd.placement', placementParam);
393
394
  }
394
- if (mustWarnOrtb2Imp) {
395
- logWarn('`placement` must be defined in the FPD `adUnits[].ortb2Imp.ext.data` object. Relying on `adUnits[].bids.adagio.params` is deprecated.');
395
+
396
+ if (!placementParam && !adgRtdPlacement) {
397
+ const p = _internal.computePlacementFromLegacy(config, adUnit);
398
+ if (p) {
399
+ deepSetValue(ortb2Imp, 'ext.data.adg_rtd.placement', p);
400
+ logWarn('`ortb2Imp.ext.data.adg_rtd.placement` has been set from a legacy source. Please set `bids[].adagio.params.placement` or `ortb2Imp.ext.data.adg_rtd.placement` value.');
401
+ }
396
402
  }
397
403
  }
398
404
  });