diff --git a/modules/jwplayerBidAdapter.js b/modules/jwplayerBidAdapter.js index 37fa480b560..98f0e1d15a0 100644 --- a/modules/jwplayerBidAdapter.js +++ b/modules/jwplayerBidAdapter.js @@ -1,12 +1,36 @@ -// import * as utils from 'src/utils'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -// import { config } from 'src/config'; import { VIDEO } from '../src/mediaTypes.js'; - +import { + isArray, + isFn, + deepAccess, + deepSetValue } from '../src/utils.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'jwplayer'; +const URL = 'https://ib.adnxs.com/openrtb2/prebid'; + const GVLID = 1046; const SUPPORTED_AD_TYPES = [VIDEO]; +// Video Parameters +// https://docs.prebid.org/dev-docs/bidder-adaptor.html#step-2-accept-video-parameters-and-pass-them-to-your-server +const VIDEO_ORTB_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'protocols', + 'startdelay', + 'placement', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity' +]; + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -29,18 +53,65 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @param {BidRequest[]} bidRequests A non-empty list of bid requests, or ad units, which should be sent to the server. + * @param bidderRequest * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests, bidderRequest) {}, + buildRequests: function(bidRequests, bidderRequest) { + if (!bidRequests) { + return; + } + + return bidRequests.map(bidRequest => { + const payload = buildRequest(bidRequest, bidderRequest); + + return { + method: 'POST', + url: URL, + data: payload + } + }); + }, /** * Unpack the response from the server into a list of bids. * * @param {*} serverResponse A successful response from the server. + * @param bidderRequest * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, request) {}, + interpretResponse: function(serverResponse) { + const bidResponses = []; + const serverResponseBody = serverResponse.body; + + const bidId = serverResponse.bidid; + const cur = serverResponse.cur; + + if (serverResponseBody && isArray(serverResponseBody.seatbid)) { + serverResponseBody.seatbid.forEach(seatBids => { + seatBids.bid.forEach(bid => { + const bidResponse = { + requestId: bidId, + cpm: bid.price, + currency: cur, + width: bid.w, + height: bid.h, + creativeId: bid.adid, + vastXml: bid.adm, + netRevenue: true, + ttl: 500, + ad: bid.adm, + meta: { + advertiserDomains: bid.adomain + } + }; + bidResponses.push(bidResponse); + }); + }); + }; + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {}, // Optional? @@ -50,4 +121,107 @@ export const spec = { // onBidderError: function({ error, bidderRequest }) {} }; +function buildRequest(bidRequest, bidderRequest) { + const openrtbRequest = { + id: bidRequest.bidId, + imp: buildRequestImpression(bidRequest, bidderRequest), + site: buildRequestSite(bidRequest), + device: buildRequestDevice() + }; + + // Attaching GDPR Consent Params + if (bidderRequest.gdprConsent) { + deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest.uspConsent) { + deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + return JSON.stringify(openrtbRequest); +} + +function buildRequestImpression(bidRequest) { + const impressionObject = { + id: bidRequest.adUnitCode, + }; + + impressionObject.video = buildImpressionVideo(bidRequest); + + const bidFloorData = buildBidFloorData(bidRequest); + if (bidFloorData) { + impressionObject.bidfloor = bidFloorData.floor; + impressionObject.bidfloorcur = bidFloorData.currency; + } + + impressionObject.ext = buildImpressionExtension(bidRequest); + + return [impressionObject]; +} + +function buildImpressionVideo(bidRequest) { + const videoParams = deepAccess(bidRequest, 'mediaTypes.video', {}); + + const video = {}; + + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + video[param] = videoParams[param]; + } + }); + + return video; +} + +function buildImpressionExtension(bidRequest) { + return { + appnexus: { + placement_id: bidRequest.params.placementId + } + }; +} + +function buildBidFloorData(bidRequest) { + const {params} = bidRequest; + const currency = params.currency || 'USD'; + + let floorData; + if (isFn(bidRequest.getFloor)) { + const bidFloorRequest = { + currency: currency, + mediaType: VIDEO, + size: '*' + }; + floorData = bidRequest.getFloor(bidFloorRequest); + } else if (params.bidfloor) { + floorData = {floor: params.bidfloor, currency: currency}; + } + + return floorData; +} + +function buildRequestSite(bidRequest) { + /* eslint-disable no-console */ + const site = config.getConfig('ortb2.site') || {}; + console.log(site); + + site.domain = site.domain || config.publisherDomain || window.location.hostname; + site.page = site.page || config.pageUrl || window.location.href; + + const referer = bidRequest.refererInfo && bidRequest.refererInfo.referer; + if (!site.ref && referer) { + site.ref = referer; + } + + return site; +} + +function buildRequestDevice() { + return { + ua: navigator.userAgent + }; +} + registerBidder(spec); diff --git a/test/spec/modules/jwplayerBidAdapter_spec.js b/test/spec/modules/jwplayerBidAdapter_spec.js index 5abf6b7e8c7..51282abfe05 100644 --- a/test/spec/modules/jwplayerBidAdapter_spec.js +++ b/test/spec/modules/jwplayerBidAdapter_spec.js @@ -3,16 +3,17 @@ import { spec } from 'modules/jwplayerBidAdapter.js'; import { config } from 'src/config.js'; describe('jwplayer adapter tests', function() { - var sandbox, clock, frozenNow = new Date(); - beforeEach(function() { - sandbox = sinon.sandbox.create(); - clock = sinon.useFakeTimers(frozenNow.getTime()); - }); - - afterEach(function() { - sandbox.restore(); - clock.restore(); + this.defaultBidderRequest = { + 'gdprConsent': { + 'consentString': '', + 'gdprApplies': true + }, + 'uspConsent': true, + 'refererInfo': { + 'referer': 'https://example.com' + } + } }); describe('isBidRequestValid', function() { @@ -37,9 +38,126 @@ describe('jwplayer adapter tests', function() { }); }); - describe('buildRequests for video', function() {}); + describe('buildRequests for video', function() { + it('buildRequests works', function() { + const bidRequests = [ + { + 'bidder': 'jwplayer', + 'params': { + 'placementId': 12345 + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'content': {} + } + }, + 'bidRequestsCount': 1, + 'adUnitCode': 'testAdUnitCode', + 'bidId': 'testBidId' + } + ] + + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake((key) => { + const config = { + 'ortb2': { + site: { + domain: 'page.example.com', + page: 'https://examplepage.com' + } + } + }; + return config[key]; + }) + + const serverRequests = spec.buildRequests(bidRequests, this.defaultBidderRequest); + + /* eslint-disable no-console */ + + serverRequests.forEach(serverRequest => { + expect(serverRequest.url).to.have.string('https://ib.adnxs.com/openrtb2/prebid'); + expect(serverRequest.method).to.equal('POST'); + + const openrtbRequest = JSON.parse(serverRequest.data); + + expect(openrtbRequest.id).to.not.equal(null); + expect(openrtbRequest.id).to.have.string('testBidId'); + + expect(openrtbRequest.site).to.not.equal(null); + expect(openrtbRequest.site).to.be.an('object'); + expect(openrtbRequest.site.domain).to.be.a('string'); + expect(openrtbRequest.site.domain).to.equal('page.example.com'); + expect(openrtbRequest.site.page).to.be.a('string'); + expect(openrtbRequest.site.page).to.equal('https://examplepage.com'); + expect(openrtbRequest.site.ref).to.be.a('string'); + expect(openrtbRequest.site.ref).to.equal('https://example.com'); + + expect(openrtbRequest.device).to.not.equal(null); + expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); + + expect(openrtbRequest.imp).to.not.equal(null); + expect(openrtbRequest.imp[0]).to.not.equal(null); + expect(openrtbRequest.imp[0].video).to.not.equal(null); + expect(openrtbRequest.imp[0].ext).to.not.equal(null); + expect(openrtbRequest.imp[0].ext.appnexus).to.not.equal(null); + expect(openrtbRequest.imp[0].ext.appnexus.placement_id).to.not.equal(null); + expect(openrtbRequest.imp[0].ext.appnexus.placement_id).to.equal(12345); + + expect(openrtbRequest.user).to.not.equal(null); + expect(openrtbRequest.user.ext).to.not.equal(null); + + expect(openrtbRequest.regs).to.not.equal(null); + expect(openrtbRequest.regs.ext).to.not.equal(null); + expect(openrtbRequest.regs.ext.gdpr).to.equal(1); + expect(openrtbRequest.regs.ext.us_privacy).to.equal(true); + }) + }); + }); + + describe('interpretResponse for video', function() { + const bidResponse = { + id: 'testId', + impid: 'test-imp-id', + price: 1.000000, + adid: '97517771', + adm: 'some-test-ad', + adomain: ['prebid.com'], + w: 1, + h: 1, + } + + const serverResponse = { + body: { + id: 'test-request-id', + seatbid: [ + { + bid: [ bidResponse ], + seat: 1000 + } + ] + }, + bidid: '123456789', + cur: 'USD' + } - describe('interpretResponse for video', function() {}); + const bidResponses = spec.interpretResponse(serverResponse); + + expect(bidResponses[0]).to.not.equal(null); + expect(bidResponses[0].requestId).to.equal('123456789'); + expect(bidResponses[0].cpm).to.equal(1); + expect(bidResponses[0].currency).to.equal('USD'); + expect(bidResponses[0].width).to.equal(1); + expect(bidResponses[0].height).to.equal(1); + expect(bidResponses[0].creativeId).to.equal('97517771'); + expect(bidResponses[0].vastXml).to.equal('some-test-ad'); + expect(bidResponses[0].netRevenue).to.equal(true); + expect(bidResponses[0].ttl).to.equal(500); + expect(bidResponses[0].ad).to.equal('some-test-ad'); + expect(bidResponses[0].meta).to.not.equal(null); + expect(bidResponses[0].meta.advertiserDomains).to.not.equal(null); + expect(bidResponses[0].meta.advertiserDomains[0]).to.equal('prebid.com'); + }); describe('user sync handler', function() {}); });