Skip to content

Commit b60d732

Browse files
Nativo Bid Adapter: support native and video (prebid#12134)
* 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. * Removed spec test .only. Fixed unit tests that were breaking. * Added Prebid version to nativo exchange request * Removed unused bidder methods * Added OpenRTB payload response. Changes requerst type to POST. * Removed debug log * Added/fixed tests * Handle video mediaType * Add built renderer files * Fix no-inner-declarations linting error * Handle native requests * Add examples in Nativo readme * Add 'mediaType' property to tests for compatibility with adapter code * Remove data URI from VAST XML * Fix 'no-unused-expressions' lint error * Fix lint error 'curcly' * Remove bidder name validation in 'isBidRequestValid' (#4) * Add GPP consent string to req (#5) --------- Co-authored-by: Josh <[email protected]> Co-authored-by: Joshua Fledderjohn <[email protected]>
1 parent 1205401 commit b60d732

File tree

3 files changed

+253
-58
lines changed

3 files changed

+253
-58
lines changed

modules/nativoBidAdapter.js

+79-31
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import { deepAccess, isEmpty } from '../src/utils.js'
22
import { registerBidder } from '../src/adapters/bidderFactory.js'
3-
import { BANNER } from '../src/mediaTypes.js'
3+
import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'
44
import { getGlobal } from '../src/prebidGlobal.js'
55
import { ortbConverter } from '../libraries/ortbConverter/converter.js'
66

77
const converter = ortbConverter({
88
context: {
99
// `netRevenue` and `ttl` are required properties of bid responses - provide a default for them
1010
netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false
11-
ttl: 30 // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp)
11+
ttl: 30, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp)
1212
},
1313
imp(buildImp, bidRequest, context) {
14-
const imp = buildImp(bidRequest, context);
14+
const imp = buildImp(bidRequest, context)
1515
imp.tagid = bidRequest.adUnitCode
16-
return imp;
17-
}
18-
});
16+
if (imp.ext) imp.ext.placementId = bidRequest.params.placementId
17+
18+
return imp
19+
},
20+
})
1921

2022
const BIDDER_CODE = 'nativo'
2123
const BIDDER_ENDPOINT = 'https://exchange.postrelease.com/prebid'
@@ -24,12 +26,22 @@ const GVLID = 263
2426

2527
const TIME_TO_LIVE = 360
2628

27-
const SUPPORTED_AD_TYPES = [BANNER]
29+
const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]
2830
const FLOOR_PRICE_CURRENCY = 'USD'
2931
const PRICE_FLOOR_WILDCARD = '*'
3032

3133
const localPbjsRef = getGlobal()
3234

