-
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
Nobid Prebid Adapter commit #4050
Changes from 7 commits
67798a5
7afc4bd
9f97d34
16e27db
999d8b7
dbc675c
d08381c
385e0a3
8024778
1e6d5c4
b351e89
e401d98
2e7216e
491843a
8060de8
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,292 @@ | ||
import * as utils from '../src/utils'; | ||
import { registerBidder } from '../src/adapters/bidderFactory'; | ||
import { BANNER } from '../src/mediaTypes'; | ||
const BIDDER_CODE = 'nobid'; | ||
window.nobidVersion = '1.1.0'; | ||
function log(msg, obj) { | ||
utils.logInfo('-NoBid- ' + msg, obj) | ||
} | ||
function nobidBuildRequests(bids, bidderRequest) { | ||
var serializeState = function(divIds, siteId, adunits) { | ||
var filterAdUnitsByIds = function(divIds, adUnits) { | ||
var filtered = []; | ||
if (!divIds || !divIds.length) { | ||
filtered = adUnits; | ||
} else if (adUnits) { | ||
var a = []; | ||
if (!(divIds instanceof Array)) a.push(divIds); | ||
else a = divIds; | ||
for (var i = 0, l = adUnits.length; i < l; i++) { | ||
var adUnit = adUnits[i]; | ||
if (adUnit && adUnit.d && (a.indexOf(adUnit.d) > -1)) { | ||
filtered.push(adUnit); | ||
} | ||
} | ||
} | ||
return filtered; | ||
} | ||
var gdprConsent = function(bidderRequest) { | ||
var gdprConsent = {}; | ||
if (bidderRequest && bidderRequest.gdprConsent) { | ||
gdprConsent = { | ||
consentString: bidderRequest.gdprConsent.consentString, | ||
// will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true | ||
consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : false | ||
} | ||
} | ||
return gdprConsent; | ||
} | ||
var topLocation = function(bidderRequest) { | ||
var ret = ''; | ||
if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { | ||
ret = bidderRequest.refererInfo.referer; | ||
} else { | ||
ret = (window.context && window.context.location && window.context.location.href) ? window.context.location.href : document.location.href; | ||
} | ||
return encodeURIComponent(ret.replace(/\%/g, '')); | ||
} | ||
var timestamp = function() { | ||
var date = new Date(); | ||
var zp = function (val) { return (val <= 9 ? '0' + val : '' + val); } | ||
var d = date.getDate(); | ||
var y = date.getFullYear(); | ||
var m = date.getMonth() + 1; | ||
var h = date.getHours(); | ||
var min = date.getMinutes(); | ||
var s = date.getSeconds(); | ||
return '' + y + '-' + zp(m) + '-' + zp(d) + ' ' + zp(h) + ':' + zp(min) + ':' + zp(s); | ||
}; | ||
var clientDim = function() { | ||
try { | ||
return `${screen.width}x${screen.height}`; | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
} | ||
var state = {}; | ||
state['sid'] = siteId; | ||
state['l'] = topLocation(bidderRequest); | ||
state['tt'] = encodeURIComponent(document.title); | ||
state['tt'] = state['tt'].replace(/'|;|quot;|39;|&|&|#|\r\n|\r|\n|\t|\f|\%0A|\"|\%22|\%5C|\%23|\%26|\%26|\%09/gm, ''); | ||
state['a'] = filterAdUnitsByIds(divIds, adunits || []); | ||
state['t'] = timestamp(); | ||
state['tz'] = Math.round(new Date().getTimezoneOffset()); | ||
state['r'] = clientDim(); | ||
state['lang'] = (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage; | ||
state['ref'] = document.referrer; | ||
state['gdpr'] = gdprConsent(bidderRequest); | ||
return state; | ||
} | ||
function newAdunit(adunitObject, adunits) { | ||
var getAdUnit = function(divid, adunits) { | ||
for (var i = 0; i < adunits.length; i++) { | ||
if (adunits[i].d === divid) { | ||
return adunits[i]; | ||
} | ||
} | ||
return false; | ||
} | ||
var removeByAttrValue = function(array, attribute, value) { | ||
for (var i = array.length - 1; i >= 0; i--) { | ||
var entry = array[i]; | ||
if (entry[attribute] && entry[attribute] === value) { | ||
array.splice(i, 1); | ||
} | ||
} | ||
} | ||
var a = getAdUnit(adunitObject.div, adunits) || {}; | ||
if (adunitObject.account) { | ||
a.s = adunitObject.account; | ||
} | ||
if (adunitObject.sizes) { | ||
a.z = adunitObject.sizes; | ||
} | ||
if (adunitObject.div) { | ||
a.d = adunitObject.div; | ||
} | ||
if (adunitObject.targeting) { | ||
a.g = adunitObject.targeting; | ||
} else { | ||
a.g = {}; | ||
} | ||
if (adunitObject.companion) { | ||
a.c = adunitObject.companion; | ||
} | ||
if (adunitObject.div) { | ||
removeByAttrValue(adunits, 'd', adunitObject.div); | ||
} | ||
if (adunitObject.sizeMapping) { | ||
a.sm = adunitObject.sizeMapping; | ||
} | ||
if (adunitObject.siteId) { | ||
a.sid = adunitObject.siteId; | ||
} | ||
/* {"BIDDER_ID":{"WxH":"TAG_ID", "WxH":"TAG_ID"}} */ | ||
if (adunitObject.rtb) { | ||
a.rtb = adunitObject.rtb; | ||
} | ||
adunits.push(a); | ||
return adunits; | ||
} | ||
/* DISCOVER SLOTS */ | ||
var divids = []; | ||
var siteId = 0; | ||
var adunits = []; | ||
for (var i = 0; i < bids.length; i++) { | ||
var bid = bids[i]; | ||
var divid = bid.adUnitCode; | ||
divids.push(divid); | ||
var sizes = bid.sizes; | ||
siteId = (typeof bid.params['siteId'] != 'undefined' && bid.params['siteId']) ? bid.params['siteId'] : siteId; | ||
if (siteId && bid.params && bid.params.tags) { | ||
newAdunit({div: divid, sizes: sizes, rtb: bid.params.tags, siteId: siteId}, adunits); | ||
} else if (siteId) { | ||
newAdunit({div: divid, sizes: sizes, siteId: siteId}, adunits); | ||
} | ||
} | ||
if (siteId) { | ||
return serializeState(divids, siteId, adunits); | ||
} else { | ||
return false; | ||
} | ||
} | ||
function nobidInterpretResponse(response, bidRequest) { | ||
var findBid = function(divid, bids) { | ||
for (var i = 0; i < bids.length; i++) { | ||
if (bids[i].adUnitCode === divid) { | ||
return bids[i]; | ||
} | ||
} | ||
return false; | ||
} | ||
var bidResponses = []; | ||
for (var i = 0; response.bids && i < response.bids.length; i++) { | ||
var bid = response.bids[i]; | ||
if (bid.bdrid < 100 || !bidRequest || !bidRequest.bidderRequest || !bidRequest.bidderRequest.bids) continue; | ||
var reqBid = findBid(bid.divid, bidRequest.bidderRequest.bids); | ||
if (!reqBid) continue; | ||
const bidResponse = { | ||
requestId: reqBid.bidId, | ||
cpm: 1 * ((bid.price) ? bid.price : (bid.bucket) ? bid.bucket : 0), | ||
width: bid.size.w, | ||
height: bid.size.h, | ||
creativeId: (bid.creativeid) || '', | ||
dealId: (bid.dealid) || '', | ||
currency: 'USD', | ||
netRevenue: true, | ||
ttl: 300, | ||
ad: bid.adm, | ||
mediaType: BANNER | ||
}; | ||
bidResponses.push(bidResponse); | ||
} | ||
return bidResponses; | ||
}; | ||
window.nobid = window.nobid || {}; | ||
nobid.renderTag = function(doc, id, win) { | ||
log('nobid.renderTag()', id); | ||
if (nobid.bidResponse && nobid.bidResponse.bids) { | ||
for (var i in nobid.bidResponse.bids) { | ||
const bid = nobid.bidResponse.bids[i]; | ||
if (id == bid.id && bid.adm2) { | ||
log('nobid.renderTag() found tag', id); | ||
var markup = bid.adm2; | ||
doc.write(markup); | ||
doc.close(); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
export const spec = { | ||
code: BIDDER_CODE, | ||
supportedMediaTypes: [BANNER], | ||
/** | ||
* Determines whether or not the given bid request is valid. | ||
* | ||
* @param {BidRequest} bid The bid params to validate. | ||
* @return boolean True if this is a valid bid, and false otherwise. | ||
*/ | ||
isBidRequestValid: function(bid) { | ||
log('isBidRequestValid', bid); | ||
return !!bid.params.siteId; | ||
}, | ||
/** | ||
* Make a server request from the list of BidRequests. | ||
* | ||
* @param {validBidRequests[]} - an array of bids | ||
* @return ServerRequest Info describing the request to the server. | ||
*/ | ||
buildRequests: function(validBidRequests, bidderRequest) { | ||
function resolveEndpoint() { | ||
var ret = 'https://ads.servenobid.com/'; | ||
var env = (typeof utils.getParameterByName === 'function') && (utils.getParameterByName('nobid-env')); | ||
if (!env) ret = 'https://ads.servenobid.com/'; | ||
else if (env == 'beta') ret = 'https://beta.servenobid.com/'; | ||
else if (env == 'dev') ret = '//localhost:8282/'; | ||
else if (env == 'qa') ret = 'https://qa-ads.nobid.com/'; | ||
return ret; | ||
} | ||
var buildEndpoint = function() { | ||
return resolveEndpoint() + 'adreq?cb=' + Math.floor(Math.random() * 11000); | ||
} | ||
log('buildRequests', validBidRequests); | ||
if (!validBidRequests || validBidRequests.length <= 0) { | ||
log('Empty validBidRequests'); | ||
return; | ||
} | ||
const payload = nobidBuildRequests(validBidRequests, bidderRequest); | ||
if (!payload) return; | ||
const payloadString = JSON.stringify(payload).replace(/'|&|#/g, '') | ||
const endpoint = buildEndpoint(); | ||
return { | ||
method: 'POST', | ||
url: endpoint, | ||
data: payloadString, | ||
bidderRequest | ||
}; | ||
}, | ||
/** | ||
* Unpack the response from the server into a list of bids. | ||
* | ||
* @param {ServerResponse} serverResponse A successful response from the server. | ||
* @return {Bid[]} An array of bids which were nested inside the server. | ||
*/ | ||
interpretResponse: function(serverResponse, bidRequest) { | ||
log('interpretResponse', serverResponse); | ||
log('interpretResponse', bidRequest); | ||
nobid.bidResponse = serverResponse.body; | ||
return nobidInterpretResponse(serverResponse.body, bidRequest); | ||
}, | ||
|
||
/** | ||
* Register the user sync pixels which should be dropped after the auction. | ||
* | ||
* @param {SyncOptions} syncOptions Which user syncs are allowed? | ||
* @param {ServerResponse[]} serverResponses List of server's responses. | ||
* @return {UserSync[]} The user syncs which should be dropped. | ||
*/ | ||
getUserSyncs: function(syncOptions, serverResponses) { | ||
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. FYI: GDPR information can be passed into the getUserSyncs function and passed along to your sync endpoint. See https://github.com/prebid/Prebid.js/blob/master/modules/rubiconBidAdapter.js#L602 as an example. Maybe add it in a future PR if needed? 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. This is done |
||
if (syncOptions.iframeEnabled) { | ||
return [{ | ||
type: 'iframe', | ||
url: 'https://s3.amazonaws.com/nobid-public/sync.html' | ||
}]; | ||
} else { | ||
utils.logWarn('-NoBid- Please enable iframe based user sync.', syncOptions); | ||
return []; | ||
} | ||
}, | ||
|
||
/** | ||
* Register bidder specific code, which will execute if bidder timed out after an auction | ||
* @param {data} Containing timeout specific data | ||
*/ | ||
onTimeout: function(data) { | ||
log('Timeout', data); | ||
}, | ||
onBidWon: function(data) { | ||
log('BidWon', data); | ||
} | ||
} | ||
registerBidder(spec); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
--- | ||
layout: bidder | ||
title: Nobid | ||
description: Prebid Nobid Bidder Adaptor | ||
biddercode: nobid | ||
hide: true | ||
media_types: banner | ||
gdpr_supported: true | ||
--- | ||
|
||
### Bid Params | ||
|
||
{: .table .table-bordered .table-striped } | ||
| Name | Scope | Description | Example | Type | | ||
|---------------|----------|-------------|---------|----------| | ||
| `siteId` | required | siteId is provided by your Nobid account manager | | `integer` | | ||
|
||
# Test Parameters | ||
``` | ||
var adUnits = [ | ||
{ | ||
code: 'test-div', | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[300, 250]], // a display size | ||
} | ||
}, | ||
bids: [ | ||
{ | ||
bidder: "nobid", | ||
params: { | ||
siteId: 2 | ||
} | ||
} | ||
] | ||
},{ | ||
code: 'test-div', | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[320, 50]], // a mobile size | ||
} | ||
}, | ||
bids: [ | ||
{ | ||
bidder: "nobid", | ||
params: { | ||
siteId: 2 | ||
} | ||
} | ||
] | ||
} | ||
]; | ||
``` |
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.
Not sure this follows our guidelines because you are still counting on the state to match up.
If a publisher runs concurrent auctions on a page you are resetting
nobid.bidResponse = serverResponse.body;
every auction.And if your passback is triggered later the result will not match up.
Most adapters send back this data and let prebid handle the rendering.
So when the Prebid Creative is passed back by DFP it finds the associated
adId
in its cache and can render it even in concurrent auction scenarios.You would have to keep a global list of ever server response that comes back.
I am not sure there is a precedence for this, as our rules do not allow adapters to keep a global running state.
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.
So the recommendation here is to keep your
nobid.bidResponse
offset by the AdId or BidId so that you can reference previous auctions if need be.So when your
nobid.renderTag
is called, it can find any auction that was run.So maybe something like so:
And your render tag function is updated to grab the correct AdId or BidId and render that tag.
Does this make sense?
Then your state is fully maintained even if multiple auctions are run.