Skip to content

Commit

Permalink
Prebid JS Native Phase 2 update: (#5411)
Browse files Browse the repository at this point in the history
* Prebid JS Native Phase 2 update:

1) Key Suppression for Native keys with sendTargetingKeys set to false at nativeParams.sendTargetingKeys or within asset i.e. nativeParams.image.sendTargetingKeys. Logic as follows:
   - If nativeParams.sendTargetingKeys set to false but asset has sendTargetingKeys set to true then add the KV
   - Else if nativeParams.sendTargetingKeys is set to true and asset does not have sendTargetingKeys defined or it is set to true then add KV
   - Else if nativeParams.sendTargetingKeys is not defined and asset does not have sendTargetingKeys defined or it is set
 to true then add KV
   If no conditions met then key is suppressed
2) Adds rendererUrl or adTemplate to the bid object for easy retrieval if assets requested from creative
3) Adds new logic to receiveMessage function in secureCreatives.js to answer data.action of 'allAssetRequest' with all available assets on the bid that have a value
4) Adds new native keys to constants.json

* Update to simplify send asset logic

* Fixed asset location typo

* Update to include message listener for native resize and update adObject for auction to include creative height and trigger resize

Co-authored-by: Michael Moschovas <mmoschovas@NY-Moschovas.local>
  • Loading branch information
