mk9-prebid
Version:
Header Bidding Management Library
834 lines (724 loc) • 29.9 kB
JavaScript
/**
* Module for auction instances.
*
* In Prebid 0.x, $$PREBID_GLOBAL$$ had _bidsRequested and _bidsReceived as public properties.
* Starting 1.0, Prebid will support concurrent auctions. Each auction instance will store private properties, bidsRequested and bidsReceived.
*
* AuctionManager will create instance of auction and will store all the auctions.
*
*/
/**
* @typedef {Object} AdUnit An object containing the adUnit configuration.
*
* @property {string} code A code which will be used to uniquely identify this bidder. This should be the same
* one as is used in the call to registerBidAdapter
* @property {Array.<size>} sizes A list of size for adUnit.
* @property {object} params Any bidder-specific params which the publisher used in their bid request.
* This is guaranteed to have passed the spec.areParamsValid() test.
*/
/**
* @typedef {Array.<number>} size
*/
/**
* @typedef {Array.<string>} AdUnitCode
*/
/**
* @typedef {Object} BidderRequest
*
* @property {string} bidderCode - adUnit bidder
* @property {number} auctionId - random UUID
* @property {string} bidderRequestId - random string, unique key set on all bidRequest.bids[]
* @property {Array.<Bid>} bids
* @property {number} auctionStart - Date.now() at auction start
* @property {number} timeout - callback timeout
* @property {refererInfo} refererInfo - referer info object
* @property {string} [tid] - random UUID (used for s2s)
* @property {string} [src] - s2s or client (used for s2s)
*/
/**
* @typedef {Object} BidReceived
* //TODO add all properties
*/
/**
* @typedef {Object} Auction
*
* @property {function(): string} getAuctionStatus - returns the auction status which can be any one of 'started', 'in progress' or 'completed'
* @property {function(): AdUnit[]} getAdUnits - return the adUnits for this auction instance
* @property {function(): AdUnitCode[]} getAdUnitCodes - return the adUnitCodes for this auction instance
* @property {function(): BidRequest[]} getBidRequests - get all bid requests for this auction instance
* @property {function(): BidReceived[]} getBidsReceived - get all bid received for this auction instance
* @property {function(): void} startAuctionTimer - sets the bidsBackHandler callback and starts the timer for auction
* @property {function(): void} callBids - sends requests to all adapters for bids
*/
import {flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue, parseUrl} from './utils.js';
import { getPriceBucketString } from './cpmBucketManager.js';
import { getNativeTargeting } from './native.js';
import { getCacheUrl, store } from './videoCache.js';
import { Renderer } from './Renderer.js';
import { config } from './config.js';
import { userSync } from './userSync.js';
import { hook } from './hook.js';
import find from 'core-js-pure/features/array/find.js';
import includes from 'core-js-pure/features/array/includes.js';
import { OUTSTREAM } from './video.js';
import { VIDEO } from './mediaTypes.js';
const { syncUsers } = userSync;
const utils = require('./utils.js');
const adapterManager = require('./adapterManager.js').default;
const events = require('./events.js');
const CONSTANTS = require('./constants.json');
export const AUCTION_STARTED = 'started';
export const AUCTION_IN_PROGRESS = 'inProgress';
export const AUCTION_COMPLETED = 'completed';
// register event for bid adjustment
events.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) {
adjustBids(bid);
});
const MAX_REQUESTS_PER_ORIGIN = 4;
const outstandingRequests = {};
const sourceInfo = {};
const queuedCalls = [];
/**
* Creates new auction instance
*
* @param {Object} requestConfig
* @param {AdUnit} requestConfig.adUnits
* @param {AdUnitCode} requestConfig.adUnitCodes
* @param {function():void} requestConfig.callback
* @param {number} requestConfig.cbTimeout
* @param {Array.<string>} requestConfig.labels
* @param {string} requestConfig.auctionId
*
* @returns {Auction} auction instance
*/
export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, auctionId}) {
let _adUnits = adUnits;
let _labels = labels;
let _adUnitCodes = adUnitCodes;
let _bidderRequests = [];
let _bidsReceived = [];
let _noBids = [];
let _auctionStart;
let _auctionEnd;
let _auctionId = auctionId || utils.generateUUID();
let _auctionStatus;
let _callback = callback;
let _timer;
let _timeout = cbTimeout;
let _winningBids = [];
let _timelyBidders = new Set();
function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests); }
function addBidReceived(bidsReceived) { _bidsReceived = _bidsReceived.concat(bidsReceived); }
function addNoBid(noBid) { _noBids = _noBids.concat(noBid); }
function getProperties() {
return {
auctionId: _auctionId,
timestamp: _auctionStart,
auctionEnd: _auctionEnd,
auctionStatus: _auctionStatus,
adUnits: _adUnits,
adUnitCodes: _adUnitCodes,
labels: _labels,
bidderRequests: _bidderRequests,
noBids: _noBids,
bidsReceived: _bidsReceived,
winningBids: _winningBids,
timeout: _timeout
};
}
function startAuctionTimer() {
const timedOut = true;
const timeoutCallback = executeCallback.bind(null, timedOut);
let timer = setTimeout(timeoutCallback, _timeout);
_timer = timer;
}
function executeCallback(timedOut, cleartimer) {
// clear timer when done calls executeCallback
if (cleartimer) {
clearTimeout(_timer);
}
if (_auctionEnd === undefined) {
let timedOutBidders = [];
if (timedOut) {
utils.logMessage(`Auction ${_auctionId} timedOut`);
timedOutBidders = getTimedOutBids(_bidderRequests, _timelyBidders);
if (timedOutBidders.length) {
events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, timedOutBidders);
}
}
_auctionStatus = AUCTION_COMPLETED;
_auctionEnd = Date.now();
events.emit(CONSTANTS.EVENTS.AUCTION_END, getProperties());
bidsBackCallback(_adUnits, function () {
try {
if (_callback != null) {
const adUnitCodes = _adUnitCodes;
const bids = _bidsReceived
.filter(utils.bind.call(adUnitsFilter, this, adUnitCodes))
.reduce(groupByPlacement, {});
_callback.apply($$PREBID_GLOBAL$$, [bids, timedOut, _auctionId]);
_callback = null;
}
} catch (e) {
utils.logError('Error executing bidsBackHandler', null, e);
} finally {
// Calling timed out bidders
if (timedOutBidders.length) {
adapterManager.callTimedOutBidders(adUnits, timedOutBidders, _timeout);
}
// Only automatically sync if the publisher has not chosen to "enableOverride"
let userSyncConfig = config.getConfig('userSync') || {};
if (!userSyncConfig.enableOverride) {
// Delay the auto sync by the config delay
syncUsers(userSyncConfig.syncDelay);
}
}
})
}
}
function auctionDone() {
config.resetBidder();
// when all bidders have called done callback atleast once it means auction is complete
utils.logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived);
_auctionStatus = AUCTION_COMPLETED;
executeCallback(false, true);
}
function onTimelyResponse(bidderCode) {
_timelyBidders.add(bidderCode);
}
function callBids() {
_auctionStatus = AUCTION_STARTED;
_auctionStart = Date.now();
let bidRequests = adapterManager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout, _labels);
utils.logInfo(`Bids Requested for Auction with id: ${_auctionId}`, bidRequests);
if (bidRequests.length < 1) {
utils.logWarn('No valid bid requests returned for auction');
auctionDone();
} else {
addBidderRequests.call({
dispatch: addBidderRequestsCallback,
context: this
}, bidRequests);
}
}
/**
* callback executed after addBidderRequests completes
* @param {BidRequest[]} bidRequests
*/
function addBidderRequestsCallback(bidRequests) {
bidRequests.forEach(bidRequest => {
addBidRequests(bidRequest);
});
let requests = {};
let call = {
bidRequests,
run: () => {
startAuctionTimer();
_auctionStatus = AUCTION_IN_PROGRESS;
events.emit(CONSTANTS.EVENTS.AUCTION_INIT, getProperties());
let callbacks = auctionCallbacks(auctionDone, this);
adapterManager.callBids(_adUnits, bidRequests, function(...args) {
addBidResponse.apply({
dispatch: callbacks.addBidResponse,
bidderRequest: this
}, args)
}, callbacks.adapterDone, {
request(source, origin) {
increment(outstandingRequests, origin);
increment(requests, source);
if (!sourceInfo[source]) {
sourceInfo[source] = {
SRA: true,
origin
};
}
if (requests[source] > 1) {
sourceInfo[source].SRA = false;
}
},
done(origin) {
outstandingRequests[origin]--;
if (queuedCalls[0]) {
if (runIfOriginHasCapacity(queuedCalls[0])) {
queuedCalls.shift();
}
}
}
}, _timeout, onTimelyResponse);
}
};
if (!runIfOriginHasCapacity(call)) {
utils.logWarn('queueing auction due to limited endpoint capacity');
queuedCalls.push(call);
}
function runIfOriginHasCapacity(call) {
let hasCapacity = true;
let maxRequests = config.getConfig('maxRequestsPerOrigin') || MAX_REQUESTS_PER_ORIGIN;
call.bidRequests.some(bidRequest => {
let requests = 1;
let source = (typeof bidRequest.src !== 'undefined' && bidRequest.src === CONSTANTS.S2S.SRC) ? 's2s'
: bidRequest.bidderCode;
// if we have no previous info on this source just let them through
if (sourceInfo[source]) {
if (sourceInfo[source].SRA === false) {
// some bidders might use more than the MAX_REQUESTS_PER_ORIGIN in a single auction. In those cases
// set their request count to MAX_REQUESTS_PER_ORIGIN so the auction isn't permanently queued waiting
// for capacity for that bidder
requests = Math.min(bidRequest.bids.length, maxRequests);
}
if (outstandingRequests[sourceInfo[source].origin] + requests > maxRequests) {
hasCapacity = false;
}
}
// return only used for terminating this .some() iteration early if it is determined we don't have capacity
return !hasCapacity;
});
if (hasCapacity) {
call.run();
}
return hasCapacity;
}
function increment(obj, prop) {
if (typeof obj[prop] === 'undefined') {
obj[prop] = 1
} else {
obj[prop]++;
}
}
}
function addWinningBid(winningBid) {
_winningBids = _winningBids.concat(winningBid);
adapterManager.callBidWonBidder(winningBid.bidder, winningBid, adUnits);
}
function setBidTargeting(bid) {
adapterManager.callSetTargetingBidder(bid.bidder, bid);
}
return {
addBidReceived,
addNoBid,
executeCallback,
callBids,
addWinningBid,
setBidTargeting,
getWinningBids: () => _winningBids,
getTimeout: () => _timeout,
getAuctionId: () => _auctionId,
getAuctionStatus: () => _auctionStatus,
getAdUnits: () => _adUnits,
getAdUnitCodes: () => _adUnitCodes,
getBidRequests: () => _bidderRequests,
getBidsReceived: () => _bidsReceived,
getNoBids: () => _noBids
}
}
export const addBidResponse = hook('async', function(adUnitCode, bid) {
this.dispatch.call(this.bidderRequest, adUnitCode, bid);
}, 'addBidResponse');
export const addBidderRequests = hook('sync', function(bidderRequests) {
this.dispatch.call(this.context, bidderRequests);
}, 'addBidderRequests');
export const bidsBackCallback = hook('async', function (adUnits, callback) {
if (callback) {
callback();
}
}, 'bidsBackCallback');
export function auctionCallbacks(auctionDone, auctionInstance) {
let outstandingBidsAdded = 0;
let allAdapterCalledDone = false;
let bidderRequestsDone = new Set();
let bidResponseMap = {};
function afterBidAdded() {
outstandingBidsAdded--;
if (allAdapterCalledDone && outstandingBidsAdded === 0) {
auctionDone()
}
}
function addBidResponse(adUnitCode, bid) {
let bidderRequest = this;
bidResponseMap[bid.requestId] = true;
outstandingBidsAdded++;
let auctionId = auctionInstance.getAuctionId();
let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId});
if (bidResponse.mediaType === 'video') {
tryAddVideoBid(auctionInstance, bidResponse, bidderRequest, afterBidAdded);
} else {
addBidToAuction(auctionInstance, bidResponse);
afterBidAdded();
}
}
function adapterDone() {
let bidderRequest = this;
let bidderRequests = auctionInstance.getBidRequests();
const auctionOptionsConfig = config.getConfig('auctionOptions');
bidderRequestsDone.add(bidderRequest);
if (auctionOptionsConfig && !utils.isEmpty(auctionOptionsConfig)) {
const secondaryBidders = auctionOptionsConfig.secondaryBidders;
if (secondaryBidders && !bidderRequests.every(bidder => includes(secondaryBidders, bidder.bidderCode))) {
bidderRequests = bidderRequests.filter(request => !includes(secondaryBidders, request.bidderCode));
}
}
allAdapterCalledDone = bidderRequests.every(bidderRequest => bidderRequestsDone.has(bidderRequest));
bidderRequest.bids.forEach(bid => {
if (!bidResponseMap[bid.bidId]) {
auctionInstance.addNoBid(bid);
events.emit(CONSTANTS.EVENTS.NO_BID, bid);
}
});
if (allAdapterCalledDone && outstandingBidsAdded === 0) {
auctionDone();
}
}
return {
addBidResponse,
adapterDone
}
}
export function doCallbacksIfTimedout(auctionInstance, bidResponse) {
if (bidResponse.timeToRespond > auctionInstance.getTimeout() + config.getConfig('timeoutBuffer')) {
auctionInstance.executeCallback(true);
}
}
// Add a bid to the auction.
export function addBidToAuction(auctionInstance, bidResponse) {
let bidderRequests = auctionInstance.getBidRequests();
let bidderRequest = find(bidderRequests, bidderRequest => bidderRequest.bidderCode === bidResponse.bidderCode);
setupBidTargeting(bidResponse, bidderRequest);
events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse);
auctionInstance.addBidReceived(bidResponse);
doCallbacksIfTimedout(auctionInstance, bidResponse);
}
// Video bids may fail if the cache is down, or there's trouble on the network.
function tryAddVideoBid(auctionInstance, bidResponse, bidRequests, afterBidAdded) {
let addBid = true;
const bidderRequest = getBidRequest(bidResponse.originalRequestId || bidResponse.requestId, [bidRequests]);
const videoMediaType =
bidderRequest && deepAccess(bidderRequest, 'mediaTypes.video');
const context = videoMediaType && deepAccess(videoMediaType, 'context');
if (config.getConfig('cache.url') && context !== OUTSTREAM) {
if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) {
addBid = false;
callPrebidCache(auctionInstance, bidResponse, afterBidAdded, bidderRequest);
} else if (!bidResponse.vastUrl) {
utils.logError('videoCacheKey specified but not required vastUrl for video bid');
addBid = false;
}
}
if (addBid) {
addBidToAuction(auctionInstance, bidResponse);
afterBidAdded();
}
}
export const callPrebidCache = hook('async', function(auctionInstance, bidResponse, afterBidAdded, bidderRequest) {
store([bidResponse], function (error, cacheIds) {
if (error) {
utils.logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`);
doCallbacksIfTimedout(auctionInstance, bidResponse);
} else {
if (cacheIds[0].uuid === '') {
utils.logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`);
doCallbacksIfTimedout(auctionInstance, bidResponse);
} else {
bidResponse.videoCacheKey = cacheIds[0].uuid;
if (!bidResponse.vastUrl) {
bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey);
}
addBidToAuction(auctionInstance, bidResponse);
afterBidAdded();
}
}
}, bidderRequest);
}, 'callPrebidCache');
// Postprocess the bids so that all the universal properties exist, no matter which bidder they came from.
// This should be called before addBidToAuction().
function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) {
const start = bidderRequest.start;
let bidObject = Object.assign({}, bid, {
auctionId,
responseTimestamp: timestamp(),
requestTimestamp: start,
cpm: parseFloat(bid.cpm) || 0,
bidder: bid.bidderCode,
adUnitCode
});
bidObject.timeToRespond = bidObject.responseTimestamp - bidObject.requestTimestamp;
// Let listeners know that now is the time to adjust the bid, if they want to.
//
// CAREFUL: Publishers rely on certain bid properties to be available (like cpm),
// but others to not be set yet (like priceStrings). See #1372 and #1389.
events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bidObject);
// a publisher-defined renderer can be used to render bids
const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode && bid.bidId == bidObject.requestId);
const adUnitRenderer = bidReq && bidReq.renderer;
// a publisher can also define a renderer for a mediaType
const bidObjectMediaType = bidObject.mediaType;
const bidMediaType = bidReq &&
bidReq.mediaTypes &&
bidReq.mediaTypes[bidObjectMediaType];
var mediaTypeRenderer = bidMediaType && bidMediaType.renderer;
var renderer = null;
// the renderer for the mediaType takes precendence
if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) {
renderer = mediaTypeRenderer;
} else if (adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render && !(adUnitRenderer.backupOnly === true && bid.renderer)) {
renderer = adUnitRenderer;
}
if (renderer) {
bidObject.renderer = Renderer.install({ url: renderer.url });
bidObject.renderer.setRender(renderer.render);
}
// Use the config value 'mediaTypeGranularity' if it has been defined for mediaType, else use 'customPriceBucket'
const mediaTypeGranularity = getMediaTypeGranularity(bid.mediaType, bidReq, config.getConfig('mediaTypePriceGranularity'));
const priceStringsObj = getPriceBucketString(
bidObject.cpm,
(typeof mediaTypeGranularity === 'object') ? mediaTypeGranularity : config.getConfig('customPriceBucket'),
config.getConfig('currency.granularityMultiplier')
);
bidObject.pbLg = priceStringsObj.low;
bidObject.pbMg = priceStringsObj.med;
bidObject.pbHg = priceStringsObj.high;
bidObject.pbAg = priceStringsObj.auto;
bidObject.pbDg = priceStringsObj.dense;
bidObject.pbCg = priceStringsObj.custom;
return bidObject;
}
function setupBidTargeting(bidObject, bidderRequest) {
let keyValues;
if (bidObject.bidderCode && (bidObject.cpm > 0 || bidObject.dealId)) {
let bidReq = find(bidderRequest.bids, bid => bid.adUnitCode === bidObject.adUnitCode);
keyValues = getKeyValueTargetingPairs(bidObject.bidderCode, bidObject, bidReq);
}
// use any targeting provided as defaults, otherwise just set from getKeyValueTargetingPairs
bidObject.adserverTargeting = Object.assign(bidObject.adserverTargeting || {}, keyValues);
}
/**
* @param {MediaType} mediaType
* @param {Bid} [bidReq]
* @param {MediaTypePriceGranularity} [mediaTypePriceGranularity]
* @returns {(Object|string|undefined)}
*/
export function getMediaTypeGranularity(mediaType, bidReq, mediaTypePriceGranularity) {
if (mediaType && mediaTypePriceGranularity) {
if (mediaType === VIDEO) {
const context = deepAccess(bidReq, `mediaTypes.${VIDEO}.context`, 'instream');
if (mediaTypePriceGranularity[`${VIDEO}-${context}`]) {
return mediaTypePriceGranularity[`${VIDEO}-${context}`];
}
}
return mediaTypePriceGranularity[mediaType];
}
}
/**
* This function returns the price granularity defined. It can be either publisher defined or default value
* @param {string} mediaType
* @param {BidRequest} bidReq
* @returns {string} granularity
*/
export const getPriceGranularity = (mediaType, bidReq) => {
// Use the config value 'mediaTypeGranularity' if it has been set for mediaType, else use 'priceGranularity'
const mediaTypeGranularity = getMediaTypeGranularity(mediaType, bidReq, config.getConfig('mediaTypePriceGranularity'));
const granularity = (typeof mediaType === 'string' && mediaTypeGranularity) ? ((typeof mediaTypeGranularity === 'string') ? mediaTypeGranularity : 'custom') : config.getConfig('priceGranularity');
return granularity;
}
/**
* This function returns a function to get bid price by price granularity
* @param {string} granularity
* @returns {function}
*/
export const getPriceByGranularity = (granularity) => {
return (bid) => {
if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) {
return bid.pbAg;
} else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) {
return bid.pbDg;
} else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) {
return bid.pbLg;
} else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) {
return bid.pbMg;
} else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) {
return bid.pbHg;
} else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) {
return bid.pbCg;
}
}
}
/**
* This function returns a function to get first advertiser domain from bid response meta
* @returns {function}
*/
export const getAdvertiserDomain = () => {
return (bid) => {
return (bid.meta && bid.meta.advertiserDomains && bid.meta.advertiserDomains.length > 0) ? bid.meta.advertiserDomains[0] : '';
}
}
/**
* @param {string} mediaType
* @param {string} bidderCode
* @param {BidRequest} bidReq
* @returns {*}
*/
export function getStandardBidderSettings(mediaType, bidderCode, bidReq) {
// factory for key value objs
function createKeyVal(key, value) {
return {
key,
val: (typeof value === 'function')
? function (bidResponse) {
return value(bidResponse);
}
: function (bidResponse) {
return getValue(bidResponse, value);
}
};
}
const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS;
const granularity = getPriceGranularity(mediaType, bidReq);
let bidderSettings = $$PREBID_GLOBAL$$.bidderSettings;
if (!bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]) {
bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = {};
}
if (!bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) {
bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = [
createKeyVal(TARGETING_KEYS.BIDDER, 'bidderCode'),
createKeyVal(TARGETING_KEYS.AD_ID, 'adId'),
createKeyVal(TARGETING_KEYS.PRICE_BUCKET, getPriceByGranularity(granularity)),
createKeyVal(TARGETING_KEYS.SIZE, 'size'),
createKeyVal(TARGETING_KEYS.DEAL, 'dealId'),
createKeyVal(TARGETING_KEYS.SOURCE, 'source'),
createKeyVal(TARGETING_KEYS.FORMAT, 'mediaType'),
createKeyVal(TARGETING_KEYS.ADOMAIN, getAdvertiserDomain()),
]
}
if (mediaType === 'video') {
const adserverTargeting = bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING];
// Adding hb_uuid + hb_cache_id
[TARGETING_KEYS.UUID, TARGETING_KEYS.CACHE_ID].forEach(targetingKeyVal => {
if (typeof find(adserverTargeting, kvPair => kvPair.key === targetingKeyVal) === 'undefined') {
adserverTargeting.push(createKeyVal(targetingKeyVal, 'videoCacheKey'));
}
});
// Adding hb_cache_host
if (config.getConfig('cache.url') && (!bidderCode || utils.deepAccess(bidderSettings, `${bidderCode}.sendStandardTargeting`) !== false)) {
const urlInfo = parseUrl(config.getConfig('cache.url'));
if (typeof find(adserverTargeting, targetingKeyVal => targetingKeyVal.key === TARGETING_KEYS.CACHE_HOST) === 'undefined') {
adserverTargeting.push(createKeyVal(TARGETING_KEYS.CACHE_HOST, function(bidResponse) {
return utils.deepAccess(bidResponse, `adserverTargeting.${TARGETING_KEYS.CACHE_HOST}`)
? bidResponse.adserverTargeting[TARGETING_KEYS.CACHE_HOST] : urlInfo.hostname;
}));
}
}
}
return bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD];
}
export function getKeyValueTargetingPairs(bidderCode, custBidObj, bidReq) {
if (!custBidObj) {
return {};
}
var keyValues = {};
var bidderSettings = $$PREBID_GLOBAL$$.bidderSettings;
// 1) set the keys from "standard" setting or from prebid defaults
if (bidderSettings) {
// initialize default if not set
const standardSettings = getStandardBidderSettings(custBidObj.mediaType, bidderCode, bidReq);
setKeys(keyValues, standardSettings, custBidObj);
// 2) set keys from specific bidder setting override if they exist
if (bidderCode && bidderSettings[bidderCode] && bidderSettings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) {
setKeys(keyValues, bidderSettings[bidderCode], custBidObj);
custBidObj.sendStandardTargeting = bidderSettings[bidderCode].sendStandardTargeting;
}
}
// set native key value targeting
if (custBidObj['native']) {
keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj, bidReq));
}
return keyValues;
}
function setKeys(keyValues, bidderSettings, custBidObj) {
var targeting = bidderSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING];
custBidObj.size = custBidObj.getSize();
utils._each(targeting, function (kvPair) {
var key = kvPair.key;
var value = kvPair.val;
if (keyValues[key]) {
utils.logWarn('The key: ' + key + ' is getting ovewritten');
}
if (utils.isFn(value)) {
try {
value = value(custBidObj);
} catch (e) {
utils.logError('bidmanager', 'ERROR', e);
}
}
if (
((typeof bidderSettings.suppressEmptyKeys !== 'undefined' && bidderSettings.suppressEmptyKeys === true) ||
key === CONSTANTS.TARGETING_KEYS.DEAL) && // hb_deal is suppressed automatically if not set
(
utils.isEmptyStr(value) ||
value === null ||
value === undefined
)
) {
utils.logInfo("suppressing empty key '" + key + "' from adserver targeting");
} else {
keyValues[key] = value;
}
});
return keyValues;
}
export function adjustBids(bid) {
let code = bid.bidderCode;
let bidPriceAdjusted = bid.cpm;
let bidCpmAdjustment;
if ($$PREBID_GLOBAL$$.bidderSettings) {
if (code && $$PREBID_GLOBAL$$.bidderSettings[code] && typeof $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment === 'function') {
bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment;
} else if ($$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] && typeof $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment === 'function') {
bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment;
}
if (bidCpmAdjustment) {
try {
bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid));
} catch (e) {
utils.logError('Error during bid adjustment', 'bidmanager.js', e);
}
}
}
if (bidPriceAdjusted >= 0) {
bid.cpm = bidPriceAdjusted;
}
}
/**
* groupByPlacement is a reduce function that converts an array of Bid objects
* to an object with placement codes as keys, with each key representing an object
* with an array of `Bid` objects for that placement
* @returns {*} as { [adUnitCode]: { bids: [Bid, Bid, Bid] } }
*/
function groupByPlacement(bidsByPlacement, bid) {
if (!bidsByPlacement[bid.adUnitCode]) { bidsByPlacement[bid.adUnitCode] = { bids: [] }; }
bidsByPlacement[bid.adUnitCode].bids.push(bid);
return bidsByPlacement;
}
/**
* Returns a list of bids that we haven't received a response yet where the bidder did not call done
* @param {BidRequest[]} bidderRequests List of bids requested for auction instance
* @param {Set} timelyBidders Set of bidders which responded in time
*
* @typedef {Object} TimedOutBid
* @property {string} bidId The id representing the bid
* @property {string} bidder The string name of the bidder
* @property {string} adUnitCode The code used to uniquely identify the ad unit on the publisher's page
* @property {string} auctionId The id representing the auction
*
* @return {Array<TimedOutBid>} List of bids that Prebid hasn't received a response for
*/
function getTimedOutBids(bidderRequests, timelyBidders) {
const timedOutBids = bidderRequests
.map(bid => (bid.bids || []).filter(bid => !timelyBidders.has(bid.bidder)))
.reduce(flatten, [])
.map(bid => ({
bidId: bid.bidId,
bidder: bid.bidder,
adUnitCode: bid.adUnitCode,
auctionId: bid.auctionId,
}));
return timedOutBids;
}