Skip to content

Commit

Permalink
updates to prebidServerBidAdapter for native support
Browse files Browse the repository at this point in the history
  • Loading branch information
snapwich committed Jun 26, 2019
1 parent 81502c7 commit e413c45
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 81 deletions.
140 changes: 72 additions & 68 deletions modules/prebidServerBidAdapter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { processNativeAdUnitParams } from 'src/native';
import { isValid } from '../../src/adapters/bidderFactory';
import events from '../../src/events';
import includes from 'core-js/library/fn/array/includes';
import find from 'core-js/library/fn/array/find';
import { S2S_VENDORS } from './config.js';

const getConfig = config.getConfig;
Expand Down Expand Up @@ -433,20 +432,33 @@ const LEGACY_PROTOCOL = {
}
};

function getNativeImgId(type) {
return ({
'icon': 1,
'image': 3
})[type];
}
// https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40
let nativeDataIdMap = {
sponsoredBy: 1, // sponsored
body: 2, // desc
rating: 3,
likes: 4,
downloads: 5,
price: 6,
salePrice: 7,
phone: 8,
address: 9,
body2: 10, // desc2
cta: 12 // ctatext
};
let nativeDataNames = Object.keys(nativeDataIdMap);

function getNativeDataId(type) {
return ({
sponsoredBy: 1,
body: 2,
cta: 12
})[type];
}
let nativeImgIdMap = {
icon: 1,
image: 3
};

// enable reverse lookup
[nativeDataIdMap, nativeImgIdMap].forEach(map => {
Object.keys(map).forEach(key => {
map[map[key]] = key;
});
});

