Skip to content

Commit

Permalink
Implementation of setBidderConfig and bidder-specific data (prebid#4334)
Browse files Browse the repository at this point in the history
* initial implementation of setBidderConfig

* fix ie11 test errors

* Support new setBidderConfig format. Include props from both config and bidderConfig in _getConfig

* Use core-js Set to avoid issues with IE

* Fix tests in IE

* put registerSyncs back on bidderFactory

* run bidder event methods with bidder config enabled
  • Loading branch information
snapwich authored and sa1omon committed Nov 28, 2019
1 parent 9871fcd commit e4490ce
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 43 deletions.
20 changes: 16 additions & 4 deletions src/adapterManager.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @module adaptermanger */

import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp, getBidderRequest } from './utils';
import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp, getBidderRequest, bind } from './utils';
import { getLabels, resolveStatus } from './sizeMapping';
import { processNativeAdUnitParams, nativeAdapters } from './native';
import { newBidder } from './adapters/bidderFactory';
Expand Down Expand Up @@ -337,9 +337,21 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request
request: requestCallbacks.request.bind(null, bidRequest.bidderCode),
done: requestCallbacks.done
} : undefined);
adapter.callBids(bidRequest, addBidResponse.bind(bidRequest), doneCb.bind(bidRequest), ajax, onTimelyResponse);
config.runWithBidder(
bidRequest.bidderCode,
bind.call(
adapter.callBids,
adapter,
bidRequest,
addBidResponse.bind(bidRequest),
doneCb.bind(bidRequest),
ajax,
onTimelyResponse,
config.callbackWithBidder(bidRequest.bidderCode)
)
);
});
}
};

