prebid.js 6.5.0 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/.eslintrc.js +8 -1
  2. package/integrationExamples/gpt/amp/creative.html +11 -33
  3. package/integrationExamples/gpt/weboramaRtdProvider_example.html +154 -115
  4. package/integrationExamples/gpt/x-domain/creative.html +63 -29
  5. package/modules/.submodules.json +2 -1
  6. package/modules/adagioBidAdapter.js +0 -8
  7. package/modules/adagioBidAdapter.md +1 -1
  8. package/modules/adbookpspBidAdapter.js +27 -10
  9. package/modules/adhashBidAdapter.js +3 -3
  10. package/modules/adkernelBidAdapter.js +2 -1
  11. package/modules/admanBidAdapter.js +10 -4
  12. package/modules/adomikAnalyticsAdapter.js +23 -11
  13. package/modules/adqueryIdSystem.js +103 -0
  14. package/modules/adqueryIdSystem.md +35 -0
  15. package/modules/appnexusBidAdapter.js +14 -2
  16. package/modules/asealBidAdapter.js +58 -0
  17. package/modules/asealBidAdapter.md +52 -0
  18. package/modules/bliinkBidAdapter.js +2 -1
  19. package/modules/brandmetricsRtdProvider.js +168 -0
  20. package/modules/brandmetricsRtdProvider.md +40 -0
  21. package/modules/colossussspBidAdapter.js +12 -8
  22. package/modules/colossussspBidAdapter.md +15 -1
  23. package/modules/compassBidAdapter.js +10 -3
  24. package/modules/consumableBidAdapter.md +1 -1
  25. package/modules/conversantBidAdapter.js +7 -0
  26. package/modules/criteoBidAdapter.js +10 -1
  27. package/modules/criteoIdSystem.js +29 -7
  28. package/modules/currency.js +26 -1
  29. package/modules/displayioBidAdapter.js +157 -0
  30. package/modules/displayioBidAdapter.md +148 -0
  31. package/modules/e_volutionBidAdapter.js +158 -0
  32. package/modules/glimpseBidAdapter.js +66 -44
  33. package/modules/gnetBidAdapter.js +3 -3
  34. package/modules/gnetBidAdapter.md +4 -4
  35. package/modules/gumgumBidAdapter.js +56 -42
  36. package/modules/idImportLibrary.js +45 -8
  37. package/modules/idImportLibrary.md +4 -0
  38. package/modules/improvedigitalBidAdapter.js +29 -2
  39. package/modules/interactiveOffersBidAdapter.js +9 -6
  40. package/modules/jwplayerRtdProvider.js +71 -6
  41. package/modules/jwplayerRtdProvider.md +27 -11
  42. package/modules/kargoBidAdapter.js +2 -2
  43. package/modules/lunamediahbBidAdapter.js +32 -4
  44. package/modules/nextMillenniumBidAdapter.js +3 -1
  45. package/modules/oguryBidAdapter.js +14 -14
  46. package/modules/onetagBidAdapter.js +4 -2
  47. package/modules/pilotxBidAdapter.js +147 -0
  48. package/modules/pilotxBidAdapter.md +50 -0
  49. package/modules/priceFloors.js +2 -1
  50. package/modules/proxistoreBidAdapter.js +0 -2
  51. package/modules/pubmaticAnalyticsAdapter.js +16 -0
  52. package/modules/richaudienceBidAdapter.js +10 -4
  53. package/modules/riseBidAdapter.js +18 -7
  54. package/modules/rtbhouseBidAdapter.js +14 -4
  55. package/modules/rtdModule/index.js +14 -15
  56. package/modules/rubiconAnalyticsAdapter.js +8 -2
  57. package/modules/seedingAllianceBidAdapter.js +3 -3
  58. package/modules/sharethroughBidAdapter.js +12 -17
  59. package/modules/showheroes-bsBidAdapter.js +13 -2
  60. package/modules/sortableAnalyticsAdapter.js +5 -4
  61. package/modules/sovrnBidAdapter.js +93 -18
  62. package/modules/sovrnBidAdapter.md +80 -2
  63. package/modules/synacormediaBidAdapter.js +31 -10
  64. package/modules/tappxBidAdapter.js +8 -5
  65. package/modules/teadsBidAdapter.js +1 -2
  66. package/modules/undertoneBidAdapter.js +17 -1
  67. package/modules/userId/eids.js +7 -1
  68. package/modules/userId/userId.md +8 -0
  69. package/modules/viewability.js +177 -0
  70. package/modules/viewability.md +87 -0
  71. package/modules/weboramaRtdProvider.js +264 -34
  72. package/modules/weboramaRtdProvider.md +110 -40
  73. package/modules/welectBidAdapter.js +106 -0
  74. package/modules/yahoosspBidAdapter.js +2 -0
  75. package/package.json +2 -1
  76. package/src/adRendering.js +38 -0
  77. package/src/adloader.js +2 -1
  78. package/src/auction.js +103 -73
  79. package/src/bidderSettings.js +69 -0
  80. package/src/hook.js +5 -1
  81. package/src/prebid.js +19 -21
  82. package/src/secureCreatives.js +131 -47
  83. package/src/targeting.js +3 -2
  84. package/src/utils.js +13 -10
  85. package/test/helpers/syncPromise.js +71 -0
  86. package/test/spec/auctionmanager_spec.js +179 -15
  87. package/test/spec/modules/adagioBidAdapter_spec.js +0 -10
  88. package/test/spec/modules/adbookpspBidAdapter_spec.js +17 -3
  89. package/test/spec/modules/adhashBidAdapter_spec.js +2 -2
  90. package/test/spec/modules/admanBidAdapter_spec.js +2 -2
  91. package/test/spec/modules/adomikAnalyticsAdapter_spec.js +3 -1
  92. package/test/spec/modules/adqueryIdSystem_spec.js +74 -0
  93. package/test/spec/modules/appnexusBidAdapter_spec.js +27 -0
  94. package/test/spec/modules/asealBidAdapter_spec.js +144 -0
  95. package/test/spec/modules/bliinkBidAdapter_spec.js +2 -0
  96. package/test/spec/modules/brandmetricsRtdProvider_spec.js +191 -0
  97. package/test/spec/modules/colossussspBidAdapter_spec.js +5 -2
  98. package/test/spec/modules/compassBidAdapter_spec.js +1 -0
  99. package/test/spec/modules/conversantBidAdapter_spec.js +54 -2
  100. package/test/spec/modules/criteoBidAdapter_spec.js +21 -0
  101. package/test/spec/modules/criteoIdSystem_spec.js +6 -3
  102. package/test/spec/modules/currency_spec.js +21 -6
  103. package/test/spec/modules/displayioBidAdapter_spec.js +239 -0
  104. package/test/spec/modules/e_volutionBidAdapter_spec.js +242 -0
  105. package/test/spec/modules/eids_spec.js +15 -0
  106. package/test/spec/modules/glimpseBidAdapter_spec.js +0 -18
  107. package/test/spec/modules/gnetBidAdapter_spec.js +6 -6
  108. package/test/spec/modules/gumgumBidAdapter_spec.js +46 -0
  109. package/test/spec/modules/idImportLibrary_spec.js +197 -10
  110. package/test/spec/modules/improvedigitalBidAdapter_spec.js +61 -0
  111. package/test/spec/modules/jwplayerRtdProvider_spec.js +195 -2
  112. package/test/spec/modules/kargoBidAdapter_spec.js +1 -1
  113. package/test/spec/modules/loglyliftBidAdapter_spec.js +1 -1
  114. package/test/spec/modules/lunamediahbBidAdapter_spec.js +27 -1
  115. package/test/spec/modules/nextMillenniumBidAdapter_spec.js +1 -1
  116. package/test/spec/modules/oguryBidAdapter_spec.js +69 -3
  117. package/test/spec/modules/pilotxBidAdapter_spec.js +244 -0
  118. package/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +13 -1
  119. package/test/spec/modules/realTimeDataModule_spec.js +67 -5
  120. package/test/spec/modules/richaudienceBidAdapter_spec.js +40 -0
  121. package/test/spec/modules/riseBidAdapter_spec.js +31 -5
  122. package/test/spec/modules/rtbhouseBidAdapter_spec.js +20 -0
  123. package/test/spec/modules/rubiconAnalyticsAdapter_spec.js +61 -1
  124. package/test/spec/modules/sharethroughBidAdapter_spec.js +91 -6
  125. package/test/spec/modules/showheroes-bsBidAdapter_spec.js +2 -0
  126. package/test/spec/modules/sortableAnalyticsAdapter_spec.js +2 -3
  127. package/test/spec/modules/sovrnBidAdapter_spec.js +413 -333
  128. package/test/spec/modules/synacormediaBidAdapter_spec.js +70 -0
  129. package/test/spec/modules/tappxBidAdapter_spec.js +0 -19
  130. package/test/spec/modules/teadsBidAdapter_spec.js +14 -59
  131. package/test/spec/modules/undertoneBidAdapter_spec.js +55 -2
  132. package/test/spec/modules/userId_spec.js +68 -19
  133. package/test/spec/modules/viewability_spec.js +280 -0
  134. package/test/spec/modules/weboramaRtdProvider_spec.js +536 -20
  135. package/test/spec/modules/welectBidAdapter_spec.js +211 -0
  136. package/test/spec/modules/yahoosspBidAdapter_spec.js +10 -0
  137. package/test/spec/unit/core/bidderSettings_spec.js +123 -0
  138. package/test/spec/unit/pbjs_api_spec.js +21 -8
  139. package/test/spec/unit/secureCreatives_spec.js +143 -24
