From 99977a9d61cee1fb6e25ed97068b06a15e99ea7a Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Wed, 27 Feb 2019 11:07:22 -0800 Subject: [PATCH] Send placeholders for configured native assets (#3573) * Send placeholders for configured native assets * Post native assets on replacement request * Update names * Change config name --- src/auction.js | 6 +++--- src/native.js | 35 +++++++++++++++++++++++++++++++++-- src/secureCreatives.js | 8 +++++++- src/utils.js | 13 +++++++++++++ test/spec/native_spec.js | 40 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/auction.js b/src/auction.js index e83f7d061af..96c59f43eef 100644 --- a/src/auction.js +++ b/src/auction.js @@ -488,7 +488,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { // if there is any key value pairs to map do here var keyValues; if (bidObject.bidderCode && (bidObject.cpm > 0 || bidObject.dealId)) { - keyValues = getKeyValueTargetingPairs(bidObject.bidderCode, bidObject); + keyValues = getKeyValueTargetingPairs(bidObject.bidderCode, bidObject, bidReq); } // use any targeting provided as defaults, otherwise just set from getKeyValueTargetingPairs @@ -563,7 +563,7 @@ export function getStandardBidderSettings(mediaType) { return bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]; } -export function getKeyValueTargetingPairs(bidderCode, custBidObj) { +export function getKeyValueTargetingPairs(bidderCode, custBidObj, bidReq) { if (!custBidObj) { return {}; } @@ -586,7 +586,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj) { // set native key value targeting if (custBidObj['native']) { - keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj)); + keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj, bidReq)); } return keyValues; diff --git a/src/native.js b/src/native.js index a5609739832..f30df7e3ef7 100644 --- a/src/native.js +++ b/src/native.js @@ -1,4 +1,4 @@ -import { deepAccess, getBidRequest, logError, triggerPixel, insertHtmlIntoIframe } from './utils'; +import { deepAccess, getBidRequest, getKeyByValue, insertHtmlIntoIframe, logError, triggerPixel } from './utils'; import includes from 'core-js/library/fn/array/includes'; const CONSTANTS = require('./constants.json'); @@ -151,7 +151,7 @@ export function fireNativeTrackers(message, adObject) { * @param {Object} bid * @return {Object} targeting */ -export function getNativeTargeting(bid) { +export function getNativeTargeting(bid, bidReq) { let keyValues = {}; Object.keys(bid['native']).forEach(asset => { @@ -163,6 +163,16 @@ export function getNativeTargeting(bid) { value = value.url; } + const sendPlaceholder = deepAccess( + bidReq, + `mediaTypes.native.${asset}.sendId` + ); + + if (sendPlaceholder) { + const placeholder = `${key}:${bid.adId}`; + value = placeholder; + } + if (key && value) { keyValues[key] = value; } @@ -170,3 +180,24 @@ export function getNativeTargeting(bid) { return keyValues; } + +/** + * Constructs a message object containing asset values for each of the + * requested data keys. + */ +export function getAssetMessage(data, adObject) { + const message = { + message: 'assetResponse', + adId: data.adId, + assets: [], + }; + + data.assets.forEach(asset => { + const key = getKeyByValue(CONSTANTS.NATIVE_KEYS, asset); + const value = adObject.native[key]; + + message.assets.push({ key, value }); + }); + + return message; +} diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 32ad27a0496..3b5f6128ee9 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -4,7 +4,7 @@ */ import events from './events'; -import { fireNativeTrackers } from './native'; +import { fireNativeTrackers, getAssetMessage } from './native'; import { EVENTS } from './constants'; import { isSlotMatchingAdUnitCode, logWarn, replaceAuctionPrice } from './utils'; import { auctionManager } from './auctionManager'; @@ -46,6 +46,12 @@ function receiveMessage(ev) { // adId: '%%PATTERN:hb_adid%%' // }), '*'); if (data.message === 'Prebid Native') { + if (data.action === 'assetRequest') { + const message = getAssetMessage(data, adObject); + ev.source.postMessage(JSON.stringify(message), ev.origin); + return; + } + fireNativeTrackers(data, adObject); auctionManager.addWinningBid(adObject); events.emit(BID_WON, adObject); diff --git a/src/utils.js b/src/utils.js index 3581b94cb25..5e70b31b0b2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -783,6 +783,19 @@ export function getValue(obj, key) { return obj[key]; } +/** + * Get the key of an object for a given value + */ +export function getKeyByValue(obj, value) { + for (let prop in obj) { + if (obj.hasOwnProperty(prop)) { + if (obj[prop] === value) { + return prop; + } + } + } +} + export function getBidderCodes(adUnits = $$PREBID_GLOBAL$$.adUnits) { // this could memoize adUnits return adUnits.map(unit => unit.bids.map(bid => bid.bidder) diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 3a016145fd2..d68d6c21958 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -1,9 +1,10 @@ import { expect } from 'chai'; -import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid } from 'src/native'; +import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid, getAssetMessage } from 'src/native'; import CONSTANTS from 'src/constants.json'; const utils = require('src/utils'); const bid = { + adId: '123', native: { title: 'Native Creative', body: 'Cool description great stuff', @@ -50,6 +51,22 @@ describe('native.js', function () { expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal(bid.native.clickUrl); }); + it('sends placeholders for configured assets', function () { + const bidRequest = { + mediaTypes: { + native: { + body: { sendId: true }, + clickUrl: { sendId: true }, + } + } + }; + const targeting = getNativeTargeting(bid, bidRequest); + + expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); + expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal('hb_native_body:123'); + expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal('hb_native_linkurl:123'); + }); + it('should only include native targeting keys with values', function () { const targeting = getNativeTargeting(bidWithUndefinedFields); @@ -72,6 +89,27 @@ describe('native.js', function () { sinon.assert.calledOnce(triggerPixelStub); sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); }); + + it('creates native asset message', function() { + const messageRequest = { + message: 'Prebid Native', + action: 'assetRequest', + adId: '123', + assets: ['hb_native_body', 'hb_native_linkurl'], + }; + + const message = getAssetMessage(messageRequest, bid); + + expect(message.assets.length).to.equal(2); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl + }); + }); }); describe('validate native', function () {