35+
function getMediaType(accessObj) {
36+
if (deepAccess(accessObj, 'mediaTypes.video')) {
37+
return VIDEO
38+
} else if (deepAccess(accessObj, 'mediaTypes.native')) {
39+
return NATIVE
40+
} else {
41+
return BANNER
42+
}
43+
}
44+
3345
/**
3446
* Keep track of bid data by keys
3547
* @returns {Object} - Map of bid data that can be referenced by multiple keys
@@ -122,8 +134,7 @@ export const spec = {
122134
*/
123135
isBidRequestValid: function (bid) {
124136
// We don't need any specific parameters to make a bid request
125-
// If not parameters are supplied just verify it's the correct bidder code
126-
if (!bid.params) return bid.bidder === BIDDER_CODE
137+
if (!bid.params) return true
127138

128139
// Check if any supplied parameters are invalid
129140
const hasInvalidParameters = Object.keys(bid.params).some((key) => {
@@ -150,7 +161,10 @@ export const spec = {
150161
*/
151162
buildRequests: function (validBidRequests, bidderRequest) {
152163
// Get OpenRTB Data
153-
const openRTBData = converter.toORTB({bidRequests: validBidRequests, bidderRequest})
164+
const openRTBData = converter.toORTB({
165+
bidRequests: validBidRequests,
166+
bidderRequest,
167+
})
154168
const openRTBDataString = JSON.stringify(openRTBData)
155169

156170
const requestData = new RequestData()
@@ -201,7 +215,8 @@ export const spec = {
201215
let params = [
202216
// Prebid version
203217
{
204-
key: 'ntv_pbv', value: localPbjsRef.version
218+
key: 'ntv_pbv',
219+
value: localPbjsRef.version,
205220
},
206221
// Prebid request id
207222
{ key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId },
@@ -278,19 +293,31 @@ export const spec = {
278293
})
279294
}
280295

296+
// Add GPP params
297+
if (bidderRequest.gppConsent) {
298+
params.unshift({
299+
key: 'ntv_gpp_consent',
300+
value: bidderRequest.gppConsent.gppString,
301+
})
302+
}
303+
281304
// Add USP params
282305
if (bidderRequest.uspConsent) {
283306
// Put on the beginning of the qs param array
284307
params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent })
285308
}
286309

287-
const qsParamStrings = [requestData.getRequestDataQueryString(), arrayToQS(params)]
310+
const qsParamStrings = [
311+
requestData.getRequestDataQueryString(),
312+
arrayToQS(params),
313+
]
288314
const requestUrl = buildRequestUrl(BIDDER_ENDPOINT, qsParamStrings)
289315

290316
let serverRequest = {
291317
method: 'POST',
292318
url: requestUrl,
293319
data: openRTBDataString,
320+
bidderRequest: bidderRequest,
294321
}
295322

296323
return serverRequest
@@ -320,9 +347,10 @@ export const spec = {
320347

321348
// Step through and grab pertinent data
322349
let bidResponse, adUnit
323-
seatbids.forEach((seatbid) => {
350+
seatbids.forEach((seatbid, i) => {
324351
seatbid.bid.forEach((bid) => {
325352
adUnit = this.getAdUnitData(body.id, bid)
353+
326354
bidResponse = {
327355
requestId: adUnit.bidId,
328356
cpm: bid.price,
@@ -337,10 +365,18 @@ export const spec = {
337365
meta: {
338366
advertiserDomains: bid.adomain,
339367
},
368+
mediaType: getMediaType(request.bidderRequest.bids[i]),
340369
}
341370

342371
if (bid.ext) extData[bid.id] = bid.ext
343-
372+
if (bidResponse.mediaType === VIDEO) {
373+
bidResponse.vastUrl = bid.adm
374+
}
375+
if (bidResponse.mediaType === NATIVE) {
376+
bidResponse.native = {
377+
ortb: JSON.parse(bidResponse.ad),
378+
}
379+
}
344380
bidResponses.push(bidResponse)
345381
})
346382
})
@@ -414,23 +450,27 @@ export const spec = {
414450
typeof response.body === 'string'
415451
? JSON.parse(response.body)
416452
: response.body
417-
} catch (err) { return }
453+
} catch (err) {
454+
return
455+
}
418456

419457
// Make sure we have valid content
420458
if (!body || !body.seatbid || body.seatbid.length === 0) return
421459

422460
body.seatbid.forEach((seatbid) => {
423461
// Grab the syncs for each seatbid
424-
seatbid.syncUrls.forEach((sync) => {
425-
if (types[sync.type]) {
426-
if (sync.url.trim() !== '') {
427-
syncs.push({
428-
type: sync.type,
429-
url: sync.url.replace('{GDPR_params}', params),
430-
})
462+
if (seatbid.syncUrls) {
463+
seatbid.syncUrls.forEach((sync) => {
464+
if (types[sync.type]) {
465+
if (sync.url.trim() !== '') {
466+
syncs.push({
467+
type: sync.type,
468+
url: sync.url.replace('{GDPR_params}', params),
469+
})
470+
}
431471
}
432-
}
433-
})
472+
})
473+
}
434474
})
435475
})
436476

@@ -491,7 +531,9 @@ export class RequestData {
491531
getRequestDataQueryString() {
492532
if (this.bidRequestDataSources.length == 0) return
493533

494-
const queryParams = this.bidRequestDataSources.map(dataSource => dataSource.getRequestQueryString()).filter(queryString => queryString !== '')
534+
const queryParams = this.bidRequestDataSources
535+
.map((dataSource) => dataSource.getRequestQueryString())
536+
.filter((queryString) => queryString !== '')
495537
return queryParams.join('&')
496538
}
497539
}
@@ -500,8 +542,10 @@ export class BidRequestDataSource {
500542
constructor() {
501543
this.type = 'BidRequestDataSource'
502544
}
503-
processBidRequestData(bidRequest, bidderRequest) { }
504-
getRequestQueryString() { return '' }
545+
processBidRequestData(bidRequest, bidderRequest) {}
546+
getRequestQueryString() {
547+
return ''
548+
}
505549
}
506550

507551
export class UserEIDs extends BidRequestDataSource {
@@ -540,7 +584,7 @@ QueryStringParam.prototype.toString = function () {
540584
export function encodeToBase64(value) {
541585
try {
542586
return btoa(JSON.stringify(value))
543-
} catch (err) { }
587+
} catch (err) {}
544588
}
545589

546590
export function parseFloorPriceData(bidRequest) {
@@ -708,9 +752,13 @@ function getLargestSize(sizes, method = area) {
708752
* Build the final request url
709753
*/
710754
export function buildRequestUrl(baseUrl, qsParamStringArray = []) {
711-
if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) return baseUrl
755+
if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) {
756+
return baseUrl
757+
}
712758

713-
const nonEmptyQSParamStrings = qsParamStringArray.filter(qsParamString => qsParamString.trim() !== '')
759+
const nonEmptyQSParamStrings = qsParamStringArray.filter(
760+
(qsParamString) => qsParamString.trim() !== ''
761+
)
714762

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

@@ -752,7 +800,7 @@ export function getPageUrlFromBidRequest(bidRequest) {
752800
try {
753801
const url = new URL(paramPageUrl)
754802
return url.href
755-
} catch (err) { }
803+
} catch (err) {}
756804
}
757805

758806
export function hasProtocol(url) {

modules/nativoBidAdapter.md

+84-17
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,91 @@ gulp serve --modules=nativoBidAdapter
1616

1717
# Test Parameters
1818

19+
## Banner
20+
21+
```js
22+
var adUnits = [
23+
{
24+
code: 'div-gpt-ad-1460505748561-0',
25+
mediaTypes: {
26+
banner: {
27+
sizes: [
28+
[300, 250],
29+
[300, 600],
30+
],
31+
},
32+
},
33+
// Replace this object to test a new Adapter!
34+
bids: [
35+
{
36+
bidder: 'nativo',
37+
params: {
38+
url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html',
39+
},
40+
},
41+
],
42+
},
43+
]
1944
```
45+
46+
## Video
47+
48+
```js
2049
var adUnits = [
21-
{
22-
code: 'div-gpt-ad-1460505748561-0',
23-
mediaTypes: {
24-
banner: {
25-
sizes: [[300, 250], [300,600]],
26-
}
27-
},
28-
// Replace this object to test a new Adapter!
29-
bids: [{
30-
bidder: 'nativo',
31-
params: {
32-
url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html'
33-
}
34-
}]
35-
36-
}
37-
];
50+
{
51+
code: 'ntvPlaceholder-1',
52+
mediaTypes: {
53+
video: {
54+
mimes: ['video/mp4'],
55+
protocols: [2, 3, 5, 6],
56+
playbackmethod: [1, 2],
57+
skip: 1,
58+
skipafter: 5,
59+
},
60+
},
61+
video: {
62+
divId: 'player',
63+
},
64+
bids: [
65+
{
66+
bidder: 'nativo',
67+
params: {
68+
url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html',
69+
},
70+
},
71+
],
72+
},
73+
]
74+
```
75+
76+
## Native
3877

78+
```js
79+
var adUnits = [
80+
{
81+
code: '/416881364/prebid-native-test-unit',
82+
sizes: [[300, 250]],
83+
mediaTypes: {
84+
native: {
85+
title: {
86+
required: true,
87+
},
88+
image: {
89+
required: true,
90+
},
91+
sponsoredBy: {
92+
required: true,
93+
},
94+
},
95+
},
96+
bids: [
97+
{
98+
bidder: 'nativo',
99+
params: {
100+
url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html',
101+
},
102+
},
103+
],
104+
},
105+
]
39106
```

0 commit comments

Comments
 (0)