diff --git a/adapters.json b/adapters.json index 48ca7cefc35..691b0a55bc6 100644 --- a/adapters.json +++ b/adapters.json @@ -15,6 +15,7 @@ "appnexusAst", "beachfront", "audienceNetwork", + "carambola", "conversant", "districtmDMX", "fidelity", diff --git a/src/adapters/carambola.js b/src/adapters/carambola.js new file mode 100644 index 00000000000..18a7c154f6d --- /dev/null +++ b/src/adapters/carambola.js @@ -0,0 +1,193 @@ +/** + * Carambola adapter + */ + +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +const utils = require('../utils.js'); +const ajax = require('../ajax.js').ajax; + +const CarambolaAdapter = function CarambolaAdapter() { + const BIDDER_CODE = 'carambola'; + const REQUEST_PATH = 'hb/inimage/getHbBIdProcessedResponse'; + + function _addErrorBidResponse(bid, response = {}, errorMsg = '') { + const bidResponse = bidfactory.createBid(2, bid); + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.reason = errorMsg; + bidmanager.addBidResponse(_getCustomAdUnitCode(bid), bidResponse); + } + // looking at the utils.js at getBidderRequest method. this is what is requested. + function _getCustomAdUnitCode(bid) { + return bid.placementCode; + } + + function _addBidResponse(bid, response) { + const bidResponse = bidfactory.createBid(1, bid); + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.ad = response.ad; + bidResponse.cpm = response.cpm; + bidResponse.width = response.width; + bidResponse.height = response.height; + bidResponse.currencyCode = response.cur; + bidResponse.token = response.token; + bidResponse.pvid = response.pageViewId; + + bidmanager.addBidResponse(_getCustomAdUnitCode(bid), bidResponse); + } + + function _getPageViewId() { + window.Cbola = window.Cbola || {}; + window.Cbola.HB = window.Cbola.HB || {}; + window.Cbola.HB.pvid = window.Cbola.HB.pvid || _createPageViewId(); + return window.Cbola.HB.pvid; + } + + function _createPageViewId() { + function _pad(number) { + return number > 9 ? number : '0' + number + } + + const MIN = 10000; + const MAX = 90000; + let now = new Date(); + + var pvid = + _pad(now.getDate()) + + _pad(now.getMonth() + 1) + + _pad(now.getFullYear() % 100) + + _pad(now.getHours()) + + _pad(now.getMinutes()) + + _pad(now.getSeconds()) + + _pad(now.getMilliseconds() % 100) + + Math.floor((Math.random() * MAX) + MIN); + + return pvid; + } + + // sends a request for each bid + function _buildRequest(bids, params) { + if (!utils.isArray(bids)) { + return; + } + // iterate on every bid and return the response to the hb manager + utils._each(bids, bid => { + let tempParams = params || {}; + tempParams.cbolaMode = bid.params.cbolaMode || 0; + tempParams.wid = bid.params.wid || 0; + tempParams.pixel = bid.params.pixel || ''; + tempParams.bidFloor = bid.params.bidFloor || 0; + tempParams.pageViewId = _getPageViewId(); + tempParams.hb_token = utils.generateUUID(); + tempParams.sizes = utils.parseSizesInput(bid.sizes) + ''; + tempParams.bidsCount = bids.length; + + for (let customParam in bid.params.customParams) { + if (bid.params.customParams.hasOwnProperty(customParam)) { + tempParams['c_' + customParam] = bid.params.customParams[customParam]; + } + } + + let server = bid.params.server || 'route.carambo.la'; + let cbolaHbApiUrl = '//' + server + '/' + REQUEST_PATH; + + // the responses of the bid requests + ajax(cbolaHbApiUrl + _jsonToQueryString(tempParams), response => { + // no response + if (!response || response.cpm <= 0) { + utils.logError('Empty bid response', BIDDER_CODE, bid); + _addErrorBidResponse(bid, response, 'Empty bid response'); + return; + } + try { + response = JSON.parse(response); + if (response && response.cpm <= 0) + { + utils.logError('Bid response returned 0', BIDDER_CODE, bid); + _addErrorBidResponse(bid, response, 'Bid response returned 0'); + return; + } + } catch (e) { + utils.logError('Invalid JSON in bid response', BIDDER_CODE, bid); + _addErrorBidResponse(bid, response, 'Invalid JSON in bid response'); + return; + } + _addBidResponse(bid, response); + }, null, {method: 'GET'}); + }); + } + + // build the genral request to the server + function _callBids(params) { + let isIfr, + bids = params.bids || [], + currentURL = (window.parent !== window) ? document.referrer : window.location.href; + currentURL = currentURL && encodeURIComponent(currentURL); + try { + isIfr = window.self !== window.top; + } + catch (e) { + isIfr = false; + } + if (bids.length === 0) { + return; + } + + _buildRequest(bids, { + pageUrl: currentURL, + did: bids[0].params.did || 0, + pid: bids[0].params.pid || '', + res: _getScreenSize(screen), + ifr: isIfr, + viewPortDim: _getViewportDimensions(isIfr) + }); + } + + function _getScreenSize(screen) { + return screen ? `${screen.width}x${screen.height}x${screen.colorDepth}` : '0'; + } + + function _getViewportDimensions(isIfr) { + let width, + height, + tWin = window, + tDoc = document, + docEl = tDoc.documentElement, + body; + + if (isIfr) { + try { + tWin = window.top; + tDoc = window.top.document; + } + catch (e) { + return; + } + docEl = tDoc.documentElement; + body = tDoc.body; + width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; + height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; + } else { + docEl = tDoc.documentElement; + width = tWin.innerWidth || docEl.clientWidth; + height = tWin.innerHeight || docEl.clientHeight; + } + return `${width}x${height}`; + } + + function _jsonToQueryString(json) { + return '?' + + Object.keys(json).map(function(key) { + return encodeURIComponent(key) + '=' + + encodeURIComponent(json[key]); + }).join('&'); + } + + // Export the `callBids` function, so that Prebid.js can execute + // this function when the page asks to send out bid requests. + return { + callBids: _callBids + }; +}; + +module.exports = CarambolaAdapter; diff --git a/test/spec/adapters/carambola_spec.js b/test/spec/adapters/carambola_spec.js new file mode 100644 index 00000000000..fe856f66c21 --- /dev/null +++ b/test/spec/adapters/carambola_spec.js @@ -0,0 +1,139 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils'; +import CarambolaAdapter from 'src/adapters/carambola'; +import bidmanager from 'src/bidmanager'; + +const DEFAULT_BIDDER_REQUEST = { + bidderCode: 'carambola', + requestId: 'c9ad932a-41d9-4821-b6dc-0c8146029faf', + adId: '2e3daacdeed03d', + start: new Date().getTime(), + bids: [{ + bidder: 'carambola', + adId: '2e3daacdeed03d', + requestId: 'c9ad932a-41d9-4821-b6dc-0c8146029faf', + adUnitCode: 'cbola_prebid_code_97', + token: 'CGYCLyIy', + pageViewId: '22478638', + params: { + pid: 'hbtest', + did: 112591, + wid: 0 + } + }] +}; + +const DEFAULT_HB_RESPONSE = { + cpm: 0.1693953107111156, + ad: ' ', + token: '9cd6bf9c-433d-4663-b67f-da727f4cebff', + width: '300', + height: '250', + currencyCode: 'USD', + pageViewId: '22478638', + requestStatus: 1 + +}; + +describe('carambolaAdapter', function () { + let adapter; + + beforeEach(() => adapter = new CarambolaAdapter()); + + function createBidderRequest({bids, params} = {}) { + var bidderRequest = utils.cloneJson(DEFAULT_BIDDER_REQUEST); + if (bids && Array.isArray(bids)) { + bidderRequest.bids = bids; + } + if (params) { + bidderRequest.bids.forEach(bid => bid.params = params); + } + return bidderRequest; + } + + describe('callBids()', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + + // bid request starts + describe('bid request', () => { + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + }); + + afterEach(() => xhr.restore()); + + it('requires parameters to be made', () => { + adapter.callBids({}); + expect(requests[0]).to.be.empty; + }); + + it('should hit the default route.carambo.la endpoint', () => { + adapter.callBids(DEFAULT_BIDDER_REQUEST); + expect(requests[0].url).to.contain('route.carambo.la'); + }); + + it('should verifiy that a page_view_id is sent', () => { + adapter.callBids(DEFAULT_BIDDER_REQUEST); + expect(requests[0].url).to.contain('pageViewId='); + }); + + it('should should send the correct did', () => { + adapter.callBids(createBidderRequest({ + params: { + did: 112591, + wid: 0 + } + })); + expect(requests[0].url).to.contain('did=112591'); + }); + }); + // bid request ends + + // bid response starts + describe('bid response', () => { + let server; + + beforeEach(() => { + server = sinon.fakeServer.create(); + sinon.stub(bidmanager, 'addBidResponse'); + }); + + afterEach(() => { + server.restore(); + bidmanager.addBidResponse.restore(); + }); + + it('should be added to bidmanager if response is valid', () => { + server.respondWith(JSON.stringify(DEFAULT_HB_RESPONSE)); + adapter.callBids(DEFAULT_BIDDER_REQUEST); + server.respond(); + expect(bidmanager.addBidResponse.calledOnce).to.be.true; + }); + + it('should be added to bidmanager with correct bidderCode', () => { + server.respondWith(JSON.stringify(DEFAULT_HB_RESPONSE)); + adapter.callBids(DEFAULT_BIDDER_REQUEST); + server.respond(); + expect(bidmanager.addBidResponse.calledOnce).to.be.true; + expect(bidmanager.addBidResponse.firstCall.args[1]).to.have.property('bidderCode', 'carambola'); + }); + + it('should have pageViewId matching the pageViewId from related bid request', () => { + server.respondWith(JSON.stringify(DEFAULT_HB_RESPONSE)); + adapter.callBids(DEFAULT_BIDDER_REQUEST); + server.respond(); + expect(bidmanager.addBidResponse.calledOnce).to.be.true; + expect(bidmanager.addBidResponse.firstCall.args[1]) + .to.have.property('pvid', DEFAULT_BIDDER_REQUEST.bids[0].pageViewId); + }); + }); + // bid response ends + }); +});