/*
* Protocol spec for OpenRTB endpoint
Expand Down Expand Up @@ -501,61 +513,58 @@ const OPEN_RTB_PROTOCOL = {

const nativeParams = processNativeAdUnitParams(utils.deepAccess(adUnit, 'mediaTypes.native'));
if (nativeParams) {
let assetCounter = 200;
try {
mediaTypes['native'] = {
request: JSON.stringify({
// TODO: determine if following OpenRTB recommended properties can be defined or are needed
// context: int,
// plcmttype: int,
// TODO: determine best way to pass these and if we allow defaults
context: nativeParams.context || 1,
plcmttype: nativeParams.plcmttype || 1,
// TODO: figure out how to support privacy field
// privacy: int
assets: Object.keys(nativeParams).reduce((assets, type, index) => {
let params = nativeParams[type];

function newAsset(obj) {
return Object.assign({
id: assetCounter++,
required: params.required ? 1 : 0
}, obj ? utils.cleanObj(obj) : {});
}

if (
(type === 'image' || type === 'icon')
) {
let imgTypeId = getNativeImgId(type);

if (Array.isArray(params.sizes)) {
let sizes = params.sizes;
if (!Array.isArray(sizes[0])) { // support single size or an array of sizes
sizes = [sizes]
let imgTypeId = nativeImgIdMap[type];
let asset = utils.cleanObj({
type: imgTypeId,
w: utils.deepAccess(params, 'sizes.0'),
h: utils.deepAccess(params, 'sizes.1'),
wmin: utils.deepAccess(params, 'aspect_ratios.0.min_width')
});
if (!(asset.w || asset.wmin)) {
throw 'invalid img sizes (must provided sizes or aspect_ratios)';
}
if (Array.isArray(params.aspect_ratios)) {
// pass aspect_ratios as ext data I guess?
asset.ext = {
aspectratios: params.aspect_ratios.map(
ratio => `${ratio.ratio_width}:${ratio.ratio_height}`
)
}
// TODO: Currently creating multiple image assets for each size. Figure out if this is correct!
sizes.forEach(size => {
assets.push(newAsset({
img: {
type: imgTypeId,
w: size[0],
h: size[1]
}
}));
});
// TODO: Figure out what to do about aspect_ratios, ext perhaps?
// } else if (Array.isArray(params.aspect_ratios)) {
} else {
assets.push(newAsset({
img: {
type: imgTypeId
}
}));
}
assets.push(newAsset({
img: asset
}));
} else if (type === 'title') {
if (!params.len) {
throw utils.logWarn('invalid title.len');
}
assets.push(newAsset({
title: {
len: params.len
}
}));
} else {
let dataAssetTypeId = getNativeDataId(type);
let dataAssetTypeId = nativeDataIdMap[type];
if (dataAssetTypeId) {
assets.push(newAsset({
data: {
Expand All @@ -565,14 +574,13 @@ const OPEN_RTB_PROTOCOL = {
}))
}
}

return assets;
}, [])
}),
ver: '1.2'
}
} catch (e) {
throw e;
utils.logError('error creating native request: ' + String(e))
}
}

Expand Down Expand Up @@ -739,32 +747,28 @@ const OPEN_RTB_PROTOCOL = {
}

if (utils.isPlainObject(adm) && Array.isArray(adm.assets)) {
bidObject.native = utils.cleanObj({
image: utils.pick(
utils.deepAccess(find(adm.assets, asset => asset.img && asset.img.type === getNativeImgId('image')), 'img'),
['url', 'w as width', 'h as height']
),
icon: utils.pick(
utils.deepAccess(find(adm.assets, asset => asset.img && asset.img.type === getNativeImgId('icon')), 'img'),
['url', 'w as width', 'h as height']
),
title: utils.deepAccess(
find(adm.assets, asset => asset.title),
'title.text'
),
body: utils.deepAccess(
find(adm.assets, asset => asset.data && asset.data.type === getNativeDataId('body')),
'data.value'
),
sponsoredBy: utils.deepAccess(
find(adm.assets, asset => asset.data && asset.data.type === getNativeDataId('sponsoredBy')),
'data.value'
),
bidObject.native = utils.cleanObj(adm.assets.reduce((native, asset) => {
if (utils.isPlainObject(asset.img)) {
native[nativeImgIdMap[asset.img.type]] = utils.pick(
asset.img,
['url', 'w as width', 'h as height']
);
} else if (utils.isPlainObject(asset.title)) {
native['title'] = asset.title.text
} else if (utils.isPlainObject(asset.data)) {
nativeDataNames.forEach(dataType => {
if (nativeDataIdMap[dataType] === asset.data.type) {
native[dataType] = asset.data.value;
}
});
}
return native;
}, utils.cleanObj({
clickUrl: adm.link,
clickTrackers: adm.clickTrackers,
impressionTrackers: adm.impressionTrackers,
javascriptTrackers: adm.javascriptTrackers
});
})));
} else {
utils.logError('prebid server native response contained no assets');
}
Expand Down
2 changes: 1 addition & 1 deletion modules/rubiconAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ function sendMessage(auctionId, bidWonId) {
// This allows the bidWon events to have these params even in the case of a delayed render
Object.keys(auctionCache.bids).forEach(function (bidId) {
let adCode = auctionCache.bids[bidId].adUnit.adUnitCode;
Object.assign(auctionCache.bids[bidId], _pick(adUnitMap[adCode], ['accountId', 'siteId', 'zoneId']));
Object.assign(auctionCache.bids[bidId], utils.pick(adUnitMap[adCode], ['accountId', 'siteId', 'zoneId']));
});

let auction = {
Expand Down
4 changes: 2 additions & 2 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1186,8 +1186,8 @@ export function cleanObj(obj) {
* @param properties An array of desired properties
*/
export function pick(obj, properties) {
if (!exports.isPlainObject(obj)) {
return undefined;
if (typeof obj !== 'object') {
return {};
}
return properties.reduce((newObj, prop, i) => {
if (typeof prop === 'function') {
Expand Down
44 changes: 34 additions & 10 deletions test/spec/modules/prebidServerBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,27 @@ const REQUEST = {
'sizes': [[ 300, 250 ], [ 300, 300 ]]
},
'native': {
'type': 'image'
'image': {
'required': true,
'sizes': [1, 1]
},
'title': {
'required': true,
'len': 80
},
'sponsoredBy': {
'required': true
},
'clickUrl': {
'required': true
},
'body': {
'required': false
},
'icon': {
'required': false,
'sizes': [1, 1]
},
}
},
'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c',
Expand Down Expand Up @@ -683,40 +703,44 @@ describe('S2S Adapter', function () {
config.setConfig(_config);
adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
const requestBid = JSON.parse(requests[0].requestBody);

expect(requestBid.imp[0].native).to.deep.equal({
request: JSON.stringify({
'context': 1,
'plcmttype': 1,
'assets': [
{
'id': 200,
'required': 1,
'img': {
'type': 3
'type': 3,
'w': 1,
'h': 1
}
},
{
'id': 201,
'required': 1,
'title': {}
'title': {
'len': 80
}
},
{
'id': 202,
'required': 1,
'data': {
'type': 1
}
},
{
'id': 203,
'required': 0,
'data': {
'type': 2
}
},
{
'id': 204,
'required': 0,
'img': {
'type': 1
'type': 1,
'w': 1,
'h': 1
}
}
]
Expand Down Expand Up @@ -1414,7 +1438,7 @@ describe('S2S Adapter', function () {
expect(response).to.have.property('adm').deep.equal(RESPONSE_OPENRTB_NATIVE.seatbid[0].bid[0].adm);
expect(response).to.have.property('mediaType', 'native');
expect(response).to.have.property('bidderCode', 'appnexus');
expect(response).to.have.property('adId', '123');
expect(response).to.have.property('requestId', '123');
expect(response).to.have.property('cpm', 0.13);
});

Expand Down

0 comments on commit e413c45

Please sign in to comment.