Skip to content

Commit

Permalink
Core & multiple modules: introduce new bidRejected event, and refac…
Browse files Browse the repository at this point in the history
…tor bid rejection logic (#9013)

* Separate code path for bid rejection

* Client side bidder rejections

* Rejection reasons in constants.json

* PBS rejections

* priceFloors

* currency

* Filter out NO_BID bids from targeting

* Reject bids whose size does not match the request

* Do not allow PBS stored impressions to go through without allowUnknownBidderCodes
  • Loading branch information
dgirardi authored Oct 11, 2022
1 parent 958acae commit 5e18b2d
Show file tree
Hide file tree
Showing 21 changed files with 509 additions and 217 deletions.
8 changes: 4 additions & 4 deletions modules/categoryTranslation.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ export const registerAdserver = hook('async', function(adServer) {
}, 'registerAdserver');
registerAdserver();

export const getAdserverCategoryHook = timedBidResponseHook('categoryTranslation', function getAdserverCategoryHook(fn, adUnitCode, bid) {
export const getAdserverCategoryHook = timedBidResponseHook('categoryTranslation', function getAdserverCategoryHook(fn, adUnitCode, bid, reject) {
if (!bid) {
return fn.call(this, adUnitCode); // if no bid, call original and let it display warnings
return fn.call(this, adUnitCode, bid, reject); // if no bid, call original and let it display warnings
}

if (!config.getConfig('adpod.brandCategoryExclusion')) {
return fn.call(this, adUnitCode, bid);
return fn.call(this, adUnitCode, bid, reject);
}

let localStorageKey = (config.getConfig('brandCategoryTranslation.translationFile')) ? DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB : DEFAULT_IAB_TO_FW_MAPPING_KEY;
Expand All @@ -63,7 +63,7 @@ export const getAdserverCategoryHook = timedBidResponseHook('categoryTranslation
logError('Translation mapping data not found in local storage');
}
}
fn.call(this, adUnitCode, bid);
fn.call(this, adUnitCode, bid, reject);
});

export function initTranslation(url, localStorageKey) {
Expand Down
22 changes: 11 additions & 11 deletions modules/currency.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { logInfo, logWarn, logError, logMessage } from '../src/utils.js';
import { getGlobal } from '../src/prebidGlobal.js';
import { createBid } from '../src/bidfactory.js';
import {logError, logInfo, logMessage, logWarn} from '../src/utils.js';
import {getGlobal} from '../src/prebidGlobal.js';
import CONSTANTS from '../src/constants.json';
import { ajax } from '../src/ajax.js';
import { config } from '../src/config.js';
import { getHook } from '../src/hook.js';
import {ajax} from '../src/ajax.js';
import {config} from '../src/config.js';
import {getHook} from '../src/hook.js';
import {defer} from '../src/utils/promise.js';
import {timedBidResponseHook} from '../src/utils/perfMetrics.js';

Expand Down Expand Up @@ -181,9 +180,9 @@ function resetCurrency() {
bidderCurrencyDefault = {};
}

export const addBidResponseHook = timedBidResponseHook('currency', function addBidResponseHook(fn, adUnitCode, bid) {
export const addBidResponseHook = timedBidResponseHook('currency', function addBidResponseHook(fn, adUnitCode, bid, reject) {
if (!bid) {
return fn.call(this, adUnitCode); // if no bid, call original and let it display warnings
return fn.call(this, adUnitCode, bid, reject); // if no bid, call original and let it display warnings
}

let bidder = bid.bidderCode || bid.bidder;
Expand All @@ -209,10 +208,10 @@ export const addBidResponseHook = timedBidResponseHook('currency', function addB

// execute immediately if the bid is already in the desired currency
if (bid.currency === adServerCurrency) {
return fn.call(this, adUnitCode, bid);
return fn.call(this, adUnitCode, bid, reject);
}

bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid]));
bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid, reject]));
if (!currencySupportEnabled || currencyRatesLoaded) {
processBidResponseQueue();
} else {
Expand All @@ -239,7 +238,8 @@ function wrapFunction(fn, context, params) {
}
} catch (e) {
logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e);
params[1] = createBid(CONSTANTS.STATUS.NO_BID, bid.getIdentifiers());
// TODO: in v8, this should not continue with a "NO_BID"
params[1] = params[2](CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY);
}
}
return fn.apply(context, params);
Expand Down
4 changes: 2 additions & 2 deletions modules/dchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ function isValidDchain(bid) {
}
}

