prebid.js 6.4.0 → 6.8.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 (147) hide show
  1. package/integrationExamples/gpt/amp/creative.html +11 -33
  2. package/integrationExamples/gpt/x-domain/creative.html +53 -26
  3. package/modules/.submodules.json +2 -1
  4. package/modules/adagioBidAdapter.js +0 -8
  5. package/modules/adagioBidAdapter.md +1 -1
  6. package/modules/adbookpspBidAdapter.js +27 -10
  7. package/modules/adhashBidAdapter.js +3 -3
  8. package/modules/adkernelBidAdapter.js +2 -1
  9. package/modules/admanBidAdapter.js +10 -4
  10. package/modules/adomikAnalyticsAdapter.js +23 -11
  11. package/modules/adqueryIdSystem.js +103 -0
  12. package/modules/adqueryIdSystem.md +35 -0
  13. package/modules/adyoulikeBidAdapter.js +13 -9
  14. package/modules/appnexusBidAdapter.js +11 -0
  15. package/modules/bliinkBidAdapter.js +3 -2
  16. package/modules/brandmetricsRtdProvider.js +168 -0
  17. package/modules/brandmetricsRtdProvider.md +40 -0
  18. package/modules/colossussspBidAdapter.js +12 -8
  19. package/modules/colossussspBidAdapter.md +15 -1
  20. package/modules/compassBidAdapter.js +10 -3
  21. package/modules/consentManagement.js +7 -1
  22. package/modules/consumableBidAdapter.md +1 -1
  23. package/modules/criteoBidAdapter.js +10 -1
  24. package/modules/criteoIdSystem.js +29 -7
  25. package/modules/currency.js +26 -1
  26. package/modules/displayioBidAdapter.js +157 -0
  27. package/modules/displayioBidAdapter.md +148 -0
  28. package/modules/docereeBidAdapter.js +10 -1
  29. package/modules/docereeBidAdapter.md +2 -0
  30. package/modules/e_volutionBidAdapter.js +158 -0
  31. package/modules/glimpseBidAdapter.js +66 -44
  32. package/modules/gnetBidAdapter.js +3 -3
  33. package/modules/gnetBidAdapter.md +4 -4
  34. package/modules/gptPreAuction.js +55 -7
  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 +24 -2
  39. package/modules/interactiveOffersBidAdapter.js +9 -6
  40. package/modules/ixBidAdapter.js +29 -12
  41. package/modules/jwplayerRtdProvider.js +71 -6
  42. package/modules/jwplayerRtdProvider.md +27 -11
  43. package/modules/kargoBidAdapter.js +2 -2
  44. package/modules/limelightDigitalBidAdapter.js +2 -1
  45. package/modules/livewrappedAnalyticsAdapter.js +3 -1
  46. package/modules/loglyliftBidAdapter.js +79 -0
  47. package/modules/loglyliftBidAdapter.md +55 -0
  48. package/modules/nextMillenniumBidAdapter.js +3 -1
  49. package/modules/oguryBidAdapter.js +9 -2
  50. package/modules/onetagBidAdapter.js +4 -2
  51. package/modules/optimeraRtdProvider.js +8 -1
  52. package/modules/ozoneBidAdapter.js +21 -64
  53. package/modules/pilotxBidAdapter.js +147 -0
  54. package/modules/pilotxBidAdapter.md +50 -0
  55. package/modules/proxistoreBidAdapter.js +0 -2
  56. package/modules/pubmaticAnalyticsAdapter.js +16 -0
  57. package/modules/richaudienceBidAdapter.js +3 -2
  58. package/modules/riseBidAdapter.js +1 -1
  59. package/modules/rtbhouseBidAdapter.js +14 -4
  60. package/modules/rtdModule/index.js +14 -15
  61. package/modules/rubiconAnalyticsAdapter.js +3 -2
  62. package/modules/seedingAllianceBidAdapter.js +3 -3
  63. package/modules/sharethroughBidAdapter.js +12 -17
  64. package/modules/showheroes-bsBidAdapter.js +13 -2
  65. package/modules/sovrnBidAdapter.js +93 -18
  66. package/modules/sovrnBidAdapter.md +80 -2
  67. package/modules/synacormediaBidAdapter.js +31 -10
  68. package/modules/tappxBidAdapter.js +8 -5
  69. package/modules/teadsBidAdapter.js +1 -2
  70. package/modules/undertoneBidAdapter.js +17 -1
  71. package/modules/userId/eids.js +7 -1
  72. package/modules/userId/userId.md +8 -0
  73. package/modules/viewability.js +177 -0
  74. package/modules/viewability.md +87 -0
  75. package/modules/welectBidAdapter.js +106 -0
  76. package/modules/yahoosspBidAdapter.js +2 -0
  77. package/modules/yieldmoBidAdapter.js +23 -5
  78. package/modules/zeta_global_sspAnalyticsAdapter.js +97 -0
  79. package/modules/zeta_global_sspAnalyticsAdapter.md +24 -0
  80. package/package.json +1 -1
  81. package/src/adRendering.js +38 -0
  82. package/src/auction.js +44 -9
  83. package/src/config.js +27 -3
  84. package/src/hook.js +5 -1
  85. package/src/prebid.js +21 -21
  86. package/src/secureCreatives.js +114 -44
  87. package/src/utils.js +25 -4
  88. package/test/helpers/syncPromise.js +71 -0
  89. package/test/spec/auctionmanager_spec.js +148 -16
  90. package/test/spec/config_spec.js +279 -0
  91. package/test/spec/modules/adagioBidAdapter_spec.js +0 -10
  92. package/test/spec/modules/adbookpspBidAdapter_spec.js +17 -3
  93. package/test/spec/modules/adhashBidAdapter_spec.js +2 -2
  94. package/test/spec/modules/admanBidAdapter_spec.js +2 -2
  95. package/test/spec/modules/adomikAnalyticsAdapter_spec.js +3 -1
  96. package/test/spec/modules/adqueryIdSystem_spec.js +74 -0
  97. package/test/spec/modules/adyoulikeBidAdapter_spec.js +49 -0
  98. package/test/spec/modules/appnexusBidAdapter_spec.js +27 -0
  99. package/test/spec/modules/bliinkBidAdapter_spec.js +2 -0
  100. package/test/spec/modules/brandmetricsRtdProvider_spec.js +191 -0
  101. package/test/spec/modules/colossussspBidAdapter_spec.js +5 -2
  102. package/test/spec/modules/compassBidAdapter_spec.js +1 -0
  103. package/test/spec/modules/consentManagement_spec.js +20 -0
  104. package/test/spec/modules/criteoBidAdapter_spec.js +21 -0
  105. package/test/spec/modules/criteoIdSystem_spec.js +6 -3
  106. package/test/spec/modules/currency_spec.js +21 -6
  107. package/test/spec/modules/displayioBidAdapter_spec.js +239 -0
  108. package/test/spec/modules/docereeBidAdapter_spec.js +9 -1
  109. package/test/spec/modules/e_volutionBidAdapter_spec.js +242 -0
  110. package/test/spec/modules/eids_spec.js +15 -0
  111. package/test/spec/modules/glimpseBidAdapter_spec.js +0 -18
  112. package/test/spec/modules/gnetBidAdapter_spec.js +6 -6
  113. package/test/spec/modules/gptPreAuction_spec.js +177 -2
  114. package/test/spec/modules/gumgumBidAdapter_spec.js +46 -0
  115. package/test/spec/modules/idImportLibrary_spec.js +197 -10
  116. package/test/spec/modules/improvedigitalBidAdapter_spec.js +42 -0
  117. package/test/spec/modules/ixBidAdapter_spec.js +104 -62
  118. package/test/spec/modules/jwplayerRtdProvider_spec.js +195 -2
  119. package/test/spec/modules/kargoBidAdapter_spec.js +1 -1
  120. package/test/spec/modules/limelightDigitalBidAdapter_spec.js +75 -17
  121. package/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +22 -0
  122. package/test/spec/modules/loglyliftBidAdapter_spec.js +172 -0
  123. package/test/spec/modules/nextMillenniumBidAdapter_spec.js +1 -1
  124. package/test/spec/modules/oguryBidAdapter_spec.js +10 -2
  125. package/test/spec/modules/optimeraRtdProvider_spec.js +14 -1
  126. package/test/spec/modules/ozoneBidAdapter_spec.js +43 -31
  127. package/test/spec/modules/pilotxBidAdapter_spec.js +244 -0
  128. package/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +13 -1
  129. package/test/spec/modules/realTimeDataModule_spec.js +67 -5
  130. package/test/spec/modules/richaudienceBidAdapter_spec.js +40 -0
  131. package/test/spec/modules/riseBidAdapter_spec.js +1 -1
  132. package/test/spec/modules/rtbhouseBidAdapter_spec.js +20 -0
  133. package/test/spec/modules/rubiconAnalyticsAdapter_spec.js +30 -0
  134. package/test/spec/modules/sharethroughBidAdapter_spec.js +91 -6
  135. package/test/spec/modules/showheroes-bsBidAdapter_spec.js +2 -0
  136. package/test/spec/modules/sovrnBidAdapter_spec.js +413 -333
  137. package/test/spec/modules/synacormediaBidAdapter_spec.js +70 -0
  138. package/test/spec/modules/tappxBidAdapter_spec.js +0 -19
  139. package/test/spec/modules/teadsBidAdapter_spec.js +14 -59
  140. package/test/spec/modules/undertoneBidAdapter_spec.js +55 -2
  141. package/test/spec/modules/userId_spec.js +68 -19
  142. package/test/spec/modules/viewability_spec.js +280 -0
  143. package/test/spec/modules/welectBidAdapter_spec.js +211 -0
  144. package/test/spec/modules/yahoosspBidAdapter_spec.js +10 -0
  145. package/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +427 -0
  146. package/test/spec/unit/pbjs_api_spec.js +20 -2
  147. package/test/spec/unit/secureCreatives_spec.js +85 -0