@@ -4,22 +4,47 @@
4
4
  */
5
5
 
6
6
  import events from './events.js';
7
- import { fireNativeTrackers, getAssetMessage, getAllAssetsMessage } from './native.js';
7
+ import {fireNativeTrackers, getAllAssetsMessage, getAssetMessage} from './native.js';
8
8
  import constants from './constants.json';
9
- import { logWarn, replaceAuctionPrice, deepAccess, isGptPubadsDefined, isApnGetTagDefined } from './utils.js';
10
- import { auctionManager } from './auctionManager.js';
9
+ import {deepAccess, isApnGetTagDefined, isGptPubadsDefined, logError, logWarn, replaceAuctionPrice} from './utils.js';
10
+ import {auctionManager} from './auctionManager.js';
11
11
  import find from 'core-js-pure/features/array/find.js';
12
- import { isRendererRequired, executeRenderer } from './Renderer.js';
12
+ import {executeRenderer, isRendererRequired} from './Renderer.js';
13
13
  import includes from 'core-js-pure/features/array/includes.js';
14
- import { config } from './config.js';
14
+ import {config} from './config.js';
15
+ import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js';
15
16
 
16
17
  const BID_WON = constants.EVENTS.BID_WON;
17
18
  const STALE_RENDER = constants.EVENTS.STALE_RENDER;
