Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adagioBidAdapter: add Video support #6038

Merged
merged 5 commits into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 146 additions & 17 deletions modules/adagioBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@ 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';
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
Expand All @@ -27,6 +31,35 @@ 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].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].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).indexOf(v) !== -1),
'maxextended': (value) => utils.isInteger(value),
'minbitrate': (value) => utils.isInteger(value),
'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;

export function adagioScriptFromLocalStorageCb(ls) {
Expand Down Expand Up @@ -64,7 +97,7 @@ export function adagioScriptFromLocalStorageCb(ls) {

export function getAdagioScript() {
storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (ls) => {
internal.adagioScriptFromLocalStorageCb(ls)
internal.adagioScriptFromLocalStorageCb(ls);
});

storage.localStorageIsEnabled(isValid => {
Expand Down Expand Up @@ -335,7 +368,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] = {};
Expand Down Expand Up @@ -435,7 +468,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) {
Expand All @@ -450,16 +483,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) {
Expand Down Expand Up @@ -507,6 +540,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,
Expand All @@ -521,7 +569,8 @@ export const internal = {
getRefererInfo,
adagioScriptFromLocalStorageCb,
getCurrentWindow,
canAccessTopWindow
canAccessTopWindow,
isRendererPreferredFromPublisher
};

function _getGdprConsent(bidderRequest) {
Expand All @@ -539,7 +588,7 @@ function _getGdprConsent(bidderRequest) {
const consent = {};

if (apiVersion !== undefined) {
consent.apiVersion = apiVersion
consent.apiVersion = apiVersion;
}

if (consentString !== undefined) {
Expand Down Expand Up @@ -575,10 +624,62 @@ 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 = {};

// 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,
Expand All @@ -600,7 +701,7 @@ export const spec = {
...params,
adUnitElementId,
environment
}
};

const debugData = () => ({
action: 'pb-dbg',
Expand Down Expand Up @@ -631,7 +732,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 || {},
Expand Down Expand Up @@ -667,14 +768,19 @@ 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;
});

// Group ad units by organizationId
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;
Expand Down Expand Up @@ -709,7 +815,7 @@ export const spec = {
options: {
contentType: 'text/plain'
}
}
};
});

return requests;
Expand All @@ -730,7 +836,30 @@ 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) {
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;
bidObj.placement = bidReq.params.placement;
bidObj.pagetype = bidReq.params.pagetype;
Expand Down
58 changes: 52 additions & 6 deletions modules/adagioBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ Connects to Adagio demand source to fetch bids.
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:
Expand All @@ -37,7 +37,54 @@ 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.
// 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: '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.
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
}
}
}]
}
Expand Down Expand Up @@ -88,5 +135,4 @@ Connects to Adagio demand source to fetch bids.
]
}
}

```
Loading