diff --git a/src/constants.json b/src/constants.json index 7e82946b65c..92b1dc6cc34 100644 --- a/src/constants.json +++ b/src/constants.json @@ -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", diff --git a/src/native.js b/src/native.js index e41d7740ffa..2d22e16c8a5 100644 --- a/src/native.js +++ b/src/native.js @@ -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; + } } }); @@ -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]); @@ -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. diff --git a/src/secureCreatives.js b/src/secureCreatives.js index cb192dd773e..def1a9abdbb 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -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'; @@ -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); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index bd56ba53e4a..ef9d407dd0c 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -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'); @@ -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: '

##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('

##hb_native_body##<\/p><\/div>'); + delete bid.native.adTemplate; + }); + it('fires impression trackers', function () { fireNativeTrackers({}, bid); sinon.assert.calledOnce(triggerPixelStub); @@ -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 () {