18
19
 
20
+ const HANDLER_MAP = {
21
+ 'Prebid Request': handleRenderRequest,
22
+ 'Prebid Native': handleNativeRequest,
23
+ 'Prebid Event': handleEventRequest,
24
+ }
25
+
19
26
  export function listenMessagesFromCreative() {
20
27
  window.addEventListener('message', receiveMessage, false);
21
28
  }
22
29
 
30
+ export function getReplier(ev) {
31
+ if (ev.origin == null && ev.ports.length === 0) {
32
+ return function () {
33
+ const msg = 'Cannot post message to a frame with null origin. Please update creatives to use MessageChannel, see https://github.com/prebid/Prebid.js/issues/7870'
34
+ logError(msg)
35
+ throw new Error(msg);
36
+ }
37
+ } else if (ev.ports.length > 0) {
38
+ return function (message) {
39
+ ev.ports[0].postMessage(JSON.stringify(message));
40
+ }
41
+ } else {
42
+ return function (message) {
43
+ ev.source.postMessage(JSON.stringify(message), ev.origin);
44
+ }
45
+ }
46
+ }
47
+
23
48
  export function receiveMessage(ev) {
24
49
  var key = ev.message ? 'message' : 'data';
25
50
  var data = {};
@@ -29,70 +54,128 @@ export function receiveMessage(ev) {
29
54
  return;
30
55
  }
31
56
 
32
- if (data && data.adId) {
57
+ if (data && data.adId && data.message) {
33
58
  const adObject = find(auctionManager.getBidsReceived(), function (bid) {
34
59
  return bid.adId === data.adId;
35
60
  });
61
+ if (HANDLER_MAP.hasOwnProperty(data.message)) {
62
+ HANDLER_MAP[data.message](getReplier(ev), data, adObject);
63
+ }
64
+ }
65
+ }
36
66
 
37
- if (adObject && data.message === 'Prebid Request') {
38
- if (adObject.status === constants.BID_STATUS.RENDERED) {
39
- logWarn(`Ad id ${adObject.adId} has been rendered before`);
40
- events.emit(STALE_RENDER, adObject);
41
- if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) {
42
- return;
43
- }
44
- }
67
+ function handleRenderRequest(reply, data, adObject) {
68
+ if (adObject == null) {
69
+ emitAdRenderFail({
70
+ reason: constants.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD,
71
+ message: `Cannot find ad '${data.adId}' for cross-origin render request`,
72
+ id: data.adId
73
+ });
74
+ return;
75
+ }
76
+ if (adObject.status === constants.BID_STATUS.RENDERED) {
77
+ logWarn(`Ad id ${adObject.adId} has been rendered before`);
78
+ events.emit(STALE_RENDER, adObject);
79
+ if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) {
80
+ return;
81
+ }
82
+ }
83
+
84
+ try {
85
+ _sendAdToCreative(adObject, reply);
86
+ } catch (e) {
87
+ emitAdRenderFail({
88
+ reason: constants.AD_RENDER_FAILED_REASON.EXCEPTION,
89
+ message: e.message,
90
+ id: data.adId,
91
+ bid: adObject
92
+ });
93
+ return;
94
+ }
45
95
 
46
- _sendAdToCreative(adObject, ev);
96
+ // save winning bids
97
+ auctionManager.addWinningBid(adObject);
47
98
 
48
- // save winning bids
49
- auctionManager.addWinningBid(adObject);
99
+ events.emit(BID_WON, adObject);
100
+ }
50
101
 
