prebid.js 6.7.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 (38) hide show
  1. package/integrationExamples/gpt/x-domain/creative.html +53 -26
  2. package/modules/adagioBidAdapter.js +0 -8
  3. package/modules/adagioBidAdapter.md +1 -1
  4. package/modules/appnexusBidAdapter.js +11 -0
  5. package/modules/brandmetricsRtdProvider.js +168 -0
  6. package/modules/brandmetricsRtdProvider.md +40 -0
  7. package/modules/criteoBidAdapter.js +9 -0
  8. package/modules/currency.js +26 -1
  9. package/modules/displayioBidAdapter.js +157 -0
  10. package/modules/displayioBidAdapter.md +148 -0
  11. package/modules/e_volutionBidAdapter.js +158 -0
  12. package/modules/gumgumBidAdapter.js +52 -38
  13. package/modules/interactiveOffersBidAdapter.js +9 -6
  14. package/modules/sovrnBidAdapter.js +93 -18
  15. package/modules/sovrnBidAdapter.md +80 -2
  16. package/modules/undertoneBidAdapter.js +17 -1
  17. package/modules/yahoosspBidAdapter.js +2 -0
  18. package/package.json +1 -1
  19. package/src/adRendering.js +38 -0
  20. package/src/auction.js +44 -9
  21. package/src/prebid.js +3 -19
  22. package/src/secureCreatives.js +111 -42
  23. package/src/utils.js +13 -3
  24. package/test/helpers/syncPromise.js +71 -0
  25. package/test/spec/auctionmanager_spec.js +148 -16
  26. package/test/spec/modules/adagioBidAdapter_spec.js +0 -10
  27. package/test/spec/modules/appnexusBidAdapter_spec.js +27 -0
  28. package/test/spec/modules/brandmetricsRtdProvider_spec.js +191 -0
  29. package/test/spec/modules/criteoBidAdapter_spec.js +21 -0
  30. package/test/spec/modules/currency_spec.js +21 -6
  31. package/test/spec/modules/displayioBidAdapter_spec.js +239 -0
  32. package/test/spec/modules/e_volutionBidAdapter_spec.js +242 -0
  33. package/test/spec/modules/gumgumBidAdapter_spec.js +46 -0
  34. package/test/spec/modules/sovrnBidAdapter_spec.js +413 -333
  35. package/test/spec/modules/undertoneBidAdapter_spec.js +55 -2
  36. package/test/spec/modules/yahoosspBidAdapter_spec.js +10 -0
  37. package/test/spec/unit/pbjs_api_spec.js +17 -1
  38. package/test/spec/unit/secureCreatives_spec.js +85 -0