mmoschovas and Michael Moschovas committed Jan 13, 2021
1 parent 19a0d72 commit 4938e4f
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 15 deletions.
4 changes: 3 additions & 1 deletion src/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@
"likes": "hb_native_likes",
"phone": "hb_native_phone",
"price": "hb_native_price",
"salePrice": "hb_native_saleprice"
"salePrice": "hb_native_saleprice",
"rendererUrl": "hb_renderer_url",
"adTemplate": "hb_adTemplate"
},
"S2S" : {
"SRC" : "s2s",
Expand Down
71 changes: 59 additions & 12 deletions src/native.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,22 +154,41 @@ export function fireNativeTrackers(message, adObject) {
export function getNativeTargeting(bid, bidReq) {
let keyValues = {};

if (deepAccess(bidReq, 'nativeParams.rendererUrl')) {
bid['native']['rendererUrl'] = getAssetValue(bidReq.nativeParams['rendererUrl']);
} else if (deepAccess(bidReq, 'nativeParams.adTemplate')) {
bid['native']['adTemplate'] = getAssetValue(bidReq.nativeParams['adTemplate']);
}

const globalSendTargetingKeys = deepAccess(
bidReq,
`nativeParams.sendTargetingKeys`
) !== false;

Object.keys(bid['native']).forEach(asset => {
const key = CONSTANTS.NATIVE_KEYS[asset];
let value = getAssetValue(bid['native'][asset]);
if (asset !== 'adTemplate') {
const key = CONSTANTS.NATIVE_KEYS[asset];
let value = getAssetValue(bid['native'][asset]);

const sendPlaceholder = deepAccess(
bidReq,
`mediaTypes.native.${asset}.sendId`
);
const sendPlaceholder = deepAccess(
bidReq,
`mediaTypes.native.${asset}.sendId`
);

if (sendPlaceholder) {
const placeholder = `${key}:${bid.adId}`;
value = placeholder;
}
if (sendPlaceholder) {
const placeholder = `${key}:${bid.adId}`;
value = placeholder;
}

const assetSendTargetingKeys = deepAccess(
bidReq,
`nativeParams.${asset}.sendTargetingKeys`);

if (key && value) {
keyValues[key] = value;
const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys;

if (key && value && sendTargeting) {
keyValues[key] = value;
}
}
});

Expand All @@ -187,6 +206,12 @@ export function getAssetMessage(data, adObject) {
assets: [],
};

if (adObject.native.hasOwnProperty('adTemplate')) {
message.adTemplate = getAssetValue(adObject.native['adTemplate']);
} if (adObject.native.hasOwnProperty('rendererUrl')) {
message.rendererUrl = getAssetValue(adObject.native['rendererUrl']);
}

data.assets.forEach(asset => {
const key = getKeyByValue(CONSTANTS.NATIVE_KEYS, asset);
const value = getAssetValue(adObject.native[key]);
Expand All @@ -197,6 +222,28 @@ export function getAssetMessage(data, adObject) {
return message;
}

export function getAllAssetsMessage(data, adObject) {
const message = {
message: 'assetResponse',
adId: data.adId,
assets: []
};

Object.keys(adObject.native).forEach(function(key, index) {
if (key === 'adTemplate' && adObject.native[key]) {
message.adTemplate = getAssetValue(adObject.native[key]);
} else if (key === 'rendererUrl' && adObject.native[key]) {
message.rendererUrl = getAssetValue(adObject.native[key]);
} else if (adObject.native[key] && CONSTANTS.NATIVE_KEYS.hasOwnProperty(key)) {
const value = getAssetValue(adObject.native[key]);

message.assets.push({ key, value });
}
});

return message;
}

/**
* Native assets can be a string or an object with a url prop. Returns the value
* appropriate for sending in adserver targeting or placeholder replacement.
Expand Down
9 changes: 8 additions & 1 deletion src/secureCreatives.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import events from './events.js';
import { fireNativeTrackers, getAssetMessage } from './native.js';
import { fireNativeTrackers, getAssetMessage, getAllAssetsMessage } from './native.js';
import constants from './constants.json';
import { logWarn, replaceAuctionPrice } from './utils.js';
import { auctionManager } from './auctionManager.js';
Expand Down Expand Up @@ -51,6 +51,13 @@ function receiveMessage(ev) {
const message = getAssetMessage(data, adObject);
ev.source.postMessage(JSON.stringify(message), ev.origin);
return;
} else if (data.action === 'allAssetRequest') {
const message = getAllAssetsMessage(data, adObject);
ev.source.postMessage(JSON.stringify(message), ev.origin);
} else if (data.action === 'resizeNativeHeight') {
adObject.height = data.height;
adObject.width = data.width;
resizeRemoteCreative(adObject);
}

const trackerType = fireNativeTrackers(data, adObject);
Expand Down
196 changes: 195 additions & 1 deletion test/spec/native_spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai';
import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid, getAssetMessage } from 'src/native.js';
import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid, getAssetMessage, getAllAssetsMessage } from 'src/native.js';
import CONSTANTS from 'src/constants.json';
const utils = require('src/utils');

Expand Down Expand Up @@ -87,6 +87,136 @@ describe('native.js', function () {
]);
});

it('should only include targeting that has sendTargetingKeys set to true', function () {
const bidRequest = {
nativeParams: {
image: {
required: true,
sizes: [150, 50]
},
title: {
required: true,
len: 80,
sendTargetingKeys: true
},
sendTargetingKeys: false,
}

};
const targeting = getNativeTargeting(bid, bidRequest);

expect(Object.keys(targeting)).to.deep.equal([
CONSTANTS.NATIVE_KEYS.title
]);
});

it('should only include targeting if sendTargetingKeys not set to false', function () {
const bidRequest = {
nativeParams: {
image: {
required: true,
sizes: [150, 50]
},
title: {
required: true,
len: 80
},
body: {
required: true
},
clickUrl: {
required: true
},
icon: {
required: false,
sendTargetingKeys: false
},
cta: {
required: false,
sendTargetingKeys: false
},
sponsoredBy: {
required: false,
sendTargetingKeys: false
}
}

};
const targeting = getNativeTargeting(bid, bidRequest);

expect(Object.keys(targeting)).to.deep.equal([
CONSTANTS.NATIVE_KEYS.title,
CONSTANTS.NATIVE_KEYS.body,
CONSTANTS.NATIVE_KEYS.image,
CONSTANTS.NATIVE_KEYS.clickUrl
]);
});

it('should copy over rendererUrl to bid object and include it in targeting', function () {
const bidRequest = {
nativeParams: {
image: {
required: true,
sizes: [150, 50]
},
title: {
required: true,
len: 80,
},
rendererUrl: {
url: 'https://www.renderer.com/'
}
}

};
const targeting = getNativeTargeting(bid, bidRequest);

expect(Object.keys(targeting)).to.deep.equal([
CONSTANTS.NATIVE_KEYS.title,
CONSTANTS.NATIVE_KEYS.body,
CONSTANTS.NATIVE_KEYS.cta,
CONSTANTS.NATIVE_KEYS.image,
CONSTANTS.NATIVE_KEYS.icon,
CONSTANTS.NATIVE_KEYS.sponsoredBy,
CONSTANTS.NATIVE_KEYS.clickUrl,
CONSTANTS.NATIVE_KEYS.rendererUrl
]);

expect(bid.native.rendererUrl).to.deep.equal('https://www.renderer.com/');
delete bid.native.rendererUrl;
});

it('should copy over adTemplate to bid object and include it in targeting', function () {
const bidRequest = {
nativeParams: {
image: {
required: true,
sizes: [150, 50]
},
title: {
required: true,
len: 80,
},
adTemplate: '<div><p>##hb_native_body##<\/p><\/div>'
}

};
const targeting = getNativeTargeting(bid, bidRequest);

expect(Object.keys(targeting)).to.deep.equal([
CONSTANTS.NATIVE_KEYS.title,
CONSTANTS.NATIVE_KEYS.body,
CONSTANTS.NATIVE_KEYS.cta,
CONSTANTS.NATIVE_KEYS.image,
CONSTANTS.NATIVE_KEYS.icon,
CONSTANTS.NATIVE_KEYS.sponsoredBy,
CONSTANTS.NATIVE_KEYS.clickUrl
]);

expect(bid.native.adTemplate).to.deep.equal('<div><p>##hb_native_body##<\/p><\/div>');
delete bid.native.adTemplate;
});

it('fires impression trackers', function () {
fireNativeTrackers({}, bid);
sinon.assert.calledOnce(triggerPixelStub);
Expand Down Expand Up @@ -125,6 +255,70 @@ describe('native.js', function () {
value: bid.native.clickUrl
});
});

it('creates native all asset message', function() {
const messageRequest = {
message: 'Prebid Native',
action: 'allAssetRequest',
adId: '123',
};

const message = getAllAssetsMessage(messageRequest, bid);

expect(message.assets.length).to.equal(7);
expect(message.assets).to.deep.include({
key: 'body',
value: bid.native.body
});
expect(message.assets).to.deep.include({
key: 'image',
value: bid.native.image.url
});
expect(message.assets).to.deep.include({
key: 'clickUrl',
value: bid.native.clickUrl
});
expect(message.assets).to.deep.include({
key: 'title',
value: bid.native.title
});
expect(message.assets).to.deep.include({
key: 'icon',
value: bid.native.icon.url
});
expect(message.assets).to.deep.include({
key: 'cta',
value: bid.native.cta
});
expect(message.assets).to.deep.include({
key: 'sponsoredBy',
value: bid.native.sponsoredBy
});
});

it('creates native all asset message with only defined fields', function() {
const messageRequest = {
message: 'Prebid Native',
action: 'allAssetRequest',
adId: '123',
};

const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields);

expect(message.assets.length).to.equal(3);
expect(message.assets).to.deep.include({
key: 'clickUrl',
value: bid.native.clickUrl
});
expect(message.assets).to.deep.include({
key: 'title',
value: bid.native.title
});
expect(message.assets).to.deep.include({
key: 'sponsoredBy',
value: bid.native.sponsoredBy
});
});
});

describe('validate native', function () {
Expand Down

0 comments on commit 4938e4f

Please sign in to comment.