Skip to content
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

Nativo Bid Adapter: add Price Floors Module support #8666

Merged
merged 41 commits into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
38e1ade
Initial nativoBidAdapter document creation (js, md and spec)
jsfledd Feb 22, 2021
0792373
Fulling working prebid using nativoBidAdapter. Support for GDPR and C…
jsfledd Mar 12, 2021
008e906
Added defult size settings based on the largest ad unit. Added respon…
jsfledd Apr 5, 2021
21f3e0c
Changed bidder endpoint url
jsfledd Apr 7, 2021
1a33f2c
Changed double quotes to single quotes.
jsfledd Apr 7, 2021
517cd5b
Reverted package-json.lock to remove modifications from PR
jsfledd Apr 8, 2021
44bf428
Added optional bidder param 'url' so the ad server can force- match a…
jsfledd Apr 15, 2021
bfe0e1e
Lint fix. Added space after if.
jsfledd Apr 15, 2021
e927584
Added new QS param to send various adUnit data to adapter endpopint
jsfledd May 26, 2021
8ac4419
Merged latest prebid master branch
jsfledd May 26, 2021
26d8b86
Updated unit test for new QS param
jsfledd May 26, 2021
ead77de
Added qs param to keep track of ad unit refreshes
jsfledd Jul 20, 2021
4ed371c
Merged latest prebid master
jsfledd Jul 20, 2021
e59a770
Merged latest. Added adUnitCode as alternate to placementID. Removed …
jsfledd Sep 29, 2021
c338c4d
Updated bidMap key default value
jsfledd Sep 30, 2021
bfe7b02
Updated refresh increment logic
jsfledd Oct 12, 2021
06407a1
Merged latest
jsfledd Oct 25, 2021
978ffd7
Refactored spread operator for IE11 support
jsfledd Oct 25, 2021
f74f370
Updated isBidRequestValid check
jsfledd Oct 26, 2021
e1554d0
Merge branch 'master' of https://github.com/prebid/Prebid.js
jsfledd Oct 26, 2021
7b9c9bb
Refactored Object.enties to use Object.keys to fix CircleCI testing e…
jsfledd Oct 26, 2021
ffe4dc9
Merge remote-tracking branch 'Prebid_Official/master'
jsfledd Jan 14, 2022
7274900
Updated bid mapping key creation to prioritize ad unit code over plac…
jsfledd Jan 14, 2022
95cc80e
Added filtering by ad, advertiser and campaign.
jsfledd Feb 7, 2022
6b248fc
Merge remote-tracking branch 'Prebid_Official/master'
jsfledd Feb 7, 2022
bea02d0
Merge remote-tracking branch 'Prebid_Official/master'
jsfledd Feb 17, 2022
a6fc789
Merged master
jsfledd Feb 17, 2022
657b35e
merged with master
jsfledd Mar 14, 2022
2c0c8f5
Added more robust bidDataMap with multiple key access
jsfledd Mar 14, 2022
97f020d
Deduped filer values
jsfledd Mar 16, 2022
b23518f
Rolled back package.json
jsfledd Mar 30, 2022
2eeaa47
Merged with latest upstream master branch
jsfledd Mar 30, 2022
729e608
Duped upstream/master's package.lock file ... not sure how it got cha…
jsfledd Mar 30, 2022
f26e064
Small refactor of filterData length check. Removed comparison with 0 …
jsfledd Mar 30, 2022
024d010
Merge branch 'upstream_master'
jsfledd Apr 20, 2022
060ff4c
Added bid sizes to request
jsfledd Jun 6, 2022
15d34f1
Fixed function name in spec. Added unit tests.
jsfledd Jun 6, 2022
4ff5d1b
Merge branch 'upstream_master'
jsfledd Jul 8, 2022
7874280
Added priceFloor module support
jsfledd Jul 8, 2022
7619590
Added protection agains empty url parameter
jsfledd Jul 13, 2022
0bdc853
Merge branch 'upstream_master'
jsfledd Jul 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 171 additions & 27 deletions modules/nativoBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const GVLID = 263
const TIME_TO_LIVE = 360

const SUPPORTED_AD_TYPES = [BANNER]
const FLOOR_PRICE_CURRENCY = 'USD'
const PRICE_FLOOR_WILDCARD = '*'

