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

Sovrn 1.0 compliance #1796

Merged
merged 24 commits into from
Nov 14, 2017
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1a04f1f
Update Sovrn adapter. Add test coverage. Enable deal IDs.
Jul 26, 2017
bf9478b
Merge remote-tracking branch 'upstream/master'
Aug 7, 2017
75337df
HS-271: Avoid using private variables such as _bidsRequested and _bid…
Aug 8, 2017
62d965a
Merge remote-tracking branch 'upstream/master'
Aug 8, 2017
60b2be6
lint
Aug 8, 2017
97b89cc
Add bidfloor param to test.
Aug 8, 2017
f69f45b
Merge remote-tracking branch 'upstream/master'
Oct 10, 2017
60f4d1e
Merge branch 'master' of https://github.com/prebid/Prebid.js
Oct 11, 2017
0338ce7
changed post content-type in bidder factory to 'application/json', as…
Oct 11, 2017
a02635b
Revert "changed post content-type in bidder factory to 'application/j…
Oct 11, 2017
5af81a8
Changed method for altering contentType so that it is configurable vi…
Oct 11, 2017
9bd1397
Altered PR to conform to change reviews. added unit tests.
Oct 12, 2017
07b4dc6
Added comment to pass Trion adapter test.
Oct 12, 2017
dbfae1e
Removed false-y check for request.options. Added request.options conf…
Oct 13, 2017
1c4016f
small optimization to request.options to remove extra object declarat…
Oct 13, 2017
a497aa8
Re-wrote the Sovrn bid adapter to be compliant with Prebid 1.0.0.
tedrand11 Oct 20, 2017
a5689a1
Pushed bugfix found during whatismyip beta test, and small refactor.
tedrand11 Nov 1, 2017
d261720
Added README for adapter with test ad units.
tedrand11 Nov 6, 2017
17ada25
Merge branch 'master' of https://github.com/prebid/Prebid.js into sov…
tedrand11 Nov 9, 2017
72a6cf2
Adjusted Sovrn bid adapter to correspond to new JSON structure passed…
tedrand11 Nov 9, 2017
9680417
removed unneeded biddercode param in adapter and fixed JSON spacing o…
tedrand11 Nov 9, 2017
5638b98
Final updates to remove bidder code from expected response in unit te…
tedrand11 Nov 9, 2017
bb55cea
Reversed changes made to package.json and package-lock.json so that t…
tedrand11 Nov 13, 2017
fb34ecb
Removed package-lock.json file.
tedrand11 Nov 14, 2017
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
217 changes: 72 additions & 145 deletions modules/sovrnBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,156 +1,83 @@
var CONSTANTS = require('src/constants.json');
var utils = require('src/utils.js');
var bidfactory = require('src/bidfactory.js');
var bidmanager = require('src/bidmanager.js');
var adloader = require('src/adloader');
var adaptermanager = require('src/adaptermanager');

