Skip to content

Commit

Permalink
NextRoll Bidder Adapter (prebid#4829)
Browse files Browse the repository at this point in the history
* First implementation of the AdRoll adapter (#1)

* Fix request and bid id (#5)

* Send Zone ID (#6)

* Add age check before fastbid eval (#7)

* Add age check before fastbid eval

* Fix linting

* Add date check (#8)

* Add date exists check

* Remove logging statement

* Fix bidRequest validation (#9)

* Fix deprecated function usage (#10)

* [SENG-2757] remove custom function from adapter (#11)

* remove loadExternalScript function

* add adroll to the adloader whitelist

* Handle nextroll id (#12)

* Handle nextroll id

* Remove double nesting in user obj

* Revert change to publisherTagAvailable

* Rename adroll -> nextroll (#14)

* Rename fastbid -> pubtag functions and variables (#15)

* Improve coverage of tests

* Add docs

* Add docs

* Improve sizes and add sellerid

* Add maintainer email

* Fix CI problem

* Fix IE tests

* Replace second instance of find

* Fix types used in the doc

Match prebid/prebid.github.io#1796

* Remove unused fields in spec

* Add ccpa support

* Remove external script usage

* Remove IP field

* Remove pubtag key

* Rename imports; Remove getUserSync function; Remove unused code; Use url.parse function

Co-authored-by: Juan Bono <juanbono94@gmail.com>
Co-authored-by: Ricardo Azpeitia Pimentel <ricardo.azpeitia@nextroll.com>
  • Loading branch information
3 people authored Feb 26, 2020
1 parent 303f0e3 commit 0ae0bff
Show file tree
Hide file tree
Showing 3 changed files with 448 additions and 0 deletions.
215 changes: 215 additions & 0 deletions modules/nextrollBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import * as utils from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER } from '../src/mediaTypes.js';
import { parse as parseUrl } from '../src/url.js';

import find from 'core-js/library/fn/array/find.js';

const BIDDER_CODE = 'nextroll';
const BIDDER_ENDPOINT = 'https://d.adroll.com/bid/prebid/';
const ADAPTER_VERSION = 4;

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 (bidRequest) {
return bidRequest !== undefined && !!bidRequest.params && !!bidRequest.bidId;
},

/**
* 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) {
let topLocation = parseUrl(utils.deepAccess(bidderRequest, 'refererInfo.referer'));
let consent = hasCCPAConsent(bidderRequest);
return validBidRequests.map((bidRequest, index) => {
return {
method: 'POST',
options: {
withCredentials: consent,
},
url: BIDDER_ENDPOINT,
data: {
id: bidRequest.bidId,
imp: {
id: bidRequest.bidId,
bidfloor: utils.getBidIdParameter('bidfloor', bidRequest.params),
banner: {
format: _getSizes(bidRequest)
},
ext: {
zone: {
id: utils.getBidIdParameter('zoneId', bidRequest.params)
},
nextroll: {
adapter_version: ADAPTER_VERSION
}
}
},

user: _getUser(validBidRequests),
site: _getSite(bidRequest, topLocation),
seller: _getSeller(bidRequest),
device: _getDevice(bidRequest),
}
}
})
},

/**
* 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) {
if (!serverResponse.body) {
return [];
} else {
let response = serverResponse.body
let bids = response.seatbid.reduce((acc, seatbid) => acc.concat(seatbid.bid), []);
return bids.map((bid) => _buildResponse(response, bid));
}
}
}

function _getUser(requests) {
let id = utils.deepAccess(requests, '0.userId.nextroll');
if (id === undefined) {
return
}

return {
ext: {
eid: [{
'source': 'nextroll',
id
}]
}
}
}

function _buildResponse(bidResponse, bid) {
const adm = utils.replaceAuctionPrice(bid.adm, bid.price);
return {
requestId: bidResponse.id,
cpm: bid.price,
width: bid.w,
height: bid.h,
creativeId: bid.crid,
dealId: bidResponse.dealId,
currency: 'USD',
netRevenue: true,
ttl: 300,
ad: adm
}
}

function _getSite(bidRequest, topLocation) {
return {
page: topLocation.href,
domain: topLocation.hostname,
publisher: {
id: utils.getBidIdParameter('publisherId', bidRequest.params)
}
}
}

function _getSeller(bidRequest) {
return {
id: utils.getBidIdParameter('sellerId', bidRequest.params)
}
}

function _getSizes(bidRequest) {
return bidRequest.sizes.filter(_isValidSize).map(size => {
return {
w: size[0],
h: size[1]
}
})
}

function _isValidSize(size) {
const isNumber = x => typeof x === 'number';
return (size.length === 2 && isNumber(size[0]) && isNumber(size[1]));
}

function _getDevice(_bidRequest) {
return {
ua: navigator.userAgent,
language: navigator['language'],
os: _getOs(navigator.userAgent.toLowerCase()),
osv: _getOsVersion(navigator.userAgent)
}
}

function _getOs(userAgent) {
const osTable = {
'android': /android/i,
'ios': /iphone|ipad/i,
'mac': /mac/i,
'linux': /linux/i,
'windows': /windows/i
};

return find(Object.keys(osTable), os => {
if (userAgent.match(osTable[os])) {
return os;
}
}) || 'etc';
}

function _getOsVersion(userAgent) {
let clientStrings = [
{ s: 'Android', r: /Android/ },
{ s: 'iOS', r: /(iPhone|iPad|iPod)/ },
{ s: 'Mac OS X', r: /Mac OS X/ },
{ s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ },
{ s: 'Linux', r: /(Linux|X11)/ },
{ s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ },
{ s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ },
{ s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ },
{ s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ },
{ s: 'Windows Vista', r: /Windows NT 6.0/ },
{ s: 'Windows Server 2003', r: /Windows NT 5.2/ },
{ s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ },
{ s: 'UNIX', r: /UNIX/ },
{ s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ }
];
let cs = find(clientStrings, cs => cs.r.test(userAgent));
return cs ? cs.s : 'unknown';
}

export function hasCCPAConsent(bidderRequest) {
if (typeof bidderRequest.uspConsent !== 'string') {
return true;
}
const usps = bidderRequest.uspConsent;
const version = usps[0];

// If we don't support the consent string, assume no-consent.
if (version !== '1' || usps.length < 3) {
return false;
}

const notice = usps[1];
const optOut = usps[2];

if (notice === 'N' || optOut === 'Y') {
return false;
}
return true;
}

registerBidder(spec);
50 changes: 50 additions & 0 deletions modules/nextrollBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Overview

```
Module Name: NextRoll Bid Adapter
Module Type: Bidder Adapter
Maintainer: prebid@nextroll.com
```

# Description

Module that connects to NextRoll's bidders.
The NextRoll bid adapter supports Banner format only.

# Test Parameters
``` javascript
var adUnits = [
{
code: 'div-1',
mediatypes: {
banner: {sizes: [[300, 250], [160, 600]]}
},
bids: [{
bidder: 'nextroll',
params: {
bidfloor: 1,
zoneId: "13144370",
publisherId: "publisherid",
sellerId: "sellerid"
}
}]
},
{
code: 'div-2',
mediatypes: {
banner: {
sizes: [[728, 90], [970, 250]]
}
},
bids: [{
bidder: 'nextroll',
params: {
bidfloor: 2.3,
zoneId: "13144370",
publisherId: "publisherid",
sellerId: "sellerid"
}
}]
}
]
```
Loading

0 comments on commit 0ae0bff

Please sign in to comment.