Skip to content

Commit

Permalink
Nativo Bid Adapter: adding UserId support (prebid#9583)
Browse files Browse the repository at this point in the history
* Initial nativoBidAdapter document creation (js, md and spec)

* Fulling working prebid using nativoBidAdapter. Support for GDPR and CCPA in user syncs.

* Added defult size settings based on the largest ad unit. Added response body validation. Added consent to request url qs params.

* Changed bidder endpoint url

* Changed double quotes to single quotes.

* Reverted package-json.lock to remove modifications from PR

* Added optional bidder param 'url' so the ad server can force- match an existing placement

* Lint fix. Added space after if.

* Added new QS param to send various adUnit data to adapter endpopint

* Updated unit test for new QS param

* Added qs param to keep track of ad unit refreshes

* Updated bidMap key default value

* Updated refresh increment logic

* Refactored spread operator for IE11 support

* Updated isBidRequestValid check

* Refactored Object.enties to use Object.keys to fix CircleCI testing errors

* Updated bid mapping key creation to prioritize ad unit code over placementId

* Added filtering by ad, advertiser and campaign.

* Merged master

* Added more robust bidDataMap with multiple key access

* Deduped filer values

* Rolled back package.json

* Duped upstream/master's package.lock file ... not sure how it got changed in the first place

* Small refactor of filterData length check. Removed comparison with 0 since a length value of 0 is already falsy.

* Added bid sizes to request

* Fixed function name in spec. Added unit tests.

* Added priceFloor module support

* Added protection agains empty url parameter

* Changed ntv_url QS param to use referrer.location instead of referrer.page

* Removed testing 'only' flag

* Added ntv_url QS param value validation

* Added userId support

* Added unit tests, refactored for bugs

* Wrapped ajax in try/catch

* Added more unit testing

* Updated eid check for duplicate values. Removed error logging as we no longer need it.
  • Loading branch information
jsfledd authored Mar 18, 2023
1 parent 402533f commit aa100bc
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 10 deletions.
115 changes: 105 additions & 10 deletions modules/nativoBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ export const spec = {
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function (validBidRequests, bidderRequest) {
const requestData = new RequestData()
requestData.addBidRequestDataSource(new UserEIDs())

// Parse values from bid requests
const placementIds = new Set()
const bidDataMap = BidDataMap()
Expand Down Expand Up @@ -166,6 +169,8 @@ export const spec = {
if (bidRequestFloorPriceData) {
floorPriceData[bidRequest.adUnitCode] = bidRequestFloorPriceData
}

requestData.processBidRequestData(bidRequest, bidderRequest)
})
bidRequestMap[bidderRequest.bidderRequestId] = bidDataMap

Expand Down Expand Up @@ -255,9 +260,12 @@ export const spec = {
params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent })
}

const qsParamStrings = [requestData.getRequestDataQueryString(), arrayToQS(params)]
const requestUrl = buildRequestUrl(BIDDER_ENDPOINT, qsParamStrings)

let serverRequest = {
method: 'GET',
url: BIDDER_ENDPOINT + arrayToQS(params),
url: requestUrl
}

return serverRequest
Expand Down Expand Up @@ -409,7 +417,7 @@ export const spec = {
* Adapter can fire a ajax or pixel call to register a timeout at thier end.
* @param {Object} timeoutData - Timeout specific data
*/
onTimeout: function (timeoutData) {},
onTimeout: function (timeoutData) { },

/**
* Will be called when a bid from the adapter won the auction.
Expand All @@ -429,7 +437,7 @@ export const spec = {
* Will be called when the adserver targeting has been set for a bid from the adapter.
* @param {Object} bidder - The bid of which the targeting has been set
*/
onSetTargeting: function (bid) {},
onSetTargeting: function (bid) { },

/**
* Maps Prebid's bidId to Nativo's placementId values per unique bidderRequestId
Expand All @@ -451,6 +459,78 @@ export const spec = {
registerBidder(spec)

// Utils
export class RequestData {
constructor() {
this.bidRequestDataSources = []
}

addBidRequestDataSource(bidRequestDataSource) {
if (!(bidRequestDataSource instanceof BidRequestDataSource)) return

this.bidRequestDataSources.push(bidRequestDataSource)
}

processBidRequestData(bidRequest, bidderRequest) {
for (let bidRequestDataSource of this.bidRequestDataSources) {
bidRequestDataSource.processBidRequestData(bidRequest, bidderRequest)
}
}

getRequestDataQueryString() {
if (this.bidRequestDataSources.length == 0) return

const queryParams = this.bidRequestDataSources.map(dataSource => dataSource.getRequestQueryString()).filter(queryString => queryString !== '')
return queryParams.join('&')
}
}

export class BidRequestDataSource {
constructor() {
this.type = 'BidRequestDataSource'
}
processBidRequestData(bidRequest, bidderRequest) { }
getRequestQueryString() { return '' }
}

export class UserEIDs extends BidRequestDataSource {
constructor() {
super()
this.type = 'UserEIDs'
this.qsParam = new QueryStringParam('ntv_pb_eid')
this.eids = []
}

processBidRequestData(bidRequest, bidderRequest) {
if (bidRequest.userIdAsEids === undefined || this.eids.length > 0) return
this.eids = bidRequest.userIdAsEids
}

getRequestQueryString() {
if (this.eids.length === 0) return ''

const encodedValueArray = encodeToBase64(this.eids)
this.qsParam.value = encodedValueArray
return this.qsParam.toString()
}
}

export class QueryStringParam {
constructor(key, value) {
this.key = key
this.value = value
}
}

QueryStringParam.prototype.toString = function () {
return `${this.key}=${this.value}`
}

export function encodeToBase64(value) {
try {
return btoa(JSON.stringify(value))
} catch (err) { }
}

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

Expand Down Expand Up @@ -589,12 +669,9 @@ function appendQSParamString(str, key, value) {
* @returns
*/
function arrayToQS(arr) {
return (
'?' +
arr.reduce((value, obj) => {
return appendQSParamString(value, obj.key, obj.value)
}, '')
)
return arr.reduce((value, obj) => {
return appendQSParamString(value, obj.key, obj.value)
}, '')
}

/**
Expand All @@ -615,6 +692,24 @@ function getLargestSize(sizes, method = area) {
})
}

/**
* Build the final request url
*/
export function buildRequestUrl(baseUrl, qsParamStringArray = []) {
if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) return baseUrl

const nonEmptyQSParamStrings = qsParamStringArray.filter(qsParamString => qsParamString.trim() !== '')

if (nonEmptyQSParamStrings.length === 0) return baseUrl

let requestUrl = `${baseUrl}?${nonEmptyQSParamStrings[0]}`
for (let i = 1; i < nonEmptyQSParamStrings.length; i++) {
requestUrl += `&${nonEmptyQSParamStrings[i]}`
}

return requestUrl
}

