diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 86021d2a90c..c70fc724611 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -44,7 +44,11 @@ const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; const GVLID = 617; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); -export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; + +const BB_PUBLICATION = 'adagio'; +const BB_RENDERER_DEFAULT = 'renderer'; +export const BB_RENDERER_URL = `https://${BB_PUBLICATION}.bbvms.com/r/$RENDERER.js`; + const MAX_SESS_DURATION = 30 * 60 * 1000; const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp'; const ADAGIO_PUBKEY_E = 65537; @@ -62,7 +66,7 @@ export const ORTB_VIDEO_PARAMS = { 'startdelay': (value) => isInteger(value), 'placement': (value) => isInteger(value), 'linearity': (value) => isInteger(value), - 'skip': (value) => isInteger(value), + 'skip': (value) => [1, 0].includes(value), 'skipmin': (value) => isInteger(value), 'skipafter': (value) => isInteger(value), 'sequence': (value) => isInteger(value), @@ -443,16 +447,6 @@ function _buildVideoBidRequest(bidRequest) { }); } -function _renderer(bid) { - bid.renderer.push(() => { - if (typeof window.ADAGIO.outstreamPlayer === 'function') { - window.ADAGIO.outstreamPlayer(bid); - } else { - logError(`${LOG_PREFIX} Adagio outstream player is not defined`); - } - }); -} - function _parseNativeBidResponse(bid) { if (!bid.admNative || !Array.isArray(bid.admNative.assets)) { logError(`${LOG_PREFIX} Invalid native response`); @@ -881,6 +875,77 @@ function storeRequestInAdagioNS(bidRequest) { }; } +// See https://support.bluebillywig.com/developers/vast-renderer/ +const OUTSTREAM_RENDERER = { + bootstrapPlayer: function(bid) { + const rendererCode = bid.outstreamRendererCode; + + const config = { + code: bid.adUnitCode, + }; + + if (bid.vastXml) { + config.vastXml = bid.vastXml; + } else if (bid.vastUrl) { + config.vastUrl = bid.vastUrl; + } + + if (!bid.vastXml && !bid.vastUrl) { + logError(`${LOG_PREFIX} no vastXml or vastUrl on bid`); + return; + } + + if (!window.bluebillywig || !window.bluebillywig.renderers || !window.bluebillywig.renderers.length) { + logError(`${LOG_PREFIX} no BlueBillywig renderers found!`); + return; + } + + const rendererId = this.getRendererId(BB_PUBLICATION, rendererCode); + + const override = {} + if (bid.skipOffset) { + override.skipOffset = bid.skipOffset.toString() + } + + const renderer = window.bluebillywig.renderers.find(bbr => bbr._id === rendererId); + if (!renderer) { + logError(`${LOG_PREFIX} couldn't find a renderer with ID ${rendererId}`); + return; + } + + const el = document.getElementById(bid.adUnitCode); + + renderer.bootstrap(config, el, override); + }, + newRenderer: function(adUnitCode, rendererCode) { + const rendererUrl = BB_RENDERER_URL.replace('$RENDERER', rendererCode); + + const renderer = Renderer.install({ + url: rendererUrl, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(this.outstreamRender); + } catch (err) { + logError(`${LOG_PREFIX} error trying to setRender`, err); + } + + return renderer; + }, + outstreamRender: function(bid) { + bid.renderer.push(() => { + OUTSTREAM_RENDERER.bootstrapPlayer(bid) + }); + }, + getRendererId: function(publication, renderer) { + // By convention, the RENDERER_ID is always the publication name (adagio) and the ad unit code (eg. renderer) + // joined together by a dash. It's used to identify the correct renderer instance on the page in case there's multiple. + return `${publication}-${renderer}`; + } +}; + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -1110,21 +1175,18 @@ export const spec = { const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context'); // Adagio SSP returns a `vastXml` only. No `vastUrl` nor `videoCacheKey`. if (!bidObj.vastUrl && bidObj.vastXml) { - bidObj.vastUrl = 'data:text/xml;charset=utf-8;base64,' + btoa(bidObj.vastXml.replace(/\\"/g, '"')); + bidObj.vastUrl = 'data:text/xml;charset=utf-8;base64,' + window.btoa(bidObj.vastXml.replace(/\\"/g, '"')); } if (mediaTypeContext === OUTSTREAM) { - bidObj.renderer = Renderer.install({ - id: bidObj.requestId, - adUnitCode: bidObj.adUnitCode, - url: bidObj.urlRenderer || RENDERER_URL, - config: { - ...deepAccess(bidReq, 'mediaTypes.video'), - ...deepAccess(bidObj, 'outstream', {}) - } - }); - - bidObj.renderer.setRender(_renderer); + bidObj.outstreamRendererCode = deepAccess(bidReq, 'params.rendererCode', BB_RENDERER_DEFAULT) + + if (deepAccess(bidReq, 'mediaTypes.video.skip')) { + const skipOffset = deepAccess(bidReq, 'mediaTypes.video.skipafter', 5) // default 5s. + bidObj.skipOffset = skipOffset + } + + bidObj.renderer = OUTSTREAM_RENDERER.newRenderer(bidObj.adUnitCode, bidObj.outstreamRendererCode); } } diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index adba79ddc96..7476482721b 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -9,7 +9,7 @@ import { spec, ENDPOINT, VERSION, - RENDERER_URL, + BB_RENDERER_URL, GlobalExchange } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; @@ -76,6 +76,7 @@ describe('Adagio bid adapter', () => { let adagioMock; let utilsMock; let sandbox; + let fakeRenderer; const fixtures = { getElementById(width, height, x, y) { @@ -1000,42 +1001,70 @@ describe('Adagio bid adapter', () => { context: 'outstream', playerSize: [[300, 250]], mimes: ['video/mp4'], - skip: true + skip: 1, + skipafter: 3 }; const serverResponseWithOutstream = utils.deepClone(serverResponse); serverResponseWithOutstream.body.bids[0].vastXml = ''; serverResponseWithOutstream.body.bids[0].mediaType = 'video'; - serverResponseWithOutstream.body.bids[0].outstream = { - bvwUrl: 'https://foo.baz', - impUrl: 'https://foo.bar' - }; - it('should set a renderer in video outstream context', function() { + const defaultRendererUrl = BB_RENDERER_URL.replace('$RENDERER', 'renderer'); + + it('should set related properties for video outstream context', function() { const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; - expect(bidResponse).to.have.any.keys('outstream', 'renderer', 'mediaType'); + expect(bidResponse).to.have.any.keys('renderer', 'mediaType'); expect(bidResponse.renderer).to.be.a('object'); - expect(bidResponse.renderer.url).to.equal(RENDERER_URL); - expect(bidResponse.renderer.config.bvwUrl).to.be.ok; - expect(bidResponse.renderer.config.impUrl).to.be.ok; + expect(bidResponse.renderer.url).to.equal(defaultRendererUrl); expect(bidResponse.renderer.loaded).to.not.be.ok; expect(bidResponse.width).to.equal(300); expect(bidResponse.height).to.equal(250); expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) }); - it('should execute Adagio outstreamPlayer if defined', function() { - window.ADAGIO.outstreamPlayer = sinon.stub(); + it('should execute Blue Billywig VAST Renderer bootstrap if defined', function() { + window.bluebillywig = { + renderers: [{ bootstrap: sinon.stub(), _id: 'adagio-renderer' }] + }; + + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; + executeRenderer(bidResponse.renderer, bidResponse) + sinon.assert.calledOnce(window.bluebillywig.renderers[0].bootstrap); + + delete window.bluebillywig; + }); + + it('Should logError if response does not have a vastXml or vastUrl', function() { + utilsMock.expects('logError').withExactArgs('Adagio: no vastXml or vastUrl on bid').once(); + + const localServerResponseWithOutstream = utils.deepClone(serverResponse); + localServerResponseWithOutstream.body.bids[0].mediaType = 'video'; + + const bidResponse = spec.interpretResponse(localServerResponseWithOutstream, bidRequestWithOutstream)[0]; + executeRenderer(bidResponse.renderer, bidResponse) + + utilsMock.verify(); + }) + + it('should logError if Blue Billywig API is not defined', function() { + utilsMock.expects('logError').withExactArgs('Adagio: no BlueBillywig renderers found!').once(); + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; executeRenderer(bidResponse.renderer, bidResponse) - sinon.assert.calledOnce(window.ADAGIO.outstreamPlayer); - delete window.ADAGIO.outstreamPlayer; + + utilsMock.verify(); }); - it('should logError if Adagio outstreamPlayer is not defined', function() { + it('should logError if correct renderer is not defined', function() { + window.bluebillywig = { renderers: [ { _id: 'adagio-another_renderer' } ] }; + + utilsMock.expects('logError').withExactArgs('Adagio: couldn\'t find a renderer with ID adagio-renderer').once(); + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; executeRenderer(bidResponse.renderer, bidResponse) - utilsMock.expects('logError').withExactArgs('Adagio: Adagio outstream player is not defined').once(); + + delete window.bluebillywig; + utilsMock.verify(); }); });