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
@@ -152,7 +152,7 @@
152
152
 
153
153
  import {config} from '../../src/config.js';
154
154
  import {module} from '../../src/hook.js';
155
- import {logError, logWarn} from '../../src/utils.js';
155
+ import {logError, logInfo, logWarn} from '../../src/utils.js';
156
156
  import events from '../../src/events.js';
157
157
  import CONSTANTS from '../../src/constants.json';
158
158
  import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js';
@@ -256,6 +256,7 @@ function initSubModules() {
256
256
  }
257
257
  });
258
258
  subModules = subModulesByOrder;
259
+ logInfo(`Real time data module enabled, using submodules: ${subModules.map((m) => m.name).join(', ')}`);
259
260
  }
260
261
 
261
262
  /**
@@ -290,18 +291,12 @@ export function setBidRequestsData(fn, reqBidsConfigObj) {
290
291
  return exitHook();
291
292
  }
292
293
 
293
- if (shouldDelayAuction) {
294
- waitTimeout = setTimeout(exitHook, _moduleConfig.auctionDelay);
295
- }
294
+ waitTimeout = setTimeout(exitHook, shouldDelayAuction ? _moduleConfig.auctionDelay : 0);
296
295
 
297
296
  relevantSubModules.forEach(sm => {
298
297
  sm.getBidRequestData(reqBidsConfigObj, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent)
299
298
  });
300
299
 
301
- if (!shouldDelayAuction) {
302
- return exitHook();
303
- }
304
-
305
300
  function onGetBidRequestDataCallback() {
306
301
  if (isDone) {
307
302
  return;
@@ -309,12 +304,15 @@ export function setBidRequestsData(fn, reqBidsConfigObj) {
309
304
  if (this.config && this.config.waitForIt) {
310
305
  callbacksExpected--;
311
306
  }
312
- if (callbacksExpected <= 0) {
313
- return exitHook();
307
+ if (callbacksExpected === 0) {
308
+ setTimeout(exitHook, 0);
314
309
  }
315
310
  }
316
311
 
317
312
  function exitHook() {
313
+ if (isDone) {
314
+ return;
315
+ }
318
316
  isDone = true;
319
317
  clearTimeout(waitTimeout);
320
318
  fn.call(this, reqBidsConfigObj);
@@ -384,8 +384,9 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) {
384
384
  'floorRuleValue', () => deepAccess(bid, 'floorData.floorRuleValue'),
385
385
  'floorRule', () => debugTurnedOn() ? deepAccess(bid, 'floorData.floorRule') : undefined,
386
386
  'adomains', () => {
387
- let adomains = deepAccess(bid, 'meta.advertiserDomains');
388
- return Array.isArray(adomains) && adomains.length > 0 ? adomains.slice(0, 10) : undefined
387
+ const adomains = deepAccess(bid, 'meta.advertiserDomains');
388
+ const validAdomains = Array.isArray(adomains) && adomains.filter(domain => typeof domain === 'string');
389
+ return validAdomains && validAdomains.length > 0 ? validAdomains.slice(0, 10) : undefined
389
390
  }
390
391
  ]);
391
392
  }
@@ -152,10 +152,10 @@ export const spec = {
152
152
 
153
153
  const { seatbid, cur } = serverResponse.body;
154
154
 
155
- const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => {
155
+ const bidResponses = (typeof seatbid != 'undefined') ? flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => {
156
156
  result[bid.impid - 1] = bid;
157
157
  return result;
158
- }, []);
158
+ }, []) : [];
159
159
 
160
160
  return bids
161
161
  .map((bid, id) => {
@@ -167,7 +167,7 @@ export const spec = {
167
167
  cpm: bidResponse.price,
168
168
  creativeId: bidResponse.crid,
169
169
  ttl: 1000,
170
- netRevenue: bid.netRevenue === 'net',
170
+ netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'),
171
171
  currency: cur,
172
172
  mediaType: NATIVE,
173
173
  bidderCode: BIDDER_CODE,
@@ -1,10 +1,10 @@
1
- import { generateUUID, deepAccess, inIframe } from '../src/utils.js';
1
+ import { deepAccess, generateUUID, inIframe } from '../src/utils.js';
2
2
  import { registerBidder } from '../src/adapters/bidderFactory.js';
3
3
  import { config } from '../src/config.js';
4
4
  import { BANNER, VIDEO } from '../src/mediaTypes.js';
5
5
  import { createEidsArray } from './userId/eids.js';
6
6
 
7
- const VERSION = '4.0.1';
7
+ const VERSION = '4.1.0';
8
8
  const BIDDER_CODE = 'sharethrough';
9
9
  const SUPPLY_ID = 'WYu2BXv1';
10
10
 
@@ -23,6 +23,7 @@ export const sharethroughAdapterSpec = {
23
23
 
24
24
  buildRequests: (bidRequests, bidderRequest) => {
25
25
  const timeout = config.getConfig('bidderTimeout');
26
+ const firstPartyData = config.getConfig('ortb2') || {};
26
27
 
27
28
  const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0;
28
29
  const secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1);
@@ -35,12 +36,8 @@ export const sharethroughAdapterSpec = {
35
36
  site: {
36
37
  domain: window.location.hostname,
37
38
  page: window.location.href,
38
- ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null,
39
- },
40
- user: {
41
- ext: {
42
- eids: userIdAsEids(bidRequests[0]),
43
- },
39
+ ref: deepAccess(bidderRequest, 'refererInfo.referer'),
40
+ ...firstPartyData.site,
44
41
  },
45
42
  device: {
46
43
  ua: navigator.userAgent,
@@ -66,6 +63,10 @@ export const sharethroughAdapterSpec = {
66
63
  test: 0,
67
64
  };
68
65
 
66
+ req.user = nullish(firstPartyData.user, {});
67
+ if (!req.user.ext) req.user.ext = {};
68
+ req.user.ext.eids = userIdAsEids(bidRequests[0]);
69
+
69
70
  if (bidderRequest.gdprConsent) {
70
71
  const gdprApplies = bidderRequest.gdprConsent.gdprApplies === true;
71
72
  req.regs.ext.gdpr = gdprApplies ? 1 : 0;
@@ -86,15 +87,9 @@ export const sharethroughAdapterSpec = {
86
87
  impression.ext = { gpid: gpid };
87
88
  }
88
89
 
89
- // if request is for video, we only support instream
90
- if (bidReq.mediaTypes && bidReq.mediaTypes.video && bidReq.mediaTypes.video.context === 'outstream') {
91
- // return null so we can easily remove this imp from the array of imps that we send to adserver
92
- return null;
93
- }
94
-
95
- if (bidReq.mediaTypes && bidReq.mediaTypes.video) {
96
- const videoRequest = bidReq.mediaTypes.video;
90
+ const videoRequest = deepAccess(bidReq, 'mediaTypes.video');
97
91
 
92
+ if (videoRequest) {
98
93
  // default playerSize, only change this if we know width and height are properly defined in the request
99
94
  let [w, h] = [640, 360];
100
95
  if (videoRequest.playerSize && videoRequest.playerSize[0] && videoRequest.playerSize[1]) {
@@ -117,9 +112,9 @@ export const sharethroughAdapterSpec = {
117
112
  startdelay: nullish(videoRequest.startdelay, 0),
118
113
  skipmin: nullish(videoRequest.skipmin, 0),
119
114
  skipafter: nullish(videoRequest.skipafter, 0),
115
+ placement: videoRequest.context === 'instream' ? 1 : +deepAccess(videoRequest, 'placement', 4),
120
116
  };
121
117
 
122
- if (videoRequest.placement) impression.video.placement = videoRequest.placement;
123
118
  if (videoRequest.delivery) impression.video.delivery = videoRequest.delivery;
124
119
  if (videoRequest.companiontype) impression.video.companiontype = videoRequest.companiontype;
125
120
  if (videoRequest.companionad) impression.video.companionad = videoRequest.companionad;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- import { getAdUnitSizes, logWarn, deepSetValue } from '../src/utils.js';
3
+ import { getAdUnitSizes, logWarn, deepSetValue, isFn, isPlainObject } from '../src/utils.js';
4
4
  import { registerBidder } from '../src/adapters/bidderFactory.js';
5
5
  import { BANNER, VIDEO } from '../src/mediaTypes.js';
6
6
  import includes from 'core-js-pure/features/array/includes.js';
@@ -67,11 +67,7 @@ export const spec = {
67
67
  } else {
68
68
  seatId = bid.params.seatId;
69
69
  }
70
- const tagIdOrplacementId = bid.params.tagId || bid.params.placementId;
71
- const bidFloor = bid.params.bidfloor ? parseFloat(bid.params.bidfloor) : null;
72
- if (isNaN(bidFloor)) {
73
- logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`);
74
- }
70
+ const tagIdOrPlacementId = bid.params.tagId || bid.params.placementId;
75
71
  let pos = parseInt(bid.params.pos, 10);
76
72
  if (isNaN(pos)) {
77
73
  logWarn(`Synacormedia: there is an invalid POS: ${bid.params.pos}`);
@@ -83,9 +79,9 @@ export const spec = {
83
79
 
84
80
  let imps = [];
85
81
  if (videoOrBannerKey === 'banner') {
86
- imps = this.buildBannerImpressions(adSizes, bid, tagIdOrplacementId, pos, bidFloor, videoOrBannerKey);
82
+ imps = this.buildBannerImpressions(adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey);
87
83
  } else if (videoOrBannerKey === 'video') {
88
- imps = this.buildVideoImpressions(adSizes, bid, tagIdOrplacementId, pos, bidFloor, videoOrBannerKey);
84
+ imps = this.buildVideoImpressions(adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey);
89
85
  }
90
86
  if (imps.length > 0) {
91
87
  imps.forEach(i => openRtbBidRequest.imp.push(i));
@@ -128,7 +124,7 @@ export const spec = {
128
124
  return eids;
129
125
  },
130
126
 
131
- buildBannerImpressions: function (adSizes, bid, tagIdOrPlacementId, pos, bidFloor, videoOrBannerKey) {
127
+ buildBannerImpressions: function (adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey) {
132
128
  let format = [];
133
129
  let imps = [];
134
130
  adSizes.forEach((size, i) => {
@@ -151,6 +147,10 @@ export const spec = {
151
147
  },
152
148
  tagid: tagIdOrPlacementId,
153
149
  };
150
+ const bidFloor = getBidFloor(bid, 'banner', '*');
151
+ if (isNaN(bidFloor)) {
152
+ logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`);
153
+ }
154
154
  if (bidFloor !== null && !isNaN(bidFloor)) {
155
155
  imp.bidfloor = bidFloor;
156
156
  }
@@ -159,7 +159,7 @@ export const spec = {
159
159
  return imps;
160
160
  },
161
161
 
162
- buildVideoImpressions: function(adSizes, bid, tagIdOrPlacementId, pos, bidFloor, videoOrBannerKey) {
162
+ buildVideoImpressions: function(adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey) {
163
163
  let imps = [];
164
164
  adSizes.forEach((size, i) => {
165
165
  if (!size || size.length != 2) {
@@ -171,6 +171,11 @@ export const spec = {
171
171
  id: `${videoOrBannerKey.substring(0, 1)}${bid.bidId}-${size0}x${size1}`,
172
172
  tagid: tagIdOrPlacementId
173
173
  };
174
+ const bidFloor = getBidFloor(bid, 'video', size);
175
+ if (isNaN(bidFloor)) {
176
+ logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`);
177
+ }
178
+
174
179
  if (bidFloor !== null && !isNaN(bidFloor)) {
175
180
  imp.bidfloor = bidFloor;
176
181
  }
@@ -287,4 +292,20 @@ export const spec = {
287
292
  }
288
293
  };
289
294
 
295
+ function getBidFloor(bid, mediaType, size) {
296
+ if (!isFn(bid.getFloor)) {
297
+ return bid.params.bidfloor ? parseFloat(bid.params.bidfloor) : null;
298
+ }
299
+ let floor = bid.getFloor({
300
+ currency: 'USD',
301
+ mediaType,
302
+ size
303
+ });
304
+
305
+ if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') {
306
+ return floor.floor;
307
+ }
308
+ return null;
309
+ }
310
+
290
311
  registerBidder(spec);
@@ -0,0 +1,177 @@
1
+ import { logWarn, logInfo, isStr, isFn, triggerPixel, insertHtmlIntoIframe } from '../src/utils.js';
2
+ import { getGlobal } from '../src/prebidGlobal.js';
3
+ import find from 'core-js-pure/features/array/find.js';
4
+
5
+ export const MODULE_NAME = 'viewability';
6
+
7
+ export function init() {
8
+ (getGlobal()).viewability = {
9
+ startMeasurement: startMeasurement,
10
+ stopMeasurement: stopMeasurement,
11
+ };
12
+
13
+ listenMessagesFromCreative();
14
+ }
15
+
16
+ const observers = {};
17
+
18
+ function isValid(vid, element, tracker, criteria) {
19
+ if (!element) {
20
+ logWarn(`${MODULE_NAME}: no html element provided`);
21
+ return false;
22
+ }
23
+
24
+ let validTracker = tracker &&
25
+ ((tracker.method === 'img' && isStr(tracker.value)) ||
26
+ (tracker.method === 'js' && isStr(tracker.value)) ||
27
+ (tracker.method === 'callback' && isFn(tracker.value)));
28
+
29
+ if (!validTracker) {
30
+ logWarn(`${MODULE_NAME}: invalid tracker`, tracker);
31
+ return false;
32
+ }
33
+
34
+ if (!criteria || !criteria.inViewThreshold || !criteria.timeInView) {
35
+ logWarn(`${MODULE_NAME}: missing criteria`, criteria);
36
+ return false;
37
+ }
38
+
39
+ if (!vid || observers[vid]) {
40
+ logWarn(`${MODULE_NAME}: must provide an unregistered vid`, vid);
41
+ return false;
42
+ }
43
+
44
+ return true;
45
+ }
46
+
47
+ function stopObserving(observer, vid, element) {
48
+ observer.unobserve(element);
49
+ observers[vid].done = true;
50
+ }
51
+
52
+ function fireViewabilityTracker(element, tracker) {
53
+ switch (tracker.method) {
54
+ case 'img':
55
+ triggerPixel(tracker.value, () => {
56
+ logInfo(`${MODULE_NAME}: viewability pixel fired`, tracker.value);
57
+ });
58
+ break;
59
+ case 'js':
60
+ insertHtmlIntoIframe(`<script src="${tracker.value}"></script>`);
61
+ break;
62
+ case 'callback':
63
+ tracker.value(element);
64
+ break;
65
+ }
66
+ }
67
+
68
+ function viewabilityCriteriaMet(observer, vid, element, tracker) {
69
+ stopObserving(observer, vid, element);
70
+ fireViewabilityTracker(element, tracker);
71
+ }
72
+
73
+ /**
74
+ * Start measuring viewability of an element
75
+ * @typedef {{ method: string='img','js','callback', value: string|function }} ViewabilityTracker { method: 'img', value: 'http://my.tracker/123' }
76
+ * @typedef {{ inViewThreshold: number, timeInView: number }} ViewabilityCriteria { inViewThreshold: 0.5, timeInView: 1000 }
77
+ * @param {string} vid unique viewability identifier
78
+ * @param {HTMLElement} element
79
+ * @param {ViewabilityTracker} tracker
80
+ * @param {ViewabilityCriteria} criteria
81
+ */
82
+ export function startMeasurement(vid, element, tracker, criteria) {
83
+ if (!isValid(vid, element, tracker, criteria)) {
84
+ return;
85
+ }
86
+
87
+ const options = {
88
+ root: null,
89
+ rootMargin: '0px',
90
+ threshold: criteria.inViewThreshold,
91
+ };
92
+
93
+ let observer;
94
+ let viewable = false;
95
+ let stateChange = (entries) => {
96
+ viewable = entries[0].isIntersecting;
97
+
98
+ if (viewable) {
99
+ observers[vid].timeoutId = window.setTimeout(() => {
100
+ viewabilityCriteriaMet(observer, vid, element, tracker);
101
+ }, criteria.timeInView);
102
+ } else if (observers[vid].timeoutId) {
103
+ window.clearTimeout(observers[vid].timeoutId);
104
+ }
105
+ };
106
+
107
+ observer = new IntersectionObserver(stateChange, options);
108
+ observers[vid] = {
109
+ observer: observer,
110
+ element: element,
111
+ timeoutId: null,
112
+ done: false,
113
+ };
114
+
115
+ observer.observe(element);
116
+
117
+ logInfo(`${MODULE_NAME}: startMeasurement called with:`, arguments);
118
+ }
119
+
120
+ /**
121
+ * Stop measuring viewability of an element
122
+ * @param {string} vid unique viewability identifier
123
+ */
124
+ export function stopMeasurement(vid) {
125
+ if (!vid || !observers[vid]) {
126
+ logWarn(`${MODULE_NAME}: must provide a registered vid`, vid);
127
+ return;
128
+ }
129
+
130
+ observers[vid].observer.unobserve(observers[vid].element);
131
+ if (observers[vid].timeoutId) {
132
+ window.clearTimeout(observers[vid].timeoutId);
133
+ }
134
+
135
+ // allow the observer under this vid to be created again
136
+ if (!observers[vid].done) {
137
+ delete observers[vid];
138
+ }
139
+ }
140
+
141
+ function listenMessagesFromCreative() {
142
+ window.addEventListener('message', receiveMessage, false);
143
+ }
144
+
145
+ /**
146
+ * Recieve messages from creatives
147
+ * @param {MessageEvent} evt
148
+ */
149
+ export function receiveMessage(evt) {
150
+ var key = evt.message ? 'message' : 'data';
151
+ var data = {};
152
+ try {
153
+ data = JSON.parse(evt[key]);
154
+ } catch (e) {
155
+ return;
156
+ }
157
+
158
+ if (!data || data.message !== 'Prebid Viewability') {
159
+ return;
160
+ }
161
+
162
+ switch (data.action) {
163
+ case 'startMeasurement':
164
+ let element = data.elementId && document.getElementById(data.elementId);
165
+ if (!element) {
166
+ element = find(document.getElementsByTagName('IFRAME'), iframe => (iframe.contentWindow || iframe.contentDocument.defaultView) == evt.source);
167
+ }
168
+
169
+ startMeasurement(data.vid, element, data.tracker, data.criteria);
170
+ break;
171
+ case 'stopMeasurement':
172
+ stopMeasurement(data.vid);
173
+ break;
174
+ }
175
+ }
176
+
177
+ init();
@@ -0,0 +1,87 @@
1
+ # Overview
2
+
3
+ Module Name: Viewability
4
+
5
+ Purpose: Track when a given HTML element becomes viewable
6
+
7
+ Maintainer: atrajkovic@magnite.com
8
+
9
+ # Configuration
10
+
11
+ Module does not need any configuration, as long as you include it in your PBJS bundle.
12
+ Viewability module has only two functions `startMeasurement` and `stopMeasurement` which can be used to enable more complex viewability measurements. Since it allows tracking from within creative (possibly inside a safe frame) this module registers a message listener, for messages with a format that is described bellow.
13
+
14
+ ## `startMeasurement`
15
+
16
+ | startMeasurement Arg Object | Scope | Type | Description | Example |
17
+ | --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- |
18
+ | vid | Required | String | Unique viewability identifier, used to reference particular observer | `"ae0f9"` |
19
+ | element | Required | HTMLElement | Reference to an HTML element that needs to be tracked | `document.getElementById('test_div')` |
20
+ | tracker | Required | ViewabilityTracker | How viewaility event is communicated back to the parties of interest | `{ method: 'img', value: 'http://my.tracker/123' }` |
21
+ | criteria | Required | ViewabilityCriteria| Defines custom viewability criteria using the threshold and duration provided | `{ inViewThreshold: 0.5, timeInView: 1000 }` |
22
+
23
+ | ViewabilityTracker | Scope | Type | Description | Example |
24
+ | --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- |
25
+ | method | Required | String | Type of method for Tracker | `'img' OR 'js' OR 'callback'` |
26
+ | value | Required | String | URL string for 'img' and 'js' Trackers, or a function for 'callback' Tracker | `'http://my.tracker/123'` |
27
+
28
+ | ViewabilityCriteria | Scope | Type | Description | Example |
29
+ | --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- |
30
+ | inViewThreshold | Required | Number | Represents a percentage threshold for the Element to be registered as in view | `0.5` |
31
+ | timeInView | Required | Number | Number of milliseconds that a given element needs to be in view continuously, above the threshold | `1000` |
32
+
33
+ ## Please Note:
34
+ - `vid` allows for multiple trackers, with different criteria to be registered for a given HTML element, independently. It's not autogenerated by `startMeasurement()`, it needs to be provided by the caller so that it doesn't have to be posted back to the source iframe (in case viewability is started from within the creative).
35
+ - In case of 'callback' method, HTML element is being passed back to the callback function.
36
+ - When a tracker needs to be started, without direct access to pbjs, postMessage mechanism can be used to invoke `startMeasurement`, with a following payload: `vid`, `tracker` and `criteria` as described above, but also with `message: 'Prebid Viewability'` and `action: 'startMeasurement'`. Optionally payload can provide `elementId`, if available at that time (for ad servers where name of the iframe is known, or adservers that render outside an iframe). If `elementId` is not provided, viewability module will try to find the iframe that corresponds to the message source.
37
+
38
+
39
+ ## `stopMeasurement`
40
+
41
+ | stopMeasurement Arg Object | Scope | Type | Description | Example |
42
+ | --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- |
43
+ | vid | Required | String | Unique viewability identifier, referencing an already started viewability tracker. | `"ae0f9"` |
44
+
45
+ ## Please Note:
46
+ - When a tracker needs to be stopped, without direct access to pbjs, postMessage mechanism can be used here as well. To invoke `stopMeasurement`, you provide the payload with `vid`, `message: 'Prebid Viewability'` and `action: 'stopMeasurement`. Check the example bellow.
47
+
48
+ # Examples
49
+
50
+ ## Example of starting a viewability measurement, when you have direct access to pbjs
51
+ ```
52
+ pbjs.viewability.startMeasurement(
53
+ 'ae0f9',
54
+ document.getElementById('test_div'),
55
+ { method: 'img', value: 'http://my.tracker/123' },
56
+ { inViewThreshold: 0.5, timeInView: 1000 }
57
+ );
58
+ ```
59
+
60
+ ## Example of starting a viewability measurement from within a rendered creative
61
+ ```
62
+ let viewabilityRecord = {
63
+ vid: 'ae0f9',
64
+ tracker: { method: 'img', value: 'http://my.tracker/123'},
65
+ criteria: { inViewThreshold: 0.5, timeInView: 1000 },
66
+ message: 'Prebid Viewability',
67
+ action: 'startMeasurement'
68
+ }
69
+
70
+ window.parent.postMessage(JSON.stringify(viewabilityRecord), '*');
71
+ ```
72
+
73
+ ## Example of stopping the viewability measurement, when you have direct access to pbjs
74
+ ```
75
+ pbjs.viewability.stopMeasurement('ae0f9');
76
+ ```
77
+
78
+ ## Example of stopping the viewability measurement from within a rendered creative
79
+ ```
80
+ let viewabilityRecord = {
81
+ vid: 'ae0f9',
82
+ message: 'Prebid Viewability',
83
+ action: 'stopMeasurement'
84
+ }
85
+
86
+ window.parent.postMessage(JSON.stringify(viewabilityRecord), '*');
87
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prebid.js",
3
- "version": "6.6.0",
3
+ "version": "6.7.0",
4
4
  "description": "Header Bidding Management Library",
5
5
  "main": "src/prebid.js",
6
6
  "scripts": {
@@ -127,11 +127,12 @@ function resizeRemoteCreative({ adId, adUnitCode, width, height }) {
127
127
  }
128
128
 
129
129
  function getDfpElementId(adId) {
130
- return find(window.googletag.pubads().getSlots(), slot => {
130
+ const slot = find(window.googletag.pubads().getSlots(), slot => {
131
131
  return find(slot.getTargetingKeys(), key => {
132
132
  return includes(slot.getTargeting(key), adId);
133
133
  });
134
- }).getSlotElementId();
134
+ });
135
+ return slot ? slot.getSlotElementId() : null;
135
136
  }
136
137
 
137
138
  function getAstElementId(adUnitCode) {
@@ -504,9 +504,11 @@ describe('adbookpsp bid adapter', () => {
504
504
  ad: '<div>ad</div>',
505
505
  adId: '5',
506
506
  adserverTargeting: {
507
+ hb_ad_ord_adbookpsp: '0_0', // the value to the left of the underscore represents the index of the ad id and the number to the right represents the order index
507
508
  hb_adid_c_adbookpsp: '5',
508
509
  hb_deal_adbookpsp: 'werwetwerw',
509
510
  hb_liid_adbookpsp: '2342345',
511
+ hb_ordid_adbookpsp: '567843',
510
512
  },
511
513
  referrer: 'http://prebid-test-page.io:8080/banner.html',
512
514
  lineItemId: '2342345',
@@ -516,9 +518,11 @@ describe('adbookpsp bid adapter', () => {
516
518
  adId: '10',
517
519
  adUnitCode: 'div-gpt-ad-837465923534-0',
518
520
  adserverTargeting: {
521
+ hb_ad_ord_adbookpsp: '0_0',
519
522
  hb_adid_c_adbookpsp: '10',
520
523
  hb_deal_adbookpsp: 'dsfxcxcvxc',
521
524
  hb_liid_adbookpsp: '2121221',
525
+ hb_ordid_adbookpsp: '5678234',
522
526
  },
523
527
  bidId: 'bid4321',
524
528
  bidderRequestId: '999ccceeee11',
@@ -556,14 +560,18 @@ describe('adbookpsp bid adapter', () => {
556
560
 
557
561
  expect(bids).to.have.length(2);
558
562
  expect(bids[0].adserverTargeting).to.deep.equal({
563
+ hb_ad_ord_adbookpsp: '0_0',
564
+ hb_adid_c_adbookpsp: '5',
559
565
  hb_deal_adbookpsp: 'werwetwerw',
560
566
  hb_liid_adbookpsp: '2342345',
561
- hb_adid_c_adbookpsp: '5',
567
+ hb_ordid_adbookpsp: '567843',
562
568
  });
563
569
  expect(bids[1].adserverTargeting).to.deep.equal({
570
+ hb_ad_ord_adbookpsp: '0_0',
571
+ hb_adid_c_adbookpsp: '10',
564
572
  hb_deal_adbookpsp: 'dsfxcxcvxc',
565
573
  hb_liid_adbookpsp: '2121221',
566
- hb_adid_c_adbookpsp: '10',
574
+ hb_ordid_adbookpsp: '5678234',
567
575
  });
568
576
  });
569
577
 
@@ -580,9 +588,11 @@ describe('adbookpsp bid adapter', () => {
580
588
  expect(bids).to.have.length(2);
581
589
  for (const bid of bids) {
582
590
  expect(bid.adserverTargeting).to.deep.equal({
591
+ hb_ad_ord_adbookpsp: '0_0,1_0',
592
+ hb_adid_c_adbookpsp: '5,10',
583
593
  hb_deal_adbookpsp: 'werwetwerw,dsfxcxcvxc',
584
594
  hb_liid_adbookpsp: '2342345,2121221',
585
- hb_adid_c_adbookpsp: '5,10',
595
+ hb_ordid_adbookpsp: '567843,5678234',
586
596
  });
587
597
  }
588
598
  });
@@ -670,9 +680,11 @@ describe('adbookpsp bid adapter', () => {
670
680
  );
671
681
 
672
682
  expect(bids[0].adserverTargeting).to.deep.equal({
683
+ hb_ad_ord_adbookpsp: '0_0',
673
684
  hb_adid_c_adbookpsp: '10',
674
685
  hb_deal_adbookpsp: 'dsfxcxcvxc',
675
686
  hb_liid_adbookpsp: '2121221',
687
+ hb_ordid_adbookpsp: '5678234',
676
688
  });
677
689
  });
678
690
 
@@ -1279,6 +1291,7 @@ const exchangeResponse = {
1279
1291
  nurl: 'http://win.example.url',
1280
1292
  ext: {
1281
1293
  liid: '2342345',
1294
+ ordid: '567843',
1282
1295
  },
1283
1296
  cat: ['IAB2-1', 'IAB2-2', 'IAB2-3'],
1284
1297
  adomain: ['advertiser.com'],
@@ -1301,6 +1314,7 @@ const exchangeResponse = {
1301
1314
  nurl: 'http://win.example.url',
1302
1315
  ext: {
1303
1316
  liid: '2121221',
1317
+ ordid: '5678234',
1304
1318
  },
1305
1319
  cat: ['IAB2-3'],
1306
1320
  adomain: ['advertiser.com', 'campaign.advertiser.com'],
@@ -77,7 +77,7 @@ describe('adhashBidAdapter', function () {
77
77
  );
78
78
  expect(result.length).to.equal(1);
79
79
  expect(result[0].method).to.equal('POST');
80
- expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true');
80
+ expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=1.0&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb');
81
81
  expect(result[0].bidRequest).to.equal(bidRequest);
82
82
  expect(result[0].data).to.have.property('timezone');
83
83
  expect(result[0].data).to.have.property('location');
@@ -93,7 +93,7 @@ describe('adhashBidAdapter', function () {
93
93
  const result = spec.buildRequests([ bidRequest ], { gdprConsent: true });
94
94
  expect(result.length).to.equal(1);
95
95
  expect(result[0].method).to.equal('POST');
96
- expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true');
96
+ expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=1.0&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb');
97
97
  expect(result[0].bidRequest).to.equal(bidRequest);
98
98
  expect(result[0].data).to.have.property('timezone');
99
99
  expect(result[0].data).to.have.property('location');