Skip to content

Commit

Permalink
Rise Bid Adapters: native and multiformat support (#12653)
Browse files Browse the repository at this point in the history
* RPRD-1638: Add support for Native media type and multi-format bid requests in `index.js`, Populate the changes on `rise/minutemedia/openweb/shinez/stn/BidAdapter.js`, Update all relevant `***BidAdapter_spec.js`, Update all relevant `***BidAdapter.md`, Keep backwards compatibility, Move `mimes` and `api` determination to VIDEO media type as its only relevant to video.

* RPRD-1638: Move all `spec` code duplication to `index.js` and populate across all maintained adapters via `makeBaseSpec` factory function, Move all rise related constants to `constants.js`.

* RPRD-1638: Align with seller response.

* RPRD-1638: fix cr comments

* RPRD-1638: Align tests with native response

* circle ci test
  • Loading branch information
michachen authored Jan 15, 2025
1 parent c52ee5d commit b5f9de7
Show file tree
Hide file tree
Showing 17 changed files with 840 additions and 495 deletions.
20 changes: 20 additions & 0 deletions libraries/riseUtils/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {BANNER, NATIVE, VIDEO} from '../../src/mediaTypes.js';

const OW_GVLID = 280
export const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE];
export const ADAPTER_VERSION = '7.0.0';
export const DEFAULT_TTL = 360;
export const DEFAULT_CURRENCY = 'USD';
export const BASE_URL = 'https://hb.yellowblue.io/';
export const BIDDER_CODE = 'rise';
export const DEFAULT_GVLID = 1043;

export const ALIASES = [
{ code: 'risexchange', gvlid: DEFAULT_GVLID },
{ code: 'openwebxchange', gvlid: OW_GVLID }
]

export const MODES = {
PRODUCTION: 'hb-multi',
TEST: 'hb-multi-test'
};
181 changes: 143 additions & 38 deletions libraries/riseUtils/index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,135 @@
import {
isArray,
isFn,
contains,
deepAccess,
getBidIdParameter,
isArray,
isEmpty,
contains,
isFn,
isInteger,
getBidIdParameter,
isPlainObject
isPlainObject,
logInfo,
triggerPixel
} from '../../src/utils.js';
import { BANNER, VIDEO } from '../../src/mediaTypes.js';
import {BANNER, NATIVE, VIDEO} from '../../src/mediaTypes.js';
import {config} from '../../src/config.js';
import {ADAPTER_VERSION, DEFAULT_CURRENCY, DEFAULT_TTL, SUPPORTED_AD_TYPES} from './constants.js';

export const makeBaseSpec = (baseUrl, modes) => {
return {
version: ADAPTER_VERSION,
supportedMediaTypes: SUPPORTED_AD_TYPES,
buildRequests: function (validBidRequests, bidderRequest) {
const combinedRequestsObject = {};

// use data from the first bid, to create the general params for all bids
const generalObject = validBidRequests[0];
const testMode = generalObject.params.testMode;
const rtbDomain = generalObject.params.rtbDomain || baseUrl;

combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest);
combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest);

return {
method: 'POST',
url: getEndpoint(testMode, rtbDomain, modes),
data: combinedRequestsObject
}
},
interpretResponse: function ({ body }) {
const bidResponses = [];

if (body.bids) {
body.bids.forEach(adUnit => {
const bidResponse = buildBidResponse(adUnit);
bidResponses.push(bidResponse);
});
}

return bidResponses;
},
getUserSyncs: function (syncOptions, serverResponses) {
const syncs = [];
for (const response of serverResponses) {
if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) {
syncs.push({
type: 'iframe',
url: deepAccess(response, 'body.params.userSyncURL')
});
}
if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) {
const pixels = response.body.params.userSyncPixels.map(pixel => {
return {
type: 'image',
url: pixel
}
});
syncs.push(...pixels);
}
}
return syncs;
},
onBidWon: function (bid) {
if (bid == null) {
return;
}

logInfo('onBidWon:', bid);
if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) {
triggerPixel(bid.nurl);
}
}
}
}

export function getBidRequestMediaTypes(bidRequest) {
const mediaTypes = deepAccess(bidRequest, 'mediaTypes');
if (isPlainObject(mediaTypes)) {
return Object.keys(mediaTypes);
}
return [];
}

export function getPos(bidRequest) {
const mediaTypes = getBidRequestMediaTypes(bidRequest);
const firstMediaType = mediaTypes[0];
if (mediaTypes.length === 1) {
return deepAccess(bidRequest, `mediaTypes.${firstMediaType}.pos`);
}
}

export function getName(bidRequest) {
const mediaTypes = getBidRequestMediaTypes(bidRequest);
const firstMediaType = mediaTypes[0];
if (mediaTypes.length === 1) {
return deepAccess(bidRequest, `mediaTypes.${firstMediaType}.name`);
}
}

