From ecf4c97a39d98f58861495948abea1f1cfea1de5 Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Mon, 22 Jun 2020 18:11:24 -0400 Subject: [PATCH 1/4] 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 --- src/constants.json | 4 +- src/native.js | 72 +++++++++++--- src/secureCreatives.js | 5 +- test/spec/native_spec.js | 196 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 259 insertions(+), 18 deletions(-) diff --git a/src/constants.json b/src/constants.json index 7ffef9db1aa..0aa5a13a852 100644 --- a/src/constants.json +++ b/src/constants.json @@ -86,7 +86,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 0d8461a5b6f..193c812deac 100644 --- a/src/native.js +++ b/src/native.js @@ -154,22 +154,36 @@ export function fireNativeTrackers(message, adObject) { export function getNativeTargeting(bid, bidReq) { let keyValues = {}; - Object.keys(bid['native']).forEach(asset => { - const key = CONSTANTS.NATIVE_KEYS[asset]; - let value = getAssetValue(bid['native'][asset]); - - const sendPlaceholder = deepAccess( - bidReq, - `mediaTypes.native.${asset}.sendId` - ); - - if (sendPlaceholder) { - const placeholder = `${key}:${bid.adId}`; - value = placeholder; - } + 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']); + } - if (key && value) { - keyValues[key] = value; + Object.keys(bid['native']).forEach(asset => { + if (asset !== 'adTemplate') { + const key = CONSTANTS.NATIVE_KEYS[asset]; + let value = getAssetValue(bid['native'][asset]); + + const sendPlaceholder = deepAccess( + bidReq, + `mediaTypes.native.${asset}.sendId` + ); + + if (sendPlaceholder) { + const placeholder = `${key}:${bid.adId}`; + value = placeholder; + } + + if (key && value) { + if (!deepAccess(bidReq, 'nativeParams.sendTargetingKeys') && typeof deepAccess(bidReq, 'nativeParams.sendTargetingKeys') !== 'undefined' && deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys')) { + keyValues[key] = value; + } else if (deepAccess(bidReq, 'nativeParams.sendTargetingKeys') && (typeof deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys') === 'undefined' || deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys'))) { + keyValues[key] = value; + } else if (typeof deepAccess(bidReq, 'nativeParams.sendTargetingKeys') === 'undefined' && (typeof deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys') === 'undefined' || deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys'))) { + keyValues[key] = value; + } + } } }); @@ -187,6 +201,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 +217,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 36e5efe22cd..b067c8f2b8a 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 { EVENTS } from './constants.json'; import { logWarn, replaceAuctionPrice } from './utils.js'; import { auctionManager } from './auctionManager.js'; @@ -51,6 +51,9 @@ 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); } 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 () { From 6e61f3e4377e7f93162ec00a0e86c62c0a59fb6f Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Wed, 24 Jun 2020 11:22:10 -0400 Subject: [PATCH 2/4] Update to simplify send asset logic --- src/native.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/native.js b/src/native.js index 193c812deac..518c44e542e 100644 --- a/src/native.js +++ b/src/native.js @@ -160,6 +160,11 @@ export function getNativeTargeting(bid, bidReq) { bid['native']['adTemplate'] = getAssetValue(bidReq.nativeParams['adTemplate']); } + const globalSendTargetingKeys = deepAccess( + bidReq, + `mediaTypes.native.sendTargetingKeys` + ) !== false; + Object.keys(bid['native']).forEach(asset => { if (asset !== 'adTemplate') { const key = CONSTANTS.NATIVE_KEYS[asset]; @@ -175,14 +180,16 @@ export function getNativeTargeting(bid, bidReq) { value = placeholder; } - if (key && value) { - if (!deepAccess(bidReq, 'nativeParams.sendTargetingKeys') && typeof deepAccess(bidReq, 'nativeParams.sendTargetingKeys') !== 'undefined' && deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys')) { - keyValues[key] = value; - } else if (deepAccess(bidReq, 'nativeParams.sendTargetingKeys') && (typeof deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys') === 'undefined' || deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys'))) { - keyValues[key] = value; - } else if (typeof deepAccess(bidReq, 'nativeParams.sendTargetingKeys') === 'undefined' && (typeof deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys') === 'undefined' || deepAccess(bidReq, 'nativeParams.' + asset + '.sendTargetingKeys'))) { - keyValues[key] = value; - } + const assetSendTargetingKeys = deepAccess( + bidReq, + `mediaTypes.native.${asset}.sendTargetingKeys` + ); + + const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys; + + + if (key && value && sendTargeting) { + keyValues[key] = value; } } }); From d32e1be6b2e3bd7f5bedc1c46f7539c1b8f8f478 Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Wed, 24 Jun 2020 11:44:22 -0400 Subject: [PATCH 3/4] Fixed asset location typo --- src/native.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/native.js b/src/native.js index 518c44e542e..3aef982e3e9 100644 --- a/src/native.js +++ b/src/native.js @@ -162,8 +162,8 @@ export function getNativeTargeting(bid, bidReq) { const globalSendTargetingKeys = deepAccess( bidReq, - `mediaTypes.native.sendTargetingKeys` - ) !== false; + `nativeParams.sendTargetingKeys` + ) !== false; Object.keys(bid['native']).forEach(asset => { if (asset !== 'adTemplate') { @@ -182,12 +182,10 @@ export function getNativeTargeting(bid, bidReq) { const assetSendTargetingKeys = deepAccess( bidReq, - `mediaTypes.native.${asset}.sendTargetingKeys` - ); + `nativeParams.${asset}.sendTargetingKeys`); const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys; - if (key && value && sendTargeting) { keyValues[key] = value; } From aef347b2a74644c293303dd807bd7b344a2ca6a8 Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Fri, 13 Nov 2020 12:06:29 -0500 Subject: [PATCH 4/4] Update to include message listener for native resize and update adObject for auction to include creative height and trigger resize --- src/secureCreatives.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/secureCreatives.js b/src/secureCreatives.js index b067c8f2b8a..145e747f299 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -54,6 +54,10 @@ function receiveMessage(ev) { } 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);