@@ -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(() => {
@@ -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() {
@@ -556,6 +556,33 @@ describe('AppNexusAdapter', function () {
556
556
  config.getConfig.restore();
557
557
  });
558
558
 
559
+ it('adds auction level keywords to request when set', function() {
560
+ let bidRequest = Object.assign({}, bidRequests[0]);
561
+ sinon
562
+ .stub(config, 'getConfig')
563
+ .withArgs('appnexusAuctionKeywords')
564
+ .returns({
565
+ gender: 'm',
566
+ music: ['rock', 'pop'],
567
+ test: ''
568
+ });
569
+
570
+ const request = spec.buildRequests([bidRequest]);
571
+ const payload = JSON.parse(request.data);
572
+
573
+ expect(payload.keywords).to.deep.equal([{
574
+ 'key': 'gender',
575
+ 'value': ['m']
576
+ }, {
577
+ 'key': 'music',
578
+ 'value': ['rock', 'pop']
579
+ }, {
580
+ 'key': 'test'
581
+ }]);
582
+
583
+ config.getConfig.restore();
584
+ });
585
+
559
586
  it('should attach native params to the request', function () {
560
587
  let bidRequest = Object.assign({},
561
588
  bidRequests[0],
@@ -0,0 +1,191 @@
1
+ import * as brandmetricsRTD from '../../../modules/brandmetricsRtdProvider.js';
2
+ import {config} from 'src/config.js';
3
+
4
+ const VALID_CONFIG = {
5
+ name: 'brandmetrics',
6
+ waitForIt: true,
7
+ params: {
8
+ scriptId: '00000000-0000-0000-0000-000000000000',
9
+ bidders: ['ozone']
10
+ }
11
+ };
12
+
13
+ const NO_BIDDERS_CONFIG = {
14
+ name: 'brandmetrics',
15
+ waitForIt: true,
16
+ params: {
17
+ scriptId: '00000000-0000-0000-0000-000000000000'
18
+ }
19
+ };
20
+
21
+ const NO_SCRIPTID_CONFIG = {
22
+ name: 'brandmetrics',
23
+ waitForIt: true
24
+ };
25
+
26
+ const USER_CONSENT = {
27
+ gdpr: {
28
+ vendorData: {
29
+ vendor: {
30
+ consents: {
31
+ 422: true
32
+ }
33
+ },
34
+ purpose: {
35
+ consents: {
36
+ 1: true,
37
+ 7: true
38
+ }
39
+ }
40
+ },
41
+ gdprApplies: true
42
+ }
43
+ };
44
+
45
+ const NO_TCF_CONSENT = {
46
+ gdpr: {
47
+ vendorData: {
48
+ vendor: {
49
+ consents: {
50
+ 422: false
51
+ }
52
+ },
53
+ purpose: {
54
+ consents: {
55
+ 1: false,
56
+ 7: false
57
+ }
58
+ }
59
+ },
60
+ gdprApplies: true
61
+ }
62
+ };
63
+
64
+ const NO_USP_CONSENT = {
65
+ usp: '1NYY'
66
+ };
67
+
68
+ function mockSurveyLoaded(surveyConf) {
69
+ const commands = window._brandmetrics || [];
70
+ commands.forEach(command => {
71
+ if (command.cmd === '_addeventlistener') {
72
+ const conf = command.val;
73
+ if (conf.event === 'surveyloaded') {
74
+ conf.handler(surveyConf);
75
+ }
76
+ }
77
+ });
78
+ }
79
+
80
+ function scriptTagExists(url) {
81
+ const tags = document.getElementsByTagName('script');
82
+ for (let i = 0; i < tags.length; i++) {
83
+ if (tags[i].src === url) {
84
+ return true;
85
+ }
86
+ }
87
+ return false;
88
+ }
89
+
90
+ describe('BrandmetricsRTD module', () => {
91
+ beforeEach(function () {
92
+ const scriptTags = document.getElementsByTagName('script');
93
+ for (let i = 0; i < scriptTags.length; i++) {
94
+ if (scriptTags[i].src.indexOf('brandmetrics') !== -1) {
95
+ scriptTags[i].remove();
96
+ }
97
+ }
98
+ });
99
+
100
+ it('should init and return true', () => {
101
+ expect(brandmetricsRTD.brandmetricsSubmodule.init(VALID_CONFIG, USER_CONSENT)).to.equal(true);
102
+ });
103
+
104
+ it('should init and return true even if bidders is not included', () => {
105
+ expect(brandmetricsRTD.brandmetricsSubmodule.init(NO_BIDDERS_CONFIG, USER_CONSENT)).to.equal(true);
106
+ });
107
+
108
+ it('should init even if script- id is not configured', () => {
109
+ expect(brandmetricsRTD.brandmetricsSubmodule.init(NO_SCRIPTID_CONFIG, USER_CONSENT)).to.equal(true);
110
+ });
111
+
112
+ it('should not init when there is no TCF- consent', () => {
113
+ expect(brandmetricsRTD.brandmetricsSubmodule.init(VALID_CONFIG, NO_TCF_CONSENT)).to.equal(false);
114
+ });
115
+
116
+ it('should not init when there is no usp- consent', () => {
117
+ expect(brandmetricsRTD.brandmetricsSubmodule.init(VALID_CONFIG, NO_USP_CONSENT)).to.equal(false);
118
+ });
119
+ });
120
+
121
+ describe('getBidRequestData', () => {
122
+ beforeEach(function () {
123
+ config.resetConfig()
124
+ })
125
+
126
+ it('should set targeting keys for specified bidders', () => {
127
+ brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => {
128
+ const bidderConfig = config.getBidderConfig()
129
+ const expected = VALID_CONFIG.params.bidders
130
+
131
+ expected.forEach(exp => {
132
+ expect(bidderConfig[exp].ortb2.user.ext.data.mockTargetKey).to.equal('mockMeasurementId')
133
+ })
134
+ }, VALID_CONFIG);
135
+
136
+ mockSurveyLoaded({
137
+ available: true,
138
+ conf: {
139
+ displayOption: {
140
+ type: 'pbjs',
141
+ targetKey: 'mockTargetKey'
142
+ }
143
+ },
144
+ survey: {
145
+ measurementId: 'mockMeasurementId'
146
+ }
147
+ });
148
+ });
149
+
150
+ it('should only set targeting keys when the brandmetrics survey- type is "pbjs"', () => {
151
+ mockSurveyLoaded({
152
+ available: true,
153
+ conf: {
154
+ displayOption: {
155
+ type: 'dfp',
156
+ targetKey: 'mockTargetKey'
157
+ }
158
+ },
159
+ survey: {
160
+ measurementId: 'mockMeasurementId'
161
+ }
162
+ });
163
+
164
+ brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => {}, VALID_CONFIG);
165
+ const bidderConfig = config.getBidderConfig()
166
+ expect(Object.keys(bidderConfig).length).to.equal(0)
167
+ });
168
+
169
+ it('should use a default targeting key name if the brandmetrics- configuration does not include one', () => {
170
+ mockSurveyLoaded({
171
+ available: true,
172
+ conf: {
173
+ displayOption: {
174
+ type: 'pbjs',
175
+ }
176
+ },
177
+ survey: {
178
+ measurementId: 'mockMeasurementId'
179
+ }
180
+ });
181
+
182
+ brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => {}, VALID_CONFIG);
183
+
184
+ const bidderConfig = config.getBidderConfig()
185
+ const expected = VALID_CONFIG.params.bidders
186
+
187
+ expected.forEach(exp => {
188
+ expect(bidderConfig[exp].ortb2.user.ext.data.brandmetrics_survey).to.equal('mockMeasurementId')
189
+ })
190
+ });
191
+ });
@@ -663,6 +663,27 @@ describe('The Criteo bidding adapter', function () {
663
663
  expect(request.data.user.uspIab).to.equal('1YNY');
664
664
  });
665
665
 
666
+ it('should properly build a request with schain object', function () {
667
+ const expectedSchain = {
668
+ someProperty: 'someValue'
669
+ };
670
+ const bidRequests = [
671
+ {
672
+ bidder: 'criteo',
673
+ schain: expectedSchain,
674
+ adUnitCode: 'bid-123',
675
+ transactionId: 'transaction-123',
676
+ sizes: [[728, 90]],
677
+ params: {
678
+ zoneId: 123,
679
+ },
680
+ },
681
+ ];
682
+
683
+ const request = spec.buildRequests(bidRequests, bidderRequest);
684
+ expect(request.data.source.ext.schain).to.equal(expectedSchain);
685
+ });
686
+
666
687
  it('should properly build a request with if ccpa consent field is not provided', function () {
667
688
  const bidRequests = [
668
689
  {
@@ -9,7 +9,8 @@ import {
9
9
  setConfig,
10
10
  addBidResponseHook,
11
11
  currencySupportEnabled,
12
- currencyRates
12
+ currencyRates,
13
+ ready
13
14
  } from 'modules/currency.js';
14
15
 
15
16
  var assert = require('chai').assert;
@@ -24,6 +25,7 @@ describe('currency', function () {
24
25
 
25
26
  beforeEach(function () {
26
27
  fakeCurrencyFileServer = sinon.fakeServer.create();
28
+ ready.reset();
27
29
  });
28
30
 
29
31
  afterEach(function () {
@@ -286,7 +288,7 @@ describe('currency', function () {
286
288
  });
287
289
 
288
290
  describe('currency.addBidResponseDecorator bidResponseQueue', function () {
289
- it('not run until currency rates file is loaded', function () {
291
+ it('not run until currency rates file is loaded', function (done) {
290
292
  setConfig({});
291
293
 
292
294
  fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates()));
@@ -296,14 +298,27 @@ describe('currency', function () {
296
298
  setConfig({ 'adServerCurrency': 'JPY' });
297
299
 
298
300
  var marker = false;
299
- addBidResponseHook(function() {
301
+ let promiseResolved = false;
302
+ addBidResponseHook(Object.assign(function() {
300
303
  marker = true;
301
- }, 'elementId', bid);
304
+ }, {
305
+ bail: function (promise) {
306
+ promise.then(() => promiseResolved = true);
307
+ }
308
+ }), 'elementId', bid);
302
309
 
303
310
  expect(marker).to.equal(false);
304
311
 
305
- fakeCurrencyFileServer.respond();
306
- expect(marker).to.equal(true);
312
+ setTimeout(() => {
313
+ expect(promiseResolved).to.be.false;
314
+ fakeCurrencyFileServer.respond();
315
+
316
+ setTimeout(() => {
317
+ expect(marker).to.equal(true);
318
+ expect(promiseResolved).to.be.true;
319
+ done();
320
+ });
321
+ });
307
322
  });
308
323
  });
309
324