/**
* Keep track of bid data by keys
Expand Down Expand Up @@ -131,84 +133,106 @@ export const spec = {
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function (validBidRequests, bidderRequest) {
// Parse values from bid requests
const placementIds = new Set()
const bidDataMap = BidDataMap()
const placementSizes = { length: 0 }
const floorPriceData = {}
let placementId, pageUrl
validBidRequests.forEach((request) => {
validBidRequests.forEach((bidRequest) => {
pageUrl = deepAccess(
request,
bidRequest,
'params.url',
bidderRequest.refererInfo.page
)
placementId = deepAccess(request, 'params.placementId')
if (pageUrl == undefined || pageUrl === '') {
pageUrl = bidderRequest.refererInfo.page
}

placementId = deepAccess(bidRequest, 'params.placementId')

const bidDataKeys = [request.adUnitCode]
const bidDataKeys = [bidRequest.adUnitCode]

if (placementId && !placementIds.has(placementId)) {
placementIds.add(placementId)
bidDataKeys.push(placementId)

placementSizes[placementId] = request.sizes
placementSizes[placementId] = bidRequest.sizes
placementSizes.length++
}

const bidData = {
bidId: request.bidId,
size: getLargestSize(request.sizes),
bidId: bidRequest.bidId,
size: getLargestSize(bidRequest.sizes),
}
bidDataMap.addBidData(bidData, bidDataKeys)

const bidRequestFloorPriceData = parseFloorPriceData(bidRequest)
if (bidRequestFloorPriceData) {
floorPriceData[bidRequest.adUnitCode] = bidRequestFloorPriceData
}
})
bidRequestMap[bidderRequest.bidderRequestId] = bidDataMap

// Build adUnit data
const adUnitData = {
adUnits: validBidRequests.map((adUnit) => {
// Track if we've already requested for this ad unit code
adUnitsRequested[adUnit.adUnitCode] =
adUnitsRequested[adUnit.adUnitCode] !== undefined
? adUnitsRequested[adUnit.adUnitCode] + 1
: 0
return {
adUnitCode: adUnit.adUnitCode,
mediaTypes: adUnit.mediaTypes,
}
}),
}
const adUnitData = buildAdUnitData(validBidRequests)

// Build QS Params
// Build basic required QS Params
let params = [
// Prebid request id
{ key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId },
// Ad unit data
{
key: 'ntv_ppc',
value: btoa(JSON.stringify(adUnitData)), // Convert to Base 64
},
// Number count of requests per ad unit
{
key: 'ntv_dbr',
value: btoa(JSON.stringify(adUnitsRequested)),
value: btoa(JSON.stringify(adUnitsRequested)), // Convert to Base 64
},
// Page url
{
key: 'ntv_url',
value: encodeURIComponent(pageUrl),
},
]

// Floor pricing
if (Object.keys(floorPriceData).length) {
params.unshift({
key: 'ntv_ppf',
value: btoa(JSON.stringify(floorPriceData)),
})
}

// Add filtering
if (adsToFilter.size > 0) {
params.unshift({ key: 'ntv_atf', value: Array.from(adsToFilter).join(',') })
params.unshift({
key: 'ntv_atf',
value: Array.from(adsToFilter).join(','),
})
}

if (advertisersToFilter.size > 0) {
params.unshift({ key: 'ntv_avtf', value: Array.from(advertisersToFilter).join(',') })
params.unshift({
key: 'ntv_avtf',
value: Array.from(advertisersToFilter).join(','),
})
}

if (campaignsToFilter.size > 0) {
params.unshift({ key: 'ntv_ctf', value: Array.from(campaignsToFilter).join(',') })
params.unshift({
key: 'ntv_ctf',
value: Array.from(campaignsToFilter).join(','),
})
}

// Placement Sizes
if (placementSizes.length) {
params.unshift({ key: 'ntv_pas', value: btoa(JSON.stringify(placementSizes)) })
params.unshift({
key: 'ntv_pas',
value: btoa(JSON.stringify(placementSizes)),
})
}

// Add placement IDs
Expand Down Expand Up @@ -429,6 +453,126 @@ export const spec = {
registerBidder(spec)

// Utils
export function parseFloorPriceData(bidRequest) {
if (typeof bidRequest.getFloor !== 'function') return

// Setup price floor data per bid request
let bidRequestFloorPriceData = {}
let bidMediaTypes = bidRequest.mediaTypes
let sizeOptions = new Set()
// Step through meach media type so we can get floor data for each media type per bid request
Object.keys(bidMediaTypes).forEach((mediaType) => {
// Setup price floor data per media type
let mediaTypeData = bidMediaTypes[mediaType]
let mediaTypeFloorPriceData = {}
// Step through each size of the media type so we can get floor data for each size per media type
mediaTypeData.sizes.forEach((size) => {
// Get floor price data per the getFloor method and respective media type / size combination
const priceFloorData = bidRequest.getFloor({
currency: FLOOR_PRICE_CURRENCY,
mediaType,
size,
})
// Save the data and track the sizes
mediaTypeFloorPriceData[sizeToString(size)] = priceFloorData.floor
sizeOptions.add(size)
})
bidRequestFloorPriceData[mediaType] = mediaTypeFloorPriceData

// Get floor price of current media type with a wildcard size
const sizeWildcardFloor = getSizeWildcardPrice(bidRequest, mediaType)
// Save the wildcard floor price if it was retrieved successfully
if (sizeWildcardFloor.floor > 0) {
mediaTypeFloorPriceData['*'] = sizeWildcardFloor.floor
}
})

// Get floor price for wildcard media type using all of the sizes present in the previous media types
const mediaWildCardPrices = getMediaWildcardPrices(bidRequest, [
PRICE_FLOOR_WILDCARD,
...Array.from(sizeOptions),
])
bidRequestFloorPriceData['*'] = mediaWildCardPrices

return bidRequestFloorPriceData
}

/**
* Get price floor data by always setting the size value to the wildcard for a specific size
* @param {Object} bidRequest - The bid request
* @param {String} mediaType - The media type
* @returns {Object} - Bid floor data
*/
export function getSizeWildcardPrice(bidRequest, mediaType) {
return bidRequest.getFloor({
currency: FLOOR_PRICE_CURRENCY,
mediaType,
size: PRICE_FLOOR_WILDCARD,
})
}

