diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 476cb5989e0a..9073a17bda39 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -1,139 +1,155 @@ -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import { STATUS } from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; -import { ajax } from 'src/ajax'; import * as url from 'src/url'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {NATIVE, VIDEO} from 'src/mediaTypes'; /** - * Adapter for requesting bids from Adxcg - * updated from latest prebid repo on 2017.08.30 + * Adapter for requesting bids from adxcg.net + * updated to latest prebid repo on 2017.10.20 */ -function AdxcgAdapter() { - let bidRequests = {}; - - function _callBids(params) { - if (params.bids && params.bids.length > 0) { - let adZoneIds = []; - let prebidBidIds = []; - let sizes = []; - - params.bids.forEach(bid => { - bidRequests[bid.bidId] = bid; - adZoneIds.push(utils.getBidIdParameter('adzoneid', bid.params)); - prebidBidIds.push(bid.bidId); - sizes.push(utils.parseSizesInput(bid.sizes).join('|')); - }); - - let location = utils.getTopWindowLocation(); - let secure = location.protocol == 'https:'; - - let requestUrl = url.parse(location.href); - requestUrl.search = null; - requestUrl.hash = null; - - let adxcgRequestUrl = url.format({ - protocol: secure ? 'https' : 'http', - hostname: secure ? 'ad-emea-secure.adxcg.net' : 'ad-emea.adxcg.net', - pathname: '/get/adi', - search: { - renderformat: 'javascript', - ver: 'r20141124', - adzoneid: adZoneIds.join(','), - format: sizes.join(','), - prebidBidIds: prebidBidIds.join(','), - url: escape(url.format(requestUrl)), - secure: secure ? '1' : '0' - } - }); - utils.logMessage(`submitting request: ${adxcgRequestUrl}`); - ajax(adxcgRequestUrl, handleResponse, null, { - withCredentials: true - }); - } - } - - function handleResponse(response) { - let adxcgBidReponseList; - - try { - adxcgBidReponseList = JSON.parse(response); - utils.logMessage(`adxcgBidReponseList: ${JSON.stringify(adxcgBidReponseList)}`); - } catch (error) { - adxcgBidReponseList = []; - utils.logError(error); - } +const BIDDER_CODE = 'adxcg'; +const SUPPORTED_AD_TYPES = [VIDEO, NATIVE]; +const SOURCE = 'pbjs10'; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.adzoneid); + }, + + /** + * Make a server request from the list of BidRequests. + * + * an array of validBidRequests + * Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + utils.logMessage(`buildRequests: ${JSON.stringify(validBidRequests)}`); + + let adZoneIds = []; + let prebidBidIds = []; + let sizes = []; + + validBidRequests.forEach(bid => { + adZoneIds.push(utils.getBidIdParameter('adzoneid', bid.params)); + prebidBidIds.push(bid.bidId); + sizes.push(utils.parseSizesInput(bid.sizes).join('|')); + }); - adxcgBidReponseList.forEach(adxcgBidReponse => { - let bidRequest = bidRequests[adxcgBidReponse.bidId]; - delete bidRequests[adxcgBidReponse.bidId]; - - let bid = bidfactory.createBid(STATUS.GOOD, bidRequest); - - bid.creative_id = adxcgBidReponse.creativeId; - bid.code = 'adxcg'; - bid.bidderCode = 'adxcg'; - bid.cpm = adxcgBidReponse.cpm; - - if (adxcgBidReponse.ad) { - bid.ad = adxcgBidReponse.ad; - } else if (adxcgBidReponse.vastUrl) { - bid.vastUrl = adxcgBidReponse.vastUrl; - bid.descriptionUrl = adxcgBidReponse.vastUrl; - bid.mediaType = 'video'; - } else if (adxcgBidReponse.nativeResponse) { - bid.mediaType = 'native'; - - let nativeResponse = adxcgBidReponse.nativeResponse; - - bid['native'] = { - clickUrl: escape(nativeResponse.link.url), - impressionTrackers: nativeResponse.imptrackers - }; - - nativeResponse.assets.forEach(asset => { - if (asset.title && asset.title.text) { - bid['native'].title = asset.title.text; - } - - if (asset.img && asset.img.url) { - bid['native'].image = asset.img.url; - } - - if (asset.data && asset.data.label == 'DESC' && asset.data.value) { - bid['native'].body = asset.data.value; - } - - if (asset.data && asset.data.label == 'SPONSORED' && asset.data.value) { - bid['native'].sponsoredBy = asset.data.value; - } - }); + let location = utils.getTopWindowLocation(); + let secure = location.protocol === 'https:'; + + let requestUrl = url.parse(location.href); + requestUrl.search = null; + requestUrl.hash = null; + + let adxcgRequestUrl = url.format({ + protocol: secure ? 'https' : 'http', + hostname: secure ? 'hbps.adxcg.net' : 'hbp.adxcg.net', + pathname: '/get/adi', + search: { + renderformat: 'javascript', + ver: 'r20171019PB10', + adzoneid: adZoneIds.join(','), + format: sizes.join(','), + prebidBidIds: prebidBidIds.join(','), + url: encodeURIComponent(url.format(requestUrl)), + secure: secure ? '1' : '0', + source: SOURCE, + pbjs: '$prebid.version$' } + }); - bid.width = adxcgBidReponse.width; - bid.height = adxcgBidReponse.height; + return { + method: 'GET', + url: adxcgRequestUrl, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {bidRequests[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequests) { + let bids = []; + + serverResponse = serverResponse.body; + if (serverResponse) { + serverResponse.forEach(serverResponseOneItem => { + let bid = {}; + + bid.requestId = serverResponseOneItem.bidId; + bid.cpm = serverResponseOneItem.cpm; + bid.creativeId = parseInt(serverResponseOneItem.creativeId); + bid.currency = 'USD'; + bid.netRevenue = serverResponseOneItem.netRevenue ? serverResponseOneItem.netRevenue : true; + bid.ttl = 300; + + if (serverResponseOneItem.deal_id != null && serverResponseOneItem.deal_id.trim().length > 0) { + bid.dealId = serverResponseOneItem.deal_id; + } - utils.logMessage(`submitting bid[${bidRequest.placementCode}]: ${JSON.stringify(bid)}`); - bidmanager.addBidResponse(bidRequest.placementCode, bid); - }); + if (serverResponseOneItem.ad) { + bid.ad = serverResponseOneItem.ad; + } else if (serverResponseOneItem.vastUrl) { + bid.vastUrl = serverResponseOneItem.vastUrl; + bid.descriptionUrl = serverResponseOneItem.vastUrl; + bid.mediaType = 'video'; + } else if (serverResponseOneItem.nativeResponse) { + bid.mediaType = 'native'; + + let nativeResponse = serverResponseOneItem.nativeResponse; + + bid['native'] = { + clickUrl: encodeURIComponent(nativeResponse.link.url), + impressionTrackers: nativeResponse.imptrackers + }; + + nativeResponse.assets.forEach(asset => { + if (asset.title && asset.title.text) { + bid['native'].title = asset.title.text; + } + + if (asset.img && asset.img.url) { + bid['native'].image = asset.img.url; + } + + if (asset.data && asset.data.label === 'DESC' && asset.data.value) { + bid['native'].body = asset.data.value; + } + + if (asset.data && asset.data.label === 'SPONSORED' && asset.data.value) { + bid['native'].sponsoredBy = asset.data.value; + } + }); + } - Object.keys(bidRequests) - .map(bidId => bidRequests[bidId].placementCode) - .forEach(placementCode => { - utils.logMessage(`creating no_bid bid for: ${placementCode}`); - bidmanager.addBidResponse(placementCode, bidfactory.createBid(STATUS.NO_BID)); + bid.width = serverResponseOneItem.width; + bid.height = serverResponseOneItem.height; + utils.logMessage(`submitting bid[${serverResponseOneItem.bidId}]: ${JSON.stringify(bid)}`); + bids.push(bid); }); - }; - - return { - callBids: _callBids - }; + } else { + utils.logMessage(`empty bid response`); + } + return bids; + }, + getUserSyncs: function (syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: '//cdn.adxcg.net/pb-sync.html' + }]; + } + } }; - -adaptermanager.registerBidAdapter(new AdxcgAdapter(), 'adxcg', { - supportedMediaTypes: ['video', 'native'] -}); - -module.exports = AdxcgAdapter; +registerBidder(spec); diff --git a/modules/adxcgBidAdapter.md b/modules/adxcgBidAdapter.md new file mode 100644 index 000000000000..f3cd8c6d3082 --- /dev/null +++ b/modules/adxcgBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +**Module Name**: Adxcg Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: info@adxcg.com + +# Description + +Module that connects to an Adxcg.com zone to fetch bids. + +# Test Parameters +``` + `` + var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'adxcg', + params: { + adzoneid: '1' + } + }] + },{ + code: 'native-ad-div', + sizes: [[300, 250], [1, 1]], + nativeParams: { + title: { required: true, len: 75 }, + image: { required: true }, + body: { len: 200 }, + sponsoredBy: { len: 20 } + }, + bids: [{ + bidder: 'adxcg', + params: { + adzoneid: '2379' + } + } + }] + },{ + code: 'video', + sizes: [[640, 480]], + bids: [{ + bidder: 'adxcg', + params: { + adzoneid: '20' + } + } + }] + }]; +``` diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index fa55bf92e2eb..dbf7359e98d7 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,116 +1,62 @@ -import { expect } from 'chai'; -import Adapter from 'modules/adxcgBidAdapter'; -import bidmanager from 'src/bidmanager'; +import {expect} from 'chai'; import * as url from 'src/url'; +import {spec} from 'modules/adxcgBidAdapter'; -const REQUEST = { - 'bidderCode': 'adxcg', - 'bids': [ - { +describe('AdxcgAdapter', () => { + describe('isBidRequestValid', () => { + let bid = { 'bidder': 'adxcg', 'params': { - 'adzoneid': '1', + 'adzoneid': '1' }, - 'sizes': [ - [300, 250], - [640, 360], - [1, 1] - ], + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2' - } - ] -}; - -const RESPONSE = [{ - 'bidId': '84ab500420319d', - 'width': 300, - 'height': 250, - 'creativeId': '42', - 'cpm': 0.45, - 'ad': '' -}] - -const VIDEO_RESPONSE = [{ - 'bidId': '84ab500420319d', - 'width': 640, - 'height': 360, - 'creativeId': '42', - 'cpm': 0.45, - 'vastUrl': 'vastContentUrl' -}] - -const NATIVE_RESPONSE = [{ - 'bidId': '84ab500420319d', - 'width': 0, - 'height': 0, - 'creativeId': '42', - 'cpm': 0.45, - 'nativeResponse': { - 'assets': [{ - 'id': 1, - 'required': 0, - 'title': { - 'text': 'titleContent' - } - }, { - 'id': 2, - 'required': 0, - 'img': { - 'url': 'imageContent', - 'w': 600, - 'h': 600 - } - }, { - 'id': 3, - 'required': 0, - 'data': { - 'label': 'DESC', - 'value': 'descriptionContent' - } - }, { - 'id': 0, - 'required': 0, - 'data': { - 'label': 'SPONSORED', - 'value': 'sponsoredByContent' - } - }], - 'link': { - 'url': 'linkContent' - }, - 'imptrackers': ['impressionTracker1', 'impressionTracker2'] - } -}] + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }; -describe('AdxcgAdapter', () => { - let adapter; - - beforeEach(() => adapter = new Adapter()); - - describe('request function', () => { - let xhr; - let requests; + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); + }); - afterEach(() => xhr.restore()); + describe('request function http', () => { + let bid = { + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }; it('creates a valid adxcg request url', () => { - adapter.callBids(REQUEST); + let request = spec.buildRequests([bid]); + expect(request).to.exist; + // console.log('IS:' + JSON.stringify(request)); - let parsedRequestUrl = url.parse(requests[0].url); + expect(request.method).to.equal('GET'); + let parsedRequestUrl = url.parse(request.url); - expect(parsedRequestUrl.hostname).to.equal('ad-emea.adxcg.net'); + expect(parsedRequestUrl.hostname).to.equal('hbp.adxcg.net'); expect(parsedRequestUrl.pathname).to.equal('/get/adi'); let query = parsedRequestUrl.search; expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20141124'); + expect(query.ver).to.equal('r20171019PB10'); + expect(query.source).to.equal('pbjs10'); + expect(query.pbjs).to.equal('$prebid.version$'); expect(query.adzoneid).to.equal('1'); expect(query.format).to.equal('300x250|640x360|1x1'); expect(query.jsonp).to.be.empty; @@ -119,94 +65,202 @@ describe('AdxcgAdapter', () => { }); describe('response handler', () => { - let server; + let BIDDER_REQUEST = { + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }; - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); + let BANNER_RESPONSE = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 300, + 'height': 250, + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'ad': '' + }], + header: {'someheader': 'fakedata'} + } - afterEach(() => { - server.restore() - bidmanager.addBidResponse.restore(); - }); + let BANNER_RESPONSE_WITHDEALID = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 300, + 'height': 250, + 'deal_id': '7722', + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'ad': '' + }], + header: {'someheader': 'fakedata'} + } + + let VIDEO_RESPONSE = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 640, + 'height': 360, + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'vastUrl': 'vastContentUrl' + }], + header: {'someheader': 'fakedata'} + } + + let NATIVE_RESPONSE = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 0, + 'height': 0, + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'nativeResponse': { + 'assets': [{ + 'id': 1, + 'required': 0, + 'title': { + 'text': 'titleContent' + } + }, { + 'id': 2, + 'required': 0, + 'img': { + 'url': 'imageContent', + 'w': 600, + 'h': 600 + } + }, { + 'id': 3, + 'required': 0, + 'data': { + 'label': 'DESC', + 'value': 'descriptionContent' + } + }, { + 'id': 0, + 'required': 0, + 'data': { + 'label': 'SPONSORED', + 'value': 'sponsoredByContent' + } + }], + 'link': { + 'url': 'linkContent' + }, + 'imptrackers': ['impressionTracker1', 'impressionTracker2'] + } + }], + header: {'someheader': 'fakedata'} + } it('handles regular responses', () => { - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.bidderCode).to.equal('adxcg'); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.statusMessage).to.equal('Bid available'); - expect(bidResponse.adId).to.equal('84ab500420319d'); - expect(bidResponse.mediaType).to.equal('banner'); - expect(bidResponse.creative_id).to.equal('42'); - expect(bidResponse.code).to.equal('adxcg'); - expect(bidResponse.cpm).to.equal(0.45); - expect(bidResponse.ad).to.equal(''); + let result = spec.interpretResponse(BANNER_RESPONSE, BIDDER_REQUEST); + + expect(result).to.have.lengthOf(1); + + expect(result[0]).to.exist; + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + expect(result[0].dealId).to.not.exist; + }); + + it('handles regular responses with dealid', () => { + let result = spec.interpretResponse(BANNER_RESPONSE_WITHDEALID, BIDDER_REQUEST); + + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); }); it('handles video responses', () => { - server.respondWith(JSON.stringify(VIDEO_RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.bidderCode).to.equal('adxcg'); - expect(bidResponse.width).to.equal(640); - expect(bidResponse.height).to.equal(360); - expect(bidResponse.statusMessage).to.equal('Bid available'); - expect(bidResponse.adId).to.equal('84ab500420319d'); - expect(bidResponse.mediaType).to.equal('video'); - expect(bidResponse.creative_id).to.equal('42'); - expect(bidResponse.code).to.equal('adxcg'); - expect(bidResponse.cpm).to.equal(0.45); - expect(bidResponse.vastUrl).to.equal('vastContentUrl'); - expect(bidResponse.descriptionUrl).to.equal('vastContentUrl'); + let result = spec.interpretResponse(VIDEO_RESPONSE, BIDDER_REQUEST); + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(640); + expect(result[0].height).to.equal(360); + expect(result[0].mediaType).to.equal('video'); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].vastUrl).to.equal('vastContentUrl'); + expect(result[0].descriptionUrl).to.equal('vastContentUrl'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); }); it('handles native responses', () => { - server.respondWith(JSON.stringify(NATIVE_RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.bidderCode).to.equal('adxcg'); - expect(bidResponse.width).to.equal(0); - expect(bidResponse.height).to.equal(0); - expect(bidResponse.statusMessage).to.equal('Bid available'); - expect(bidResponse.adId).to.equal('84ab500420319d'); - expect(bidResponse.mediaType).to.equal('native'); - expect(bidResponse.creative_id).to.equal('42'); - expect(bidResponse.code).to.equal('adxcg'); - expect(bidResponse.cpm).to.equal(0.45); - - expect(bidResponse.native.clickUrl).to.equal('linkContent'); - expect(bidResponse.native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']); - expect(bidResponse.native.title).to.equal('titleContent'); - expect(bidResponse.native.image).to.equal('imageContent'); - expect(bidResponse.native.body).to.equal('descriptionContent'); - expect(bidResponse.native.sponsoredBy).to.equal('sponsoredByContent'); + let result = spec.interpretResponse(NATIVE_RESPONSE, BIDDER_REQUEST); + + expect(result[0].width).to.equal(0); + expect(result[0].height).to.equal(0); + expect(result[0].mediaType).to.equal('native'); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + + expect(result[0].native.clickUrl).to.equal('linkContent'); + expect(result[0].native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']); + expect(result[0].native.title).to.equal('titleContent'); + expect(result[0].native.image).to.equal('imageContent'); + expect(result[0].native.body).to.equal('descriptionContent'); + expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent'); }); it('handles nobid responses', () => { - server.respondWith('[]'); + let response = []; + let bidderRequest = BIDDER_REQUEST; + + let result = spec.interpretResponse(response, bidderRequest); + expect(result.length).to.equal(0); + }); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + describe('getUserSyncs', () => { + let syncoptionsIframe = { + 'iframeEnabled': 'true' + }; - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.statusMessage).to.equal('Bid returned empty or error response'); + it('should return iframe sync option', () => { + expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe'); + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal('//cdn.adxcg.net/pb-sync.html'); }); }); });