package/src/prebid.js CHANGED
@@ -19,6 +19,7 @@ import { adunitCounter } from './adUnits.js';
19
19
  import { executeRenderer, isRendererRequired } from './Renderer.js';
20
20
  import { createBid } from './bidfactory.js';
21
21
  import { storageCallbacks } from './storageManager.js';
22
+ import { emitAdRenderSucceeded, emitAdRenderFail } from './adRendering.js';
22
23
 
23
24
  const $$PREBID_GLOBAL$$ = getGlobal();
24
25
  const CONSTANTS = require('./constants.json');
@@ -27,7 +28,7 @@ const events = require('./events.js');
27
28
  const { triggerUserSyncs } = userSync;
28
29
 
29
30
  /* private variables */
30
- const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER } = CONSTANTS.EVENTS;
31
+ const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, STALE_RENDER } = CONSTANTS.EVENTS;
31
32
  const { PREVENT_WRITING_ON_MAIN_DOCUMENT, NO_AD, EXCEPTION, CANNOT_FIND_AD, MISSING_DOC_OR_ADID } = CONSTANTS.AD_RENDER_FAILED_REASON;
32
33
 
33
34
  const eventValidators = {
@@ -146,7 +147,7 @@ function validateNativeMediaType(adUnit) {
146
147
  function validateAdUnitPos(adUnit, mediaType) {
147
148
  let pos = deepAccess(adUnit, `mediaTypes.${mediaType}.pos`);
148
149
 
149
- if (!pos || !isNumber(pos) || !isFinite(pos)) {
150
+ if (!isNumber(pos) || isNaN(pos) || !isFinite(pos)) {
150
151
  let warning = `Value of property 'pos' on ad unit ${adUnit.code} should be of type: Number`;
151
152
 
152
153
  logWarn(warning);
@@ -385,27 +386,23 @@ $$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) {
385
386
  events.emit(SET_TARGETING, targeting.getAllTargeting());
386
387
  };
387
388
 
388
- function emitAdRenderFail({ reason, message, bid, id }) {
389
- const data = { reason, message };
390
- if (bid) data.bid = bid;
391
- if (id) data.adId = id;
392
-
393
- logError(message);
394
- events.emit(AD_RENDER_FAILED, data);
395
- }
396
-
397
- function emitAdRenderSucceeded({ doc, bid, id }) {
398
- const data = { doc };
399
- if (bid) data.bid = bid;
400
- if (id) data.adId = id;
401
-
402
- events.emit(AD_RENDER_SUCCEEDED, data);
389
+ /**
390
+ * This function will check for presence of given node in given parent. If not present - will inject it.
391
+ * @param {Node} node node, whose existance is in question
392
+ * @param {Document} doc document element do look in
393
+ * @param {string} tagName tag name to look in
394
+ */
395
+ function reinjectNodeIfRemoved(node, doc, tagName) {
396
+ const injectionNode = doc.querySelector(tagName);
397
+ if (!node.parentNode || node.parentNode !== injectionNode) {
398
+ insertElement(node, doc, tagName);
399
+ }
403
400
  }
404
401
 
405
402
  /**
406
403
  * This function will render the ad (based on params) in the given iframe document passed through.
407
404
  * Note that doc SHOULD NOT be the parent document page as we can't doc.write() asynchronously
408
- * @param {HTMLDocument} doc document
405
+ * @param {Document} doc document
409
406
  * @param {string} id bid id to locate the ad
410
407
  * @alias module:pbjs.renderAd
411
408
  */
@@ -449,10 +446,11 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) {
449
446
  const {height, width, ad, mediaType, adUrl, renderer} = bid;
450
447
 
451
448
  const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`);
449
+ insertElement(creativeComment, doc, 'html');
452
450
 
453
451
  if (isRendererRequired(renderer)) {
454
452
  executeRenderer(renderer, bid);
455
- insertElement(creativeComment, doc, 'html');
453
+ reinjectNodeIfRemoved(creativeComment, doc, 'html');
456
454
  emitAdRenderSucceeded({ doc, bid, id });
457
455
  } else if ((doc === document && !inIframe()) || mediaType === 'video') {
458
456
  const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`;
@@ -471,7 +469,7 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) {
471
469
  doc.write(ad);
472
470
  doc.close();
473
471
  setRenderSize(doc, width, height);
474
- insertElement(creativeComment, doc, 'html');
472
+ reinjectNodeIfRemoved(creativeComment, doc, 'html');
475
473
  callBurl(bid);
476
474
  emitAdRenderSucceeded({ doc, bid, id });
477
475
  } else if (adUrl) {
@@ -484,7 +482,7 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) {
484
482
 
485
483
  insertElement(iframe, doc, 'body');
486
484
  setRenderSize(doc, width, height);
487
- insertElement(creativeComment, doc, 'html');
485
+ reinjectNodeIfRemoved(creativeComment, doc, 'html');
488
486
  callBurl(bid);
489
487
  emitAdRenderSucceeded({ doc, bid, id });
490
488
  } else {
@@ -901,6 +899,8 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) {
901
899
  */
902
900
  $$PREBID_GLOBAL$$.getConfig = config.getConfig;
903
901
  $$PREBID_GLOBAL$$.readConfig = config.readConfig;
902
+ $$PREBID_GLOBAL$$.mergeConfig = config.mergeConfig;
903
+ $$PREBID_GLOBAL$$.mergeBidderConfig = config.mergeBidderConfig;
904
904
 
905
905
  /**
906
906
  * Set Prebid config options.
@@ -4,18 +4,25 @@
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
  }
@@ -29,52 +36,114 @@ export function receiveMessage(ev) {
29
36
  return;
30
37
  }
31
38
 
32
- if (data && data.adId) {
39
+ if (data && data.adId && data.message) {
33
40
  const adObject = find(auctionManager.getBidsReceived(), function (bid) {
34
41
  return bid.adId === data.adId;
35
42
  });
43
+ if (HANDLER_MAP.hasOwnProperty(data.message)) {
44
+ HANDLER_MAP[data.message](ev, data, adObject);
45
+ }
46
+ }
47
+ }
36
48
 
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
- }
49
+ function handleRenderRequest(ev, data, adObject) {
50
+ if (adObject == null) {
51
+ emitAdRenderFail({
52
+ reason: constants.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD,
53
+ message: `Cannot find ad '${data.adId}' for cross-origin render request`,
54
+ id: data.adId
55
+ });
56
+ return;
57
+ }
58
+ if (adObject.status === constants.BID_STATUS.RENDERED) {
59
+ logWarn(`Ad id ${adObject.adId} has been rendered before`);
60
+ events.emit(STALE_RENDER, adObject);
61
+ if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) {
62
+ return;
63
+ }
64
+ }
45
65
 
46
- _sendAdToCreative(adObject, ev);
66
+ try {
67
+ _sendAdToCreative(adObject, ev);
68
+ } catch (e) {
69
+ emitAdRenderFail({
70
+ reason: constants.AD_RENDER_FAILED_REASON.EXCEPTION,
71
+ message: e.message,
72
+ id: data.adId,
73
+ bid: adObject
74
+ });
75
+ return;
76
+ }
47
77
 
48
- // save winning bids
49
- auctionManager.addWinningBid(adObject);
78
+ // save winning bids
79
+ auctionManager.addWinningBid(adObject);
50
80
 
51
- events.emit(BID_WON, adObject);
52
- }
81
+ events.emit(BID_WON, adObject);
82
+ }
53
83
 
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);
84
+ function handleNativeRequest(ev, data, adObject) {
85
+ // handle this script from native template in an ad server
86
+ // window.parent.postMessage(JSON.stringify({
87
+ // message: 'Prebid Native',
88
+ // adId: '%%PATTERN:hb_adid%%'
89
+ // }), '*');
90
+ if (adObject == null) {
91
+ logError(`Cannot find ad '${data.adId}' for x-origin event request`);
92
+ return;
93
+ }
94
+ switch (data.action) {
95
+ case 'assetRequest':
96
+ reply(getAssetMessage(data, adObject));
97
+ break;
98
+ case 'allAssetRequest':
99
+ reply(getAllAssetsMessage(data, adObject));
100
+ break;
101
+ case 'resizeNativeHeight':
102
+ adObject.height = data.height;
103
+ adObject.width = data.width;
104
+ resizeRemoteCreative(adObject);
105
+ break;
106
+ default:
107
+ const trackerType = fireNativeTrackers(data, adObject);
108
+ if (trackerType === 'click') {
109
+ return;
76
110
  }
77
- }
111
+ auctionManager.addWinningBid(adObject);
112
+ events.emit(BID_WON, adObject);
113
+ }
114
+
115
+ function reply(message) {
116
+ ev.source.postMessage(JSON.stringify(message), ev.origin);
117
+ }
118
+ }
119
+
120
+ function handleEventRequest(ev, data, adObject) {
121
+ if (adObject == null) {
122
+ logError(`Cannot find ad '${data.adId}' for x-origin event request`);
123
+ return;
124
+ }
125
+ if (adObject.status !== constants.BID_STATUS.RENDERED) {
126
+ logWarn(`Received x-origin event request without corresponding render request for ad '${data.adId}'`);
127
+ return;
128
+ }
129
+ switch (data.event) {
130
+ case constants.EVENTS.AD_RENDER_FAILED:
131
+ emitAdRenderFail({
132
+ bid: adObject,
133
+ id: data.adId,
134
+ reason: data.info.reason,
135
+ message: data.info.message
136
+ });
137
+ break;
138
+ case constants.EVENTS.AD_RENDER_SUCCEEDED:
139
+ emitAdRenderSucceeded({
140
+ doc: null,
141
+ bid: adObject,
142
+ id: data.adId
143
+ });
144
+ break;
145
+ default:
146
+ logError(`Received x-origin event request for unsupported event: '${data.event}' (adId: '${data.adId}')`)
78
147
  }
79
148
  }
80
149
 
@@ -127,11 +196,12 @@ function resizeRemoteCreative({ adId, adUnitCode, width, height }) {
127
196
  }
128
197
 
129
198
  function getDfpElementId(adId) {
130
- return find(window.googletag.pubads().getSlots(), slot => {
199
+ const slot = find(window.googletag.pubads().getSlots(), slot => {
131
200
  return find(slot.getTargetingKeys(), key => {
132
201
  return includes(slot.getTargeting(key), adId);
133
202
  });
134
- }).getSlotElementId();
203
+ });
204
+ return slot ? slot.getSlotElementId() : null;
135
205
  }
136
206
 
137
207
  function getAstElementId(adUnitCode) {
package/src/utils.js CHANGED
@@ -22,7 +22,17 @@ let consoleLogExists = Boolean(consoleExists && window.console.log);
22
22
  let consoleInfoExists = Boolean(consoleExists && window.console.info);
23
23
  let consoleWarnExists = Boolean(consoleExists && window.console.warn);
24
24
  let consoleErrorExists = Boolean(consoleExists && window.console.error);
25
- var events = require('./events.js');
25
+
26
+ const emitEvent = (function () {
27
+ // lazy load events to avoid circular import
28
+ let ev;
29
+ return function() {
30
+ if (ev == null) {
31
+ ev = require('./events.js');
32
+ }
33
+ return ev.emit.apply(ev, arguments);
34
+ }
35
+ })();
26
36
 
27
37
  // this allows stubbing of utility functions that are used internally by other utility functions
28
38
  export const internal = {
@@ -265,14 +275,14 @@ export function logWarn() {
265
275
  if (debugTurnedOn() && consoleWarnExists) {
266
276
  console.warn.apply(console, decorateLog(arguments, 'WARNING:'));
267
277
  }
268
- events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments});
278
+ emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments});
269
279
  }
270
280
 
271
281
  export function logError() {
272
282
  if (debugTurnedOn() && consoleErrorExists) {
273
283
  console.error.apply(console, decorateLog(arguments, 'ERROR:'));
274
284
  }
275
- events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments});
285
+ emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments});
276
286
  }
277
287
 
278
288
  function decorateLog(args, prefix) {
@@ -1272,7 +1282,18 @@ export function mergeDeep(target, ...sources) {
1272
1282
  if (!target[key]) {
1273
1283
  Object.assign(target, { [key]: source[key] });
1274
1284
  } else if (isArray(target[key])) {
1275
- target[key] = target[key].concat(source[key]);
1285
+ source[key].forEach(obj => {
1286
+ let addItFlag = 1;
1287
+ for (let i = 0; i < target[key].length; i++) {
1288
+ if (deepEqual(target[key][i], obj)) {
1289
+ addItFlag = 0;
1290
+ break;
1291
+ }
1292
+ }
1293
+ if (addItFlag) {
1294
+ target[key].push(obj);
1295
+ }
1296
+ });
1276
1297
  }
1277
1298
  } else {
1278
1299
  Object.assign(target, { [key]: source[key] });
@@ -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
+ }
@@ -3,7 +3,7 @@ import {
3
3
  auctionCallbacks,
4
4
  AUCTION_COMPLETED,
5
5
  adjustBids,
6
- getMediaTypeGranularity,
6
+ getMediaTypeGranularity, addBidResponse,
7
7
  } from 'src/auction.js';
8
8
  import CONSTANTS from 'src/constants.json';
9
9
  import * as auctionModule from 'src/auction.js';
@@ -14,6 +14,10 @@ import * as store from 'src/videoCache.js';
14
14
  import * as ajaxLib from 'src/ajax.js';
15
15
  import find from 'core-js-pure/features/array/find.js';
16
16
  import { server } from 'test/mocks/xhr.js';
17
+ import {expect} from 'chai';
18
+ import {hook} from '../../src/hook.js';
19
+ import 'src/debugging.js'
20
+ import {synchronizePromise} from '../helpers/syncPromise.js'; // some of these tests require debugging hooks to be loaded
17
21
 
18
22
  var assert = require('assert');
19
23
 
@@ -124,6 +128,27 @@ function mockAjaxBuilder() {
124
128
  }
125
129
 
126
130
  describe('auctionmanager.js', function () {
131
+ let promiseSandbox;
132
+
133
+ before(() => {
134
+ // hooks are global and their side effects depend on what has been loaded... not ideal for unit tests
135
+ [
136
+ auctionModule.addBidResponse,
137
+ auctionModule.addBidderRequests,
138
+ auctionModule.bidsBackCallback
139
+ ].forEach((h) => h.getHooks().remove())
140
+ hook.ready();
141
+ });
142
+
143
+ beforeEach(() => {
144
+ promiseSandbox = sinon.createSandbox();
145
+ synchronizePromise(promiseSandbox);
146
+ });
147
+
148
+ afterEach(() => {
149
+ promiseSandbox.restore();
150
+ })
151
+
127
152
  describe('getKeyValueTargetingPairs', function () {
128
153
  const DEFAULT_BID = {
129
154
  cpm: 5.578,
@@ -1259,12 +1284,7 @@ describe('auctionmanager.js', function () {
1259
1284
  let bids = TEST_BIDS;
1260
1285
  let bidRequests;
1261
1286
  let doneSpy;
1262
- let auction = {
1263
- getBidRequests: () => bidRequests,
1264
- getAuctionId: () => '1',
1265
- addBidReceived: () => true,
1266
- getTimeout: () => 1000
1267
- }
1287
+ let auction;
1268
1288
 
1269
1289
  beforeEach(() => {
1270
1290
  doneSpy = sinon.spy();
@@ -1272,12 +1292,21 @@ describe('auctionmanager.js', function () {
1272
1292
  cache: {
1273
1293
  url: 'https://prebid.adnxs.com/pbc/v1/cache'
1274
1294
  }
1275
- })
1295
+ });
1296
+ const start = Date.now();
1297
+ auction = {
1298
+ getBidRequests: () => bidRequests,
1299
+ getAuctionId: () => '1',
1300
+ addBidReceived: () => true,
1301
+ getTimeout: () => 1000,
1302
+ getAuctionStart: () => start,
1303
+ }
1276
1304
  });
1277
1305
 
1278
1306
  afterEach(() => {
1279
1307
  doneSpy.resetHistory();
1280
1308
  config.resetConfig();
1309
+ bidRequests = null;
1281
1310
  });
1282
1311
 
1283
1312
  it('should call auction done after bid is added to auction for mediaType banner', function () {
@@ -1328,19 +1357,114 @@ describe('auctionmanager.js', function () {
1328
1357
  const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`;
1329
1358
  server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody);
1330
1359
  assert.equal(doneSpy.callCount, 1);
1331
- })
1360
+ });
1361
+
1362
+ describe('when addBidResponse hook returns promises', () => {
1363
+ let resolvers, callbacks, bids;
1364
+
1365
+ function hook(next, ...args) {
1366
+ next.bail(new Promise((resolve, reject) => {
1367
+ resolvers.resolve.push(resolve);
1368
+ resolvers.reject.push(reject);
1369
+ }).finally(() => next(...args)));
1370
+ }
1371
+
1372
+ function invokeCallbacks() {
1373
+ bids.forEach((bid, i) => callbacks.addBidResponse.call(bidRequests[i], ADUNIT_CODE, bid));
1374
+ bidRequests.forEach(bidRequest => callbacks.adapterDone.call(bidRequest));
1375
+ }
1376
+
1377
+ function delay(ms = 0) {
1378
+ return new Promise((resolve) => {
1379
+ setTimeout(resolve, ms)
1380
+ });
1381
+ }
1382
+
1383
+ beforeEach(() => {
1384
+ promiseSandbox.restore();
1385
+ bids = [
1386
+ mockBid({bidderCode: BIDDER_CODE1}),
1387
+ mockBid({bidderCode: BIDDER_CODE})
1388
+ ]
1389
+ bidRequests = bids.map((b) => mockBidRequest(b));
1390
+ resolvers = {resolve: [], reject: []};
1391
+ addBidResponse.before(hook);
1392
+ callbacks = auctionCallbacks(doneSpy, auction);
1393
+ Object.assign(auction, {
1394
+ addNoBid: sinon.spy()
1395
+ });
1396
+ });
1397
+
1398
+ afterEach(() => {
1399
+ addBidResponse.getHooks({hook: hook}).remove();
1400
+ });
1401
+
1402
+ Object.entries({
1403
+ 'all succeed': ['resolve', 'resolve'],
1404
+ 'some fail': ['resolve', 'reject'],
1405
+ 'all fail': ['reject', 'reject']
1406
+ }).forEach(([test, results]) => {
1407
+ describe(`(and ${test})`, () => {
1408
+ it('should wait for them to complete before calling auctionDone', () => {
1409
+ invokeCallbacks();
1410
+ return delay().then(() => {
1411
+ expect(doneSpy.called).to.be.false;
1412
+ expect(auction.addNoBid.called).to.be.false;
1413
+ resolvers[results[0]][0]();
1414
+ return delay();
1415
+ }).then(() => {
1416
+ expect(doneSpy.called).to.be.false;
1417
+ expect(auction.addNoBid.called).to.be.false;
1418
+ resolvers[results[1]][1]();
1419
+ return delay();
1420
+ }).then(() => {
1421
+ expect(doneSpy.called).to.be.true;
1422
+ });
1423
+ });
1424
+ });
1425
+ });
1426
+
1427
+ Object.entries({
1428
+ bidder: (timeout) => {
1429
+ bidRequests.forEach((r) => r.timeout = timeout);
1430
+ auction.getTimeout = () => timeout + 10000
1431
+ },
1432
+ auction: (timeout) => {
1433
+ auction.getTimeout = () => timeout;
1434
+ bidRequests.forEach((r) => r.timeout = timeout + 10000)
1435
+ }
1436
+ }).forEach(([test, setTimeout]) => {
1437
+ it(`should respect ${test} timeout if they never complete`, () => {
1438
+ const start = Date.now() - 2900;
1439
+ auction.getAuctionStart = () => start;
1440
+ setTimeout(3000);
1441
+ invokeCallbacks();
1442
+ return delay().then(() => {
1443
+ expect(doneSpy.called).to.be.false;
1444
+ return delay(100);
1445
+ }).then(() => {
1446
+ expect(doneSpy.called).to.be.true;
1447
+ });
1448
+ });
1449
+
1450
+ it(`should not wait if ${test} has already timed out`, () => {
1451
+ const start = Date.now() - 2000;
1452
+ auction.getAuctionStart = () => start;
1453
+ setTimeout(1000);
1454
+ invokeCallbacks();
1455
+ return delay().then(() => {
1456
+ expect(doneSpy.called).to.be.true;
1457
+ });
1458
+ });
1459
+ })
1460
+ });
1332
1461
  });
1333
1462
 
1334
1463
  describe('auctionOptions', function() {
1335
1464
  let bidRequests;
1336
1465
  let doneSpy;
1337
1466
  let clock;
1338
- let auction = {
1339
- getBidRequests: () => bidRequests,
1340
- getAuctionId: () => '1',
1341
- addBidReceived: () => true,
1342
- getTimeout: () => 1000
1343
- }
1467
+ let auction;
1344
1468
  let requiredBidder = BIDDER_CODE;
1345
1469
  let requiredBidder1 = BIDDER_CODE1;
1346
1470
  let secondaryBidder = 'doNotWaitForMe';
@@ -1352,7 +1476,15 @@ describe('auctionmanager.js', function () {
1352
1476
  'auctionOptions': {
1353
1477
  secondaryBidders: [ secondaryBidder ]
1354
1478
  }
1355
- })
1479
+ });
1480
+ const start = Date.now();
1481
+ auction = {
1482
+ getBidRequests: () => bidRequests,
1483
+ getAuctionId: () => '1',
1484
+ addBidReceived: () => true,
1485
+ getTimeout: () => 1000,
1486
+ getAuctionStart: () => start,
1487
+ }
1356
1488
  });
1357
1489
 
1358
1490
  afterEach(() => {