102
+ function handleNativeRequest(reply, data, adObject) {
103
+ // handle this script from native template in an ad server
104
+ // window.parent.postMessage(JSON.stringify({
105
+ // message: 'Prebid Native',
106
+ // adId: '%%PATTERN:hb_adid%%'
107
+ // }), '*');
108
+ if (adObject == null) {
109
+ logError(`Cannot find ad '${data.adId}' for x-origin event request`);
110
+ return;
111
+ }
112
+ switch (data.action) {
113
+ case 'assetRequest':
114
+ reply(getAssetMessage(data, adObject));
115
+ break;
116
+ case 'allAssetRequest':
117
+ reply(getAllAssetsMessage(data, adObject));
118
+ break;
119
+ case 'resizeNativeHeight':
120
+ adObject.height = data.height;
121
+ adObject.width = data.width;
122
+ resizeRemoteCreative(adObject);
123
+ break;
124
+ default:
125
+ const trackerType = fireNativeTrackers(data, adObject);
126
+ if (trackerType === 'click') {
127
+ return;
128
+ }
129
+ auctionManager.addWinningBid(adObject);
51
130
  events.emit(BID_WON, adObject);
52
- }
131
+ }
132
+ }
53
133
 
54
- // handle this script from native template in an ad server
55
- // window.parent.postMessage(JSON.stringify({
56
- // message: 'Prebid Native',
57
- // adId: '%%PATTERN:hb_adid%%'
58
- // }), '*');
59
- if (adObject && data.message === 'Prebid Native') {
60
- if (data.action === 'assetRequest') {
61
- const message = getAssetMessage(data, adObject);
62
- ev.source.postMessage(JSON.stringify(message), ev.origin);
63
- } else if (data.action === 'allAssetRequest') {
64
- const message = getAllAssetsMessage(data, adObject);
65
- ev.source.postMessage(JSON.stringify(message), ev.origin);
66
- } else if (data.action === 'resizeNativeHeight') {
67
- adObject.height = data.height;
68
- adObject.width = data.width;
69
- resizeRemoteCreative(adObject);
70
- } else {
71
- const trackerType = fireNativeTrackers(data, adObject);
72
- if (trackerType === 'click') { return; }
73
-
74
- auctionManager.addWinningBid(adObject);
75
- events.emit(BID_WON, adObject);
76
- }
77
- }
134
+ function handleEventRequest(reply, data, adObject) {
135
+ if (adObject == null) {
136
+ logError(`Cannot find ad '${data.adId}' for x-origin event request`);
137
+ return;
138
+ }
139
+ if (adObject.status !== constants.BID_STATUS.RENDERED) {
140
+ logWarn(`Received x-origin event request without corresponding render request for ad '${data.adId}'`);
141
+ return;
142
+ }
143
+ switch (data.event) {
144
+ case constants.EVENTS.AD_RENDER_FAILED:
145
+ emitAdRenderFail({
146
+ bid: adObject,
147
+ id: data.adId,
148
+ reason: data.info.reason,
149
+ message: data.info.message
150
+ });
151
+ break;
152
+ case constants.EVENTS.AD_RENDER_SUCCEEDED:
153
+ emitAdRenderSucceeded({
154
+ doc: null,
155
+ bid: adObject,
156
+ id: data.adId
157
+ });
158
+ break;
159
+ default:
160
+ logError(`Received x-origin event request for unsupported event: '${data.event}' (adId: '${data.adId}')`)
78
161
  }
79
162
  }
80
163
 
