prebid.js 6.6.0 → 6.7.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 (36) hide show
  1. package/integrationExamples/gpt/amp/creative.html +11 -33
  2. package/modules/adbookpspBidAdapter.js +27 -10
  3. package/modules/adhashBidAdapter.js +3 -3
  4. package/modules/colossussspBidAdapter.js +12 -8
  5. package/modules/colossussspBidAdapter.md +15 -1
  6. package/modules/compassBidAdapter.js +1 -1
  7. package/modules/consumableBidAdapter.md +1 -1
  8. package/modules/gnetBidAdapter.js +3 -3
  9. package/modules/gnetBidAdapter.md +4 -4
  10. package/modules/gumgumBidAdapter.js +4 -4
  11. package/modules/jwplayerRtdProvider.js +71 -6
  12. package/modules/jwplayerRtdProvider.md +27 -11
  13. package/modules/kargoBidAdapter.js +2 -2
  14. package/modules/pilotxBidAdapter.js +147 -0
  15. package/modules/pilotxBidAdapter.md +50 -0
  16. package/modules/rtdModule/index.js +8 -10
  17. package/modules/rubiconAnalyticsAdapter.js +3 -2
  18. package/modules/seedingAllianceBidAdapter.js +3 -3
  19. package/modules/sharethroughBidAdapter.js +12 -17
  20. package/modules/synacormediaBidAdapter.js +31 -10
  21. package/modules/viewability.js +177 -0
  22. package/modules/viewability.md +87 -0
  23. package/package.json +1 -1
  24. package/src/secureCreatives.js +3 -2
  25. package/test/spec/modules/adbookpspBidAdapter_spec.js +17 -3
  26. package/test/spec/modules/adhashBidAdapter_spec.js +2 -2
  27. package/test/spec/modules/colossussspBidAdapter_spec.js +5 -2
  28. package/test/spec/modules/gnetBidAdapter_spec.js +6 -6
  29. package/test/spec/modules/jwplayerRtdProvider_spec.js +195 -2
  30. package/test/spec/modules/kargoBidAdapter_spec.js +1 -1
  31. package/test/spec/modules/pilotxBidAdapter_spec.js +244 -0
  32. package/test/spec/modules/realTimeDataModule_spec.js +51 -2
  33. package/test/spec/modules/rubiconAnalyticsAdapter_spec.js +30 -0
  34. package/test/spec/modules/sharethroughBidAdapter_spec.js +91 -6
  35. package/test/spec/modules/synacormediaBidAdapter_spec.js +70 -0
  36. package/test/spec/modules/viewability_spec.js +280 -0
@@ -1,4 +1,5 @@
1
1
  import { expect } from 'chai';
2
+ import * as sinon from 'sinon';
2
3
  import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js';
3
4
  import { newBidder } from 'src/adapters/bidderFactory.js';
4
5
  import { config } from 'src/config';