export const addBidResponseHook = timedBidResponseHook('dchain', function addBidResponseHook(fn, adUnitCode, bid) {
export const addBidResponseHook = timedBidResponseHook('dchain', function addBidResponseHook(fn, adUnitCode, bid, reject) {
const basicDchain = {
ver: '1.0',
complete: 0,
Expand Down Expand Up @@ -140,7 +140,7 @@ export const addBidResponseHook = timedBidResponseHook('dchain', function addBid
bid.meta.dchain = basicDchain;
}

fn(adUnitCode, bid);
fn(adUnitCode, bid, reject);
});

export function init() {
Expand Down
4 changes: 2 additions & 2 deletions modules/debugging/legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function applyBidOverrides(overrideObj, bidObj, bidType, logger) {
}, bidObj);
}

export function addBidResponseHook(next, adUnitCode, bid) {
export function addBidResponseHook(next, adUnitCode, bid, reject) {
const {overrides, logger} = this;

if (bidderExcluded(overrides.bidders, bid.bidderCode)) {
Expand All @@ -70,7 +70,7 @@ export function addBidResponseHook(next, adUnitCode, bid) {
});
}

next(adUnitCode, bid);
next(adUnitCode, bid, reject);
}

export function addBidderRequestsHook(next, bidderRequests) {
Expand Down
4 changes: 2 additions & 2 deletions modules/mass.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export function updateRenderers() {
/**
* Before hook for 'addBidResponse'.
*/
export const addBidResponseHook = timedBidResponseHook('mass', function addBidResponseHook(next, adUnitCode, bid, {index = auctionManager.index} = {}) {
export const addBidResponseHook = timedBidResponseHook('mass', function addBidResponseHook(next, adUnitCode, bid, reject, {index = auctionManager.index} = {}) {
let renderer;
for (let i = 0; i < renderers.length; i++) {
if (renderers[i].match(bid)) {
Expand All @@ -104,7 +104,7 @@ export const addBidResponseHook = timedBidResponseHook('mass', function addBidRe
addListenerOnce();
}

next(adUnitCode, bid);
next(adUnitCode, bid, reject);
});

/**
Expand Down
10 changes: 5 additions & 5 deletions modules/multibid/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ export function adjustBidderRequestsHook(fn, bidderRequests) {
* @param {String} ad unit code for bid
* @param {Object} bid object
*/
export const addBidResponseHook = timedBidResponseHook('multibid', function addBidResponseHook(fn, adUnitCode, bid) {
export const addBidResponseHook = timedBidResponseHook('multibid', function addBidResponseHook(fn, adUnitCode, bid, reject) {
let floor = deepAccess(bid, 'floorData.floorValue');

if (!config.getConfig('multibid')) resetMultiConfig();
// Checks if multiconfig exists and bid bidderCode exists within config and is an adpod bid
// Else checks if multiconfig exists and bid bidderCode exists within config
// Else continue with no modifications
if (hasMultibid && multiConfig[bid.bidderCode] && deepAccess(bid, 'video.context') === 'adpod') {
fn.call(this, adUnitCode, bid);
fn.call(this, adUnitCode, bid, reject);
} else if (hasMultibid && multiConfig[bid.bidderCode]) {
// Set property multibidPrefix on bid
if (multiConfig[bid.bidderCode].prefix) bid.multibidPrefix = multiConfig[bid.bidderCode].prefix;
Expand All @@ -127,7 +127,7 @@ export const addBidResponseHook = timedBidResponseHook('multibid', function addB
if (multiConfig[bid.bidderCode].prefix) bid.targetingBidder = multiConfig[bid.bidderCode].prefix + length;
if (length === multiConfig[bid.bidderCode].maxbids) multibidUnits[adUnitCode][bid.bidderCode].maxReached = true;

fn.call(this, adUnitCode, bid);
fn.call(this, adUnitCode, bid, reject);
} else {
logWarn(`Filtering multibid received from bidder ${bid.bidderCode}: ` + ((multibidUnits[adUnitCode][bid.bidderCode].maxReached) ? `Maximum bid limit reached for ad unit code ${adUnitCode}` : 'Bid cpm under floors value.'));
}
Expand All @@ -137,10 +137,10 @@ export const addBidResponseHook = timedBidResponseHook('multibid', function addB
deepSetValue(multibidUnits, [adUnitCode, bid.bidderCode], {ads: [bid]});
if (multibidUnits[adUnitCode][bid.bidderCode].ads.length === multiConfig[bid.bidderCode].maxbids) multibidUnits[adUnitCode][bid.bidderCode].maxReached = true;

fn.call(this, adUnitCode, bid);
fn.call(this, adUnitCode, bid, reject);
}
} else {
fn.call(this, adUnitCode, bid);
fn.call(this, adUnitCode, bid, reject);
}
});

Expand Down
16 changes: 10 additions & 6 deletions modules/prebidServerBidAdapter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -950,10 +950,6 @@ Object.assign(ORTB2.prototype, {
(seatbid.bid || []).forEach(bid => {
let bidRequest = this.getBidRequest(bid.impid, seatbid.seat);
if (bidRequest == null) {
if (!s2sConfig.allowUnknownBidderCodes) {
logWarn(`PBS adapter received bid from unknown bidder (${seatbid.seat}), but 's2sConfig.allowUnknownBidderCodes' is not set. Ignoring bid.`);
return;
}
// for stored impression, a request was made with bidder code `null`. Pick it up here so that NO_BID, BID_WON, etc events
// can work as expected (otherwise, the original request will always result in NO_BID).
bidRequest = this.getBidRequest(bid.impid, null);
Expand All @@ -968,6 +964,7 @@ Object.assign(ORTB2.prototype, {
transactionId: this.adUnitsByImp[bid.impid].transactionId,
auctionId: this.auctionId,
});
bidObject.requestBidder = bidRequest?.bidder;
bidObject.requestTimestamp = this.requestTimestamp;
bidObject.cpm = cpm;
if (bid?.ext?.prebid?.meta?.adaptercode) {
Expand Down Expand Up @@ -1158,8 +1155,15 @@ export function PrebidServer() {
onBid: function ({adUnit, bid}) {
const metrics = bid.metrics = s2sBidRequest.metrics.fork().renameWith();
metrics.checkpoint('addBidResponse');
if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnit, bid))) {
addBidResponse(adUnit, bid);
if ((bid.requestId == null || bid.requestBidder == null) && !s2sBidRequest.s2sConfig.allowUnknownBidderCodes) {
logWarn(`PBS adapter received bid from unknown bidder (${bid.bidder}), but 's2sConfig.allowUnknownBidderCodes' is not set. Ignoring bid.`);
addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.BIDDER_DISALLOWED);
} else {
if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnit, bid))) {
addBidResponse(adUnit, bid);
} else {
addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.INVALID);
}
}
}
})
Expand Down
30 changes: 8 additions & 22 deletions modules/priceFloors.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {ajaxBuilder} from '../src/ajax.js';
import * as events from '../src/events.js';
import CONSTANTS from '../src/constants.json';
import {getHook} from '../src/hook.js';
import {createBid} from '../src/bidfactory.js';
import {find} from '../src/polyfill.js';
import {getRefererInfo} from '../src/refererDetection.js';
import {bidderSettings} from '../src/bidderSettings.js';
Expand Down Expand Up @@ -694,11 +693,11 @@ function shouldFloorBid(floorData, floorInfo, bid) {
* @summary The main driving force of floors. On bidResponse we hook in and intercept bidResponses.
* And if the rule we find determines a bid should be floored we will do so.
*/
export const addBidResponseHook = timedBidResponseHook('priceFloors', function addBidResponseHook(fn, adUnitCode, bid) {
export const addBidResponseHook = timedBidResponseHook('priceFloors', function addBidResponseHook(fn, adUnitCode, bid, reject) {
let floorData = _floorDataForAuction[bid.auctionId];
// if no floor data then bail
if (!floorData || !bid || floorData.skipped) {
return fn.call(this, adUnitCode, bid);
return fn.call(this, adUnitCode, bid, reject);
}

const matchingBidRequest = auctionManager.index.getBidRequest(bid);
Expand All @@ -708,7 +707,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a

if (!floorInfo.matchingFloor) {
logWarn(`${MODULE_NAME}: unable to determine a matching price floor for bidResponse`, bid);
return fn.call(this, adUnitCode, bid);
return fn.call(this, adUnitCode, bid, reject);
}

// determine the base cpm to use based on if the currency matches the floor currency
Expand All @@ -724,7 +723,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a
adjustedCpm = getGlobal().convertCurrency(bid.cpm, bidResponseCurrency.toUpperCase(), floorCurrency);
} catch (err) {
logError(`${MODULE_NAME}: Unable do get currency conversion for bidResponse to Floor Currency. Do you have Currency module enabled? ${bid}`);
return fn.call(this, adUnitCode, bid);
return fn.call(this, adUnitCode, bid, reject);
}
}

Expand All @@ -737,25 +736,12 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a
// now do the compare!
if (shouldFloorBid(floorData, floorInfo, bid)) {
// bid fails floor -> throw it out
// create basic bid no-bid with necessary data fro analytics adapters
let flooredBid = createBid(CONSTANTS.STATUS.NO_BID, bid.getIdentifiers());
Object.assign(flooredBid, pick(bid, [
'floorData',
'width',
'height',
'mediaType',
'currency',
'originalCpm',
'originalCurrency',
'getCpmInNewCurrency',
]));
flooredBid.status = CONSTANTS.BID_STATUS.BID_REJECTED;
// if floor not met update bid with 0 cpm so it is not included downstream and marked as no-bid
flooredBid.cpm = 0;
// continue with a "NO_BID" bid, TODO: remove this in v8
const flooredBid = reject(CONSTANTS.REJECTION_REASON.FLOOR_NOT_MET);
logWarn(`${MODULE_NAME}: ${flooredBid.bidderCode}'s Bid Response for ${adUnitCode} was rejected due to floor not met (adjusted cpm: ${bid?.floorData?.cpmAfterAdjustments}, floor: ${floorInfo?.matchingFloor})`, bid);
return fn.call(this, adUnitCode, flooredBid);
return fn.call(this, adUnitCode, flooredBid, reject);
}
return fn.call(this, adUnitCode, bid);
return fn.call(this, adUnitCode, bid, reject);
});

config.getConfig('floors', config => handleSetFloorsConfig(config.floors));
24 changes: 13 additions & 11 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ export function newBidder(spec) {
adUnitCodesHandled[adUnitCode] = true;
if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnitCode, bid))) {
addBidResponse(adUnitCode, bid);
} else {
addBidResponse.reject(adUnitCode, bid, CONSTANTS.REJECTION_REASON.INVALID)
}
}

Expand Down Expand Up @@ -262,6 +264,7 @@ export function newBidder(spec) {
bid.adapterCode = bidRequest.bidder;
if (isInvalidAlternateBidder(bid.bidderCode, bidRequest.bidder)) {
logWarn(`${bid.bidderCode} is not a registered partner or known bidder of ${bidRequest.bidder}, hence continuing without bid. If you wish to support this bidder, please mark allowAlternateBidderCodes as true in bidderSettings.`);
addBidResponse.reject(bidRequest.adUnitCode, bid, CONSTANTS.REJECTION_REASON.BIDDER_DISALLOWED)
return;
}
// creating a copy of original values as cpm and currency are modified later
Expand All @@ -272,6 +275,7 @@ export function newBidder(spec) {
addBidWithCode(bidRequest.adUnitCode, prebidBid);
} else {
logWarn(`Bidder ${spec.code} made bid for unknown request ID: ${bid.requestId}. Ignoring.`);
addBidResponse.reject(null, bid, CONSTANTS.REJECTION_REASON.INVALID_REQUEST_ID);
}
},
onCompletion: afterAllResponses,
Expand Down Expand Up @@ -541,24 +545,22 @@ export function getIabSubCategory(bidderCode, category) {

// check that the bid has a width and height set
function validBidSize(adUnitCode, bid, {index = auctionManager.index} = {}) {
if ((bid.width || parseInt(bid.width, 10) === 0) && (bid.height || parseInt(bid.height, 10) === 0)) {
bid.width = parseInt(bid.width, 10);
bid.height = parseInt(bid.height, 10);
return true;
}

const bidRequest = index.getBidRequest(bid);
const mediaTypes = index.getMediaTypes(bid);

const sizes = (bidRequest && bidRequest.sizes) || (mediaTypes && mediaTypes.banner && mediaTypes.banner.sizes);
const parsedSizes = parseSizesInput(sizes);
const parsedSizes = parseSizesInput(sizes).map(sz => sz.split('x').map(n => parseInt(n, 10)));

if ((bid.width || parseInt(bid.width, 10) === 0) && (bid.height || parseInt(bid.height, 10) === 0)) {
bid.width = parseInt(bid.width, 10);
bid.height = parseInt(bid.height, 10);
return parsedSizes.length === 0 || parsedSizes.some(([w, h]) => bid.width === w && bid.height === h);
}

// if a banner impression has one valid size, we assign that size to any bid
// response that does not explicitly set width or height
if (parsedSizes.length === 1) {
const [ width, height ] = parsedSizes[0].split('x');
bid.width = parseInt(width, 10);
bid.height = parseInt(height, 10);
([bid.width, bid.height] = parsedSizes[0]);
return true;
}

Expand Down Expand Up @@ -600,7 +602,7 @@ export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) {
return false;
}
if (bid.mediaType === 'banner' && !validBidSize(adUnitCode, bid, {index})) {
logError(errorMessage(`Banner bids require a width and height`));
logError(errorMessage(`Banner bids require a width and height that match one of the requested sizes`));
return false;
}

Expand Down
Loading

0 comments on commit 5e18b2d

Please sign in to comment.