export function getFloor(bid, mediaType) {
export function getFloor(bid) {
if (!isFn(bid.getFloor)) {
return 0;
}

const mediaTypes = getBidRequestMediaTypes(bid)
const firstMediaType = mediaTypes[0];

let floorResult = bid.getFloor({
currency: 'USD',
mediaType: mediaType,
mediaType: mediaTypes.length === 1 ? firstMediaType : '*',
size: '*'
});
return isPlainObject(floorResult) && floorResult.currency === 'USD' && floorResult.floor ? floorResult.floor : 0;
}

export function getSizesArray(bid, mediaType) {
export function getSizesArray(bid) {
let sizesArray = [];

if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) {
sizesArray = bid.mediaTypes[mediaType].sizes;
} else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) {
const mediaTypes = getBidRequestMediaTypes(bid);
const firstMediaType = mediaTypes[0];

if (mediaTypes.length === 1 && deepAccess(bid, `mediaTypes.${firstMediaType}.sizes`)) {
sizesArray = bid.mediaTypes[firstMediaType].sizes;
} else if (isArray(bid.sizes) && bid.sizes.length > 0) {
sizesArray = bid.sizes;
}

Expand Down Expand Up @@ -111,27 +212,26 @@ export function generateBidsParams(validBidRequests, bidderRequest) {

export function generateBidParameters(bid, bidderRequest) {
const { params } = bid;
const mediaType = isBanner(bid) ? BANNER : VIDEO;
const sizesArray = getSizesArray(bid, mediaType);
const mediaTypes = getBidRequestMediaTypes(bid);

if (isNaN(params.floorPrice)) {
params.floorPrice = 0;
}

const bidObject = {
mediaType,
mediaType: mediaTypes.join(','),
adUnitCode: getBidIdParameter('adUnitCode', bid),
sizes: sizesArray,
floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice),
sizes: getSizesArray(bid),
floorPrice: Math.max(getFloor(bid), params.floorPrice),
bidId: getBidIdParameter('bidId', bid),
loop: bid.bidderRequestsCount || 0,
bidderRequestId: getBidIdParameter('bidderRequestId', bid),
transactionId: bid.ortb2Imp?.ext?.tid || '',
coppa: 0,
};

const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`);
if (pos) {
const pos = getPos(bid);
if (isInteger(pos)) {
bidObject.pos = pos;
}

Expand All @@ -140,21 +240,11 @@ export function generateBidParameters(bid, bidderRequest) {
bidObject.gpid = gpid;
}

const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`);
const placementId = params.placementId || getName(bid);
if (placementId) {
bidObject.placementId = placementId;
}

const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`);
if (mimes) {
bidObject.mimes = mimes;
}

const api = deepAccess(bid, `mediaTypes.${mediaType}.api`);
if (api) {
bidObject.api = api;
}

const sua = deepAccess(bid, `ortb2.device.sua`);
if (sua) {
bidObject.sua = sua;
Expand All @@ -165,11 +255,11 @@ export function generateBidParameters(bid, bidderRequest) {
bidObject.coppa = 1;
}

if (mediaType === VIDEO) {
if (mediaTypes.includes(VIDEO)) {
const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`);
let playbackMethodValue;

if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) {
if (isArray(playbackMethod) && isInteger(playbackMethod[0])) {
playbackMethodValue = playbackMethod[0];
} else if (isInteger(playbackMethod)) {
playbackMethodValue = playbackMethod;
Expand Down Expand Up @@ -213,19 +303,36 @@ export function generateBidParameters(bid, bidderRequest) {
if (plcmt) {
bidObject.plcmt = plcmt;
}

const mimes = deepAccess(bid, `mediaTypes.video.mimes`);
if (mimes) {
bidObject.mimes = mimes;
}

const api = deepAccess(bid, `mediaTypes.video.api`);
if (api) {
bidObject.api = api;
}
}

if (mediaTypes.includes(NATIVE)) {
const nativeOrtbRequest = deepAccess(bid, `nativeOrtbRequest`);
if (nativeOrtbRequest) {
bidObject.nativeOrtbRequest = nativeOrtbRequest;
}
}

return bidObject;
}

export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) {
export function buildBidResponse(adUnit) {
const bidResponse = {
requestId: adUnit.requestId,
cpm: adUnit.cpm,
currency: adUnit.currency || DEFAULT_CURRENCY,
width: adUnit.width,
height: adUnit.height,
ttl: adUnit.ttl || TTL,
ttl: adUnit.ttl || DEFAULT_TTL,
creativeId: adUnit.creativeId,
netRevenue: adUnit.netRevenue || true,
nurl: adUnit.nurl,
Expand All @@ -239,6 +346,8 @@ export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) {
bidResponse.vastXml = adUnit.vastXml;
} else if (adUnit.mediaType === BANNER) {
bidResponse.ad = adUnit.ad;
} else if (adUnit.mediaType === NATIVE) {
bidResponse.native = {ortb: adUnit.native};
}

if (adUnit.adomain && adUnit.adomain.length) {
Expand All @@ -248,10 +357,6 @@ export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) {
return bidResponse;
}

function isBanner(bid) {
return bid.mediaTypes && bid.mediaTypes.banner;
}

export function generateGeneralParams(generalObject, bidderRequest, adapterVersion) {
const domain = window.location.hostname;
const { syncEnabled, filterSettings } = config.getConfig('userSync') || {};
Expand Down
Loading

0 comments on commit b5f9de7

Please sign in to comment.