@@ -157,7 +158,6 @@ describe('sharethrough adapter spec', function () {
157
158
  startdelay: 42,
158
159
  skipmin: 10,
159
160
  skipafter: 20,
160
- placement: 1,
161
161
  delivery: 1,
162
162
  companiontype: 'companion type',
163
163
  companionad: 'companion ad',
@@ -205,6 +205,7 @@ describe('sharethrough adapter spec', function () {
205
205
  expect(openRtbReq.cur).to.deep.equal(['USD']);
206
206
  expect(openRtbReq.tmax).to.equal(242);
207
207
 
208
+ expect(Object.keys(openRtbReq.site)).to.have.length(3);
208
209
  expect(openRtbReq.site.domain).not.to.be.undefined;
209
210
  expect(openRtbReq.site.page).not.to.be.undefined;
210
211
  expect(openRtbReq.site.ref).to.equal('https://referer.com');
@@ -256,6 +257,17 @@ describe('sharethrough adapter spec', function () {
256
257
  });
257
258
  });
258
259
 
260
+ describe('no referer provided', () => {
261
+ beforeEach(() => {
262
+ bidderRequest = {};
263
+ });
264
+
265
+ it('should set referer to undefined', () => {
266
+ const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data;
267
+ expect(openRtbReq.site.ref).to.be.undefined;
268
+ });
269
+ });
270
+
259
271
  describe('regulation', () => {
260
272
  describe('gdpr', () => {
261
273
  it('should populate request accordingly when gdpr applies', () => {
@@ -419,17 +431,90 @@ describe('sharethrough adapter spec', function () {
419
431
  expect(videoImp.startdelay).to.equal(0);
420
432
  expect(videoImp.skipmin).to.equal(0);
421
433
  expect(videoImp.skipafter).to.equal(0);
422
- expect(videoImp.placement).to.be.undefined;
434
+ expect(videoImp.placement).to.equal(1);
423
435
  expect(videoImp.delivery).to.be.undefined;
424
436
  expect(videoImp.companiontype).to.be.undefined;
425
437
  expect(videoImp.companionad).to.be.undefined;
426
438
  });
427
439
 
428
- it('should not return a video impression if context is outstream', () => {
429
- bidRequests[1].mediaTypes.video.context = 'outstream';
430
- const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1];
440
+ describe('outstream', () => {
441
+ it('should use placement value if provided', () => {
442
+ bidRequests[1].mediaTypes.video.context = 'outstream';
443
+ bidRequests[1].mediaTypes.video.placement = 3;
444
+
445
+ const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1];
446
+ const videoImp = builtRequest.data.imp[0].video;
447
+
448
+ expect(videoImp.placement).to.equal(3);
449
+ });
450
+
451
+ it('should default placement to 4 if not provided', () => {
452
+ bidRequests[1].mediaTypes.video.context = 'outstream';
453
+
454
+ const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1];
455
+ const videoImp = builtRequest.data.imp[0].video;
456
+
457
+ expect(videoImp.placement).to.equal(4);
458
+ });
459
+ });
460
+ });
461
+
462
+ describe('first party data', () => {
463
+ const firstPartyData = {
464
+ site: {
465
+ name: 'example',
466
+ keywords: 'power tools, drills',
467
+ search: 'drill',
468
+ content: {
469
+ userrating: '4',
470
+ },
471
+ ext: {
472
+ data: {
473
+ pageType: 'article',
474
+ category: 'repair',
475
+ },
476
+ },
477
+ },
478
+ user: {
479
+ yob: 1985,
480
+ gender: 'm',
481
+ ext: {
482
+ data: {
483
+ registered: true,
484
+ interests: ['cars'],
485
+ },
486
+ },
487
+ },
488
+ };
489
+
490
+ let configStub;
491
+
492
+ beforeEach(() => {
493
+ configStub = sinon.stub(config, 'getConfig');
494
+ configStub.withArgs('ortb2').returns(firstPartyData);
495
+ });
496
+
497
+ afterEach(() => {
498
+ configStub.restore();
499
+ });
500
+
501
+ it('should include first party data in open rtb request, site section', () => {
502
+ const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data;
503
+
504
+ expect(openRtbReq.site.name).to.equal(firstPartyData.site.name);
505
+ expect(openRtbReq.site.keywords).to.equal(firstPartyData.site.keywords);
506
+ expect(openRtbReq.site.search).to.equal(firstPartyData.site.search);
507
+ expect(openRtbReq.site.content).to.deep.equal(firstPartyData.site.content);
508
+ expect(openRtbReq.site.ext).to.deep.equal(firstPartyData.site.ext);
509
+ });
510
+
511
+ it('should include first party data in open rtb request, user section', () => {
512
+ const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data;
431
513
 
432
- expect(builtRequest).to.be.undefined;
514
+ expect(openRtbReq.user.yob).to.equal(firstPartyData.user.yob);
515
+ expect(openRtbReq.user.gender).to.equal(firstPartyData.user.gender);
516
+ expect(openRtbReq.user.ext.data).to.deep.equal(firstPartyData.user.ext.data);
517
+ expect(openRtbReq.user.ext.eids).not.to.be.undefined;
433
518
  });
434
519
  });
435
520
  });
@@ -1231,4 +1231,74 @@ describe('synacormediaBidAdapter ', function () {
1231
1231
  expect(usersyncs).to.be.an('array').that.is.empty;
1232
1232
  });
1233
1233
  });
1234
+
1235
+ describe('Bid Requests with price module should use if available', function () {
1236
+ let validVideoBidRequest = {
1237
+ bidder: 'synacormedia',
1238
+ params: {
1239
+ bidfloor: '0.50',
1240
+ seatId: 'prebid',
1241
+ placementId: 'demo1',
1242
+ pos: 1,
1243
+ video: {}
1244
+ },
1245
+ renderer: {
1246
+ url: '../syncOutstreamPlayer.js'
1247
+ },
1248
+ mediaTypes: {
1249
+ video: {
1250
+ playerSize: [[300, 250]],
1251
+ context: 'outstream'
1252
+ }
1253
+ },
1254
+ adUnitCode: 'div-1',
1255
+ transactionId: '0869f34e-090b-4b20-84ee-46ff41405a39',
1256
+ sizes: [[300, 250]],
1257
+ bidId: '22b3a2268d9f0e',
1258
+ bidderRequestId: '1d195910597e13',
1259
+ auctionId: '3375d336-2aea-4ee7-804c-6d26b621ad20',
1260
+ src: 'client',
1261
+ bidRequestsCount: 1,
1262
+ bidderRequestsCount: 1,
1263
+ bidderWinsCount: 0
1264
+ };
1265
+
1266
+ let validBannerBidRequest = {
1267
+ bidId: '9876abcd',
1268
+ sizes: [[300, 250]],
1269
+ params: {
1270
+ bidfloor: '0.50',
1271
+ seatId: 'prebid',
1272
+ placementId: '1234',
1273
+ }
1274
+ };
1275
+
1276
+ let bidderRequest = {
1277
+ refererInfo: {
1278
+ referer: 'http://localhost:9999/'
1279
+ },
1280
+ bidderCode: 'synacormedia',
1281
+ auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110'
1282
+ };
1283
+
1284
+ it('should return valid bidfloor using price module for banner/video impression', function () {
1285
+ let bannerRequest = spec.buildRequests([validBannerBidRequest], bidderRequest);
1286
+ let videoRequest = spec.buildRequests([validVideoBidRequest], bidderRequest);
1287
+
1288
+ expect(bannerRequest.data.imp[0].bidfloor).to.equal(0.5);
1289
+ expect(videoRequest.data.imp[0].bidfloor).to.equal(0.5);
1290
+
1291
+ let priceModuleFloor = 3;
1292
+ let floorResponse = { currency: 'USD', floor: priceModuleFloor };
1293
+
1294
+ validBannerBidRequest.getFloor = () => { return floorResponse; };
1295
+ validVideoBidRequest.getFloor = () => { return floorResponse; };
1296
+
1297
+ bannerRequest = spec.buildRequests([validBannerBidRequest], bidderRequest);
1298
+ videoRequest = spec.buildRequests([validVideoBidRequest], bidderRequest);
1299
+
1300
+ expect(bannerRequest.data.imp[0].bidfloor).to.equal(priceModuleFloor);
1301
+ expect(videoRequest.data.imp[0].bidfloor).to.equal(priceModuleFloor);
1302
+ });
1303
+ });
1234
1304
  });
