mk9-prebid
Version:
Header Bidding Management Library
276 lines (230 loc) • 8.54 kB
JavaScript
import { deepAccess, getBidRequest, getKeyByValue, insertHtmlIntoIframe, logError, triggerPixel } from './utils.js';
import includes from 'core-js-pure/features/array/includes.js';
const CONSTANTS = require('./constants.json');
export const nativeAdapters = [];
export const NATIVE_TARGETING_KEYS = Object.keys(CONSTANTS.NATIVE_KEYS).map(
key => CONSTANTS.NATIVE_KEYS[key]
);
const IMAGE = {
image: { required: true },
title: { required: true },
sponsoredBy: { required: true },
clickUrl: { required: true },
body: { required: false },
icon: { required: false },
};
const SUPPORTED_TYPES = {
image: IMAGE
};
/**
* Recieves nativeParams from an adUnit. If the params were not of type 'type',
* passes them on directly. If they were of type 'type', translate
* them into the predefined specific asset requests for that type of native ad.
*/
export function processNativeAdUnitParams(params) {
if (params && params.type && typeIsSupported(params.type)) {
return SUPPORTED_TYPES[params.type];
}
return params;
}
/**
* Check if the native type specified in the adUnit is supported by Prebid.
*/
function typeIsSupported(type) {
if (!(type && includes(Object.keys(SUPPORTED_TYPES), type))) {
logError(`${type} nativeParam is not supported`);
return false;
}
return true;
}
/**
* Helper functions for working with native-enabled adUnits
* TODO: abstract this and the video helper functions into general
* adunit validation helper functions
*/
export const nativeAdUnit = adUnit => {
const mediaType = adUnit.mediaType === 'native';
const mediaTypes = deepAccess(adUnit, 'mediaTypes.native');
return mediaType || mediaTypes;
}
export const nativeBidder = bid => includes(nativeAdapters, bid.bidder);
export const hasNonNativeBidder = adUnit =>
adUnit.bids.filter(bid => !nativeBidder(bid)).length;
/**
* Validate that the native assets on this bid contain all assets that were
* marked as required in the adUnit configuration.
* @param {Bid} bid Native bid to validate
* @param {BidRequest[]} bidRequests All bid requests for an auction
* @return {Boolean} If object is valid
*/
export function nativeBidIsValid(bid, bidRequests) {
const bidRequest = getBidRequest(bid.requestId, bidRequests);
if (!bidRequest) { return false; }
// all native bid responses must define a landing page url
if (!deepAccess(bid, 'native.clickUrl')) {
return false;
}
const requestedAssets = bidRequest.nativeParams;
if (!requestedAssets) {
return true;
}
const requiredAssets = Object.keys(requestedAssets).filter(
key => requestedAssets[key].required
);
const returnedAssets = Object.keys(bid['native']).filter(
key => bid['native'][key]
);
return requiredAssets.every(asset => includes(returnedAssets, asset));
}
/*
* Native responses may have associated impression or click trackers.
* This retrieves the appropriate tracker urls for the given ad object and
* fires them. As a native creatives may be in a cross-origin frame, it may be
* necessary to invoke this function via postMessage. secureCreatives is
* configured to fire this function when it receives a `message` of 'Prebid Native'
* and an `adId` with the value of the `bid.adId`. When a message is posted with
* these parameters, impression trackers are fired. To fire click trackers, the
* message should contain an `action` set to 'click'.
*
* // Native creative template example usage
* <a href="%%CLICK_URL_UNESC%%%%PATTERN:hb_native_linkurl%%"
* target="_blank"
* onclick="fireTrackers('click')">
* %%PATTERN:hb_native_title%%
* </a>
*
* <script>
* function fireTrackers(action) {
* var message = {message: 'Prebid Native', adId: '%%PATTERN:hb_adid%%'};
* if (action === 'click') {message.action = 'click';} // fires click trackers
* window.parent.postMessage(JSON.stringify(message), '*');
* }
* fireTrackers(); // fires impressions when creative is loaded
* </script>
*/
export function fireNativeTrackers(message, adObject) {
let trackers;
if (message.action === 'click') {
trackers = adObject['native'] && adObject['native'].clickTrackers;
} else {
trackers = adObject['native'] && adObject['native'].impressionTrackers;
if (adObject['native'] && adObject['native'].javascriptTrackers) {
insertHtmlIntoIframe(adObject['native'].javascriptTrackers);
}
}
(trackers || []).forEach(triggerPixel);
return message.action;
}
/**
* Gets native targeting key-value pairs
* @param {Object} bid
* @return {Object} targeting
*/
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;
const nativeKeys = getNativeKeys(bidReq);
const flatBidNativeKeys = { ...bid.native, ...bid.native.ext };
delete flatBidNativeKeys.ext;
Object.keys(flatBidNativeKeys).forEach(asset => {
const key = nativeKeys[asset];
let value = getAssetValue(bid.native[asset]) || getAssetValue(deepAccess(bid, `native.ext.${asset}`));
if (asset === 'adTemplate' || !key || !value) {
return;
}
let sendPlaceholder = deepAccess(bidReq, `nativeParams.${asset}.sendId`);
if (typeof sendPlaceholder !== 'boolean') {
sendPlaceholder = deepAccess(bidReq, `nativeParams.ext.${asset}.sendId`);
}
if (sendPlaceholder) {
const placeholder = `${key}:${bid.adId}`;
value = placeholder;
}
let assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.${asset}.sendTargetingKeys`)
if (typeof assetSendTargetingKeys !== 'boolean') {
assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.ext.${asset}.sendTargetingKeys`);
}
const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys;
if (sendTargeting) {
keyValues[key] = value;
}
});
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: [],
};
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]);
message.assets.push({ key, value });
});
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 (key === 'ext') {
Object.keys(adObject.native[key]).forEach(extKey => {
if (adObject.native[key][extKey]) {
const value = getAssetValue(adObject.native[key][extKey]);
message.assets.push({ key: extKey, value });
}
})
} 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.
*/
function getAssetValue(value) {
if (typeof value === 'object' && value.url) {
return value.url;
}
return value;
}
function getNativeKeys(bidReq) {
const extraNativeKeys = {}
if (deepAccess(bidReq, 'nativeParams.ext')) {
Object.keys(bidReq.nativeParams.ext).forEach(extKey => {
extraNativeKeys[extKey] = `hb_native_${extKey}`;
})
}
return {
...CONSTANTS.NATIVE_KEYS,
...extraNativeKeys
}
}