diff --git a/modules/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js new file mode 100644 index 00000000000..63a308f1f62 --- /dev/null +++ b/modules/ebdrBidAdapter.js @@ -0,0 +1,133 @@ +import * as utils from 'src/utils'; +import { VIDEO, BANNER } from 'src/mediaTypes'; +import { registerBidder } from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'ebdr'; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [ BANNER, VIDEO ], + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.zoneid); + }, + buildRequests: function(bids) { + const rtbServerDomain = 'dsp.bnmla.com'; + let domain = window.location.host; + let page = window.location.pathname + location.search + location.hash; + let ebdrImps = []; + const ebdrReq = {}; + let ebdrParams = {}; + let zoneid = ''; + let requestId = ''; + bids.forEach(bid => { + utils.logInfo('Log bid', bid); + let bidFloor = utils.getBidIdParameter('bidfloor', bid.params); + let whArr = getWidthAndHeight(bid); + let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video) ? VIDEO : BANNER; + zoneid = utils.getBidIdParameter('zoneid', bid.params); + requestId = bid.bidderRequestId; + ebdrImps.push({ + id: bid.bidId, + [_mediaTypes]: { + w: whArr[0], + h: whArr[1] + }, + bidfloor: bidFloor + }) + ebdrReq[bid.bidId] = {mediaTypes: _mediaTypes, + w: whArr[0], + h: whArr[1] + }; + ebdrParams['latitude'] = utils.getBidIdParameter('latitude', bid.params); + ebdrParams['longitude'] = utils.getBidIdParameter('longitude', bid.params); + ebdrParams['ifa'] = (utils.getBidIdParameter('IDFA', bid.params).length > utils.getBidIdParameter('ADID', bid.params).length) ? utils.getBidIdParameter('IDFA', bid.params) : utils.getBidIdParameter('ADID', bid.params); + }); + let ebdrBidReq = { + id: requestId, + imp: ebdrImps, + site: { + domain: domain, + page: page + }, + device: { + geo: { + lat: ebdrParams.latitude, + log: ebdrParams.longitude + }, + ifa: ebdrParams.ifa + } + }; + return { + method: 'GET', + url: '//' + rtbServerDomain + '/hb?' + '&zoneid=' + zoneid + '&br=' + encodeURIComponent(JSON.stringify(ebdrBidReq)), + bids: ebdrReq + }; + }, + interpretResponse: function(serverResponse, bidRequest) { + utils.logInfo('Log serverResponse', serverResponse); + utils.logInfo('Log bidRequest', bidRequest); + let ebdrResponseImps = []; + const ebdrResponseObj = serverResponse.body; + if (!ebdrResponseObj || !ebdrResponseObj.seatbid || ebdrResponseObj.seatbid.length === 0 || !ebdrResponseObj.seatbid[0].bid || ebdrResponseObj.seatbid[0].bid.length === 0) { + return []; + } + ebdrResponseObj.seatbid[0].bid.forEach(ebdrBid => { + let responseCPM; + responseCPM = parseFloat(ebdrBid.price); + let adm; + let type; + let _mediaTypes; + let vastURL; + if (bidRequest.bids[ebdrBid.id].mediaTypes == BANNER) { + adm = decodeURIComponent(ebdrBid.adm) + type = 'ad'; + _mediaTypes = BANNER; + } else { + adm = ebdrBid.adm + type = 'vastXml' + _mediaTypes = VIDEO; + if (ebdrBid.nurl) { + vastURL = ebdrBid.nurl; + } + } + let response = { + requestId: ebdrBid.id, + [type]: adm, + mediaType: _mediaTypes, + creativeId: ebdrBid.crid, + cpm: responseCPM, + width: ebdrBid.w, + height: ebdrBid.h, + currency: 'USD', + netRevenue: true, + ttl: 3600 } + if (vastURL) { + response.vastUrl = vastURL; + } + ebdrResponseImps.push(response); + }); + return ebdrResponseImps; + } +} +function getWidthAndHeight(bid) { + let adW = null; + let adH = null; + // Handing old bidder only has size object + if (bid.sizes && bid.sizes.length) { + let sizeArrayLength = bid.sizes.length; + if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { + adW = bid.sizes[0]; + adH = bid.sizes[1]; + } + } + let _mediaTypes = bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER; + if (bid.mediaTypes && bid.mediaTypes[_mediaTypes]) { + if (_mediaTypes == BANNER && bid.mediaTypes[_mediaTypes].sizes && bid.mediaTypes[_mediaTypes].sizes[0] && bid.mediaTypes[_mediaTypes].sizes[0].length === 2) { + adW = bid.mediaTypes[_mediaTypes].sizes[0][0]; + adH = bid.mediaTypes[_mediaTypes].sizes[0][1]; + } else if (_mediaTypes == VIDEO && bid.mediaTypes[_mediaTypes].playerSize && bid.mediaTypes[_mediaTypes].playerSize.length === 2) { + adW = bid.mediaTypes[_mediaTypes].playerSize[0]; + adH = bid.mediaTypes[_mediaTypes].playerSize[1]; + } + } + return [adW, adH]; +} +registerBidder(spec); diff --git a/modules/ebdrBidAdapter.md b/modules/ebdrBidAdapter.md new file mode 100644 index 00000000000..64483797b25 --- /dev/null +++ b/modules/ebdrBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: EngageBDR Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech@engagebdr.com +``` + +# Description + +Adapter that connects to EngageBDR's demand sources. + +# Test Parameters +``` + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [{ + bidder: 'ebdr', + params: { + zoneid: '99999', + bidfloor: '1.00', + IDFA:'xxx-xxx', + ADID:'xxx-xxx', + latitude:'34.089811', + longitude:'-118.392805' + } + }] + },{ + code: 'test-video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [300, 250] + } + }, + bids: [{ + bidder: 'ebdr', + params: { + zoneid: '99998', + bidfloor: '1.00', + IDFA:'xxx-xxx', + ADID:'xxx-xxx', + latitude:'34.089811', + longitude:'-118.392805' + } + }] + }]; +``` diff --git a/test/spec/modules/ebdrBidAdapter_spec.js b/test/spec/modules/ebdrBidAdapter_spec.js new file mode 100644 index 00000000000..c5328f9ebb9 --- /dev/null +++ b/test/spec/modules/ebdrBidAdapter_spec.js @@ -0,0 +1,209 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ebdrBidAdapter'; +import { VIDEO, BANNER } from 'src/mediaTypes'; +import * as utils from 'src/utils'; + +describe('ebdrBidAdapter', () => { + let bidRequests; + + beforeEach(() => { + bidRequests = [ + { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidder: 'ebdr', + params: { + zoneid: '99999', + bidfloor: '1.00', + IDFA: 'xxx-xxx', + ADID: 'xxx-xxx', + latitude: '34.089811', + longitude: '-118.392805' + }, + bidId: '2c5e8a1a84522d', + bidderRequestId: '1d0c4017f02458', + auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f314' + }, { + adUnitCode: 'div-gpt-ad-1460505748561-1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [300, 250] + } + }, + bidder: 'ebdr', + params: { + zoneid: '99998', + bidfloor: '1.00', + IDFA: 'xxx-xxx', + ADID: 'xxx-xxx', + latitude: '34.089811', + longitude: '-118.392805' + }, + bidId: '23a01e95856577', + bidderRequestId: '1d0c4017f02458', + auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f314' + } + ]; + }); + + describe('spec.isBidRequestValid', () => { + it('should return true when the required params are passed', () => { + const bidRequest = bidRequests[0]; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when the only required param is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.params = { + zoneid: '99998', + bidfloor: '1.00', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when the "bidfloor" param is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.params = { + zoneid: '99998', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when no bid params are passed', () => { + const bidRequest = bidRequests[0]; + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when a bid request is not passed', () => { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); + + describe('spec.buildRequests', () => { + describe('for banner bids', () => { + it('must handle an empty bid size', () => { + bidRequests[0].mediaTypes = { banner: {} }; + const requests = spec.buildRequests(bidRequests); + const bidRequest = {}; + bidRequest['2c5e8a1a84522d'] = { mediaTypes: BANNER, w: null, h: null }; + expect(requests.bids['2c5e8a1a84522d']).to.deep.equals(bidRequest['2c5e8a1a84522d']); + }); + it('should create a single GET', () => { + bidRequests[0].mediaTypes = { banner: {} }; + bidRequests[1].mediaTypes = { banner: {} }; + const requests = spec.buildRequests(bidRequests); + expect(requests.method).to.equal('GET'); + }); + it('must parse bid size from a nested array', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {sizes: [[ width, height ]]} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = {}; + data['2c5e8a1a84522d'] = { mediaTypes: BANNER, w: width, h: height }; + expect(requests.bids['2c5e8a1a84522d']).to.deep.equal(data['2c5e8a1a84522d']); + }); + }); + describe('for video bids', () => { + it('must handle an empty bid size', () => { + bidRequests[1].mediaTypes = { video: {} }; + const requests = spec.buildRequests(bidRequests); + const bidRequest = {}; + bidRequest['23a01e95856577'] = { mediaTypes: VIDEO, w: null, h: null }; + expect(requests.bids['23a01e95856577']).to.deep.equals(bidRequest['23a01e95856577']); + }); + + it('should create a GET request for each bid', () => { + const bidRequest = bidRequests[1]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests.method).to.equal('GET'); + }); + }); + }); + + describe('spec.interpretResponse', () => { + describe('for video bids', () => { + it('should return no bids if the response is not valid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid video bid response', () => { + const ebdrReq = {bids: {}}; + bidRequests.forEach(bid => { + let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); + ebdrReq.bids[bid.bidId] = {mediaTypes: _mediaTypes, + w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], + h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] + }; + }); + const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '23a01e95856577', impid: '23a01e95856577', price: 0.81, adid: 'abcde-12345', nurl: 'https://cdn0.bnmla.com/vtest.xml', adm: '\nStatic VASTStatic VAST Tag00:00:15http://www.engagebdr.com/c', adomain: ['advertiserdomain.com'], iurl: '', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, ebdrReq); + expect(bidResponse[0]).to.deep.equal({ + requestId: bidRequests[1].bidId, + vastXml: serverResponse.seatbid[0].bid[0].adm, + mediaType: 'video', + creativeId: serverResponse.seatbid[0].bid[0].crid, + cpm: serverResponse.seatbid[0].bid[0].price, + width: serverResponse.seatbid[0].bid[0].w, + height: serverResponse.seatbid[0].bid[0].h, + currency: 'USD', + netRevenue: true, + ttl: 3600, + vastUrl: serverResponse.seatbid[0].bid[0].nurl + }); + }); + }); + + describe('for banner bids', () => { + it('should return no bids if the response is not valid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response is empty', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return valid banner bid responses', () => { + const ebdrReq = {bids: {}}; + bidRequests.forEach(bid => { + let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); + ebdrReq.bids[bid.bidId] = {mediaTypes: _mediaTypes, + w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], + h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] + }; + }); + const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '2c5e8a1a84522d', impid: '2c5e8a1a84522d', price: 0.81, adid: 'abcde-12345', nurl: '', adm: '
', adomain: ['advertiserdomain.com'], iurl: '', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD', w: 300, h: 250}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, ebdrReq); + expect(bidResponse[0]).to.deep.equal({ + requestId: bidRequests[ 0 ].bidId, + ad: serverResponse.seatbid[0].bid[0].adm, + mediaType: 'banner', + creativeId: serverResponse.seatbid[0].bid[0].crid, + cpm: serverResponse.seatbid[0].bid[0].price, + width: serverResponse.seatbid[0].bid[0].w, + height: serverResponse.seatbid[0].bid[0].h, + currency: 'USD', + netRevenue: true, + ttl: 3600 + }); + }); + }); + }); +});