-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IX Bid Adapter: add priceFloors support and an integration example #6390
Changes from 6 commits
59d7891
9dae6c5
e31765a
31b6eec
1babcf9
2031ddf
045680d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
<!-- | ||
This page calls a single bidder for a single ad slot. | ||
It is a specialized example for adding floors to bids using the priceFloors Module | ||
It also makes a good test page for new adapter PR submissions. Simply set your server's Bid Params object in the | ||
bids array inside the adUnits, and it will use your adapter to load an ad. | ||
NOTE that many ad servers won't send back an ad if the URL is localhost... so you might need to | ||
set an alias in your /etc/hosts file so that you can load this page from a different domain. | ||
--> | ||
|
||
<html> | ||
|
||
<head> | ||
<script async src="../../build/dist/prebid.js"></script> | ||
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script> | ||
<script> | ||
var FAILSAFE_TIMEOUT = 3300; | ||
var PREBID_TIMEOUT = 1000; | ||
var adUnits = [{ | ||
code: 'div-gpt-ad-51545-0', | ||
sizes: [[300, 250], [600, 500]], | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[300, 250], [600, 500]] | ||
} | ||
}, | ||
// Replace this object to test a new Adapter! | ||
bids: [{ | ||
bidder: 'ix', | ||
params: { | ||
siteId: '300', | ||
size: [300, 250] | ||
} | ||
}] | ||
}]; | ||
var pbjs = pbjs || {}; | ||
pbjs.que = pbjs.que || []; | ||
</script> | ||
<script> | ||
var googletag = googletag || {}; | ||
googletag.cmd = googletag.cmd || []; | ||
googletag.cmd.push(function () { | ||
googletag.pubads().disableInitialLoad(); | ||
}); | ||
|
||
pbjs.que.push(function () { | ||
pbjs.addAdUnits(adUnits); | ||
pbjs.setConfig({ | ||
floors: { | ||
enforcement: { | ||
floorDeals: false, //default to false | ||
bidAdjustment: true | ||
}, | ||
data: { // default if endpoint doesn't return in time | ||
currency: 'USD', | ||
skipRate: 5, | ||
modelVersion: 'BlackBerryZap', | ||
schema: { | ||
fields: ['gptSlot', 'mediaType', 'size'] | ||
}, | ||
values: { | ||
'*|banner|600x500': 6.5, | ||
'*|banner|300x250': 3.25, | ||
'*|video': 3.5 | ||
} | ||
} | ||
} | ||
}); | ||
pbjs.requestBids({ | ||
bidsBackHandler: sendAdserverRequest, | ||
timeout: PREBID_TIMEOUT | ||
}); | ||
}); | ||
|
||
function sendAdserverRequest() { | ||
if (pbjs.adserverRequestSent) return; | ||
pbjs.adserverRequestSent = true; | ||
googletag.cmd.push(function () { | ||
pbjs.que.push(function () { | ||
pbjs.setTargetingForGPTAsync(); | ||
googletag.pubads().refresh(); | ||
}); | ||
}); | ||
} | ||
|
||
setTimeout(function () { | ||
sendAdserverRequest(); | ||
}, FAILSAFE_TIMEOUT); | ||
|
||
</script> | ||
|
||
<script> | ||
googletag.cmd.push(function () { | ||
googletag.defineSlot('/19968336/header-bid-tag-0', [[300, 250], [300, 600]], 'div-gpt-ad-51545-0').addService(googletag.pubads()); | ||
|
||
googletag.pubads().enableSingleRequest(); | ||
googletag.enableServices(); | ||
}); | ||
</script> | ||
</head> | ||
|
||
<body> | ||
<h2>Prebid.js Test</h2> | ||
<h5>Div-1</h5> | ||
<div id='div-gpt-ad-51545-0'> | ||
<script type='text/javascript'> | ||
googletag.cmd.push(function () { googletag.display('div-gpt-ad-51545-0'); }); | ||
</script> | ||
</div> | ||
</body> | ||
|
||
</html> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,11 +14,14 @@ const CENT_TO_DOLLAR_FACTOR = 100; | |
const BANNER_TIME_TO_LIVE = 300; | ||
const VIDEO_TIME_TO_LIVE = 3600; // 1hr | ||
const NET_REVENUE = true; | ||
|
||
const PRICE_TO_DOLLAR_FACTOR = { | ||
JPY: 1 | ||
}; | ||
const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; | ||
|
||
const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; | ||
|
||
/** | ||
* Transform valid bid request config object to banner impression object that will be sent to ad server. | ||
* | ||
|
@@ -33,6 +36,8 @@ function bidToBannerImp(bid) { | |
imp.banner.h = bid.params.size[1]; | ||
imp.banner.topframe = utils.inIframe() ? 0 : 1; | ||
|
||
_applyFloor(bid, imp, BANNER); | ||
|
||
return imp; | ||
} | ||
|
||
|
@@ -46,7 +51,7 @@ function bidToVideoImp(bid) { | |
const imp = bidToImp(bid); | ||
const videoAdUnitRef = utils.deepAccess(bid, 'mediaTypes.video'); | ||
const context = utils.deepAccess(bid, 'mediaTypes.video.context'); | ||
const videoAdUnitWhitelist = [ | ||
const videoAdUnitAllowlist = [ | ||
'mimes', 'minduration', 'maxduration', 'protocols', 'protocol', | ||
'startdelay', 'placement', 'linearity', 'skip', 'skipmin', | ||
'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', | ||
|
@@ -68,12 +73,14 @@ function bidToVideoImp(bid) { | |
} | ||
} | ||
|
||
for (let adUnitProperty in videoAdUnitRef) { | ||
if (videoAdUnitWhitelist.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { | ||
for (const adUnitProperty in videoAdUnitRef) { | ||
if (videoAdUnitAllowlist.indexOf(adUnitProperty) !== -1 && !imp.video[adUnitProperty]) { | ||
imp.video[adUnitProperty] = videoAdUnitRef[adUnitProperty]; | ||
} | ||
} | ||
|
||
_applyFloor(bid, imp, VIDEO); | ||
|
||
return imp; | ||
} | ||
|
||
|
@@ -92,12 +99,73 @@ function bidToImp(bid) { | |
imp.ext.sid = `${bid.params.size[0]}x${bid.params.size[1]}`; | ||
} | ||
|
||
if (bid.params.hasOwnProperty('bidFloor') && bid.params.hasOwnProperty('bidFloorCur')) { | ||
imp.bidfloor = bid.params.bidFloor; | ||
imp.bidfloorcur = bid.params.bidFloorCur; | ||
return imp; | ||
} | ||
|
||
/** | ||
* Gets priceFloors floors and IX adapter floors, | ||
* Validates and sets the higher one on the impression | ||
* @param {object} bid bid object | ||
* @param {object} imp impression object | ||
* @param {string} mediaType the impression ad type, one of the SUPPORTED_AD_TYPES | ||
*/ | ||
function _applyFloor(bid, imp, mediaType) { | ||
let adapterFloor = null; | ||
let moduleFloor = null; | ||
|
||
if (bid.params.bidFloor && bid.params.bidFloorCur) { | ||
adapterFloor = { floor: bid.params.bidFloor, currency: bid.params.bidFloorCur }; | ||
} | ||
|
||
return imp; | ||
if (utils.isFn(bid.getFloor)) { | ||
let _mediaType = '*'; | ||
let _size = '*'; | ||
|
||
if (mediaType && utils.contains(SUPPORTED_AD_TYPES, mediaType)) { | ||
const { w: width, h: height } = imp[mediaType]; | ||
_mediaType = mediaType; | ||
_size = [width, height]; | ||
} | ||
try { | ||
moduleFloor = bid.getFloor({ | ||
mediaType: _mediaType, | ||
size: _size | ||
}); | ||
} catch (err) { | ||
// continue with no module floors | ||
utils.logWarn('priceFloors module call getFloor failed, error : ', err); | ||
} | ||
} | ||
|
||
if (adapterFloor && moduleFloor) { | ||
if (adapterFloor.currency !== moduleFloor.currency) { | ||
utils.logWarn('The bid floor currency mismatch between IX params and priceFloors module config'); | ||
return; | ||
} | ||
|
||
if (adapterFloor.floor > moduleFloor.floor) { | ||
imp.bidfloor = adapterFloor.floor; | ||
imp.bidfloorcur = adapterFloor.currency; | ||
imp.ext.fl = FLOOR_SOURCE.IX; | ||
} else { | ||
imp.bidfloor = moduleFloor.floor; | ||
imp.bidfloorcur = moduleFloor.currency; | ||
imp.ext.fl = FLOOR_SOURCE.PBJS; | ||
} | ||
return; | ||
} | ||
|
||
if (moduleFloor) { | ||
imp.bidfloor = moduleFloor.floor; | ||
imp.bidfloorcur = moduleFloor.currency; | ||
imp.ext.fl = FLOOR_SOURCE.PBJS; | ||
} else if (adapterFloor) { | ||
imp.bidfloor = adapterFloor.floor; | ||
imp.bidfloorcur = adapterFloor.currency; | ||
imp.ext.fl = FLOOR_SOURCE.IX; | ||
} else { | ||
utils.logInfo('No floors available, no floors applied') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a prefix to this so when looking at logs it is clear where it is coming from: utils.logInfo('IX Bid Adapter: No floors available, no floors applied') There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added it |
||
} | ||
} | ||
|
||
/** | ||
|
@@ -270,7 +338,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { | |
if (identityInfo.hasOwnProperty(partnerName)) { | ||
let response = identityInfo[partnerName]; | ||
if (!response.responsePending && response.data && typeof response.data === 'object' && | ||
Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { | ||
Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { | ||
userEids.push(response.data); | ||
} | ||
} | ||
|
@@ -498,7 +566,7 @@ function buildIXDiag(validBidRequests) { | |
iu: 0, | ||
nu: 0, | ||
ou: 0, | ||
allU: 0, | ||
allu: 0, | ||
ren: false, | ||
version: '$prebid.version$' | ||
}; | ||
|
@@ -534,7 +602,7 @@ function buildIXDiag(validBidRequests) { | |
ixdiag.iu++; | ||
} | ||
|
||
ixdiag.allU++; | ||
ixdiag.allu++; | ||
} | ||
} | ||
|
||
|
@@ -610,16 +678,19 @@ function updateMissingSizes(validBidRequest, missingBannerSizes, imp) { | |
} | ||
|
||
/** | ||
* | ||
* @param {object} bid ValidBidRequest object, used to adjust floor | ||
* @param {object} imp Impression object to be modified | ||
* @param {array} newSize The new size to be applied | ||
* @return {object} newImp Updated impression object | ||
*/ | ||
function createMissingBannerImp(imp, newSize) { | ||
function createMissingBannerImp(bid, imp, newSize) { | ||
const newImp = utils.deepClone(imp); | ||
newImp.ext.sid = `${newSize[0]}x${newSize[1]}`; | ||
newImp.banner.w = newSize[0]; | ||
newImp.banner.h = newSize[1]; | ||
|
||
_applyFloor(bid, newImp, BANNER); | ||
|
||
return newImp; | ||
} | ||
|
||
|
@@ -658,7 +729,7 @@ export const spec = { | |
} | ||
|
||
if (!includesSize(bid.sizes, paramsSize) && !((mediaTypeVideoPlayerSize && includesSize(mediaTypeVideoPlayerSize, paramsSize)) || | ||
(mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) { | ||
(mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) { | ||
utils.logError('ix bidder params: bid size is not included in ad unit sizes or player size.'); | ||
return false; | ||
} | ||
|
@@ -730,13 +801,12 @@ export const spec = { | |
if (!videoImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { | ||
videoImps[validBidRequest.transactionId].ixImps = []; | ||
} | ||
|
||
videoImps[validBidRequest.transactionId].ixImps.push(bidToVideoImp(validBidRequest)); | ||
} | ||
} | ||
if (validBidRequest.mediaType === BANNER || | ||
(utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) || | ||
(!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { | ||
(utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) || | ||
(!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { | ||
let imp = bidToBannerImp(validBidRequest); | ||
|
||
if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { | ||
|
@@ -767,7 +837,7 @@ export const spec = { | |
|
||
let origImp = missingBannerSizes[transactionId].impression; | ||
for (let i = 0; i < missingSizes.length; i++) { | ||
let newImp = createMissingBannerImp(origImp, missingSizes[i]); | ||
let newImp = createMissingBannerImp(validBidRequest, origImp, missingSizes[i]); | ||
bannerImps[transactionId].missingImps.push(newImp); | ||
bannerImps[transactionId].missingCount++; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could there be cases where the
imp.video[adUnitProperty]
value is falsey?Like a couple in your videoAdUnitAllowlist for example look like they might be at times:
skip
(boolean?) if this is false?boxingallowed
? is this a boolean? if false do you want to pass along?startdelay
could this be set to 0 (zero) ?hasOwnProperty is better in this case, otherwise if you do not care about passing along falsey values, then this change can stay.
I do not actually know if these are legit values on the imp.video object, but want to ask and make sure you are aware.
These two above examples would be
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added.