/**
* Get price data for a range of sizes and always setting the media type to the wildcard value
* @param {*} bidRequest - The bid request
* @param {*} sizes - The sizes to get the floor price data for
* @returns {Object} - Bid floor data
*/
export function getMediaWildcardPrices(
bidRequest,
sizes = [PRICE_FLOOR_WILDCARD]
) {
const sizePrices = {}
sizes.forEach((size) => {
// MODIFY the bid request's mediaTypes property (so we can get the wildcard media type value)
const temp = bidRequest.mediaTypes
bidRequest.mediaTypes = { PRICE_FLOOR_WILDCARD: temp.sizes }
// Get price floor data
const priceFloorData = bidRequest.getFloor({
currency: FLOOR_PRICE_CURRENCY,
mediaType: PRICE_FLOOR_WILDCARD,
size,
})
// RESTORE initial property value
bidRequest.mediaTypes = temp

// Only save valid floor price data
const key =
size !== PRICE_FLOOR_WILDCARD ? sizeToString(size) : PRICE_FLOOR_WILDCARD
sizePrices[key] = priceFloorData.floor
})
return sizePrices
}

/**
* Format size array to a string
* @param {Array} size - Size data [width, height]
* @returns {String} - Formated size string
*/
export function sizeToString(size) {
if (!Array.isArray(size) || size.length < 2) return ''
return `${size[0]}x${size[1]}`
}

/**
* Build the ad unit data to send back to the request endpoint
* @param {Array<Object>} requests - Bid requests
* @returns {Array<Object>} - Array of ad unit data
*/
function buildAdUnitData(requests) {
return requests.map((request) => {
// Track if we've already requested for this ad unit code
adUnitsRequested[request.adUnitCode] =
adUnitsRequested[request.adUnitCode] !== undefined
? adUnitsRequested[request.adUnitCode] + 1
: 0
// Return a new object with only the data we need
return {
adUnitCode: request.adUnitCode,
mediaTypes: request.mediaTypes,
}
})
}

/**
* Append QS param to existing string
* @param {String} str - String to append to
Expand Down
Loading