forked from prebid/Prebid.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new adapter ServerBid (by Adzerk) (prebid#1024)
* Wrote initial call logic to make request to serverbid. * Add initial bid response handling logic. * Handle requests and responses, successfully pass to add server, write initial tests. * Clean up pricing and fix tests. * Enable request credentials. * JSHint and JSCS * Incorporate code review comments. * Add function invocation to getTopWindowUrl
- Loading branch information
1 parent
5a6894f
commit 630598a
Showing
3 changed files
with
342 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ | |
"smartyads", | ||
"smartadserver", | ||
"sekindoUM", | ||
"serverbid", | ||
"sonobi", | ||
"sovrn", | ||
"springserve", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import Adapter from 'src/adapters/adapter'; | ||
import bidfactory from 'src/bidfactory'; | ||
import bidmanager from 'src/bidmanager'; | ||
import * as utils from 'src/utils'; | ||
import { ajax } from 'src/ajax'; | ||
|
||
const ServerBidAdapter = function ServerBidAdapter() { | ||
|
||
const baseAdapter = Adapter.createNew('serverbid'); | ||
|
||
const BASE_URI = '//e.serverbid.com/api/v2'; | ||
|
||
const sizeMap = [null, | ||
"120x90", | ||
"120x90", | ||
"468x60", | ||
"728x90", | ||
"300x250", | ||
"160x600", | ||
"120x600", | ||
"300x100", | ||
"180x150", | ||
"336x280", | ||
"240x400", | ||
"234x60", | ||
"88x31", | ||
"120x60", | ||
"120x240", | ||
"125x125", | ||
"220x250", | ||
"250x250", | ||
"250x90", | ||
"0x0", | ||
"200x90", | ||
"300x50", | ||
"320x50", | ||
"320x480", | ||
"185x185", | ||
"620x45", | ||
"300x125", | ||
"800x250" | ||
]; | ||
|
||
const bidIds = []; | ||
|
||
baseAdapter.callBids = function(params) { | ||
|
||
if (params && params.bids && utils.isArray(params.bids) && params.bids.length) { | ||
|
||
const data = { | ||
placements: [], | ||
time: Date.now(), | ||
user: {}, | ||
url: utils.getTopWindowUrl(), | ||
referrer: document.referrer, | ||
enableBotFiltering: true, | ||
includePricingData: true | ||
}; | ||
|
||
const bids = params.bids || []; | ||
for (let i = 0; i < bids.length; i++) { | ||
const bid = bids[i]; | ||
|
||
bidIds.push(bid.bidId); | ||
|
||
const bid_data = { | ||
networkId: bid.params.networkId, | ||
siteId: bid.params.siteId, | ||
zoneIds: bid.params.zoneIds, | ||
campaignId: bid.params.campaignId, | ||
flightId: bid.params.flightId, | ||
adId: bid.params.adId, | ||
divName: bid.bidId, | ||
adTypes: bid.adTypes || getSize(bid.sizes) | ||
}; | ||
|
||
if (bid_data.networkId && bid_data.siteId) { | ||
data.placements.push(bid_data); | ||
} | ||
|
||
} | ||
|
||
if (data.placements.length) { | ||
ajax(BASE_URI, _responseCallback, JSON.stringify(data), { method: 'POST', withCredentials: true, contentType: 'application/json' }); | ||
} | ||
|
||
} | ||
|
||
}; | ||
|
||
function _responseCallback(result) { | ||
|
||
let bid; | ||
let bidId; | ||
let bidObj; | ||
let bidCode; | ||
let placementCode; | ||
|
||
try { | ||
result = JSON.parse(result); | ||
} catch (error) { | ||
utils.logError(error); | ||
} | ||
|
||
for (let i = 0; i < bidIds.length; i++) { | ||
|
||
bidId = bidIds[i]; | ||
bidObj = utils.getBidRequest(bidId); | ||
bidCode = bidObj.bidder; | ||
placementCode = bidObj.placementCode; | ||
|
||
if (result) { | ||
const decision = result.decisions && result.decisions[bidId]; | ||
const price = decision && decision.pricing && decision.pricing.clearPrice; | ||
|
||
if (decision && price) { | ||
bid = bidfactory.createBid(1, bidObj); | ||
bid.bidderCode = bidCode; | ||
bid.cpm = price; | ||
bid.width = decision.width; | ||
bid.height = decision.height; | ||
bid.ad = retrieveAd(decision); | ||
} else { | ||
bid = bidfactory.createBid(2, bidObj); | ||
bid.bidderCode = bidCode; | ||
} | ||
|
||
} else { | ||
bid = bidfactory.createBid(2, bidObj); | ||
bid.bidderCode = bidCode; | ||
} | ||
bidmanager.addBidResponse(placementCode, bid); | ||
} | ||
} | ||
|
||
function retrieveAd(decision) { | ||
return decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); | ||
} | ||
|
||
function getSize(sizes) { | ||
const result = []; | ||
sizes.forEach(function(size) { | ||
const index = sizeMap.indexOf(size[0] + "x" + size[1]); | ||
if (index >= 0) { | ||
result.push(index); | ||
} | ||
}); | ||
return result; | ||
} | ||
|
||
// Export the `callBids` function, so that Prebid.js can execute | ||
// this function when the page asks to send out bid requests. | ||
return { | ||
callBids: baseAdapter.callBids | ||
}; | ||
|
||
}; | ||
|
||
ServerBidAdapter.createNew = function() { | ||
return new ServerBidAdapter(); | ||
}; | ||
|
||
module.exports = ServerBidAdapter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
/* jshint -W024 */ | ||
/* jshint expr:true */ | ||
|
||
import { expect } from 'chai'; | ||
import Adapter from 'src/adapters/serverbid'; | ||
import bidmanager from 'src/bidmanager'; | ||
import * as utils from 'src/utils'; | ||
|
||
const ENDPOINT = '//e.serverbid.com/api/v2'; | ||
|
||
const REQUEST = { | ||
"bidderCode": "serverbid", | ||
"requestId": "a4713c32-3762-4798-b342-4ab810ca770d", | ||
"bidderRequestId": "109f2a181342a9", | ||
"bids": [{ | ||
"bidder": "serverbid", | ||
"params": { | ||
"networkId": 9969, | ||
"siteId": 730181 | ||
}, | ||
"placementCode": "div-gpt-ad-1487778092495-0", | ||
"sizes": [ | ||
[728, 90], | ||
[970, 90] | ||
], | ||
"bidId": "2b0f82502298c9", | ||
"bidderRequestId": "109f2a181342a9", | ||
"requestId": "a4713c32-3762-4798-b342-4ab810ca770d" | ||
}], | ||
"start": 1487883186070, | ||
"auctionStart": 1487883186069, | ||
"timeout": 3000 | ||
}; | ||
|
||
const RESPONSE = { | ||
"user": { "key": "ue1-2d33e91b71e74929b4aeecc23f4376f1" }, | ||
"decisions": { | ||
"2b0f82502298c9": { | ||
"adId": 2364764, | ||
"creativeId": 1950991, | ||
"flightId": 2788300, | ||
"campaignId": 542982, | ||
"clickUrl": "http://e.serverbid.com/r", | ||
"impressionUrl": "http://e.serverbid.com/i.gif", | ||
"contents": [{ | ||
"type": "html", | ||
"body": "<html></html>", | ||
"data": { | ||
"height": 90, | ||
"width": 728, | ||
"imageUrl": "http://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif", | ||
"fileName": "b0ab77db8a7848c8b78931aed022a5ef.gif" | ||
}, | ||
"template": "image" | ||
}], | ||
"height": 90, | ||
"width": 728, | ||
"events": [], | ||
"pricing":{"price":0.5,"clearPrice":0.5,"revenue":0.0005,"rateType":2,"eCPM":0.5} | ||
}, | ||
} | ||
}; | ||
|
||
describe('serverbidAdapter', () => { | ||
|
||
let adapter; | ||
|
||
beforeEach(() => adapter = Adapter.createNew()); | ||
|
||
describe('request function', () => { | ||
|
||
let xhr; | ||
let requests; | ||
let pbConfig; | ||
|
||
|
||
beforeEach(() => { | ||
xhr = sinon.useFakeXMLHttpRequest(); | ||
requests = []; | ||
xhr.onCreate = request => requests.push(request); | ||
pbConfig = REQUEST; | ||
//just a single slot | ||
pbConfig.bids = [pbConfig.bids[0]]; | ||
}); | ||
|
||
afterEach(() => xhr.restore()); | ||
|
||
it('exists and is a function', () => { | ||
expect(adapter.callBids).to.exist.and.to.be.a('function'); | ||
}); | ||
|
||
it('requires paramaters to make request', () => { | ||
adapter.callBids({}); | ||
expect(requests).to.be.empty; | ||
}); | ||
|
||
it('requires networkId and siteId', () => { | ||
let backup = pbConfig.bids[0].params; | ||
pbConfig.bids[0].params = { networkId: 1234 }; //no hbid | ||
adapter.callBids(pbConfig); | ||
expect(requests).to.be.empty; | ||
|
||
pbConfig.bids[0].params = { siteId: 1234 }; //no placementid | ||
adapter.callBids(pbConfig); | ||
expect(requests).to.be.empty; | ||
|
||
pbConfig.bids[0].params = backup; | ||
}); | ||
|
||
it('sends bid request to ENDPOINT via POST', () => { | ||
adapter.callBids(pbConfig); | ||
expect(requests[0].url).to.equal(ENDPOINT); | ||
expect(requests[0].method).to.equal('POST'); | ||
}); | ||
}); | ||
|
||
describe('response handler', () => { | ||
|
||
let server; | ||
|
||
beforeEach(() => { | ||
server = sinon.fakeServer.create(); | ||
sinon.stub(bidmanager, 'addBidResponse'); | ||
sinon.stub(utils, "getBidRequest").returns(REQUEST); | ||
}); | ||
|
||
afterEach(() => { | ||
server.restore(); | ||
bidmanager.addBidResponse.restore(); | ||
utils.getBidRequest.restore(); | ||
}); | ||
|
||
it('registers bids', () => { | ||
server.respondWith(JSON.stringify(RESPONSE)); | ||
|
||
adapter.callBids(REQUEST); | ||
server.respond(); | ||
sinon.assert.calledOnce(bidmanager.addBidResponse); | ||
|
||
const response = bidmanager.addBidResponse.firstCall.args[1]; | ||
expect(response).to.have.property('statusMessage', 'Bid available'); | ||
expect(response).to.have.property('cpm'); | ||
expect(response.cpm).to.be.above(0); | ||
}); | ||
|
||
it('handles nobid responses', () => { | ||
server.respondWith(JSON.stringify({ | ||
"decisions": [] | ||
})); | ||
|
||
adapter.callBids(REQUEST); | ||
server.respond(); | ||
sinon.assert.calledOnce(bidmanager.addBidResponse); | ||
|
||
const response = bidmanager.addBidResponse.firstCall.args[1]; | ||
expect(response).to.have.property( | ||
'statusMessage', | ||
'Bid returned empty or error response' | ||
); | ||
}); | ||
|
||
it('handles JSON.parse errors', () => { | ||
server.respondWith(''); | ||
|
||
adapter.callBids(REQUEST); | ||
server.respond(); | ||
sinon.assert.calledOnce(bidmanager.addBidResponse); | ||
|
||
const response = bidmanager.addBidResponse.firstCall.args[1]; | ||
expect(response).to.have.property( | ||
'statusMessage', | ||
'Bid returned empty or error response' | ||
); | ||
}); | ||
|
||
}); | ||
|
||
}); |