From 992ec2e9fe50a4beb8a12853dce47568c4bfed0a Mon Sep 17 00:00:00 2001 From: Olivier Sazos Date: Thu, 19 Nov 2020 07:26:36 +0100 Subject: [PATCH 1/5] adagioBidAdapter: add outstream video support --- modules/adagioBidAdapter.js | 126 ++++++++++++++++++++- modules/adagioBidAdapter.md | 13 +++ test/spec/modules/adagioBidAdapter_spec.js | 119 ++++++++++++++++++- 3 files changed, 253 insertions(+), 5 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index b20f832fd42..34539e65647 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -8,17 +8,21 @@ import sha256 from 'crypto-js/sha256.js'; import { getStorageManager } from '../src/storageManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { createEidsArray } from './userId/eids.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { OUTSTREAM } from '../src/video.js'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.5.0'; +export const VERSION = '2.6.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; -export const SUPPORTED_MEDIA_TYPES = ['banner']; +export const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; export const GVLID = 617; export const storage = getStorageManager(GVLID, 'adagio'); +export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0 @@ -27,6 +31,34 @@ t0b0lsHN+W4n9kitS/DZ/xnxWK/9vxhv0ZtL1LL/rwR5Mup7rmJbNtDoNBw4TIGj pV6EP3MTLosuUEpLaQIDAQAB -----END PUBLIC KEY-----`; +// This provide a whitelist and a basic validation +// of OpenRTB 2.5 options used by the Adagio SSP. +// https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf +export const ORTB_VIDEO_PARAMS = { + 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), + 'minduration': (value) => utils.isInteger(value), + 'maxduration': (value) => utils.isInteger(value), + 'protocols': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].includes(v)), + 'w': (value) => utils.isInteger(value), + 'h': (value) => utils.isInteger(value), + 'startdelay': (value) => utils.isInteger(value), + 'placement': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5].includes(v)), + 'linearity': (value) => [1, 2].includes(value), + 'skip': (value) => [0, 1].includes(value), + 'skipmin': (value) => utils.isInteger(value), + 'skipafter': (value) => utils.isInteger(value), + 'sequence': (value) => utils.isInteger(value), + 'battr': (value) => Array.isArray(value) && value.every(v => Array.from({length: 17}, (_, i) => i + 1).includes(v)), + 'maxextended': (value) => utils.isInteger(value), + 'minbitrate': (value) => utils.isInteger(value), + 'boxingallowed': (value) => [0, 1].includes(value), + 'playbackmethod': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].includes(v)), + 'playbackend': (value) => [1, 2, 3].includes(value), + 'delivery': (value) => [1, 2, 3].includes(value), + 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].includes(value), + 'api': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].includes(v)) +} + let currentWindow; export function adagioScriptFromLocalStorageCb(ls) { @@ -507,6 +539,21 @@ function getFeatures(bidRequest, bidderRequest) { return features; }; +function isRendererPreferredFromPublisher(bidRequest) { + // renderer defined at adUnit level + const adUnitRenderer = utils.deepAccess(bidRequest, 'renderer'); + const hasValidAdUnitRenderer = !!(adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render); + + // renderer defined at adUnit.mediaTypes level + const mediaTypeRenderer = utils.deepAccess(bidRequest, 'mediaTypes.video.renderer'); + const hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render) + + return !!( + (hasValidAdUnitRenderer && !(adUnitRenderer.backupOnly === true)) || + (hasValidMediaTypeRenderer && !(mediaTypeRenderer.backupOnly === true)) + ); +} + export const internal = { enqueue, getOrAddAdagioAdUnit, @@ -521,7 +568,8 @@ export const internal = { getRefererInfo, adagioScriptFromLocalStorageCb, getCurrentWindow, - canAccessTopWindow + canAccessTopWindow, + isRendererPreferredFromPublisher }; function _getGdprConsent(bidderRequest) { @@ -579,6 +627,58 @@ function _getEids(bidRequest) { } } +function _buildVideoBidRequest(bidRequest) { + const videoAdUnitParams = utils.deepAccess(bidRequest, 'mediaTypes.video', {}) + const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}) + const computedParams = {} + + // Special case for playerSize. + // Eeach props will be overrided if they are defined in config. + if (Array.isArray(videoAdUnitParams.playerSize)) { + const tempSize = (Array.isArray(videoAdUnitParams.playerSize[0])) ? videoAdUnitParams.playerSize[0] : videoAdUnitParams.playerSize; + computedParams.w = tempSize[0] + computedParams.h = tempSize[1] + } + + const videoParams = { + ...computedParams, + ...videoAdUnitParams, + ...videoBidderParams + }; + + if (videoParams.context && videoParams.context === OUTSTREAM) { + bidRequest.mediaTypes.video.playerName = (internal.isRendererPreferredFromPublisher(bidRequest)) ? 'other' : 'adagio'; + + if (bidRequest.mediaTypes.video.playerName === 'other') { + utils.logWarn(`${LOG_PREFIX} renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.`) + } + } + + // Only whitelisted OpenRTB options need to be validated. + // Other options will still remain in the `mediaTypes.video` object + // sent in the ad-request, but will be ignored by the SSP. + Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => { + if (videoParams.hasOwnProperty(paramName)) { + if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) { + bidRequest.mediaTypes.video[paramName] = videoParams[paramName] + } else { + delete bidRequest.mediaTypes.video[paramName]; + utils.logWarn(`${LOG_PREFIX} The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`) + } + } + }) +} + +function _renderer(bid) { + bid.renderer.push(() => { + if (typeof window.ADAGIO.outstreamPlayer === 'function') { + window.ADAGIO.outstreamPlayer(bid); + } else { + utils.logError(`${LOG_PREFIX} Adagio outstream player is not defined`); + } + }); +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -667,6 +767,11 @@ export const spec = { const eids = _getEids(validBidRequests[0]) || []; const adUnits = utils._map(validBidRequests, (bidRequest) => { bidRequest.features = internal.getFeatures(bidRequest, bidderRequest); + + if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + _buildVideoBidRequest(bidRequest) + } + return bidRequest; }); @@ -730,7 +835,22 @@ export const spec = { if (response.bids) { response.bids.forEach(bidObj => { const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); + if (bidReq) { + if (bidObj.mediaType === VIDEO && utils.deepAccess(bidReq, 'mediaTypes.video.context') === OUTSTREAM) { + bidObj.renderer = Renderer.install({ + id: bidObj.requestId, + adUnitCode: bidObj.adUnitCode, + url: bidObj.urlRenderer || RENDERER_URL, + config: { + ...utils.deepAccess(bidReq, 'mediaTypes.video'), + ...utils.deepAccess(bidObj, 'outstream', {}) + } + }); + + bidObj.renderer.setRender(_renderer); + } + bidObj.site = bidReq.params.site; bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index b34cc3fe37a..ca9992528e3 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -18,6 +18,15 @@ Connects to Adagio demand source to fetch bids. banner: { sizes: [[300, 250], [300, 600]], } + video: { + outstream: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + skip: 1 + // Other OpenRTB 2.5 video options… + } + } }, bids: [{ bidder: 'adagio', // Required @@ -38,6 +47,10 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false // Optional. Use it in case of Post-bid integration only. + video: { + skip: 0 + // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. + } } }] } diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 86fb2e7cbd3..365e35ea853 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,6 +1,16 @@ import find from 'core-js-pure/features/array/find.js'; import { expect } from 'chai'; -import { _features, internal as adagio, adagioScriptFromLocalStorageCb, getAdagioScript, storage, spec, ENDPOINT, VERSION } from '../../../modules/adagioBidAdapter.js'; +import { + _features, + internal as adagio, + adagioScriptFromLocalStorageCb, + getAdagioScript, + storage, + spec, + ENDPOINT, + VERSION, + RENDERER_URL +} from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; import { config } from 'src/config.js'; @@ -107,7 +117,7 @@ describe('Adagio bid adapter', () => { adagioMock = sinon.mock(adagio); utilsMock = sinon.mock(utils); - sandbox = sinon.sandbox.create(); + sandbox = sinon.createSandbox(); }); afterEach(() => { @@ -384,6 +394,81 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[0].features.url).to.not.exist; }); + describe('With video mediatype', function() { + context('Outstream video', function() { + it('should logWarn if user does not set renderer.backupOnly: true', function() { + sandbox.spy(utils, 'logWarn'); + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + renderer: { + url: 'https://url.tld', + render: () => true + } + } + }, + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + const request = spec.buildRequests([bid01], bidderRequest)[0]; + + expect(request.data.adUnits[0].mediaTypes.video.playerName).to.equal('other'); + sinon.assert.calledWith(utils.logWarn, 'Adagio: renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.') + }); + }); + + it('Update mediaTypes.video with OpenRTB options. Validate and sanitize whitelisted OpenRTB', function() { + sandbox.spy(utils, 'logWarn'); + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + api: 5, // will be removed because invalid + playbackmethod: [7], // will be removed because invalid + } + }, + }).withParams({ + // options in video, will overide + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: [3], + protocols: [8] + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + context: 'outstream', + playerSize: [[300, 250]], + playerName: 'adagio', + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: [3], + protocols: [8], + w: 300, + h: 250 + } + + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].data.adUnits[0].mediaTypes.video).to.deep.equal(expected); + sinon.assert.calledTwice(utils.logWarn); + }) + }); + describe('with sChain', function() { const schain = { ver: '1.0', @@ -732,6 +817,36 @@ describe('Adagio bid adapter', () => { utilsMock.verify(); }); + + describe('Response with video outstream', () => { + const bidRequestWithOutstream = utils.deepClone(bidRequest); + bidRequestWithOutstream.data.adUnits[0].mediaTypes.video = { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + skip: true + } + + const serverResponseWithOutstream = utils.deepClone(serverResponse); + serverResponseWithOutstream.body.bids[0].ad = 'https://foo.bar/vast.xml'; + 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 bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; + expect(bidResponse).to.have.any.keys('outstream', '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.loaded).to.not.be.ok + expect(bidResponse.width).to.equal(300) + expect(bidResponse.height).to.equal(250) + }); + }); }); describe('getUserSyncs()', function() { From 05ada4fb8a543f3f7b26fa2aa97bb0843527afc0 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 26 Nov 2020 15:11:13 +0100 Subject: [PATCH 2/5] Lint: semi rule consistency --- modules/adagioBidAdapter.js | 52 +++++++++++----------- test/spec/modules/adagioBidAdapter_spec.js | 44 +++++++++--------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 34539e65647..3392982dcc8 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -2,7 +2,7 @@ import find from 'core-js-pure/features/array/find.js'; import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { loadExternalScript } from '../src/adloader.js' +import { loadExternalScript } from '../src/adloader.js'; import JSEncrypt from 'jsencrypt/bin/jsencrypt.js'; import sha256 from 'crypto-js/sha256.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -57,7 +57,7 @@ export const ORTB_VIDEO_PARAMS = { 'delivery': (value) => [1, 2, 3].includes(value), 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].includes(value), 'api': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].includes(v)) -} +}; let currentWindow; @@ -96,7 +96,7 @@ export function adagioScriptFromLocalStorageCb(ls) { export function getAdagioScript() { storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (ls) => { - internal.adagioScriptFromLocalStorageCb(ls) + internal.adagioScriptFromLocalStorageCb(ls); }); storage.localStorageIsEnabled(isValid => { @@ -367,7 +367,7 @@ function getOrAddAdagioAdUnit(adUnitCode) { w.ADAGIO = w.ADAGIO || {}; if (w.ADAGIO.adUnits[adUnitCode]) { - return w.ADAGIO.adUnits[adUnitCode] + return w.ADAGIO.adUnits[adUnitCode]; } return w.ADAGIO.adUnits[adUnitCode] = {}; @@ -467,7 +467,7 @@ function getElementFromTopWindow(element, currentWindow) { }; function autoDetectAdUnitElementId(adUnitCode) { - const autoDetectedAdUnit = utils.getGptSlotInfoForAdUnitCode(adUnitCode) + const autoDetectedAdUnit = utils.getGptSlotInfoForAdUnitCode(adUnitCode); let adUnitElementId = null; if (autoDetectedAdUnit && autoDetectedAdUnit.divId) { @@ -482,16 +482,16 @@ function autoDetectEnvironment() { let environment; switch (device) { case 2: - environment = 'desktop' + environment = 'desktop'; break; case 4: - environment = 'mobile' + environment = 'mobile'; break; case 5: - environment = 'tablet' + environment = 'tablet'; break; }; - return environment + return environment; }; function getFeatures(bidRequest, bidderRequest) { @@ -546,7 +546,7 @@ function isRendererPreferredFromPublisher(bidRequest) { // renderer defined at adUnit.mediaTypes level const mediaTypeRenderer = utils.deepAccess(bidRequest, 'mediaTypes.video.renderer'); - const hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render) + const hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render); return !!( (hasValidAdUnitRenderer && !(adUnitRenderer.backupOnly === true)) || @@ -587,7 +587,7 @@ function _getGdprConsent(bidderRequest) { const consent = {}; if (apiVersion !== undefined) { - consent.apiVersion = apiVersion + consent.apiVersion = apiVersion; } if (consentString !== undefined) { @@ -623,21 +623,21 @@ function _getSchain(bidRequest) { function _getEids(bidRequest) { if (utils.deepAccess(bidRequest, 'userId')) { - return createEidsArray(bidRequest.userId) + return createEidsArray(bidRequest.userId); } } function _buildVideoBidRequest(bidRequest) { - const videoAdUnitParams = utils.deepAccess(bidRequest, 'mediaTypes.video', {}) - const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}) - const computedParams = {} + const videoAdUnitParams = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}); + const computedParams = {}; // Special case for playerSize. // Eeach props will be overrided if they are defined in config. if (Array.isArray(videoAdUnitParams.playerSize)) { const tempSize = (Array.isArray(videoAdUnitParams.playerSize[0])) ? videoAdUnitParams.playerSize[0] : videoAdUnitParams.playerSize; - computedParams.w = tempSize[0] - computedParams.h = tempSize[1] + computedParams.w = tempSize[0]; + computedParams.h = tempSize[1]; } const videoParams = { @@ -650,7 +650,7 @@ function _buildVideoBidRequest(bidRequest) { bidRequest.mediaTypes.video.playerName = (internal.isRendererPreferredFromPublisher(bidRequest)) ? 'other' : 'adagio'; if (bidRequest.mediaTypes.video.playerName === 'other') { - utils.logWarn(`${LOG_PREFIX} renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.`) + utils.logWarn(`${LOG_PREFIX} renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.`); } } @@ -660,13 +660,13 @@ function _buildVideoBidRequest(bidRequest) { Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => { if (videoParams.hasOwnProperty(paramName)) { if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) { - bidRequest.mediaTypes.video[paramName] = videoParams[paramName] + bidRequest.mediaTypes.video[paramName] = videoParams[paramName]; } else { delete bidRequest.mediaTypes.video[paramName]; - utils.logWarn(`${LOG_PREFIX} The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`) + utils.logWarn(`${LOG_PREFIX} The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`); } } - }) + }); } function _renderer(bid) { @@ -700,7 +700,7 @@ export const spec = { ...params, adUnitElementId, environment - } + }; const debugData = () => ({ action: 'pb-dbg', @@ -731,7 +731,7 @@ export const spec = { // Store adUnits config. // If an adUnitCode has already been stored, it will be replaced. w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode) + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode); w.ADAGIO.pbjsAdUnits.push({ code: adUnitCode, mediaTypes: mediaTypes || {}, @@ -769,7 +769,7 @@ export const spec = { bidRequest.features = internal.getFeatures(bidRequest, bidderRequest); if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { - _buildVideoBidRequest(bidRequest) + _buildVideoBidRequest(bidRequest); } return bidRequest; @@ -779,7 +779,7 @@ export const spec = { const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => { adUnit.params.organizationId = adUnit.params.organizationId.toString(); - groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || [] + groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || []; groupedAdUnits[adUnit.params.organizationId].push(adUnit); return groupedAdUnits; @@ -814,7 +814,7 @@ export const spec = { options: { contentType: 'text/plain' } - } + }; }); return requests; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 365e35ea853..a0aec18e7be 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -264,7 +264,7 @@ describe('Adagio bid adapter', () => { // replace by the values defined in beforeEach window.top.ADAGIO = { ...window.ADAGIO - } + }; spec.isBidRequestValid(bid01); spec.isBidRequestValid(bid02); @@ -416,7 +416,7 @@ describe('Adagio bid adapter', () => { const request = spec.buildRequests([bid01], bidderRequest)[0]; expect(request.data.adUnits[0].mediaTypes.video.playerName).to.equal('other'); - sinon.assert.calledWith(utils.logWarn, 'Adagio: renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.') + sinon.assert.calledWith(utils.logWarn, 'Adagio: renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.'); }); }); @@ -460,13 +460,13 @@ describe('Adagio bid adapter', () => { protocols: [8], w: 300, h: 250 - } + }; const requests = spec.buildRequests([bid01], bidderRequest); expect(requests).to.have.lengthOf(1); expect(requests[0].data.adUnits[0].mediaTypes.video).to.deep.equal(expected); sinon.assert.calledTwice(utils.logWarn); - }) + }); }); describe('with sChain', function() { @@ -635,7 +635,7 @@ describe('Adagio bid adapter', () => { describe('with USPrivacy', function() { const bid01 = new BidRequestBuilder().withParams().build(); - const consent = 'Y11N' + const consent = 'Y11N'; it('should send the USPrivacy "ccpa.uspConsent" in the request', function () { const bidderRequest = new BidderRequestBuilder({ @@ -664,7 +664,7 @@ describe('Adagio bid adapter', () => { const userId = { sharedid: {id: '01EAJWWNEPN3CYMM5N8M5VXY22', third: '01EAJWWNEPN3CYMM5N8M5VXY22'}, unsuported: '666' - } + }; it('should send "user.eids" in the request for Prebid.js supported modules only', function() { const bid01 = new BidRequestBuilder({ @@ -686,11 +686,11 @@ describe('Adagio bid adapter', () => { id: '01EAJWWNEPN3CYMM5N8M5VXY22' } ] - }] + }]; - expect(requests[0].data.user.eids).to.have.lengthOf(1) - expect(requests[0].data.user.eids).to.deep.equal(expected) - }) + expect(requests[0].data.user.eids).to.have.lengthOf(1); + expect(requests[0].data.user.eids).to.deep.equal(expected); + }); it('should send an empty "user.eids" array in the request if userId module is unsupported', function() { const bid01 = new BidRequestBuilder({ @@ -703,9 +703,9 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.user.eids).to.be.empty - }) - }) + expect(requests[0].data.user.eids).to.be.empty; + }); + }); }); describe('interpretResponse()', function() { @@ -825,7 +825,7 @@ describe('Adagio bid adapter', () => { playerSize: [[300, 250]], mimes: ['video/mp4'], skip: true - } + }; const serverResponseWithOutstream = utils.deepClone(serverResponse); serverResponseWithOutstream.body.bids[0].ad = 'https://foo.bar/vast.xml'; @@ -838,13 +838,13 @@ describe('Adagio bid adapter', () => { it('should set a renderer in video outstream context', function() { const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; expect(bidResponse).to.have.any.keys('outstream', '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.loaded).to.not.be.ok - expect(bidResponse.width).to.equal(300) - expect(bidResponse.height).to.equal(250) + 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.loaded).to.not.be.ok; + expect(bidResponse.width).to.equal(300); + expect(bidResponse.height).to.equal(250); }); }); }); @@ -1310,7 +1310,7 @@ describe('Adagio bid adapter', () => { expect(loadExternalScript.called).to.be.false; expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; - }) + }); }); it('should verify valid hash with valid script', function () { From 054f138f1b9f52093d3ce3039945431381676db7 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 26 Nov 2020 17:20:58 +0100 Subject: [PATCH 3/5] IE11 support: remove Array.includes() --- modules/adagioBidAdapter.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 3392982dcc8..8fd39831efa 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -38,25 +38,26 @@ export const ORTB_VIDEO_PARAMS = { 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), 'minduration': (value) => utils.isInteger(value), 'maxduration': (value) => utils.isInteger(value), - 'protocols': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].includes(v)), + 'protocols': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].indexOf(v) !== -1), 'w': (value) => utils.isInteger(value), 'h': (value) => utils.isInteger(value), 'startdelay': (value) => utils.isInteger(value), - 'placement': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5].includes(v)), - 'linearity': (value) => [1, 2].includes(value), - 'skip': (value) => [0, 1].includes(value), + 'placement': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5].indexOf(v) !== -1), + 'linearity': (value) => [1, 2].indexOf(value) !== -1, + 'skip': (value) => [0, 1].indexOf(value) !== -1, 'skipmin': (value) => utils.isInteger(value), 'skipafter': (value) => utils.isInteger(value), 'sequence': (value) => utils.isInteger(value), - 'battr': (value) => Array.isArray(value) && value.every(v => Array.from({length: 17}, (_, i) => i + 1).includes(v)), + 'battr': (value) => Array.isArray(value) && value.every(v => Array.from({length: 17}, (_, i) => i + 1).indexOf(v) !== -1), 'maxextended': (value) => utils.isInteger(value), 'minbitrate': (value) => utils.isInteger(value), - 'boxingallowed': (value) => [0, 1].includes(value), - 'playbackmethod': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].includes(v)), - 'playbackend': (value) => [1, 2, 3].includes(value), - 'delivery': (value) => [1, 2, 3].includes(value), - 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].includes(value), - 'api': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].includes(v)) + 'maxbitrate': (value) => utils.isInteger(value), + 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, + 'playbackmethod': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1), + 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, + 'delivery': (value) => [1, 2, 3].indexOf(value) !== -1, + 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].indexOf(value) !== -1, + 'api': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1) }; let currentWindow; From 012c6472d8dc457ac1e16ea8fb8e616a39e229b4 Mon Sep 17 00:00:00 2001 From: Olivier Date: Fri, 27 Nov 2020 10:37:47 +0100 Subject: [PATCH 4/5] Generate bidResponse.vastUrl based on vastXml dataUri encoding --- modules/adagioBidAdapter.js | 32 ++++++++++++++-------- test/spec/modules/adagioBidAdapter_spec.js | 3 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 8fd39831efa..cab233d5387 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -838,18 +838,26 @@ export const spec = { const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); if (bidReq) { - if (bidObj.mediaType === VIDEO && utils.deepAccess(bidReq, 'mediaTypes.video.context') === OUTSTREAM) { - bidObj.renderer = Renderer.install({ - id: bidObj.requestId, - adUnitCode: bidObj.adUnitCode, - url: bidObj.urlRenderer || RENDERER_URL, - config: { - ...utils.deepAccess(bidReq, 'mediaTypes.video'), - ...utils.deepAccess(bidObj, 'outstream', {}) - } - }); - - bidObj.renderer.setRender(_renderer); + if (bidObj.mediaType === VIDEO) { + const mediaTypeContext = utils.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, '"')); + } + + if (mediaTypeContext === OUTSTREAM) { + bidObj.renderer = Renderer.install({ + id: bidObj.requestId, + adUnitCode: bidObj.adUnitCode, + url: bidObj.urlRenderer || RENDERER_URL, + config: { + ...utils.deepAccess(bidReq, 'mediaTypes.video'), + ...utils.deepAccess(bidObj, 'outstream', {}) + } + }); + + bidObj.renderer.setRender(_renderer); + } } bidObj.site = bidReq.params.site; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index a0aec18e7be..2cf97a1129b 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -828,7 +828,7 @@ describe('Adagio bid adapter', () => { }; const serverResponseWithOutstream = utils.deepClone(serverResponse); - serverResponseWithOutstream.body.bids[0].ad = 'https://foo.bar/vast.xml'; + serverResponseWithOutstream.body.bids[0].vastXml = ''; serverResponseWithOutstream.body.bids[0].mediaType = 'video'; serverResponseWithOutstream.body.bids[0].outstream = { bvwUrl: 'https://foo.baz', @@ -845,6 +845,7 @@ describe('Adagio bid adapter', () => { 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;/) }); }); }); From 6fc58c13be6501e562595b7bdb3eb6d9a9507491 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 3 Dec 2020 15:50:38 +0100 Subject: [PATCH 5/5] Update .md file --- modules/adagioBidAdapter.md | 59 +++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index ca9992528e3..c55a24f1115 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -18,23 +18,52 @@ Connects to Adagio demand source to fetch bids. banner: { sizes: [[300, 250], [300, 600]], } - video: { - outstream: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'], - skip: 1 - // Other OpenRTB 2.5 video options… + }, + bids: [{ + bidder: 'adagio', // Required + params: { + organizationId: '1002', // Required - Organization ID provided by Adagio. + site: 'adagio-io', // Required - Site Name provided by Adagio. + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_outstream', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. + environment: 'mobile', // Recommended. Environment where the page is displayed. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + postBid: false, // Optional. Use it in case of Post-bid integration only. + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 } } + }] + }, + { + code: 'article_outstream', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + skip: 1 + // Other OpenRTB 2.5 video options… + } }, bids: [{ bidder: 'adagio', // Required params: { - organizationId: '0', // Required - Organization ID provided by Adagio. - site: 'news-of-the-day', // Required - Site Name provided by Adagio. - placement: 'ban_atf', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. - adUnitElementId: 'dfp_banniere_atf', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + organizationId: '1002', // Required - Organization ID provided by Adagio. + site: 'adagio-io', // Required - Site Name provided by Adagio. + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_outstream', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. // The following params are limited to 30 characters, // and can only contain the following characters: @@ -46,10 +75,15 @@ Connects to Adagio demand source to fetch bids. environment: 'mobile', // Recommended. Environment where the page is displayed. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. - postBid: false // Optional. Use it in case of Post-bid integration only. + postBid: false, // Optional. Use it in case of Post-bid integration only. video: { skip: 0 // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. + }, + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 } } }] @@ -101,5 +135,4 @@ Connects to Adagio demand source to fetch bids. ] } } - ```