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.
- package/integrationExamples/gpt/amp/creative.html +11 -33
- package/modules/adbookpspBidAdapter.js +27 -10
- package/modules/adhashBidAdapter.js +3 -3
- package/modules/colossussspBidAdapter.js +12 -8
- package/modules/colossussspBidAdapter.md +15 -1
- package/modules/compassBidAdapter.js +1 -1
- package/modules/consumableBidAdapter.md +1 -1
- package/modules/gnetBidAdapter.js +3 -3
- package/modules/gnetBidAdapter.md +4 -4
- package/modules/gumgumBidAdapter.js +4 -4
- package/modules/jwplayerRtdProvider.js +71 -6
- package/modules/jwplayerRtdProvider.md +27 -11
- package/modules/kargoBidAdapter.js +2 -2
- package/modules/pilotxBidAdapter.js +147 -0
- package/modules/pilotxBidAdapter.md +50 -0
- package/modules/rtdModule/index.js +8 -10
- package/modules/rubiconAnalyticsAdapter.js +3 -2
- package/modules/seedingAllianceBidAdapter.js +3 -3
- package/modules/sharethroughBidAdapter.js +12 -17
- package/modules/synacormediaBidAdapter.js +31 -10
- package/modules/viewability.js +177 -0
- package/modules/viewability.md +87 -0
- package/package.json +1 -1
- package/src/secureCreatives.js +3 -2
- package/test/spec/modules/adbookpspBidAdapter_spec.js +17 -3
- package/test/spec/modules/adhashBidAdapter_spec.js +2 -2
- package/test/spec/modules/colossussspBidAdapter_spec.js +5 -2
- package/test/spec/modules/gnetBidAdapter_spec.js +6 -6
- package/test/spec/modules/jwplayerRtdProvider_spec.js +195 -2
- package/test/spec/modules/kargoBidAdapter_spec.js +1 -1
- package/test/spec/modules/pilotxBidAdapter_spec.js +244 -0
- package/test/spec/modules/realTimeDataModule_spec.js +51 -2
- package/test/spec/modules/rubiconAnalyticsAdapter_spec.js +30 -0
- package/test/spec/modules/sharethroughBidAdapter_spec.js +91 -6
- package/test/spec/modules/synacormediaBidAdapter_spec.js +70 -0
- 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
|
-
|
|
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
|
|
313
|
-
|
|
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
|
-
|
|
388
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
82
|
+
imps = this.buildBannerImpressions(adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey);
|
|
87
83
|
} else if (videoOrBannerKey === 'video') {
|
|
88
|
-
imps = this.buildVideoImpressions(adSizes, bid,
|
|
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,
|
|
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,
|
|
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
package/src/secureCreatives.js
CHANGED
|
@@ -127,11 +127,12 @@ function resizeRemoteCreative({ adId, adUnitCode, width, height }) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
function getDfpElementId(adId) {
|
|
130
|
-
|
|
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
|
-
})
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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');
|