81
- export function _sendAdToCreative(adObject, ev) {
164
+ export function _sendAdToCreative(adObject, reply) {
82
165
  const { adId, ad, adUrl, width, height, renderer, cpm } = adObject;
83
166
  // rendering for outstream safeframe
84
167
  if (isRendererRequired(renderer)) {
85
168
  executeRenderer(renderer, adObject);
86
169
  } else if (adId) {
87
170
  resizeRemoteCreative(adObject);
88
- ev.source.postMessage(JSON.stringify({
171
+ reply({
89
172
  message: 'Prebid Response',
90
173
  ad: replaceAuctionPrice(ad, cpm),
91
174
  adUrl: replaceAuctionPrice(adUrl, cpm),
92
175
  adId,
93
176
  width,
94
177
  height
95
- }), ev.origin);
178
+ });
96
179
  }
97
180
  }
98
181
 
@@ -127,11 +210,12 @@ function resizeRemoteCreative({ adId, adUnitCode, width, height }) {
127
210
  }
128
211
 
129
212
  function getDfpElementId(adId) {
130
- return find(window.googletag.pubads().getSlots(), slot => {
213
+ const slot = find(window.googletag.pubads().getSlots(), slot => {
131
214
  return find(slot.getTargetingKeys(), key => {
132
215
  return includes(slot.getTargeting(key), adId);
133
216
  });
134
- }).getSlotElementId();
217
+ });
218
+ return slot ? slot.getSlotElementId() : null;
135
219
  }
136
220
 
137
221
  function getAstElementId(adUnitCode) {
package/src/targeting.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  uniques, isGptPubadsDefined, getHighestCpm, getOldestHighestCpmBid, groupBy, isAdUnitCodeMatchingSlot, timestamp,
3
- deepAccess, deepClone, logError, logWarn, logInfo, isFn, isArray, logMessage, isStr, isAllowZeroCpmBidsEnabled
3
+ deepAccess, deepClone, logError, logWarn, logInfo, isFn, isArray, logMessage, isStr,
4
4
  } from './utils.js';
5
5
  import { config } from './config.js';
6
6
  import { NATIVE_TARGETING_KEYS } from './native.js';
@@ -8,6 +8,7 @@ import { auctionManager } from './auctionManager.js';
8
8
  import { sizeSupported } from './sizeMapping.js';
9
9
  import { ADPOD } from './mediaTypes.js';
10
10
  import { hook } from './hook.js';
11
+ import { bidderSettings } from './bidderSettings.js';
11
12
  import includes from 'core-js-pure/features/array/includes.js';
12
13
  import find from 'core-js-pure/features/array/find.js';
13
14
 
@@ -459,7 +460,7 @@ export function newTargeting(auctionManager) {
459
460
  const adUnitCodes = getAdUnitCodes(adUnitCode);
460
461
  return bidsReceived
461
462
  .filter(bid => includes(adUnitCodes, bid.adUnitCode))
462
- .filter(bid => (isAllowZeroCpmBidsEnabled(bid.bidderCode)) ? bid.cpm >= 0 : bid.cpm > 0)
463
+ .filter(bid => (bidderSettings.get(bid.bidderCode, 'allowZeroCpmBids') === true) ? bid.cpm >= 0 : bid.cpm > 0)
463
464
  .map(bid => bid.adUnitCode)
464
465
  .filter(uniques)
465
466
  .map(adUnitCode => bidsReceived
package/src/utils.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable no-console */
2
2
  import { config } from './config.js';
3
- import { getGlobal } from './prebidGlobal.js';
4
3
  import clone from 'just-clone';
5
4
  import find from 'core-js-pure/features/array/find.js';
6
5
  import includes from 'core-js-pure/features/array/includes.js';
@@ -22,7 +21,17 @@ let consoleLogExists = Boolean(consoleExists && window.console.log);
22
21
  let consoleInfoExists = Boolean(consoleExists && window.console.info);
23
22
  let consoleWarnExists = Boolean(consoleExists && window.console.warn);
24
23
  let consoleErrorExists = Boolean(consoleExists && window.console.error);
25
- var events = require('./events.js');
24
+
25
+ const emitEvent = (function () {
26
+ // lazy load events to avoid circular import
27
+ let ev;
28
+ return function() {
29
+ if (ev == null) {
30
+ ev = require('./events.js');
31
+ }
32
+ return ev.emit.apply(ev, arguments);
33
+ }
34
+ })();
26
35
 
27
36
  // this allows stubbing of utility functions that are used internally by other utility functions
28
37
  export const internal = {
@@ -265,14 +274,14 @@ export function logWarn() {
265
274
  if (debugTurnedOn() && consoleWarnExists) {
266
275
  console.warn.apply(console, decorateLog(arguments, 'WARNING:'));
267
276
  }
268
- events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments});
277
+ emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments});
269
278
  }
270
279
 
271
280
  export function logError() {
272
281
  if (debugTurnedOn() && consoleErrorExists) {
273
282
  console.error.apply(console, decorateLog(arguments, 'ERROR:'));
274
283
  }
275
- events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments});
284
+ emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments});
276
285
  }
277
286
 