@@ -0,0 +1,280 @@
1
+ import { expect } from 'chai';
2
+ import * as sinon from 'sinon';
3
+ import * as utils from 'src/utils.js';
4
+ import * as viewability from 'modules/viewability.js';
5
+
6
+ describe('viewability test', () => {
7
+ describe('start measurement', () => {
8
+ let sandbox;
9
+ let intersectionObserverStub;
10
+ let setTimeoutStub;
11
+ let observeCalled;
12
+ let unobserveCalled;
13
+ let ti = 1;
14
+ beforeEach(() => {
15
+ observeCalled = false;
16
+ unobserveCalled = false;
17
+ sandbox = sinon.sandbox.create();
18
+
19
+ let fakeIntersectionObserver = (stateChange, options) => {
20
+ return {
21
+ observe: (element) => {
22
+ observeCalled = true;
23
+ stateChange([{ isIntersecting: true }]);
24
+ },
25
+ unobserve: (element) => {
26
+ unobserveCalled = true;
27
+ },
28
+ };
29
+ };
30
+
31
+ intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver);
32
+ setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => {
33
+ callback();
34
+ return ti++;
35
+ });
36
+ });
37
+
38
+ afterEach(() => {
39
+ sandbox.restore();
40
+ });
41
+
42
+ it('should trigger appropriate callbacks', () => {
43
+ viewability.startMeasurement('0', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 });
44
+
45
+ sinon.assert.called(intersectionObserverStub);
46
+ sinon.assert.called(setTimeoutStub);
47
+ expect(observeCalled).to.equal(true);
48
+ expect(unobserveCalled).to.equal(true);
49
+ });
50
+
51
+ it('should trigger img tracker', () => {
52
+ let triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']);
53
+ viewability.startMeasurement('1', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 });
54
+ expect(triggerPixelSpy.callCount).to.equal(1);
55
+ });
56
+
57
+ it('should trigger js tracker', () => {
58
+ let insertHtmlIntoIframeSpy = sandbox.spy(utils, ['insertHtmlIntoIframe']);
59
+ viewability.startMeasurement('2', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 });
60
+ expect(insertHtmlIntoIframeSpy.callCount).to.equal(1);
61
+ });
62
+
63
+ it('should trigger callback tracker', () => {
64
+ let callbackFired = false;
65
+ viewability.startMeasurement('3', {}, { method: 'callback', value: () => { callbackFired = true; } }, { inViewThreshold: 0.5, timeInView: 1000 });
66
+ expect(callbackFired).to.equal(true);
67
+ });
68
+
69
+ it('should check for vid uniqueness', () => {
70
+ let logWarnSpy = sandbox.spy(utils, 'logWarn');
71
+ viewability.startMeasurement('4', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 });
72
+ expect(logWarnSpy.callCount).to.equal(0);
73
+
74
+ viewability.startMeasurement('4', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 });
75
+ expect(logWarnSpy.callCount).to.equal(1);
76
+ expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide an unregistered vid`, '4')).to.equal(true);
77
+ });
78
+
79
+ it('should check for valid criteria', () => {
80
+ let logWarnSpy = sandbox.spy(utils, 'logWarn');
81
+ viewability.startMeasurement('5', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { timeInView: 1000 });
82
+ expect(logWarnSpy.callCount).to.equal(1);
83
+ expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: missing criteria`, { timeInView: 1000 })).to.equal(true);
84
+ });
85
+
86
+ it('should check for valid tracker', () => {
87
+ let logWarnSpy = sandbox.spy(utils, 'logWarn');
88
+ viewability.startMeasurement('6', {}, { method: 'callback', value: 'string' }, { inViewThreshold: 0.5, timeInView: 1000 });
89
+ expect(logWarnSpy.callCount).to.equal(1);
90
+ expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: invalid tracker`, { method: 'callback', value: 'string' })).to.equal(true);
91
+ });
92
+
93
+ it('should check if element provided', () => {
94
+ let logWarnSpy = sandbox.spy(utils, 'logWarn');
95
+ viewability.startMeasurement('7', undefined, { method: 'js', value: 'http://my.tracker/123.js' }, { timeInView: 1000 });
96
+ expect(logWarnSpy.callCount).to.equal(1);
97
+ expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: no html element provided`)).to.equal(true);
98
+ });
99
+ });
100
+
101
+ describe('stop measurement', () => {
102
+ let sandbox;
103
+ let intersectionObserverStub;
104
+ let setTimeoutStub;
105
+ let clearTimeoutStub;
106
+ let observeCalled;
107
+ let unobserveCalled;
108
+ let stateChangeBackup;
109
+ let ti = 1;
110
+ beforeEach(() => {
111
+ observeCalled = false;
112
+ unobserveCalled = false;
113
+ sandbox = sinon.sandbox.create();
114
+
115
+ let fakeIntersectionObserver = (stateChange, options) => {
116
+ return {
117
+ observe: (element) => {
118
+ stateChangeBackup = stateChange;
119
+ observeCalled = true;
120
+ stateChange([{ isIntersecting: true }]);
121
+ },
122
+ unobserve: (element) => {
123
+ unobserveCalled = true;
124
+ },
125
+ };
126
+ };
127
+
128
+ intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver);
129
+ setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => {
130
+ // skipping the callback
131
+ return ti++;
132
+ });
133
+ clearTimeoutStub = sandbox.stub(window, 'clearTimeout').callsFake((timeoutId) => { });
134
+ });
135
+
136
+ afterEach(() => {
137
+ sandbox.restore();
138
+ });
139
+
140
+ it('should clear the timeout', () => {
141
+ viewability.startMeasurement('10', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 });
142
+ stateChangeBackup([{ isIntersecting: false }]);
143
+ sinon.assert.called(intersectionObserverStub);
144
+ sinon.assert.called(setTimeoutStub);
145
+ sinon.assert.called(clearTimeoutStub);
146
+ expect(observeCalled).to.equal(true);
147
+ });
148
+
149
+ it('should unobserve', () => {
150
+ viewability.startMeasurement('11', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 });
151
+ sinon.assert.called(intersectionObserverStub);
152
+ sinon.assert.called(setTimeoutStub);
153
+ expect(observeCalled).to.equal(true);
154
+ expect(unobserveCalled).to.equal(false);
155
+
156
+ viewability.stopMeasurement('11');
157
+ expect(unobserveCalled).to.equal(true);
158
+ sinon.assert.called(clearTimeoutStub);
159
+ });
160
+
161
+ it('should check for vid existence', () => {
162
+ let logWarnSpy = sandbox.spy(utils, 'logWarn');
163
+ viewability.stopMeasurement('100');
164
+ expect(logWarnSpy.callCount).to.equal(1);
165
+ expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide a registered vid`, '100')).to.equal(true);
166
+ });
167
+ });
168
+
169
+ describe('handle creative messages', () => {
170
+ let sandbox;
171
+ let intersectionObserverStub;
172
+ let setTimeoutStub;
173
+ let observeCalled;
174
+ let unobserveCalled;
175
+ let ti = 1;
176
+ let getElementsByTagStub;
177
+ let getElementByIdStub;
178
+
179
+ let fakeContentWindow = {};
180
+ beforeEach(() => {
181
+ observeCalled = false;
182
+ unobserveCalled = false;
183
+ sandbox = sinon.sandbox.create();
184
+
185
+ let fakeIntersectionObserver = (stateChange, options) => {
186
+ return {
187
+ observe: (element) => {
188
+ observeCalled = true;
189
+ stateChange([{ isIntersecting: true }]);
190
+ },
191
+ unobserve: (element) => {
192
+ unobserveCalled = true;
193
+ },
194
+ };
195
+ };
196
+
197
+ intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver);
198
+ setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => {
199
+ callback();
200
+ return ti++;
201
+ });
202
+
203
+ getElementsByTagStub = sandbox.stub(document, 'getElementsByTagName').callsFake((tagName) => {
204
+ return [{
205
+ contentWindow: fakeContentWindow,
206
+ }];
207
+ });
208
+ getElementByIdStub = sandbox.stub(document, 'getElementById').callsFake((id) => {
209
+ return {};
210
+ });
211
+ });
212
+
213
+ afterEach(() => {
214
+ sandbox.restore();
215
+ });
216
+
217
+ it('should find element by contentWindow', () => {
218
+ let viewabilityRecord = {
219
+ vid: 1000,
220
+ tracker: {
221
+ value: 'http://my.tracker/123',
222
+ method: 'img',
223
+ },
224
+ criteria: { inViewThreshold: 0.5, timeInView: 1000 },
225
+ message: 'Prebid Viewability',
226
+ action: 'startMeasurement',
227
+ };
228
+ let data = JSON.stringify(viewabilityRecord);
229
+
230
+ viewability.receiveMessage({
231
+ data: data,
232
+ source: fakeContentWindow,
233
+ });
234
+
235
+ sinon.assert.called(getElementsByTagStub);
236
+ sinon.assert.called(intersectionObserverStub);
237
+ sinon.assert.called(setTimeoutStub);
238
+ expect(observeCalled).to.equal(true);
239
+ expect(unobserveCalled).to.equal(true);
240
+ });
241
+
242
+ it('should find element by id', () => {
243
+ let viewabilityRecord = {
244
+ vid: 1001,
245
+ tracker: {
246
+ value: 'http://my.tracker/123',
247
+ method: 'img',
248
+ },
249
+ criteria: { inViewThreshold: 0.5, timeInView: 1000 },
250
+ message: 'Prebid Viewability',
251
+ action: 'startMeasurement',
252
+ elementId: '1',
253
+ };
254
+ let data = JSON.stringify(viewabilityRecord);
255
+ viewability.receiveMessage({
256
+ data: data,
257
+ });
258
+
259
+ sinon.assert.called(getElementByIdStub);
260
+ sinon.assert.called(intersectionObserverStub);
261
+ sinon.assert.called(setTimeoutStub);
262
+ expect(observeCalled).to.equal(true);
263
+ expect(unobserveCalled).to.equal(true);
264
+ });
265
+
266
+ it('should stop measurement', () => {
267
+ let viewabilityRecord = {
268
+ vid: 1001,
269
+ message: 'Prebid Viewability',
270
+ action: 'stopMeasurement',
271
+ };
272
+ let data = JSON.stringify(viewabilityRecord);
273
+ viewability.receiveMessage({
274
+ data: data,
275
+ });
276
+
277
+ expect(unobserveCalled).to.equal(true);
278
+ });
279
+ });
280
+ });