/**
* Calculate the area
* @param {Array} size - [width, height]
Expand Down Expand Up @@ -645,7 +740,7 @@ export function getPageUrlFromBidRequest(bidRequest) {
try {
const url = new URL(paramPageUrl)
return url.href
} catch (err) {}
} catch (err) { }
}

export function hasProtocol(url) {
Expand Down
100 changes: 100 additions & 0 deletions test/spec/modules/nativoBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {
getPageUrlFromBidRequest,
hasProtocol,
addProtocol,
BidRequestDataSource,
RequestData,
UserEIDs,
buildRequestUrl,
} from '../../../modules/nativoBidAdapter'

describe('bidDataMap', function () {
Expand Down Expand Up @@ -731,3 +735,99 @@ describe('getPageUrlFromBidRequest', () => {
expect(url).not.to.be.undefined
})
})

describe('RequestData', () => {
describe('addBidRequestDataSource', () => {
it('Adds a BidRequestDataSource', () => {
const requestData = new RequestData()
const testBidRequestDataSource = new BidRequestDataSource()

requestData.addBidRequestDataSource(testBidRequestDataSource)

expect(requestData.bidRequestDataSources.length == 1)
})

it("Doeasn't add a non BidRequestDataSource", () => {
const requestData = new RequestData()

requestData.addBidRequestDataSource({})
requestData.addBidRequestDataSource('test')
requestData.addBidRequestDataSource(1)
requestData.addBidRequestDataSource(true)

expect(requestData.bidRequestDataSources.length == 0)
})
})

describe('getRequestDataString', () => {
it("Doesn't append empty query strings", () => {
const requestData = new RequestData()
const testBidRequestDataSource = new BidRequestDataSource()

requestData.addBidRequestDataSource(testBidRequestDataSource)

let qs = requestData.getRequestDataQueryString()
expect(qs).to.be.empty

testBidRequestDataSource.getRequestQueryString = () => {
return 'ntv_test=true'
}
qs = requestData.getRequestDataQueryString()
expect(qs).to.be.equal('ntv_test=true')
})
})
})

describe('UserEIDs', () => {
const userEids = new UserEIDs()
const eids = [{ 'testId': 1111 }]

describe('processBidRequestData', () => {
it('Processes bid request without eids', () => {
userEids.processBidRequestData({})

expect(userEids.values).to.be.empty
})

it('Processed bid request with eids', () => {
userEids.processBidRequestData({ userIdAsEids: eids })

expect(userEids.values).to.not.be.empty
})
})

describe('getRequestQueryString', () => {
it('Correctly prints out QS param string', () => {
const qs = userEids.getRequestQueryString()
const value = qs.slice(11)

expect(qs).to.include('ntv_pb_eid=')
try {
expect(JSON.parse(value)).to.be.equal(eids)
} catch (err) { }
})
})
})

describe.only('buildRequestUrl', () => {
const baseUrl = 'https://www.testExchange.com'
it('Returns baseUrl if no QS strings passed', () => {
const url = buildRequestUrl(baseUrl)
expect(url).to.be.equal(baseUrl)
})

it('Returns baseUrl if empty QS strings passed', () => {
const url = buildRequestUrl(baseUrl, ['', '', ''])
expect(url).to.be.equal(baseUrl)
})

it('Returns baseUrl + QS params if QS strings passed', () => {
const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', 'ntv_foo=bar'])
expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`)
})

it('Returns baseUrl + QS params if mixed QS strings passed', () => {
const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', '', '', 'ntv_foo=bar'])
expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`)
})
})

0 comments on commit aa100bc

Please sign in to comment.