src/targeting.js
import {
deepAccess,
deepClone,
groupBy,
isAdUnitCodeMatchingSlot,
isArray,
isFn,
isGptPubadsDefined,
isStr,
logError,
logInfo,
logMessage,
logWarn,
timestamp,
uniques,
} from './utils.js';
import {config} from './config.js';
import {NATIVE_TARGETING_KEYS} from './native.js';
import {auctionManager} from './auctionManager.js';
import {ADPOD} from './mediaTypes.js';
import {hook} from './hook.js';
import {bidderSettings} from './bidderSettings.js';
import {find, includes} from './polyfill.js';
import { BID_STATUS, JSON_MAPPING, DEFAULT_TARGETING_KEYS, TARGETING_KEYS, NATIVE_KEYS, STATUS } from './constants.js';
import {getHighestCpm, getOldestHighestCpmBid} from './utils/reducers.js';
import {getTTL} from './bidTTL.js';
var pbTargetingKeys = [];
const MAX_DFP_KEYLENGTH = 20;
const CFG_ALLOW_TARGETING_KEYS = `targetingControls.allowTargetingKeys`;
const CFG_ADD_TARGETING_KEYS = `targetingControls.addTargetingKeys`;
const TARGETING_KEY_CONFIGURATION_ERROR_MSG = `Only one of "${CFG_ALLOW_TARGETING_KEYS}" or "${CFG_ADD_TARGETING_KEYS}" can be set`;
export const TARGETING_KEYS_ARR = Object.keys(TARGETING_KEYS).map(
key => TARGETING_KEYS[key]
);
// return unexpired bids
const isBidNotExpired = (bid) => (bid.responseTimestamp + getTTL(bid) * 1000) > timestamp();
// return bids whose status is not set. Winning bids can only have a status of `rendered`.
const isUnusedBid = (bid) => bid && ((bid.status && !includes([BID_STATUS.RENDERED], bid.status)) || !bid.status);
export let filters = {
isActualBid(bid) {
return bid.getStatusCode() === STATUS.GOOD
},
isBidNotExpired,
isUnusedBid
};
export function isBidUsable(bid) {
return !Object.values(filters).some((predicate) => !predicate(bid));
}
// If two bids are found for same adUnitCode, we will use the highest one to take part in auction
// This can happen in case of concurrent auctions
// If adUnitBidLimit is set above 0 return top N number of bids
export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) {
if (!hasModified) {
const bids = [];
const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization');
// bucket by adUnitcode
let buckets = groupBy(bidsReceived, 'adUnitCode');
// filter top bid for each bucket by bidder
Object.keys(buckets).forEach(bucketKey => {
let bucketBids = [];
let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode');
Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback)));
// if adUnitBidLimit is set, pass top N number bids
if (adUnitBidLimit > 0) {
bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm);
bids.push(...bucketBids.slice(0, adUnitBidLimit));
} else {
bids.push(...bucketBids);
}
});
return bids;
}
return bidsReceived;
});
/**
* A descending sort function that will sort the list of objects based on the following two dimensions:
* - bids with a deal are sorted before bids w/o a deal
* - then sort bids in each grouping based on the hb_pb value
* eg: the following list of bids would be sorted like:
* [{
* "hb_adid": "vwx",
* "hb_pb": "28",
* "hb_deal": "7747"
* }, {
* "hb_adid": "jkl",
* "hb_pb": "10",
* "hb_deal": "9234"
* }, {
* "hb_adid": "stu",
* "hb_pb": "50"
* }, {
* "hb_adid": "def",
* "hb_pb": "2"
* }]
*/
export function sortByDealAndPriceBucketOrCpm(useCpm = false) {
return function(a, b) {
if (a.adserverTargeting.hb_deal !== undefined && b.adserverTargeting.hb_deal === undefined) {
return -1;
}
if ((a.adserverTargeting.hb_deal === undefined && b.adserverTargeting.hb_deal !== undefined)) {
return 1;
}
// assuming both values either have a deal or don't have a deal - sort by the hb_pb param
if (useCpm) {
return b.cpm - a.cpm;
}
return b.adserverTargeting.hb_pb - a.adserverTargeting.hb_pb;
}
}
/**
* @typedef {Object.<string,string>} targeting
* @property {string} targeting_key
*/
/**
* @typedef {Object.<string,Object.<string,string[]>[]>[]} targetingArray
*/
export function newTargeting(auctionManager) {
let targeting = {};
let latestAuctionForAdUnit = {};
targeting.setLatestAuctionForAdUnit = function(adUnitCode, auctionId) {
latestAuctionForAdUnit[adUnitCode] = auctionId;
};
targeting.resetPresetTargeting = function(adUnitCode, customSlotMatching) {
if (isGptPubadsDefined()) {
const adUnitCodes = getAdUnitCodes(adUnitCode);
const adUnits = auctionManager.getAdUnits().filter(adUnit => includes(adUnitCodes, adUnit.code));
let unsetKeys = pbTargetingKeys.reduce((reducer, key) => {
reducer[key] = null;
return reducer;
}, {});
window.googletag.pubads().getSlots().forEach(slot => {
let customSlotMatchingFunc = isFn(customSlotMatching) && customSlotMatching(slot);
// reset only registered adunits
adUnits.forEach(unit => {
if (unit.code === slot.getAdUnitPath() ||
unit.code === slot.getSlotElementId() ||
(isFn(customSlotMatchingFunc) && customSlotMatchingFunc(unit.code))) {
slot.updateTargetingFromMap(unsetKeys);
}
});
});
}
};
targeting.resetPresetTargetingAST = function(adUnitCode) {
const adUnitCodes = getAdUnitCodes(adUnitCode);
adUnitCodes.forEach(function(unit) {
const astTag = window.apntag.getTag(unit);
if (astTag && astTag.keywords) {
const currentKeywords = Object.keys(astTag.keywords);
const newKeywords = {};
currentKeywords.forEach((key) => {
if (!includes(pbTargetingKeys, key.toLowerCase())) {
newKeywords[key] = astTag.keywords[key];
}
})
window.apntag.modifyTag(unit, { keywords: newKeywords })
}
});
};
/**
* checks if bid has targeting set and belongs based on matching ad unit codes
* @return {boolean} true or false
*/
function bidShouldBeAddedToTargeting(bid, adUnitCodes) {
return bid.adserverTargeting && adUnitCodes &&
((isArray(adUnitCodes) && includes(adUnitCodes, bid.adUnitCode)) ||
(typeof adUnitCodes === 'string' && bid.adUnitCode === adUnitCodes));
};
/**
* Returns targeting for any bids which have deals if alwaysIncludeDeals === true
*/
function getDealBids(adUnitCodes, bidsReceived) {
if (config.getConfig('targetingControls.alwaysIncludeDeals') === true) {
const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice();
// we only want the top bid from bidders who have multiple entries per ad unit code
const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm);
// populate targeting keys for the remaining bids if they have a dealId
return bids.map(bid => {
if (bid.dealId && bidShouldBeAddedToTargeting(bid, adUnitCodes)) {
return {
[bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter(
key => typeof bid.adserverTargeting[key] !== 'undefined')
)
};
}
}).filter(bid => bid); // removes empty elements in array
}
return [];
};
/**
* Returns filtered ad server targeting for custom and allowed keys.
* @param {targetingArray} targeting
* @param {string[]} allowedKeys
* @return {targetingArray} filtered targeting
*/
function getAllowedTargetingKeyValues(targeting, allowedKeys) {
const defaultKeyring = Object.assign({}, TARGETING_KEYS, NATIVE_KEYS);
const defaultKeys = Object.keys(defaultKeyring);
const keyDispositions = {};
logInfo(`allowTargetingKeys - allowed keys [ ${allowedKeys.map(k => defaultKeyring[k]).join(', ')} ]`);
targeting.map(adUnit => {
const adUnitCode = Object.keys(adUnit)[0];
const keyring = adUnit[adUnitCode];
const keys = keyring.filter(kvPair => {
const key = Object.keys(kvPair)[0];
// check if key is in default keys, if not, it's custom, we won't remove it.
const isCustom = defaultKeys.filter(defaultKey => key.indexOf(defaultKeyring[defaultKey]) === 0).length === 0;
// check if key explicitly allowed, if not, we'll remove it.
const found = isCustom || find(allowedKeys, allowedKey => {
const allowedKeyName = defaultKeyring[allowedKey];
// we're looking to see if the key exactly starts with one of our default keys.
// (which hopefully means it's not custom)
const found = key.indexOf(allowedKeyName) === 0;
return found;
});
keyDispositions[key] = !found;
return found;
});
adUnit[adUnitCode] = keys;
});
const removedKeys = Object.keys(keyDispositions).filter(d => keyDispositions[d]);
logInfo(`allowTargetingKeys - removed keys [ ${removedKeys.join(', ')} ]`);
// remove any empty targeting objects, as they're unnecessary.
const filteredTargeting = targeting.filter(adUnit => {
const adUnitCode = Object.keys(adUnit)[0];
const keyring = adUnit[adUnitCode];
return keyring.length > 0;
});
return filteredTargeting
}
/**
* Returns all ad server targeting for all ad units.
* @param {string=} adUnitCode
* @return {Object.<string,targeting>} targeting
*/
targeting.getAllTargeting = function(adUnitCode, bidsReceived = getBidsReceived()) {
const adUnitCodes = getAdUnitCodes(adUnitCode);
// Get targeting for the winning bid. Add targeting for any bids that have
// `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids.
var targeting = getWinningBidTargeting(adUnitCodes, bidsReceived)
.concat(getCustomBidTargeting(adUnitCodes, bidsReceived))
.concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived) : getDealBids(adUnitCodes, bidsReceived))
.concat(getAdUnitTargeting(adUnitCodes));
// store a reference of the targeting keys
targeting.map(adUnitCode => {
Object.keys(adUnitCode).map(key => {
adUnitCode[key].map(targetKey => {
if (pbTargetingKeys.indexOf(Object.keys(targetKey)[0]) === -1) {
pbTargetingKeys = Object.keys(targetKey).concat(pbTargetingKeys);
}
});
});
});
const defaultKeys = Object.keys(Object.assign({}, DEFAULT_TARGETING_KEYS, NATIVE_KEYS));
let allowedKeys = config.getConfig(CFG_ALLOW_TARGETING_KEYS);
const addedKeys = config.getConfig(CFG_ADD_TARGETING_KEYS);
if (addedKeys != null && allowedKeys != null) {
throw new Error(TARGETING_KEY_CONFIGURATION_ERROR_MSG);
} else if (addedKeys != null) {
allowedKeys = defaultKeys.concat(addedKeys);
} else {
allowedKeys = allowedKeys || defaultKeys;
}
if (Array.isArray(allowedKeys) && allowedKeys.length > 0) {
targeting = getAllowedTargetingKeyValues(targeting, allowedKeys);
}
targeting = flattenTargeting(targeting);
const auctionKeysThreshold = config.getConfig('targetingControls.auctionKeyMaxChars');
if (auctionKeysThreshold) {
logInfo(`Detected 'targetingControls.auctionKeyMaxChars' was active for this auction; set with a limit of ${auctionKeysThreshold} characters. Running checks on auction keys...`);
targeting = filterTargetingKeys(targeting, auctionKeysThreshold);
}
// make sure at least there is a entry per adUnit code in the targetingSet so receivers of SET_TARGETING call's can know what ad units are being invoked
adUnitCodes.forEach(code => {
if (!targeting[code]) {
targeting[code] = {};
}
});
return targeting;
};
// warn about conflicting configuration
config.getConfig('targetingControls', function (config) {
if (deepAccess(config, CFG_ALLOW_TARGETING_KEYS) != null && deepAccess(config, CFG_ADD_TARGETING_KEYS) != null) {
logError(TARGETING_KEY_CONFIGURATION_ERROR_MSG);
}
});
// create an encoded string variant based on the keypairs of the provided object
// - note this will encode the characters between the keys (ie = and &)
function convertKeysToQueryForm(keyMap) {
return Object.keys(keyMap).reduce(function (queryString, key) {
let encodedKeyPair = `${key}%3d${encodeURIComponent(keyMap[key])}%26`;
return queryString += encodedKeyPair;
}, '');
}
function filterTargetingKeys(targeting, auctionKeysThreshold) {
// read each targeting.adUnit object and sort the adUnits into a list of adUnitCodes based on priorization setting (eg CPM)
let targetingCopy = deepClone(targeting);
let targetingMap = Object.keys(targetingCopy).map(adUnitCode => {
return {
adUnitCode,
adserverTargeting: targetingCopy[adUnitCode]
};
}).sort(sortByDealAndPriceBucketOrCpm());
// iterate through the targeting based on above list and transform the keys into the query-equivalent and count characters
return targetingMap.reduce(function (accMap, currMap, index, arr) {
let adUnitQueryString = convertKeysToQueryForm(currMap.adserverTargeting);
// for the last adUnit - trim last encoded ampersand from the converted query string
if ((index + 1) === arr.length) {
adUnitQueryString = adUnitQueryString.slice(0, -3);
}
// if under running threshold add to result
let code = currMap.adUnitCode;
let querySize = adUnitQueryString.length;
if (querySize <= auctionKeysThreshold) {
auctionKeysThreshold -= querySize;
logInfo(`AdUnit '${code}' auction keys comprised of ${querySize} characters. Deducted from running threshold; new limit is ${auctionKeysThreshold}`, targetingCopy[code]);
accMap[code] = targetingCopy[code];
} else {
logWarn(`The following keys for adUnitCode '${code}' exceeded the current limit of the 'auctionKeyMaxChars' setting.\nThe key-set size was ${querySize}, the current allotted amount was ${auctionKeysThreshold}.\n`, targetingCopy[code]);
}
if ((index + 1) === arr.length && Object.keys(accMap).length === 0) {
logError('No auction targeting keys were permitted due to the setting in setConfig(targetingControls.auctionKeyMaxChars). Please review setup and consider adjusting.');
}
return accMap;
}, {});
}
/**
* Converts targeting array and flattens to make it easily iteratable
* e.g: Sample input to this function
* ```
* [
* {
* "div-gpt-ad-1460505748561-0": [{"hb_bidder": ["appnexusAst"]}]
* },
* {
* "div-gpt-ad-1460505748561-0": [{"hb_bidder_appnexusAs": ["appnexusAst", "other"]}]
* }
* ]
* ```
* Resulting array
* ```
* {
* "div-gpt-ad-1460505748561-0": {
* "hb_bidder": "appnexusAst",
* "hb_bidder_appnexusAs": "appnexusAst,other"
* }
* }
* ```
*
* @param {targetingArray} targeting
* @return {Object.<string,targeting>} targeting
*/
function flattenTargeting(targeting) {
let targetingObj = targeting.map(targeting => {
return {
[Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]]
.map(target => {
return {
[Object.keys(target)[0]]: target[Object.keys(target)[0]].join(',')
};
}).reduce((p, c) => Object.assign(c, p), {})
};
}).reduce(function (accumulator, targeting) {
var key = Object.keys(targeting)[0];
accumulator[key] = Object.assign({}, accumulator[key], targeting[key]);
return accumulator;
}, {});
return targetingObj;
}
/**
* Sets targeting for DFP
* @param {Object.<string,Object.<string,string>>} targetingConfig
*/
targeting.setTargetingForGPT = function(targetingConfig, customSlotMatching) {
window.googletag.pubads().getSlots().forEach(slot => {
Object.keys(targetingConfig).filter(customSlotMatching ? customSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot))
.forEach(targetId => {
Object.keys(targetingConfig[targetId]).forEach(key => {
let value = targetingConfig[targetId][key];
if (typeof value === 'string' && value.indexOf(',') !== -1) {
// due to the check the array will be formed only if string has ',' else plain string will be assigned as value
value = value.split(',');
}
targetingConfig[targetId][key] = value;
});
logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingConfig[targetId]);
slot.updateTargetingFromMap(targetingConfig[targetId])
})
})
};
/**
* normlizes input to a `adUnit.code` array
* @param {(string|string[])} adUnitCode [description]
* @return {string[]} AdUnit code array
*/
function getAdUnitCodes(adUnitCode) {
if (typeof adUnitCode === 'string') {
return [adUnitCode];
} else if (isArray(adUnitCode)) {
return adUnitCode;
}
return auctionManager.getAdUnitCodes() || [];
}
function getBidsReceived() {
let bidsReceived = auctionManager.getBidsReceived();
if (!config.getConfig('useBidCache')) {
// don't use bid cache (i.e. filter out bids not in the latest auction)
bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId)
} else {
// if custom bid cache filter function exists, run for each bid from
// previous auctions. If it returns true, include bid in bid pool
const filterFunction = config.getConfig('bidCacheFilterFunction');
if (typeof filterFunction === 'function') {
bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId || !!filterFunction(bid))
}
}
bidsReceived = bidsReceived
.filter(bid => deepAccess(bid, 'video.context') !== ADPOD)
.filter(isBidUsable);
bidsReceived
.forEach(bid => {
bid.latestTargetedAuctionId = latestAuctionForAdUnit[bid.adUnitCode];
return bid;
});
return getHighestCpmBidsFromBidPool(bidsReceived, getOldestHighestCpmBid);
}
/**
* Returns top bids for a given adUnit or set of adUnits.
* @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes
* @return {[type]} [description]
*/
targeting.getWinningBids = function(adUnitCode, bidsReceived = getBidsReceived()) {
const adUnitCodes = getAdUnitCodes(adUnitCode);
return bidsReceived
.filter(bid => includes(adUnitCodes, bid.adUnitCode))
.filter(bid => (bidderSettings.get(bid.bidderCode, 'allowZeroCpmBids') === true) ? bid.cpm >= 0 : bid.cpm > 0)
.map(bid => bid.adUnitCode)
.filter(uniques)
.map(adUnitCode => bidsReceived
.filter(bid => bid.adUnitCode === adUnitCode ? bid : null)
.reduce(getHighestCpm));
};
/**
* @param {(string|string[])} adUnitCodes adUnitCode or array of adUnitCodes
* Sets targeting for AST
*/
targeting.setTargetingForAst = function(adUnitCodes) {
let astTargeting = targeting.getAllTargeting(adUnitCodes);
try {
targeting.resetPresetTargetingAST(adUnitCodes);
} catch (e) {
logError('unable to reset targeting for AST' + e)
}
Object.keys(astTargeting).forEach(targetId =>
Object.keys(astTargeting[targetId]).forEach(key => {
logMessage(`Attempting to set targeting for targetId: ${targetId} key: ${key} value: ${astTargeting[targetId][key]}`);
// setKeywords supports string and array as value
if (isStr(astTargeting[targetId][key]) || isArray(astTargeting[targetId][key])) {
let keywordsObj = {};
let regex = /pt[0-9]/;
if (key.search(regex) < 0) {
keywordsObj[key.toUpperCase()] = astTargeting[targetId][key];
} else {
// pt${n} keys should not be uppercased
keywordsObj[key] = astTargeting[targetId][key];
}
window.apntag.setKeywords(targetId, keywordsObj, { overrideKeyValue: true });
}
})
);
};
/**
* Get targeting key value pairs for winning bid.
* @param {string[]} adUnitCodes code array
* @return {targetingArray} winning bids targeting
*/
function getWinningBidTargeting(adUnitCodes, bidsReceived) {
let winners = targeting.getWinningBids(adUnitCodes, bidsReceived);
let standardKeys = getStandardKeys();
winners = winners.map(winner => {
return {
[winner.adUnitCode]: Object.keys(winner.adserverTargeting)
.filter(key =>
typeof winner.sendStandardTargeting === 'undefined' ||
winner.sendStandardTargeting ||
standardKeys.indexOf(key) === -1)
.reduce((acc, key) => {
const targetingValue = [winner.adserverTargeting[key]];
const targeting = { [key.substring(0, MAX_DFP_KEYLENGTH)]: targetingValue };
if (key === TARGETING_KEYS.DEAL) {
const bidderCodeTargetingKey = `${key}_${winner.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH);
const bidderCodeTargeting = { [bidderCodeTargetingKey]: targetingValue };
return [...acc, targeting, bidderCodeTargeting];
}
return [...acc, targeting];
}, [])
};
});
return winners;
}
function getStandardKeys() {
return auctionManager.getStandardBidderAdServerTargeting() // in case using a custom standard key set
.map(targeting => targeting.key)
.concat(TARGETING_KEYS_ARR).filter(uniques); // standard keys defined in the library.
}
/**
* Merge custom adserverTargeting with same key name for same adUnitCode.
* e.g: Appnexus defining custom keyvalue pair foo:bar and Rubicon defining custom keyvalue pair foo:baz will be merged to foo: ['bar','baz']
*
* @param {Object[]} acc Accumulator for reducer. It will store updated bidResponse objects
* @param {Object} bid BidResponse
* @param {number} index current index
* @param {Array} arr original array
*/
function mergeAdServerTargeting(acc, bid, index, arr) {
function concatTargetingValue(key) {
return function(currentBidElement) {
if (!isArray(currentBidElement.adserverTargeting[key])) {
currentBidElement.adserverTargeting[key] = [currentBidElement.adserverTargeting[key]];
}
currentBidElement.adserverTargeting[key] = currentBidElement.adserverTargeting[key].concat(bid.adserverTargeting[key]).filter(uniques);
delete bid.adserverTargeting[key];
}
}
function hasSameAdunitCodeAndKey(key) {
return function(currentBidElement) {
return currentBidElement.adUnitCode === bid.adUnitCode && currentBidElement.adserverTargeting[key]
}
}
Object.keys(bid.adserverTargeting)
.filter(getCustomKeys())
.forEach(key => {
if (acc.length) {
acc.filter(hasSameAdunitCodeAndKey(key))
.forEach(concatTargetingValue(key));
}
});
acc.push(bid);
return acc;
}
function getCustomKeys() {
let standardKeys = getStandardKeys();
if (FEATURES.NATIVE) {
standardKeys = standardKeys.concat(NATIVE_TARGETING_KEYS);
}
return function(key) {
return standardKeys.indexOf(key) === -1;
}
}
function truncateCustomKeys(bid) {
return {
[bid.adUnitCode]: Object.keys(bid.adserverTargeting)
// Get only the non-standard keys of the losing bids, since we
// don't want to override the standard keys of the winning bid.
.filter(getCustomKeys())
.map(key => {
return {
[key.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]]
};
})
}
}
/**
* Get custom targeting key value pairs for bids.
* @param {string[]} adUnitCodes code array
* @return {targetingArray} bids with custom targeting defined in bidderSettings
*/
function getCustomBidTargeting(adUnitCodes, bidsReceived) {
return bidsReceived
.filter(bid => includes(adUnitCodes, bid.adUnitCode))
.map(bid => Object.assign({}, bid))
.reduce(mergeAdServerTargeting, [])
.map(truncateCustomKeys)
.filter(bid => bid); // removes empty elements in array;
}
/**
* Get targeting key value pairs for non-winning bids.
* @param {string[]} adUnitCodes code array
* @return {targetingArray} all non-winning bids targeting
*/
function getBidLandscapeTargeting(adUnitCodes, bidsReceived) {
const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice();
const adUnitBidLimit = config.getConfig('sendBidsControl.bidLimit');
const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, adUnitBidLimit);
const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys');
const allowedSendAllBidTargeting = allowSendAllBidsTargetingKeys
? allowSendAllBidsTargetingKeys.map((key) => TARGETING_KEYS[key])
: standardKeys;
// populate targeting keys for the remaining bids
return bids.map(bid => {
if (bidShouldBeAddedToTargeting(bid, adUnitCodes)) {
return {
[bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter(
key => typeof bid.adserverTargeting[key] !== 'undefined' &&
allowedSendAllBidTargeting.indexOf(key) !== -1)
)
};
}
}).filter(bid => bid); // removes empty elements in array
}
function getTargetingMap(bid, keys) {
return keys.map(key => {
return {
[`${key}_${bid.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]]
};
});
}
function getAdUnitTargeting(adUnitCodes) {
function getTargetingObj(adUnit) {
return deepAccess(adUnit, JSON_MAPPING.ADSERVER_TARGETING);
}
function getTargetingValues(adUnit) {
const aut = getTargetingObj(adUnit);
return Object.keys(aut)
.map(function(key) {
if (isStr(aut[key])) aut[key] = aut[key].split(',').map(s => s.trim());
if (!isArray(aut[key])) aut[key] = [ aut[key] ];
return { [key]: aut[key] };
});
}
return auctionManager.getAdUnits()
.filter(adUnit => includes(adUnitCodes, adUnit.code) && getTargetingObj(adUnit))
.map(adUnit => {
return {[adUnit.code]: getTargetingValues(adUnit)}
});
}
targeting.isApntagDefined = function() {
if (window.apntag && isFn(window.apntag.setKeywords)) {
return true;
}
};
return targeting;
}
export const targeting = newTargeting(auctionManager);