278
287
  function decorateLog(args, prefix) {
@@ -1333,9 +1342,3 @@ export function cyrb53Hash(str, seed = 0) {
1333
1342
  h2 = imul(h2 ^ (h2 >>> 16), 2246822507) ^ imul(h1 ^ (h1 >>> 13), 3266489909);
1334
1343
  return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString();
1335
1344
  }
1336
-
1337
- export function isAllowZeroCpmBidsEnabled(bidderCode) {
1338
- const bidderSettings = getGlobal().bidderSettings;
1339
- return ((bidderSettings[bidderCode] && bidderSettings[bidderCode].allowZeroCpmBids === true) ||
1340
- (bidderSettings.standard && bidderSettings.standard.allowZeroCpmBids === true));
1341
- }
@@ -0,0 +1,71 @@
1
+ const orig = {};
2
+ ['resolve', 'reject', 'all', 'race', 'allSettled'].forEach((k) => orig[k] = Promise[k].bind(Promise))
3
+
4
+ // Callbacks attached through Promise.resolve(value).then(...) will usually
5
+ // not execute immediately even if `value` is immediately available. This
6
+ // breaks tests that were written before promises even though they are semantically still valid.
7
+ // They can be made to work by making promises quasi-synchronous.
8
+
9
+ export function SyncPromise(value, fail = false) {
10
+ if (value instanceof SyncPromise) {
11
+ return value;
12
+ } else if (typeof value === 'object' && typeof value.then === 'function') {
13
+ return orig.resolve(value);
14
+ } else {
15
+ Object.assign(this, {
16
+ then: function (cb, err) {
17
+ const handler = fail ? err : cb;
18
+ if (handler != null) {
19
+ return new SyncPromise(handler(value));
20
+ } else {
21
+ return this;
22
+ }
23
+ },
24
+ catch: function (cb) {
25
+ if (fail) {
26
+ return new SyncPromise(cb(value))
27
+ } else {
28
+ return this;
29
+ }
30
+ },
31
+ finally: function (cb) {
32
+ cb();
33
+ return this;
34
+ },
35
+ __value: fail ? {status: 'rejected', reason: value} : {status: 'fulfilled', value}
36
+ })
37
+ }
38
+ }
39
+
40
+ Object.assign(SyncPromise, {
41
+ resolve: (val) => new SyncPromise(val),
42
+ reject: (val) => new SyncPromise(val, true),
43
+ race: (promises) => promises.find((p) => p instanceof SyncPromise) || orig.race(promises),
44
+ allSettled: (promises) => {
45
+ if (promises.every((p) => p instanceof SyncPromise)) {
46
+ return new SyncPromise(promises.map((p) => p.__value))
47
+ } else {
48
+ return orig.allSettled(promises);
49
+ }
50
+ },
51
+ all: (promises) => {
52
+ if (promises.every((p) => p instanceof SyncPromise)) {
53
+ return SyncPromise.allSettled(promises).then((result) => {
54
+ const err = result.find((r) => r.status === 'rejected');
55
+ if (err != null) {
56
+ return new SyncPromise(err.reason, true);
57
+ } else {
58
+ return new SyncPromise(result.map((r) => r.value))
59
+ }
60
+ })
61
+ } else {
62
+ return orig.all(promises);
63
+ }
64
+ }
65
+ })
66
+
67
+ export function synchronizePromise(sandbox) {
68
+ Object.keys(orig).forEach((k) => {
69
+ sandbox.stub(window.Promise, k).callsFake(SyncPromise[k]);
70
+ })
71
+ }
@@ -4,6 +4,8 @@ import {
4
4
  AUCTION_COMPLETED,
5
5
  adjustBids,
6
6
  getMediaTypeGranularity,
7
+ getPriceByGranularity,
8
+ addBidResponse
7
9
  } from 'src/auction.js';
8
10
  import CONSTANTS from 'src/constants.json';
9
11
  import * as auctionModule from 'src/auction.js';
@@ -14,6 +16,10 @@ import * as store from 'src/videoCache.js';
14
16
  import * as ajaxLib from 'src/ajax.js';
15
17
  import find from 'core-js-pure/features/array/find.js';
16
18
  import { server } from 'test/mocks/xhr.js';
19
+ import {expect} from 'chai';
20
+ import {hook} from '../../src/hook.js';
21
+ import 'src/debugging.js'
22
+ import {synchronizePromise} from '../helpers/syncPromise.js'; // some of these tests require debugging hooks to be loaded
17
23
 
18
24
  var assert = require('assert');
19
25
 
@@ -124,6 +130,27 @@ function mockAjaxBuilder() {
124
130
  }
125
131
 
126
132
  describe('auctionmanager.js', function () {
133
+ let promiseSandbox;
134
+
135
+ before(() => {
136
+ // hooks are global and their side effects depend on what has been loaded... not ideal for unit tests
137
+ [
138
+ auctionModule.addBidResponse,
139
+ auctionModule.addBidderRequests,
140
+ auctionModule.bidsBackCallback
141
+ ].forEach((h) => h.getHooks().remove())
142
+ hook.ready();
143
+ });
144
+
145
+ beforeEach(() => {
146
+ promiseSandbox = sinon.createSandbox();
147
+ synchronizePromise(promiseSandbox);
148
+ });
149
+
150
+ afterEach(() => {
151
+ promiseSandbox.restore();
152
+ })
153
+
127
154
  describe('getKeyValueTargetingPairs', function () {
128
155
  const DEFAULT_BID = {
129
156
  cpm: 5.578,
@@ -1255,16 +1282,41 @@ describe('auctionmanager.js', function () {
1255
1282
  });
1256
1283
  });
1257
1284
 
1285
+ describe('getPriceByGranularity', () => {
1286
+ beforeEach(() => {
1287
+ config.setConfig({
1288
+ mediaTypePriceGranularity: {
1289
+ video: 'medium',
1290
+ banner: 'low'
1291
+ }
1292
+ });
1293
+ })
1294
+
1295
+ afterEach(() => {
1296
+ config.resetConfig();
1297
+ })
1298
+
1299
+ it('evaluates undef granularity on each call', () => {
1300
+ const gpbg = getPriceByGranularity();
1301
+ expect(gpbg({
1302
+ mediaType: 'video', pbMg: 'medium'
1303
+ }, {
1304
+ 'mediaTypes': {video: {id: '1'}}
1305
+ })).to.equal('medium');
1306
+ expect(gpbg({
1307
+ mediaType: 'banner',
1308
+ pbLg: 'low'
1309
+ }, {
1310
+ 'mediaTypes': {banner: {}}
1311
+ })).to.equal('low');
1312
+ });
1313
+ })
1314
+
1258
1315
  describe('auctionCallbacks', function() {
1259
1316
  let bids = TEST_BIDS;
1260
1317
  let bidRequests;
1261
1318
  let doneSpy;
1262
- let auction = {
1263
- getBidRequests: () => bidRequests,
1264
- getAuctionId: () => '1',
1265
- addBidReceived: () => true,
1266
- getTimeout: () => 1000
1267
- }
1319
+ let auction;
1268
1320
 
1269
1321
  beforeEach(() => {
1270
1322
  doneSpy = sinon.spy();
@@ -1272,12 +1324,21 @@ describe('auctionmanager.js', function () {
1272
1324
  cache: {
1273
1325
  url: 'https://prebid.adnxs.com/pbc/v1/cache'
1274
1326
  }
1275
- })
1327
+ });
1328
+ const start = Date.now();
1329
+ auction = {
1330
+ getBidRequests: () => bidRequests,
1331
+ getAuctionId: () => '1',
1332
+ addBidReceived: () => true,
1333
+ getTimeout: () => 1000,
1334
+ getAuctionStart: () => start,
1335
+ }
1276
1336
  });
1277
1337
 
1278
1338
  afterEach(() => {
1279
1339
  doneSpy.resetHistory();
1280
1340
  config.resetConfig();
1341
+ bidRequests = null;
1281
1342
  });
1282
1343
 
1283
1344
  it('should call auction done after bid is added to auction for mediaType banner', function () {
@@ -1328,19 +1389,114 @@ describe('auctionmanager.js', function () {
1328
1389
  const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`;
1329
1390
  server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody);
1330
1391
  assert.equal(doneSpy.callCount, 1);
1331
- })
1392
+ });
1393
+
1394
+ describe('when addBidResponse hook returns promises', () => {
1395
+ let resolvers, callbacks, bids;
1396
+
1397
+ function hook(next, ...args) {
1398
+ next.bail(new Promise((resolve, reject) => {
1399
+ resolvers.resolve.push(resolve);
1400
+ resolvers.reject.push(reject);
1401
+ }).finally(() => next(...args)));
1402
+ }
1403
+
1404
+ function invokeCallbacks() {
1405
+ bids.forEach((bid, i) => callbacks.addBidResponse.call(bidRequests[i], ADUNIT_CODE, bid));
1406
+ bidRequests.forEach(bidRequest => callbacks.adapterDone.call(bidRequest));
1407
+ }
1408
+
1409
+ function delay(ms = 0) {
1410
+ return new Promise((resolve) => {
1411
+ setTimeout(resolve, ms)
1412
+ });
1413
+ }
1414
+
1415
+ beforeEach(() => {
1416
+ promiseSandbox.restore();
1417
+ bids = [
1418
+ mockBid({bidderCode: BIDDER_CODE1}),
1419
+ mockBid({bidderCode: BIDDER_CODE})
1420
+ ]
1421
+ bidRequests = bids.map((b) => mockBidRequest(b));
1422
+ resolvers = {resolve: [], reject: []};
1423
+ addBidResponse.before(hook);
1424
+ callbacks = auctionCallbacks(doneSpy, auction);
1425
+ Object.assign(auction, {
1426
+ addNoBid: sinon.spy()
1427
+ });
1428
+ });
1429
+
1430
+ afterEach(() => {
1431
+ addBidResponse.getHooks({hook: hook}).remove();
1432
+ });
1433
+
1434
+ Object.entries({
1435
+ 'all succeed': ['resolve', 'resolve'],
1436
+ 'some fail': ['resolve', 'reject'],
1437
+ 'all fail': ['reject', 'reject']
1438
+ }).forEach(([test, results]) => {
1439
+ describe(`(and ${test})`, () => {
1440
+ it('should wait for them to complete before calling auctionDone', () => {
1441
+ invokeCallbacks();
1442
+ return delay().then(() => {
1443
+ expect(doneSpy.called).to.be.false;
1444
+ expect(auction.addNoBid.called).to.be.false;
1445
+ resolvers[results[0]][0]();
1446
+ return delay();
1447
+ }).then(() => {
1448
+ expect(doneSpy.called).to.be.false;
1449
+ expect(auction.addNoBid.called).to.be.false;
1450
+ resolvers[results[1]][1]();
1451
+ return delay();
1452
+ }).then(() => {
1453
+ expect(doneSpy.called).to.be.true;
1454
+ });
1455
+ });
1456
+ });
1457
+ });
1458
+
1459
+ Object.entries({
1460
+ bidder: (timeout) => {
1461
+ bidRequests.forEach((r) => r.timeout = timeout);
1462
+ auction.getTimeout = () => timeout + 10000
1463
+ },
1464
+ auction: (timeout) => {
1465
+ auction.getTimeout = () => timeout;
1466
+ bidRequests.forEach((r) => r.timeout = timeout + 10000)
1467
+ }
1468
+ }).forEach(([test, setTimeout]) => {
1469
+ it(`should respect ${test} timeout if they never complete`, () => {
1470
+ const start = Date.now() - 2900;
1471
+ auction.getAuctionStart = () => start;
1472
+ setTimeout(3000);
1473
+ invokeCallbacks();
1474
+ return delay().then(() => {
1475
+ expect(doneSpy.called).to.be.false;
1476
+ return delay(100);
1477
+ }).then(() => {
1478
+ expect(doneSpy.called).to.be.true;
1479
+ });
1480
+ });
1481
+
1482
+ it(`should not wait if ${test} has already timed out`, () => {
1483
+ const start = Date.now() - 2000;
1484
+ auction.getAuctionStart = () => start;
1485
+ setTimeout(1000);
1486
+ invokeCallbacks();
1487
+ return delay().then(() => {
1488
+ expect(doneSpy.called).to.be.true;
1489
+ });
1490
+ });
1491
+ })
1492
+ });
1332
1493
  });
1333
1494
 
1334
1495
  describe('auctionOptions', function() {
1335
1496
  let bidRequests;
1336
1497
  let doneSpy;
1337
1498
  let clock;
1338
- let auction = {
1339
- getBidRequests: () => bidRequests,
1340
- getAuctionId: () => '1',
1341
- addBidReceived: () => true,
1342
- getTimeout: () => 1000
1343
- }
1499
+ let auction;
1344
1500
  let requiredBidder = BIDDER_CODE;
1345
1501
  let requiredBidder1 = BIDDER_CODE1;
1346
1502
  let secondaryBidder = 'doNotWaitForMe';
@@ -1352,7 +1508,15 @@ describe('auctionmanager.js', function () {
1352
1508
  'auctionOptions': {
1353
1509
  secondaryBidders: [ secondaryBidder ]
1354
1510
  }
1355
- })
1511
+ });
1512
+ const start = Date.now();
1513
+ auction = {
1514
+ getBidRequests: () => bidRequests,
1515
+ getAuctionId: () => '1',
1516
+ addBidReceived: () => true,
1517
+ getTimeout: () => 1000,
1518
+ getAuctionStart: () => start,
1519
+ }
1356
1520
  });
1357
1521
 
1358
1522
  afterEach(() => {
@@ -244,16 +244,6 @@ describe('Adagio bid adapter', () => {
244
244
  expect(spec.isBidRequestValid(bid03)).to.equal(false);
245
245
  expect(spec.isBidRequestValid(bid04)).to.equal(false);
246
246
  });
247
-
248
- it('should return false when refererInfo.reachedTop is false', function() {
249
- sandbox.spy(utils, 'logWarn');
250
- sandbox.stub(adagio, 'getRefererInfo').returns({ reachedTop: false });
251
- const bid = new BidRequestBuilder().withParams().build();
252
-
253
- expect(spec.isBidRequestValid(bid)).to.equal(false);
254
- sinon.assert.callCount(utils.logWarn, 1);
255
- sinon.assert.calledWith(utils.logWarn, 'Adagio: the main page url is unreachabled.');
256
- });
257
247
  });
258
248
 
259
249
  describe('buildRequests()', function() {