/**
* Adapter for requesting bids from Sovrn
*/
var SovrnAdapter = function SovrnAdapter() {
var sovrnUrl = 'ap.lijit.com/rtb/bid';

function _callBids(params) {
var sovrnBids = params.bids || [];

_requestBids(sovrnBids);
}

function _requestBids(bidReqs) {
// build bid request object
var domain = window.location.host;
var page = window.location.pathname + location.search + location.hash;

var sovrnImps = [];

// build impression array for sovrn
import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
import { BANNER } from 'src/mediaTypes';
import { REPO_AND_VERSION } from 'src/constants';

export const spec = {
code: 'sovrn',
supportedMediaTypes: [BANNER],

/**
* Check if the bid is a valid zone ID in either number or string form
* @param {object} bid the Sovrn bid to validate
* @return boolean for whether or not a bid is valid
*/
isBidRequestValid: function(bid) {
return !!(bid.params.tagid && !isNaN(parseFloat(bid.params.tagid)) && isFinite(bid.params.tagid));
},

/**
* Format the bid request object for our endpoint
* @param {BidRequest[]} bidRequests Array of Sovrn bidders
* @return object of parameters for Prebid AJAX request
*/
buildRequests: function(bidReqs) {
let sovrnImps = [];
utils._each(bidReqs, function (bid) {
var tagId = utils.getBidIdParameter('tagid', bid.params);
var bidFloor = utils.getBidIdParameter('bidfloor', bid.params);
var adW = 0;
var adH = 0;

// sovrn supports only one size per tagid, so we just take the first size if there are more
// if we are a 2 item array of 2 numbers, we must be a SingleSize array
var bidSizes = Array.isArray(bid.params.sizes) ? bid.params.sizes : bid.sizes;
var sizeArrayLength = bidSizes.length;
if (sizeArrayLength === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') {
adW = bidSizes[0];
adH = bidSizes[1];
} else {
adW = bidSizes[0][0];
adH = bidSizes[0][1];
}

var imp =
{
id: bid.bidId,
banner: {
w: adW,
h: adH
},
tagid: tagId,
bidfloor: bidFloor
};
sovrnImps.push(imp);
sovrnImps.push({
id: bid.bidId,
banner: { w: 1, h: 1 },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tedrand Is this hardcoded as 1x1 because Sovrn knows the size based on the ID? It seems like the bid.sizes and bid.params.sizes properties will be totally ignored by the adapter. Is that correct?

@mkendall07 If that’s the case, then https://github.com/prebid/Prebid.js/blob/master/modules/sovrnBidAdapter.md should be updated to omit the sizes fields, right?

tagid: utils.getBidIdParameter('tagid', bid.params),
bidfloor: utils.getBidIdParameter('bidfloor', bid.params)
});
});

// build bid request with impressions
var sovrnBidReq = {
const sovrnBidReq = {
id: utils.getUniqueIdentifierStr(),
imp: sovrnImps,
site: {
domain: domain,
page: page
domain: window.location.host,
page: window.location.pathname + location.search + location.hash
}
};

var scriptUrl = '//' + sovrnUrl + '?callback=window.$$PREBID_GLOBAL$$.sovrnResponse' +
'&src=' + CONSTANTS.REPO_AND_VERSION +
'&br=' + encodeURIComponent(JSON.stringify(sovrnBidReq));
adloader.loadScript(scriptUrl);
}

function addBlankBidResponses(impidsWithBidBack) {
var missing = utils.getBidderRequestAllAdUnits('sovrn');
if (missing) {
missing = missing.bids.filter(bid => impidsWithBidBack.indexOf(bid.bidId) < 0);
} else {
missing = [];
}

missing.forEach(function (bidRequest) {
// Add a no-bid response for this bid request.
var bid = {};
bid = bidfactory.createBid(2, bidRequest);
bid.bidderCode = 'sovrn';
bidmanager.addBidResponse(bidRequest.placementCode, bid);
});
}

// expose the callback to the global object:
$$PREBID_GLOBAL$$.sovrnResponse = function (sovrnResponseObj) {
var impidsWithBidBack = [];

// valid response object from sovrn
if (sovrnResponseObj && sovrnResponseObj.id && sovrnResponseObj.seatbid && sovrnResponseObj.seatbid.length !== 0 &&
sovrnResponseObj.seatbid[0].bid && sovrnResponseObj.seatbid[0].bid.length !== 0) {
sovrnResponseObj.seatbid[0].bid.forEach(function (sovrnBid) {
var responseCPM;
var placementCode = '';
var id = sovrnBid.impid;
var bid = {};

var bidObj = utils.getBidRequest(id);

if (bidObj) {
placementCode = bidObj.placementCode;
bidObj.status = CONSTANTS.STATUS.GOOD;

responseCPM = parseFloat(sovrnBid.price);

if (responseCPM !== 0) {
sovrnBid.placementCode = placementCode;
sovrnBid.size = bidObj.sizes;
var responseAd = sovrnBid.adm;

// build impression url from response
var responseNurl = '<img src="' + sovrnBid.nurl + '">';

// store bid response
// bid status is good (indicating 1)
bid = bidfactory.createBid(1, bidObj);
bid.creative_id = sovrnBid.id;
bid.bidderCode = 'sovrn';
bid.cpm = responseCPM;

// set ad content + impression url
// sovrn returns <script> block, so use bid.ad, not bid.adurl
bid.ad = decodeURIComponent(responseAd + responseNurl);

// Set width and height from response now
bid.width = parseInt(sovrnBid.w);
bid.height = parseInt(sovrnBid.h);

if (sovrnBid.dealid) {
bid.dealId = sovrnBid.dealid;
}

bidmanager.addBidResponse(placementCode, bid);
impidsWithBidBack.push(id);
}
}
return {
method: 'POST',
url: `//ap.lijit.com/rtb/bid?src=${REPO_AND_VERSION}`,
data: JSON.stringify(sovrnBidReq),
options: {contentType: 'text/plain'}
};
},

/**
* Format Sovrn responses as Prebid bid responses
* @param {id, seatbid} sovrnResponse A successful response from Sovrn.
* @return {Bid[]} An array of formatted bids.
*/
interpretResponse: function({id, seatbid}) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1748 changed the first argument of interpretResponse to:

{
  body: responseBody,
  headers: {
    get: function(header) { /* returns a header from the HTTP response */ }
  }
}

so destructuring like

{body: {id, seatbid}}

or however you'd prefer to grab the body, and updating corresponding tests should get this back to working properly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @matthewlane for the info here. Just so I am on the same page, destructuring as you have suggested will lead to the same functionality as what we currently have implemented? I am just making sure that we do not have to change the bid response's JSON structure on our end.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, updating in this way will give the same functionality as before #1748 was merged to master, and there are no changes required to the JSON structure sent from your endpoint.

For a (non-destructuring) example, appnexusAst was updated here to pick the response data off the body property. Before #1748 we used the first parameter directly. This update lets us keep working properly and no changes were made to our endpoint because behind the scenes it's the same data coming through, just on the body property of the first parameter rather than the first parameter itself now.

Let me know if you have more questions, and I'll verify the change works whenever the PR is updated

let sovrnBidResponses = [];
if (id &&
seatbid &&
seatbid.length > 0 &&
seatbid[0].bid &&
seatbid[0].bid.length > 0) {
seatbid[0].bid.map(sovrnBid => {
sovrnBidResponses.push({
requestId: sovrnBid.impid,
bidderCode: spec.code,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bidderCode will be set automatically by bidderFactory now, this line can be dropped

cpm: parseFloat(sovrnBid.price),
width: parseInt(sovrnBid.w),
height: parseInt(sovrnBid.h),
creativeId: sovrnBid.id,
dealId: sovrnBid.dealId || null,
currency: 'USD',
netRevenue: true,
mediaType: BANNER,
ad: decodeURIComponent(`${sovrnBid.adm}<img src="${sovrnBid.nurl}">`),
ttl: 60000
});
});
}
addBlankBidResponses(impidsWithBidBack);
};

return {
callBids: _callBids
};
return sovrnBidResponses;
}
};

adaptermanager.registerBidAdapter(new SovrnAdapter(), 'sovrn');

module.exports = SovrnAdapter;
registerBidder(spec);
45 changes: 45 additions & 0 deletions modules/sovrnBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Overview

```
Module Name: Sovrn Bid Adapter
Module Type: Bidder Adapter
Maintainer: trand@sovrn.com
```

# Description

Sovrn's adapter integration to the Prebid library. Posts plain-text JSON to the /rtb/bid endpoint.

# Test Parameters

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line and line 46 should mark the code with ```

var adUnits = [
{
code: 'test-leaderboard',
sizes: [[728, 90]],
bids: [{
bidder: 'sovrn',
params: {
tagid: '403370',
bidfloor: 0.01
}
}]
}, {
code: 'test-banner',
sizes: [[300, 250]],
bids: [{
bidder: 'sovrn',
params: {
tagid: '403401'
}
}]
}, {
code: 'test-sidebar',
size: [[160, 600]],
bids: [{
bidder: 'sovrn',
params: {
tagid: '531000'
}
}]
}
]
9 changes: 5 additions & 4 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import { logWarn, logError, parseQueryStringParameters, delayExecution } from 's
* @property {('GET'|'POST')} method The type of request which this is.
* @property {string} url The endpoint for the request. For example, "//bids.example.com".
* @property {string|object} data Data to be sent in the request.
* @property {object} options Content-Type set in the header of the bid request, overrides default 'text/plain'.
Copy link
Collaborator

@matthewlane matthewlane Nov 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposed changes to core modules are welcome but should be made in a separate PR. Also calls from 1.0 bidders will have contentType: 'text/plain' set by default, so needing to set options: {contentType: 'text/plain'} on the object returned by your buildRequests function is unnecessary I believe?

Edit: oh this is already in master via #1681? GitHub shows these files as changed for some reason

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah sorry I couldn't figure out why that was happening either.

* If this is a GET request, they'll become query params. If it's a POST request, they'll be added to the body.
* Strings will be added as-is. Objects will be unpacked into query params based on key/value mappings, or
* JSON-serialized into the Request body.
Expand Down Expand Up @@ -233,10 +234,10 @@ export function newBidder(spec) {
error: onFailure
},
undefined,
{
Object.assign({
method: 'GET',
withCredentials: true
}
}, request.options)
);
break;
case 'POST':
Expand All @@ -247,11 +248,11 @@ export function newBidder(spec) {
error: onFailure
},
typeof request.data === 'string' ? request.data : JSON.stringify(request.data),
{
Object.assign({
method: 'POST',
contentType: 'text/plain',
withCredentials: true
}
}, request.options)
);
break;
default:
Expand Down
Loading