function doingS2STesting() {
return _s2sConfig && _s2sConfig.enabled && _s2sConfig.testing && s2sTestingModule;
Expand Down Expand Up @@ -464,7 +476,7 @@ function tryCallBidderMethod(bidder, method, param) {
const spec = adapter.getSpec();
if (spec && spec[method] && typeof spec[method] === 'function') {
utils.logInfo(`Invoking ${bidder}.${method}`);
spec[method](param);
config.runWithBidder(bidder, bind.call(spec[method], spec, param));
}
} catch (e) {
utils.logWarn(`Error calling ${method} of ${bidder}`);
Expand Down
8 changes: 4 additions & 4 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export function newBidder(spec) {
return Object.freeze(spec);
},
registerSyncs,
callBids: function(bidderRequest, addBidResponse, done, ajax, onTimelyResponse) {
callBids: function(bidderRequest, addBidResponse, done, ajax, onTimelyResponse, configEnabledCallback) {
if (!Array.isArray(bidderRequest.bids)) {
return;
}
Expand Down Expand Up @@ -216,7 +216,7 @@ export function newBidder(spec) {
// Callbacks don't compose as nicely as Promises. We should call done() once _all_ the
// Server requests have returned and been processed. Since `ajax` accepts a single callback,
// we need to rig up a function which only executes after all the requests have been responded.
const onResponse = delayExecution(afterAllResponses, requests.length)
const onResponse = delayExecution(configEnabledCallback(afterAllResponses), requests.length)
requests.forEach(processRequest);

function formatGetParameters(data) {
Expand All @@ -233,7 +233,7 @@ export function newBidder(spec) {
ajax(
`${request.url}${formatGetParameters(request.data)}`,
{
success: onSuccess,
success: configEnabledCallback(onSuccess),
error: onFailure
},
undefined,
Expand All @@ -247,7 +247,7 @@ export function newBidder(spec) {
ajax(
request.url,
{
success: onSuccess,
success: configEnabledCallback(onSuccess),
error: onFailure
},
typeof request.data === 'string' ? request.data : JSON.stringify(request.data),
Expand Down
110 changes: 102 additions & 8 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import { isValidPriceConfig } from './cpmBucketManager';
import find from 'core-js/library/fn/array/find';
import includes from 'core-js/library/fn/array/includes';
import Set from 'core-js/library/fn/set';
import { parseQS } from './url';

const from = require('core-js/library/fn/array/from');
const utils = require('./utils');
const CONSTANTS = require('./constants');

Expand Down Expand Up @@ -49,6 +51,8 @@ export function newConfig() {
let listeners = [];
let defaults;
let config;
let bidderConfig;
let currBidder = null;

function resetConfig() {
defaults = {};
Expand Down Expand Up @@ -86,7 +90,7 @@ export function newConfig() {
if (validatePriceGranularity(val)) {
if (typeof val === 'string') {
this._priceGranularity = (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM;
} else if (typeof val === 'object') {
} else if (utils.isPlainObject(val)) {
this._customPriceBucket = val;
this._priceGranularity = GRANULARITY_OPTIONS.CUSTOM;
utils.logMessage('Using custom price granularity');
Expand All @@ -111,7 +115,7 @@ export function newConfig() {
if (validatePriceGranularity(val[item])) {
if (typeof val === 'string') {
aggregate[item] = (hasGranularity(val[item])) ? val[item] : this._priceGranularity;
} else if (typeof val === 'object') {
} else if (utils.isPlainObject(val)) {
aggregate[item] = val[item];
utils.logMessage(`Using custom price granularity for ${item}`);
}
Expand Down Expand Up @@ -182,6 +186,7 @@ export function newConfig() {
}

config = newConfig;
bidderConfig = {};

function hasGranularity(val) {
return find(Object.keys(GRANULARITY_OPTIONS), option => val === GRANULARITY_OPTIONS[option]);
Expand All @@ -196,7 +201,7 @@ export function newConfig() {
if (!hasGranularity(val)) {
utils.logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.');
}
} else if (typeof val === 'object') {
} else if (utils.isPlainObject(val)) {
if (!isValidPriceConfig(val)) {
utils.logError('Invalid custom price value passed to `setPriceGranularity()`');
return false;
Expand All @@ -206,6 +211,33 @@ export function newConfig() {
}
}

/**
* Returns base config with bidder overrides (if there is currently a bidder)
* @private
*/
function _getConfig() {
if (currBidder && bidderConfig && utils.isPlainObject(bidderConfig[currBidder])) {
let currBidderConfig = bidderConfig[currBidder];
const configTopicSet = new Set(Object.keys(config).concat(Object.keys(currBidderConfig)));

return from(configTopicSet).reduce((memo, topic) => {
if (!currBidderConfig[topic]) {
memo[topic] = config[topic];
} else if (!config[topic]) {
memo[topic] = currBidderConfig[topic];
} else {
if (utils.isPlainObject(currBidderConfig[topic])) {
memo[topic] = Object.assign({}, config[topic], currBidderConfig[topic]);
} else {
memo[topic] = currBidderConfig[topic];
}
}
return memo;
}, {});
}
return Object.assign({}, config);
}

/*
* Returns configuration object if called without parameters,
* or single configuration property if given a string matching a configuration
Expand All @@ -217,18 +249,25 @@ export function newConfig() {
function getConfig(...args) {
if (args.length <= 1 && typeof args[0] !== 'function') {
const option = args[0];
return option ? utils.deepAccess(config, option) : config;
return option ? utils.deepAccess(_getConfig(), option) : _getConfig();
}

return subscribe(...args);
}

/**
* Internal API for modules (such as prebid-server) that might need access to all bidder config
*/
function getBidderConfig() {
return bidderConfig;
}

/*
* Sets configuration given an object containing key-value pairs and calls
* listeners that were added by the `subscribe` function
*/
function setConfig(options) {
if (typeof options !== 'object') {
if (!utils.isPlainObject(options)) {
utils.logError('setConfig options must be an object');
return;
}
Expand All @@ -239,7 +278,7 @@ export function newConfig() {
topics.forEach(topic => {
let option = options[topic];

if (typeof defaults[topic] === 'object' && typeof option === 'object') {
if (utils.isPlainObject(defaults[topic]) && utils.isPlainObject(option)) {
option = Object.assign({}, defaults[topic], option);
}

Expand All @@ -254,7 +293,7 @@ export function newConfig() {
* @param {object} options
*/
function setDefaults(options) {
if (typeof defaults !== 'object') {
if (!utils.isPlainObject(defaults)) {
utils.logError('defaults must be an object');
return;
}
Expand Down Expand Up @@ -328,13 +367,68 @@ export function newConfig() {
.forEach(listener => listener.callback(options));
}

function setBidderConfig(config) {
try {
check(config);
config.bidders.forEach(bidder => {
if (!bidderConfig[bidder]) {
bidderConfig[bidder] = {};
}
Object.keys(config.config).forEach(topic => {
let option = config.config[topic];
if (utils.isPlainObject(option)) {
bidderConfig[bidder][topic] = Object.assign({}, bidderConfig[bidder][topic] || {}, option);
} else {
bidderConfig[bidder][topic] = option;
}
});
});
} catch (e) {
utils.logError(e);
}
function check(obj) {
if (!utils.isPlainObject(obj)) {
throw 'setBidderConfig bidder options must be an object';
}
if (!(Array.isArray(obj.bidders) && obj.bidders.length)) {
throw 'setBidderConfig bidder options must contain a bidders list with at least 1 bidder';
}
if (!utils.isPlainObject(obj.config)) {
throw 'setBidderConfig bidder options must contain a config object';
}
}
}

/**
* Internal functions for core to execute some synchronous code while having an active bidder set.
*/
function runWithBidder(bidder, fn) {
currBidder = bidder;
try {
return fn();
} finally {
currBidder = null;
}
}
function callbackWithBidder(bidder) {
return function(cb) {
return function(...args) {
return runWithBidder(bidder, utils.bind.call(cb, this, ...args))
}
}
}

resetConfig();

return {
getConfig,
setConfig,
setDefaults,
resetConfig
resetConfig,
runWithBidder,
callbackWithBidder,
setBidderConfig,
getBidderConfig
};
}

Expand Down
1 change: 1 addition & 0 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ $$PREBID_GLOBAL$$.getConfig = config.getConfig;
* ```
*/
$$PREBID_GLOBAL$$.setConfig = config.setConfig;
$$PREBID_GLOBAL$$.setBidderConfig = config.setBidderConfig;

$$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative());

Expand Down
2 changes: 1 addition & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@ export function delayExecution(func, numRequiredCalls) {
return function () {
numCalls++;
if (numCalls === numRequiredCalls) {
func.apply(null, arguments);
func.apply(this, arguments);
}
}
}
Expand Down
Loading

0 comments on commit e4490ce

Please sign in to comment.