This repository has been archived by the owner on Mar 30, 2018. It is now read-only.
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.
Merge pull request #1 from ad4game/feature/INC-9
Add A4G prebid adapter
- Loading branch information
Showing
3 changed files
with
283 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 |
---|---|---|
@@ -1,4 +1,5 @@ | ||
[ | ||
"a4g", | ||
"aardvark", | ||
"adblade", | ||
"adbund", | ||
|
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,170 @@ | ||
const bidfactory = require('../bidfactory.js'), | ||
bidmanager = require('../bidmanager.js'), | ||
constants = require('../constants.json'), | ||
adloader = require('../adloader'), | ||
utils = require('../utils.js'); | ||
|
||
const A4G_BIDDER_CODE = 'a4g'; | ||
const A4G_DEFAULT_BID_URL = '//ads.ad4game.com/v1/bid'; | ||
|
||
const IFRAME_NESTING_PARAM_NAME = 'if'; | ||
const LOCATION_PARAM_NAME = 'siteurl'; | ||
const ID_PARAM_NAME = 'id'; | ||
const ZONE_ID_PARAM_NAME = 'zoneId'; | ||
const SIZE_PARAM_NAME = 'size'; | ||
|
||
const A4G_SUPPORTED_PARAMS = [ | ||
IFRAME_NESTING_PARAM_NAME, | ||
LOCATION_PARAM_NAME, | ||
ID_PARAM_NAME, | ||
ZONE_ID_PARAM_NAME, | ||
SIZE_PARAM_NAME | ||
]; | ||
|
||
const ARRAY_PARAM_SEPARATOR = ';'; | ||
const ARRAY_SIZE_SEPARATOR = ','; | ||
const SIZE_SEPARATOR = 'x'; | ||
|
||
const JSONP_PARAM_NAME = 'jsonp'; | ||
|
||
function appendUrlParam(url, paramName, paramValue) { | ||
const isQueryParams = url.indexOf('?') !== -1, | ||
separator = isQueryParams ? '&' : '?'; | ||
return url + separator + encodeURIComponent(paramName) + '=' + encodeURIComponent(paramValue); | ||
} | ||
|
||
function isInIframe(windowObj) { | ||
return windowObj !== windowObj.parent; | ||
} | ||
|
||
function getIframeInfo(window) { | ||
let currentWindow = window, | ||
iframeNestingLevel = 0, | ||
hasExternalMeet = false, | ||
hostHref = window.location.href; | ||
|
||
while (isInIframe(currentWindow)) { | ||
currentWindow = currentWindow.parent; | ||
|
||
try { | ||
if (hasExternalMeet) { | ||
iframeNestingLevel = 1; | ||
} else { | ||
iframeNestingLevel++; | ||
} | ||
|
||
hostHref = currentWindow.document.referrer || currentWindow.location.href; | ||
} catch (e) { | ||
hasExternalMeet = true; | ||
} | ||
} | ||
|
||
return { | ||
nestingLevel: iframeNestingLevel, | ||
hostHref: hostHref | ||
}; | ||
} | ||
|
||
function isValidStatus(status) { | ||
return status === 200; | ||
} | ||
|
||
function bidParamsToQuery(params) { | ||
return A4G_SUPPORTED_PARAMS | ||
.reduce((url, paramName) => paramName in params | ||
? appendUrlParam(url, paramName, params[paramName]) | ||
: url, | ||
''); | ||
} | ||
|
||
function buildBidRequestUrl(bidDeliveryUrl, params) { | ||
return bidDeliveryUrl + bidParamsToQuery(params); | ||
} | ||
|
||
function createBidRequest(bidRequest) { | ||
return (status) => bidfactory.createBid(status, bidRequest); | ||
} | ||
|
||
function mapBidToPrebidFormat(bidRequest, bid) { | ||
const bidResponse = bidRequest(constants.STATUS.GOOD); | ||
|
||
bidResponse.bidderCode = A4G_BIDDER_CODE; | ||
bidResponse.cpm = bid.cpm; | ||
bidResponse.ad = bid.ad; | ||
bidResponse.width = bid.width; | ||
bidResponse.height = bid.height; | ||
|
||
return bidResponse; | ||
} | ||
|
||
function mapBidErrorToPrebid(bidRequest) { | ||
return bidRequest(constants.STATUS.NO_BID); | ||
} | ||
|
||
function extractBidParams(bids) { | ||
const idParams = []; | ||
const sizeParams = []; | ||
const zoneIds = []; | ||
|
||
let deliveryUrl = ''; | ||
|
||
for (let i = 0; i < bids.length; i++) { | ||
const bid = bids[i]; | ||
if (!deliveryUrl && typeof bid.params.deliveryUrl === 'string') { | ||
deliveryUrl = bid.params.deliveryUrl; | ||
} | ||
idParams.push(bid.placementCode); | ||
sizeParams.push(bid.sizes.map(size => size.join(SIZE_SEPARATOR)).join(ARRAY_SIZE_SEPARATOR)); | ||
zoneIds.push(bid.params.zoneId); | ||
} | ||
|
||
return [deliveryUrl, { | ||
[ID_PARAM_NAME]: idParams.join(ARRAY_PARAM_SEPARATOR), | ||
[ZONE_ID_PARAM_NAME]: zoneIds.join(ARRAY_PARAM_SEPARATOR), | ||
[SIZE_PARAM_NAME]: sizeParams.join(ARRAY_PARAM_SEPARATOR) | ||
}]; | ||
} | ||
|
||
function a4gBidFactory() { | ||
|
||
function generateJsonpCallbackName() { | ||
return '__A4G' + Date.now(); | ||
} | ||
|
||
function jsonp(url, callback) { | ||
const callbackName = generateJsonpCallbackName(), | ||
jsnopUrl = appendUrlParam(url, JSONP_PARAM_NAME, callbackName); | ||
|
||
window[callbackName] = ({ status, response }) => { | ||
!isValidStatus(status) | ||
? callback(new Error(`Failed fetching ad with status ${status}`), response) | ||
: callback(null, response); | ||
delete window[callbackName]; | ||
}; | ||
|
||
adloader.loadScript(jsnopUrl); | ||
} | ||
|
||
return { | ||
callBids({ bids }) { | ||
const bidRequests = bids.map(bid => createBidRequest(utils.getBidRequest(bid.bidId))); | ||
const [ deliveryUrl, bidParams ] = extractBidParams(bids); | ||
const { nestingLevel, hostHref } = getIframeInfo(window); | ||
const envParams = { [IFRAME_NESTING_PARAM_NAME]: nestingLevel, [LOCATION_PARAM_NAME]: hostHref }; | ||
const bidsRequestUrl = buildBidRequestUrl(deliveryUrl || A4G_DEFAULT_BID_URL, Object.assign({}, bidParams, envParams)); | ||
|
||
jsonp(bidsRequestUrl, (error, bidsResponse) => { | ||
for (let i = 0; i < bidRequests.length; i++) { | ||
const bidRequest = bidRequests[i], | ||
placementCode = bids[i].placementCode; | ||
|
||
bidmanager.addBidResponse(placementCode, | ||
error | ||
? mapBidErrorToPrebid(bidRequest) | ||
: mapBidToPrebidFormat(bidRequest, bidsResponse[i])); | ||
}}); | ||
} | ||
}; | ||
} | ||
|
||
module.exports = a4gBidFactory; |
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,112 @@ | ||
describe('a4g adapter tests', function () { | ||
const expect = require('chai').expect; | ||
const a4gBidFactory = require('src/adapters/a4g'); | ||
const bidmanager = require('src/bidmanager'); | ||
const adloader = require('src/adloader'); | ||
const constants = require('src/constants.json'); | ||
|
||
function readJsonpCallbackName(url) { | ||
return /&jsonp=([_a-zA-Z0-9]+)/.exec(url)[1]; | ||
} | ||
|
||
let spyLoadScript, | ||
spyAddBidResponse, | ||
a4gAdapter; | ||
|
||
before(() => { | ||
spyLoadScript = sinon.spy(adloader, 'loadScript'); | ||
spyAddBidResponse = sinon.spy(bidmanager, 'addBidResponse'); | ||
}); | ||
|
||
after(() => { | ||
adloader.loadScript.restore(); | ||
bidmanager.addBidResponse.restore(); | ||
}); | ||
|
||
beforeEach(() => { | ||
a4gAdapter = a4gBidFactory(); | ||
}); | ||
|
||
it('should send proper jsonp request to default deliveryUrl', () => { | ||
a4gAdapter.callBids({ bids: [{ | ||
placementCode: 'pc1', | ||
sizes: [[1, 2], [3, 4]], | ||
params: { | ||
zoneId: 1 | ||
} | ||
}, { | ||
placementCode: 'pc2', | ||
sizes: [[5, 6]], | ||
params: { | ||
zoneId: 2 | ||
} | ||
}]}); | ||
|
||
let targetUrl = spyLoadScript.lastCall.args[0]; | ||
expect(targetUrl).to.contain('ads.ad4game.com/v1/bid'); | ||
expect(targetUrl).to.contain('jsonp='); | ||
expect(targetUrl).to.contain('id=pc1%3Bpc2'); | ||
expect(targetUrl).to.contain('size=1x2%2C3x4%3B5x6'); | ||
expect(targetUrl).to.contain('zoneId=1%3B2'); | ||
}); | ||
|
||
it('should send proper jsonp request to deliveryUrl from 1st bid', () => { | ||
a4gAdapter.callBids({ bids: [{ | ||
placementCode: 'pc1', | ||
sizes: [[1, 2], [3, 4]], | ||
params: { | ||
zoneId: 1, | ||
deliveryUrl: 'new.test.delivery.com:8080/v105/new_bid' | ||
} | ||
}, { | ||
placementCode: 'pc2', | ||
sizes: [[5, 6]], | ||
params: { | ||
zoneId: 2, | ||
deliveryUrl: 'nonused.test.delivery.com:8080/v105/new_bid' | ||
} | ||
}]}); | ||
|
||
let targetUrl = spyLoadScript.lastCall.args[0]; | ||
expect(targetUrl).to.contain('new.test.delivery.com:8080/v105/new_bid'); | ||
}); | ||
|
||
describe('on jsonp callback', () => { | ||
let jsonpCallbackName; | ||
|
||
beforeEach(() => { | ||
a4gAdapter.callBids({ bids: [{ | ||
placementCode: 'pc1', | ||
sizes: [[1, 2], [3, 4]], | ||
params: { | ||
zoneId: 1 | ||
} | ||
}]}); | ||
jsonpCallbackName = readJsonpCallbackName(spyLoadScript.lastCall.args[0]); | ||
}); | ||
|
||
it('should unregister', () => { | ||
window[jsonpCallbackName]({status: 200, response: [{id: 'pc1', width: 1, height: 2, cpm: 1.0, ad: '' }]}); | ||
expect(window[jsonpCallbackName]).to.not.be.a('function'); | ||
}); | ||
|
||
it('should set all responses as bad if error received', () => { | ||
window[jsonpCallbackName]({status: 400, response: []}); | ||
let [placementCode, bid] = spyAddBidResponse.lastCall.args; | ||
expect(placementCode).to.equal('pc1'); | ||
expect(bid.getStatusCode()).to.equal(constants.STATUS.NO_BID); | ||
}); | ||
|
||
it('should set all responses as good with appropriate values if ok', () => { | ||
window[jsonpCallbackName]({status: 200, response: [{id: 'pc1', width: 1, height: 2, cpm: 1.0, ad: 'test' }]}); | ||
let [placementCode, bid] = spyAddBidResponse.lastCall.args; | ||
expect(placementCode).to.equal('pc1'); | ||
|
||
expect(bid.getStatusCode()).to.equal(constants.STATUS.GOOD); | ||
expect(bid.cpm).to.equal(1); | ||
expect(bid.ad).to.equal('test'); | ||
expect(bid.width).to.equal(1); | ||
expect(bid.height).to.equal(2); | ||
}); | ||
}); | ||
}); |