diff --git a/adapters.json b/adapters.json index e2c09b9095e5..8de2c2ebc3c1 100644 --- a/adapters.json +++ b/adapters.json @@ -127,6 +127,7 @@ }, { "adkernel": { + "supportedMediaTypes": ["video"], "alias": "headbidding" } }, diff --git a/src/adapters/adkernel.js b/src/adapters/adkernel.js index 32415bcba412..2ef168f6ea11 100644 --- a/src/adapters/adkernel.js +++ b/src/adapters/adkernel.js @@ -16,6 +16,9 @@ const AdKernelAdapter = function AdKernelAdapter() { }; const EMPTY_BID_RESPONSE = {'seatbid': [{'bid': []}]}; + const VIDEO_TARGETING = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'linearity', 'sequence', + 'boxingallowed', 'playbackmethod', 'delivery', 'pos', 'api', 'ext']; + let baseAdapter = Adapter.createNew('adkernel'); /** @@ -25,15 +28,13 @@ const AdKernelAdapter = function AdKernelAdapter() { function RtbRequestDispatcher() { const _dispatch = {}; const originalBids = {}; - const site = createSite(); const syncedHostZones = {}; + const site = createSite(); // translate adunit info into rtb impression dispatched by host/zone this.addImp = function (bid) { let host = bid.params.host; let zone = bid.params.zoneId; - let size = bid.sizes[0]; - let bidId = bid.bidId; if (!(host in _dispatch)) { _dispatch[host] = {}; @@ -42,17 +43,10 @@ const AdKernelAdapter = function AdKernelAdapter() { if (!(zone in _dispatch[host])) { _dispatch[host][zone] = []; } - let imp = { - 'id': bidId, - 'tagid': bid.placementCode, - 'banner': {'w': size[0], 'h': size[1]} - }; - if (utils.getTopWindowLocation().protocol === 'https:') { - imp.secure = 1; - } + let imp = buildImp(bid); // save rtb impression for specified ad-network host and zone _dispatch[host][zone].push(imp); - originalBids[bidId] = bid; + originalBids[bid.bidId] = bid; // perform user-sync if (!(host in syncedHostZones)) { syncedHostZones[host] = []; @@ -62,6 +56,42 @@ const AdKernelAdapter = function AdKernelAdapter() { } }; + function buildImp(bid) { + const size = getBidSize(bid); + const imp = { 'id': bid.bidId, 'tagid': bid.placementCode}; + + if (bid.params.video) { + imp.video = {w: size[0], h: size[1]}; + Object.keys(bid.params.video) + .filter(param => VIDEO_TARGETING.includes(param)) + .forEach(param => imp.video[param] = bid.params.video[param]); + } else { + imp.banner = {w: size[0], h: size[1]}; + } + if (utils.getTopWindowLocation().protocol === 'https:') { + imp.secure = 1; + } + return imp; + } + + function getBidSize(bid) { + if (bid.mediaType === 'video') { + return bid.sizes; + } + return bid.sizes[0]; + } + + function insertUserSync(host, zone) { + var iframe = utils.createInvisibleIframe(); + iframe.src = `//${host}/user-sync?zone=${zone}`; + try { + document.body.appendChild(iframe); + } catch (error) { + /* istanbul ignore next */ + utils.logError(error); + } + } + /** * Main function to get bid requests */ @@ -162,6 +192,7 @@ const AdKernelAdapter = function AdKernelAdapter() { dispatcher.addImp(bid); } }); + // process bids grouped into bid requests // start async usersync processUserSyncQueue(dispatcher.buildUserSyncQueue()); @@ -170,7 +201,8 @@ const AdKernelAdapter = function AdKernelAdapter() { let adUnitId = bid.placementCode; if (bidResp) { utils.logMessage(`got response for ${adUnitId}`); - bidmanager.addBidResponse(adUnitId, createBidObject(bidResp, bid, imp.banner.w, imp.banner.h)); + let dimensions = getCreativeSize(imp, bidResp); + bidmanager.addBidResponse(adUnitId, createBidObject(bidResp, bid, dimensions.w, dimensions.h)); } else { utils.logMessage(`got empty response for ${adUnitId}`); bidmanager.addBidResponse(adUnitId, createEmptyBidObject(bid)); @@ -178,17 +210,33 @@ const AdKernelAdapter = function AdKernelAdapter() { }); } + /** + * Evaluate creative size from response or from request + */ + function getCreativeSize(imp, bid) { + let dimensions = (bid.h && bid.w) ? bid : (imp.banner || imp.video); + return { + w: dimensions.w, + h: dimensions.h + }; + } + /** * Create bid object for the bid manager */ function createBidObject(resp, bid, width, height) { - return Object.assign(bidfactory.createBid(1, bid), { + let bidObj = Object.assign(bidfactory.createBid(1, bid), { bidderCode: bid.bidder, - ad: formatAdMarkup(resp), width: width, height: height, cpm: parseFloat(resp.price) }); + if (bid.params.video) { + bidObj.vastUrl = resp.nurl; + } else { + bidObj.ad = formatAdMarkup(resp); + } + return bidObj; } /** diff --git a/test/spec/adapters/adkernel_spec.js b/test/spec/adapters/adkernel_spec.js index dd7eb82060a3..06e7bf1783f7 100644 --- a/test/spec/adapters/adkernel_spec.js +++ b/test/spec/adapters/adkernel_spec.js @@ -36,31 +36,60 @@ describe('Adkernel adapter', () => { params: {zoneId: 1}, placementCode: 'ad-unit-1', sizes: [[728, 90]] + }, bid_video = { + bidder: 'adkernel', + bidId: 'Bid_Video', + sizes: [640, 480], + mediaType: 'video', + params: { + zoneId: 1, + host: 'rtb.adkernel.com', + video: { + mimes: ['video/mp4', 'video/webm', 'video/x-flv'] + } + }, + placementCode: 'ad-unit-1' }; const bidResponse1 = { - 'id': 'bid1', - 'seatbid': [{ - 'bid': [{ - 'id': '1', - 'impid': 'Bid_01', - 'price': 3.01, - 'nurl': 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', - 'adm': '' + id: 'bid1', + seatbid: [{ + bid: [{ + id: '1', + impid: 'Bid_01', + price: 3.01, + nurl: 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', + adm: '' }] }], - 'cur': 'USD' + cur: 'USD' }, bidResponse2 = { - 'id': 'bid2', - 'seatbid': [{ - 'bid': [{ - 'id': '2', - 'impid': 'Bid_02', - 'price': 1.31, - 'adm': '' + id: 'bid2', + seatbid: [{ + bid: [{ + id: '2', + impid: 'Bid_02', + price: 1.31, + adm: '' + }] + }], + cur: 'USD' + }, videoBidResponse = { + id: '47ce4badcf7482', + seatbid: [{ + bid: [{ + id: 'sZSYq5zYMxo_0', + impid: 'Bid_Video', + price: 0.00145, + adid: '158801', + nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl', + cid: '16855', + crid: '158801', + w: 600, + h: 400 }] }], - 'cur': 'USD' + cur: 'USD' }; let adapter, @@ -113,7 +142,7 @@ describe('Adkernel adapter', () => { }); }); - describe('request building', () => { + describe('banner request building', () => { let bidRequest; beforeEach(() => { @@ -150,7 +179,6 @@ describe('Adkernel adapter', () => { }); it('should have tagid', () => { - // console.warn(bidRequest.imp[0]); expect(bidRequest.imp[0]).to.have.property('tagid', 'ad-unit-1'); }); @@ -166,6 +194,38 @@ describe('Adkernel adapter', () => { }) }); + describe('video request building', () => { + let bidRequest; + + beforeEach(() => { + sandbox.stub(utils, 'getTopWindowLocation', () => { + return { + protocol: 'https:', + hostname: 'example.com', + host: 'example.com', + pathname: '/index.html', + href: 'http://example.com/index.html' + }; + }); + ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(videoBidResponse)); + doRequest([bid_video]); + bidRequest = JSON.parse(decodeURIComponent(ajaxStub.getCall(0).args[2].r)); + }); + + it('should have video object', () => { + expect(bidRequest.imp[0]).to.have.property('video'); + }); + + it('should have h/w', () => { + expect(bidRequest.imp[0].video).to.have.property('w', 640); + expect(bidRequest.imp[0].video).to.have.property('h', 480); + }); + + it('should have tagid', () => { + expect(bidRequest.imp[0]).to.have.property('tagid', 'ad-unit-1'); + }); + }); + describe('requests routing', () => { it('should issue a request for each network', () => { ajaxStub.onFirstCall().callsArgWith(1, '') @@ -192,12 +252,12 @@ describe('Adkernel adapter', () => { }); }); - describe('responses processing', () => { + describe('banner responses processing', () => { beforeEach(() => { sandbox.stub(bidmanager, 'addBidResponse'); }); - it('should return fully-initialized bid-response', () => { + it('should return fully-initialized banner bid-response', () => { ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); doRequest([bid1_zone1]); let bidResponse = bidmanager.addBidResponse.firstCall.args[1]; @@ -210,6 +270,19 @@ describe('Adkernel adapter', () => { expect(bidResponse.height).to.equal(250); }); + it('should return fully-initialized video bid-response', () => { + ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(videoBidResponse)); + doRequest([bid_video]); + let bidResponse = bidmanager.addBidResponse.firstCall.args[1]; + expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('ad-unit-1'); + expect(bidResponse.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bidResponse.bidderCode).to.equal('adkernel'); + expect(bidResponse.cpm).to.equal(0.00145); + expect(bidResponse.vastUrl).to.equal('https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl'); + expect(bidResponse.width).to.equal(600); + expect(bidResponse.height).to.equal(400); + }); + it('should map responses to proper ad units', () => { ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); ajaxStub.onCall(1).callsArgWith(1, JSON.stringify(bidResponse2)); @@ -234,7 +307,7 @@ describe('Adkernel adapter', () => { expect(bidmanager.addBidResponse.secondCall.args[0]).to.equal('ad-unit-2'); }); - it('should add nurl as pixel', () => { + it('should add nurl as pixel for banner response', () => { sandbox.spy(utils, 'createTrackPixelHtml'); ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); doRequest([bid1_zone1]);