prebid.js 9.53.3 → 9.53.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/33acrossAnalyticsAdapter.js +1 -1
- package/dist/33acrossBidAdapter.js +1 -1
- package/dist/33acrossIdSystem.js +1 -1
- package/dist/BTBidAdapter.js +1 -1
- package/dist/adagioAnalyticsAdapter.js +1 -1
- package/dist/adagioBidAdapter.js +1 -1
- package/dist/adagioUtils.js +1 -1
- package/dist/addefendBidAdapter.js +1 -1
- package/dist/adgenerationBidAdapter.js +1 -1
- package/dist/adlooxRtdProvider.js +1 -1
- package/dist/adqueryBidAdapter.js +1 -1
- package/dist/adrelevantisBidAdapter.js +1 -1
- package/dist/adstirBidAdapter.js +1 -1
- package/dist/adtrgtmeBidAdapter.js +1 -1
- package/dist/adxcgAnalyticsAdapter.js +1 -1
- package/dist/adxcgBidAdapter.js +1 -1
- package/dist/adyoulikeBidAdapter.js +1 -1
- package/dist/agmaAnalyticsAdapter.js +1 -1
- package/dist/ajaBidAdapter.js +1 -1
- package/dist/amxBidAdapter.js +1 -1
- package/dist/amxIdSystem.js +1 -1
- package/dist/aniviewBidAdapter.js +1 -1
- package/dist/appierAnalyticsAdapter.js +1 -1
- package/dist/appnexusBidAdapter.js +1 -1
- package/dist/asoBidAdapter.js +1 -1
- package/dist/axonixBidAdapter.js +1 -1
- package/dist/beopBidAdapter.js +1 -1
- package/dist/bidderTimeoutUtils.js +1 -0
- package/dist/bidglassBidAdapter.js +1 -1
- package/dist/big-richmediaBidAdapter.js +1 -1
- package/dist/bitmediaBidAdapter.js +1 -1
- package/dist/bridBidAdapter.js +1 -1
- package/dist/bridgeuppBidAdapter.js +1 -1
- package/dist/bridgewellBidAdapter.js +1 -1
- package/dist/brightMountainMediaBidAdapter.js +1 -1
- package/dist/carodaBidAdapter.js +1 -1
- package/dist/chtnwBidAdapter.js +1 -1
- package/dist/chunk-core.js +1 -1
- package/dist/concertBidAdapter.js +1 -1
- package/dist/connectadBidAdapter.js +1 -1
- package/dist/consumableBidAdapter.js +1 -1
- package/dist/contxtfulBidAdapter.js +1 -1
- package/dist/conversantAnalyticsAdapter.js +1 -1
- package/dist/conversantBidAdapter.js +1 -1
- package/dist/craftBidAdapter.js +1 -1
- package/dist/criteoBidAdapter.js +1 -1
- package/dist/cwireBidAdapter.js +1 -1
- package/dist/dailymotionBidAdapter.js +1 -1
- package/dist/dependencies.json +10 -1
- package/dist/dspxBidAdapter.js +1 -1
- package/dist/dxkultureBidAdapter.js +1 -1
- package/dist/eplanningBidAdapter.js +1 -1
- package/dist/equativBidAdapter.js +1 -1
- package/dist/eskimiBidAdapter.js +1 -1
- package/dist/euidIdSystem.js +1 -1
- package/dist/exadsBidAdapter.js +1 -1
- package/dist/excoBidAdapter.js +1 -1
- package/dist/feedadBidAdapter.js +1 -1
- package/dist/finativeBidAdapter.js +1 -1
- package/dist/freewheel-sspBidAdapter.js +1 -1
- package/dist/fwsspBidAdapter.js +1 -1
- package/dist/gmosspBidAdapter.js +1 -1
- package/dist/greenbidsAnalyticsAdapter.js +1 -1
- package/dist/greenbidsBidAdapter.js +1 -1
- package/dist/greenbidsRtdProvider.js +1 -1
- package/dist/gridBidAdapter.js +1 -1
- package/dist/gumgumBidAdapter.js +1 -1
- package/dist/h12mediaBidAdapter.js +1 -1
- package/dist/hypelabBidAdapter.js +1 -1
- package/dist/id5AnalyticsAdapter.js +1 -1
- package/dist/id5IdSystem.js +1 -1
- package/dist/imdsBidAdapter.js +1 -1
- package/dist/improvedigitalBidAdapter.js +1 -1
- package/dist/inmobiBidAdapter.js +1 -1
- package/dist/insticatorBidAdapter.js +1 -1
- package/dist/intentIqAnalyticsAdapter.js +1 -1
- package/dist/ixBidAdapter.js +1 -1
- package/dist/jixieBidAdapter.js +1 -1
- package/dist/justpremiumBidAdapter.js +1 -1
- package/dist/kargoBidAdapter.js +1 -1
- package/dist/kimberliteBidAdapter.js +1 -1
- package/dist/konduitAnalyticsAdapter.js +1 -1
- package/dist/kueezBidAdapter.js +1 -1
- package/dist/lassoBidAdapter.js +1 -1
- package/dist/lifestreetBidAdapter.js +1 -1
- package/dist/liveIntentId.js +1 -1
- package/dist/logicadBidAdapter.js +1 -1
- package/dist/loglyliftBidAdapter.js +1 -1
- package/dist/luceadBidAdapter.js +1 -1
- package/dist/mabidderBidAdapter.js +1 -1
- package/dist/madsenseBidAdapter.js +1 -1
- package/dist/magniteAnalyticsAdapter.js +1 -1
- package/dist/malltvAnalyticsAdapter.js +1 -1
- package/dist/marsmediaBidAdapter.js +1 -1
- package/dist/mediafuseBidAdapter.js +1 -1
- package/dist/medianetBidAdapter.js +1 -1
- package/dist/medianetUtils.js +1 -1
- package/dist/mediasquareBidAdapter.js +1 -1
- package/dist/mgidBidAdapter.js +1 -1
- package/dist/missenaBidAdapter.js +1 -1
- package/dist/mobilefuseBidAdapter.js +1 -1
- package/dist/nextMillenniumBidAdapter.js +1 -1
- package/dist/nexx360Utils.js +1 -1
- package/dist/nobidAnalyticsAdapter.js +1 -1
- package/dist/nobidBidAdapter.js +1 -1
- package/dist/nodalsAiRtdProvider.js +1 -1
- package/dist/not-for-prod/prebid.js +175 -172
- package/dist/objectGuard.js +1 -1
- package/dist/oguryBidAdapter.js +1 -1
- package/dist/onetagBidAdapter.js +1 -1
- package/dist/ooloAnalyticsAdapter.js +1 -1
- package/dist/openxBidAdapter.js +1 -1
- package/dist/optableRtdProvider.js +1 -1
- package/dist/optidigitalBidAdapter.js +1 -1
- package/dist/orbidderBidAdapter.js +1 -1
- package/dist/outbrainBidAdapter.js +1 -1
- package/dist/pixfutureBidAdapter.js +1 -1
- package/dist/publinkIdSystem.js +1 -1
- package/dist/pubmaticAnalyticsAdapter.js +1 -1
- package/dist/pubmaticBidAdapter.js +1 -1
- package/dist/pubmaticRtdProvider.js +1 -1
- package/dist/pubmaticUtils.js +1 -0
- package/dist/pubwiseAnalyticsAdapter.js +1 -1
- package/dist/pubxaiAnalyticsAdapter.js +1 -1
- package/dist/pxyzBidAdapter.js +1 -1
- package/dist/quantcastBidAdapter.js +1 -1
- package/dist/readpeakBidAdapter.js +1 -1
- package/dist/relaidoBidAdapter.js +1 -1
- package/dist/retailspotBidAdapter.js +1 -1
- package/dist/rhythmoneBidAdapter.js +1 -1
- package/dist/riseUtils.js +1 -1
- package/dist/rtdModule.js +1 -1
- package/dist/rubiconBidAdapter.js +1 -1
- package/dist/seedingAllianceBidAdapter.js +1 -1
- package/dist/seedtagBidAdapter.js +1 -1
- package/dist/sevioBidAdapter.js +1 -0
- package/dist/sharethroughAnalyticsAdapter.js +1 -1
- package/dist/sharethroughBidAdapter.js +1 -1
- package/dist/showheroes-bsBidAdapter.js +1 -1
- package/dist/smaatoBidAdapter.js +1 -1
- package/dist/smartadserverBidAdapter.js +1 -1
- package/dist/smartxBidAdapter.js +1 -1
- package/dist/smilewantedBidAdapter.js +1 -1
- package/dist/snigelBidAdapter.js +1 -1
- package/dist/sonobiBidAdapter.js +1 -1
- package/dist/sovrnBidAdapter.js +1 -1
- package/dist/sparteoBidAdapter.js +1 -1
- package/dist/sspBCBidAdapter.js +1 -1
- package/dist/stvBidAdapter.js +1 -1
- package/dist/sublimeBidAdapter.js +1 -1
- package/dist/taboolaBidAdapter.js +1 -1
- package/dist/tappxBidAdapter.js +1 -1
- package/dist/targetVideoBidAdapter.js +1 -1
- package/dist/teadsBidAdapter.js +1 -1
- package/dist/terceptAnalyticsAdapter.js +1 -1
- package/dist/themoneytizerBidAdapter.js +1 -1
- package/dist/timeoutRtdProvider.js +1 -1
- package/dist/trionBidAdapter.js +1 -1
- package/dist/tripleliftBidAdapter.js +1 -1
- package/dist/ttdBidAdapter.js +1 -1
- package/dist/ucfunnelAnalyticsAdapter.js +1 -1
- package/dist/uid2IdSystem.js +1 -1
- package/dist/underdogmediaBidAdapter.js +1 -1
- package/dist/undertoneBidAdapter.js +1 -1
- package/dist/unrulyBidAdapter.js +1 -1
- package/dist/vidazooUtils.js +1 -1
- package/dist/videobyteBidAdapter.js +1 -1
- package/dist/visxBidAdapter.js +1 -1
- package/dist/vuukleBidAdapter.js +1 -1
- package/dist/widespaceBidAdapter.js +1 -1
- package/dist/winrBidAdapter.js +1 -1
- package/dist/yahooAdsBidAdapter.js +1 -1
- package/dist/yandexBidAdapter.js +1 -1
- package/dist/yieldmoBidAdapter.js +1 -1
- package/dist/yieldoneAnalyticsAdapter.js +1 -1
- package/integrationExamples/gpt/pubmaticRtdProvider_Example.html +161 -0
- package/libraries/bidderTimeoutUtils/bidderTimeoutUtils.js +119 -0
- package/libraries/objectGuard/objectGuard.js +36 -15
- package/libraries/pubmaticUtils/plugins/dynamicTimeout.js +209 -0
- package/libraries/pubmaticUtils/plugins/floorProvider.js +168 -0
- package/libraries/pubmaticUtils/plugins/pluginManager.js +106 -0
- package/libraries/pubmaticUtils/plugins/unifiedPricingRule.js +375 -0
- package/libraries/pubmaticUtils/pubmaticUtils.js +76 -0
- package/modules/fwsspBidAdapter.js +134 -69
- package/modules/fwsspBidAdapter.md +121 -26
- package/modules/optableRtdProvider.js +33 -12
- package/modules/pubmaticAnalyticsAdapter.js +5 -1
- package/modules/pubmaticRtdProvider.js +105 -565
- package/modules/rtdModule/index.js +23 -2
- package/modules/sevioBidAdapter.js +413 -0
- package/modules/sevioBidAdapter.md +29 -0
- package/modules/sparteoBidAdapter.js +122 -10
- package/modules/timeoutRtdProvider.js +2 -105
- package/package.json +1 -1
- package/test/spec/activities/objectGuard_spec.js +49 -16
- package/test/spec/libraries/bidderTimeoutUtils/bidderTimeoutUtils_spec.js +213 -0
- package/test/spec/libraries/pubmaticUtils/plugins/dynamicTimeout_spec.js +746 -0
- package/test/spec/libraries/pubmaticUtils/plugins/floorProvider_spec.js +184 -0
- package/test/spec/libraries/pubmaticUtils/plugins/pluginManager_spec.js +489 -0
- package/test/spec/libraries/pubmaticUtils/plugins/unifiedPricingRule_spec.js +359 -0
- package/test/spec/libraries/pubmaticUtils/pubmaticUtils_spec.js +236 -0
- package/test/spec/modules/fwsspBidAdapter_spec.js +513 -78
- package/test/spec/modules/optableRtdProvider_spec.js +55 -5
- package/test/spec/modules/pubmaticRtdProvider_spec.js +252 -1183
- package/test/spec/modules/realTimeDataModule_spec.js +58 -8
- package/test/spec/modules/sevioBidAdapter_spec.js +513 -0
- package/test/spec/modules/sparteoBidAdapter_spec.js +528 -43
- package/test/spec/modules/timeoutRtdProvider_spec.js +1 -201
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// plugins/floorProvider.js
|
|
2
|
+
import { logInfo, logError, isFn, logMessage, isEmpty } from '../../../src/utils.js';
|
|
3
|
+
import { getDeviceType as fetchDeviceType, getOS } from '../../userAgentUtils/index.js';
|
|
4
|
+
import { getBrowserType, getCurrentTimeOfDay, getUtmValue } from '../pubmaticUtils.js';
|
|
5
|
+
import { config as conf } from '../../../src/config.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This RTD module has a dependency on the priceFloors module.
|
|
9
|
+
* We utilize the continueAuction function from the priceFloors module to incorporate price floors data into the current auction.
|
|
10
|
+
*/
|
|
11
|
+
import { continueAuction } from '../../../modules/priceFloors.js'; // eslint-disable-line prebid/validate-imports
|
|
12
|
+
|
|
13
|
+
let _floorConfig = null;
|
|
14
|
+
export const getFloorConfig = () => _floorConfig;
|
|
15
|
+
export const setFloorsConfig = (config) => { _floorConfig = config; }
|
|
16
|
+
|
|
17
|
+
let _configJsonManager = null;
|
|
18
|
+
export const getConfigJsonManager = () => _configJsonManager;
|
|
19
|
+
export const setConfigJsonManager = (configJsonManager) => { _configJsonManager = configJsonManager; }
|
|
20
|
+
|
|
21
|
+
export const CONSTANTS = Object.freeze({
|
|
22
|
+
LOG_PRE_FIX: 'PubMatic-Floor-Provider: '
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the floor provider
|
|
27
|
+
* @param {Object} pluginName - Plugin name
|
|
28
|
+
* @param {Object} configJsonManager - Configuration JSON manager object
|
|
29
|
+
* @returns {Promise<boolean>} - Promise resolving to initialization status
|
|
30
|
+
*/
|
|
31
|
+
export async function init(pluginName, configJsonManager) {
|
|
32
|
+
// Process floor-specific configuration
|
|
33
|
+
const config = configJsonManager.getConfigByName(pluginName);
|
|
34
|
+
if (!config) {
|
|
35
|
+
logInfo(`${CONSTANTS.LOG_PRE_FIX} Floor configuration not found`);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
setFloorsConfig(config);
|
|
39
|
+
|
|
40
|
+
if (!getFloorConfig()?.enabled) {
|
|
41
|
+
logInfo(`${CONSTANTS.LOG_PRE_FIX} Floor configuration is disabled`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!isFn(continueAuction)) {
|
|
46
|
+
logError(`${CONSTANTS.LOG_PRE_FIX} continueAuction is not a function. Please ensure to add priceFloors module.`);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setConfigJsonManager(configJsonManager);
|
|
51
|
+
try {
|
|
52
|
+
conf.setConfig(prepareFloorsConfig());
|
|
53
|
+
logMessage(`${CONSTANTS.LOG_PRE_FIX} dynamicFloors config set successfully`);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logError(`${CONSTANTS.LOG_PRE_FIX} Error setting dynamicFloors config: ${error}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
logInfo(`${CONSTANTS.LOG_PRE_FIX} Floor configuration loaded`);
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Process bid request
|
|
65
|
+
* @param {Object} reqBidsConfigObj - Bid request config object
|
|
66
|
+
* @returns {Object} - Updated bid request config object
|
|
67
|
+
*/
|
|
68
|
+
export function processBidRequest(reqBidsConfigObj) {
|
|
69
|
+
try {
|
|
70
|
+
const hookConfig = {
|
|
71
|
+
reqBidsConfigObj,
|
|
72
|
+
context: null, // Removed 'this' as it's not applicable in function-based implementation
|
|
73
|
+
nextFn: () => true,
|
|
74
|
+
haveExited: false,
|
|
75
|
+
timer: null
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Apply floor configuration
|
|
79
|
+
continueAuction(hookConfig);
|
|
80
|
+
logInfo(`${CONSTANTS.LOG_PRE_FIX} Applied floor configuration to auction`);
|
|
81
|
+
|
|
82
|
+
return reqBidsConfigObj;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
logError(`${CONSTANTS.LOG_PRE_FIX} Error applying floor configuration: ${error}`);
|
|
85
|
+
return reqBidsConfigObj;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get targeting data
|
|
91
|
+
* @param {Array} adUnitCodes - Ad unit codes
|
|
92
|
+
* @param {Object} config - Module configuration
|
|
93
|
+
* @param {Object} userConsent - User consent data
|
|
94
|
+
* @param {Object} auction - Auction object
|
|
95
|
+
* @returns {Object} - Targeting data
|
|
96
|
+
*/
|
|
97
|
+
export function getTargeting(adUnitCodes, config, userConsent, auction) {
|
|
98
|
+
// Implementation for targeting data, if not applied then do nothing
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Export the floor provider functions
|
|
102
|
+
export const FloorProvider = {
|
|
103
|
+
init,
|
|
104
|
+
processBidRequest,
|
|
105
|
+
getTargeting
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Helper Functions
|
|
109
|
+
|
|
110
|
+
export const defaultValueTemplate = {
|
|
111
|
+
currency: 'USD',
|
|
112
|
+
skipRate: 0,
|
|
113
|
+
schema: {
|
|
114
|
+
fields: ['mediaType', 'size']
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Getter Functions
|
|
119
|
+
export const getTimeOfDay = () => getCurrentTimeOfDay();
|
|
120
|
+
export const getBrowser = () => getBrowserType();
|
|
121
|
+
export const getOs = () => getOS().toString();
|
|
122
|
+
export const getDeviceType = () => fetchDeviceType().toString();
|
|
123
|
+
export const getCountry = () => getConfigJsonManager().country;
|
|
124
|
+
export const getBidder = (request) => request?.bidder;
|
|
125
|
+
export const getUtm = () => getUtmValue();
|
|
126
|
+
|
|
127
|
+
export const prepareFloorsConfig = () => {
|
|
128
|
+
if (!getFloorConfig()?.enabled || !getFloorConfig()?.config) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Floor configs from adunit / setconfig
|
|
133
|
+
const defaultFloorConfig = conf.getConfig('floors') ?? {};
|
|
134
|
+
if (defaultFloorConfig?.endpoint) {
|
|
135
|
+
delete defaultFloorConfig.endpoint;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let ymUiConfig = { ...getFloorConfig().config };
|
|
139
|
+
|
|
140
|
+
// default values provided by publisher on YM UI
|
|
141
|
+
const defaultValues = ymUiConfig.defaultValues ?? {};
|
|
142
|
+
// If floorsData is not present or is an empty object, use default values
|
|
143
|
+
const ymFloorsData = isEmpty(getFloorConfig().data)
|
|
144
|
+
? { ...defaultValueTemplate, values: { ...defaultValues } }
|
|
145
|
+
: getFloorConfig().data;
|
|
146
|
+
|
|
147
|
+
delete ymUiConfig.defaultValues;
|
|
148
|
+
// If skiprate is provided in configs, overwrite the value in ymFloorsData
|
|
149
|
+
(ymUiConfig.skipRate !== undefined) && (ymFloorsData.skipRate = ymUiConfig.skipRate);
|
|
150
|
+
|
|
151
|
+
// merge default configs from page, configs
|
|
152
|
+
return {
|
|
153
|
+
floors: {
|
|
154
|
+
...defaultFloorConfig,
|
|
155
|
+
...ymUiConfig,
|
|
156
|
+
data: ymFloorsData,
|
|
157
|
+
additionalSchemaFields: {
|
|
158
|
+
deviceType: getDeviceType,
|
|
159
|
+
timeOfDay: getTimeOfDay,
|
|
160
|
+
browser: getBrowser,
|
|
161
|
+
os: getOs,
|
|
162
|
+
utm: getUtm,
|
|
163
|
+
country: getCountry,
|
|
164
|
+
bidder: getBidder,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { logInfo, logWarn, logError } from "../../../src/utils.js";
|
|
2
|
+
|
|
3
|
+
// pluginManager.js
|
|
4
|
+
export const plugins = new Map();
|
|
5
|
+
export const CONSTANTS = Object.freeze({
|
|
6
|
+
LOG_PRE_FIX: 'PubMatic-Plugin-Manager: '
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Initialize the plugin manager with constants
|
|
11
|
+
* @returns {Object} - Plugin manager functions
|
|
12
|
+
*/
|
|
13
|
+
export const PluginManager = () => ({
|
|
14
|
+
register,
|
|
15
|
+
initialize,
|
|
16
|
+
executeHook
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Register a plugin with the plugin manager
|
|
21
|
+
* @param {string} name - Plugin name
|
|
22
|
+
* @param {Object} plugin - Plugin object
|
|
23
|
+
* @returns {Object} - Plugin manager functions
|
|
24
|
+
*/
|
|
25
|
+
const register = (name, plugin) => {
|
|
26
|
+
if (plugins.has(name)) {
|
|
27
|
+
logWarn(`${CONSTANTS.LOG_PRE_FIX} Plugin ${name} already registered`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
plugins.set(name, plugin);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Unregister a plugin from the plugin manager
|
|
35
|
+
* @param {string} name - Plugin name
|
|
36
|
+
* @returns {Object} - Plugin manager functions
|
|
37
|
+
*/
|
|
38
|
+
const unregister = (name) => {
|
|
39
|
+
if (plugins.has(name)) {
|
|
40
|
+
logInfo(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin ${name}`);
|
|
41
|
+
plugins.delete(name);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize all registered plugins with their specific config
|
|
47
|
+
* @param {Object} configJsonManager - Configuration JSON manager object
|
|
48
|
+
* @returns {Promise} - Promise resolving when all plugins are initialized
|
|
49
|
+
*/
|
|
50
|
+
const initialize = async (configJsonManager) => {
|
|
51
|
+
const initPromises = [];
|
|
52
|
+
|
|
53
|
+
// Initialize each plugin with its specific config
|
|
54
|
+
for (const [name, plugin] of plugins.entries()) {
|
|
55
|
+
if (plugin.init) {
|
|
56
|
+
const initialized = await plugin.init(name, configJsonManager);
|
|
57
|
+
if (!initialized) {
|
|
58
|
+
unregister(name);
|
|
59
|
+
}
|
|
60
|
+
initPromises.push(initialized);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return Promise.all(initPromises);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute a hook on all registered plugins synchronously
|
|
69
|
+
* @param {string} hookName - Name of the hook to execute
|
|
70
|
+
* @param {...any} args - Arguments to pass to the hook
|
|
71
|
+
* @returns {Object} - Object containing merged results from all plugins
|
|
72
|
+
*/
|
|
73
|
+
const executeHook = (hookName, ...args) => {
|
|
74
|
+
// Cache results to avoid repeated processing
|
|
75
|
+
const results = {};
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Get all plugins that have the specified hook method
|
|
79
|
+
const pluginsWithHook = Array.from(plugins.entries())
|
|
80
|
+
.filter(([_, plugin]) => typeof plugin[hookName] === 'function');
|
|
81
|
+
|
|
82
|
+
// Process each plugin synchronously
|
|
83
|
+
for (const [name, plugin] of pluginsWithHook) {
|
|
84
|
+
try {
|
|
85
|
+
// Call the plugin's hook method synchronously
|
|
86
|
+
const result = plugin[hookName](...args);
|
|
87
|
+
|
|
88
|
+
// Skip null/undefined results
|
|
89
|
+
if (result === null || result === undefined) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// If result is an object, merge it
|
|
94
|
+
if (typeof result === 'object') {
|
|
95
|
+
Object.assign(results, result);
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
logError(`${CONSTANTS.LOG_PRE_FIX} Error executing hook ${hookName} in plugin ${name}: ${error.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logError(`${CONSTANTS.LOG_PRE_FIX} Error in executeHookSync: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return results;
|
|
106
|
+
};
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
// plugins/unifiedPricingRule.js
|
|
2
|
+
import { logError, logInfo } from '../../../src/utils.js';
|
|
3
|
+
import { getGlobal } from '../../../src/prebidGlobal.js';
|
|
4
|
+
import { REJECTION_REASON } from '../../../src/constants.js';
|
|
5
|
+
|
|
6
|
+
const CONSTANTS = Object.freeze({
|
|
7
|
+
LOG_PRE_FIX: 'PubMatic-Unified-Pricing-Rule: ',
|
|
8
|
+
BID_STATUS: {
|
|
9
|
+
NOBID: 0,
|
|
10
|
+
WON: 1,
|
|
11
|
+
FLOORED: 2
|
|
12
|
+
},
|
|
13
|
+
MULTIPLIERS: {
|
|
14
|
+
WIN: 1.0,
|
|
15
|
+
FLOORED: 1.0,
|
|
16
|
+
NOBID: 1.0
|
|
17
|
+
},
|
|
18
|
+
TARGETING_KEYS: {
|
|
19
|
+
PM_YM_FLRS: 'pm_ym_flrs', // Whether RTD floor was applied
|
|
20
|
+
PM_YM_FLRV: 'pm_ym_flrv', // Final floor value (after applying multiplier)
|
|
21
|
+
PM_YM_BID_S: 'pm_ym_bid_s' // Bid status (0: No bid, 1: Won, 2: Floored)
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
export const getProfileConfigs = () => getConfigJsonManager()?.getYMConfig();
|
|
25
|
+
|
|
26
|
+
let _configJsonManager = null;
|
|
27
|
+
export const getConfigJsonManager = () => _configJsonManager;
|
|
28
|
+
export const setConfigJsonManager = (configJsonManager) => { _configJsonManager = configJsonManager; }
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the floor provider
|
|
32
|
+
* @param {Object} pluginName - Plugin name
|
|
33
|
+
* @param {Object} configJsonManager - Configuration JSON manager object
|
|
34
|
+
* @returns {Promise<boolean>} - Promise resolving to initialization status
|
|
35
|
+
*/
|
|
36
|
+
export async function init(pluginName, configJsonManager) {
|
|
37
|
+
setConfigJsonManager(configJsonManager);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Process bid request
|
|
43
|
+
* @param {Object} reqBidsConfigObj - Bid request config object
|
|
44
|
+
* @returns {Object} - Updated bid request config object
|
|
45
|
+
*/
|
|
46
|
+
export function processBidRequest(reqBidsConfigObj) {
|
|
47
|
+
return reqBidsConfigObj;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get targeting data
|
|
52
|
+
* @param {Array} adUnitCodes - Ad unit codes
|
|
53
|
+
* @param {Object} config - Module configuration
|
|
54
|
+
* @param {Object} userConsent - User consent data
|
|
55
|
+
* @param {Object} auction - Auction object
|
|
56
|
+
* @returns {Object} - Targeting data
|
|
57
|
+
*/
|
|
58
|
+
export function getTargeting(adUnitCodes, config, userConsent, auction) {
|
|
59
|
+
// Access the profile configs stored globally
|
|
60
|
+
const profileConfigs = getProfileConfigs();
|
|
61
|
+
|
|
62
|
+
// Return empty object if profileConfigs is undefined or pmTargetingKeys.enabled is explicitly set to false
|
|
63
|
+
if (!profileConfigs || profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.enabled === false) {
|
|
64
|
+
logInfo(`${CONSTANTS.LOG_PRE_FIX} pmTargetingKeys is disabled or profileConfigs is undefined`);
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Helper to check if RTD floor is applied to a bid
|
|
69
|
+
const isRtdFloorApplied = bid => bid.floorData?.floorProvider === "PM" && !bid.floorData.skipped;
|
|
70
|
+
|
|
71
|
+
// Check if any bid has RTD floor applied
|
|
72
|
+
const hasRtdFloorAppliedBid =
|
|
73
|
+
auction?.adUnits?.some(adUnit => adUnit.bids?.some(isRtdFloorApplied)) ||
|
|
74
|
+
auction?.bidsReceived?.some(isRtdFloorApplied);
|
|
75
|
+
|
|
76
|
+
// Only log when RTD floor is applied
|
|
77
|
+
if (hasRtdFloorAppliedBid) {
|
|
78
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, 'Setting targeting via getTargetingData:');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Process each ad unit code
|
|
82
|
+
const targeting = {};
|
|
83
|
+
|
|
84
|
+
adUnitCodes.forEach(code => {
|
|
85
|
+
targeting[code] = {};
|
|
86
|
+
|
|
87
|
+
// For non-RTD floor applied cases, only set pm_ym_flrs to 0
|
|
88
|
+
if (!hasRtdFloorAppliedBid) {
|
|
89
|
+
targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 0;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Find bids and determine status for RTD floor applied cases
|
|
94
|
+
const bidsForAdUnit = findBidsForAdUnit(auction, code);
|
|
95
|
+
const rejectedBidsForAdUnit = findRejectedBidsForAdUnit(auction, code);
|
|
96
|
+
const rejectedFloorBid = findRejectedFloorBid(rejectedBidsForAdUnit);
|
|
97
|
+
const winningBid = findWinningBid(code);
|
|
98
|
+
|
|
99
|
+
// Determine bid status and values
|
|
100
|
+
const { bidStatus, baseValue, multiplier } = determineBidStatusAndValues(
|
|
101
|
+
winningBid,
|
|
102
|
+
rejectedFloorBid,
|
|
103
|
+
bidsForAdUnit,
|
|
104
|
+
auction,
|
|
105
|
+
code
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Set all targeting keys
|
|
109
|
+
targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 1;
|
|
110
|
+
targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRV] = (baseValue * multiplier).toFixed(2);
|
|
111
|
+
targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_BID_S] = bidStatus;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return targeting;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Export the floor provider functions
|
|
118
|
+
export const UnifiedPricingRule = {
|
|
119
|
+
init,
|
|
120
|
+
processBidRequest,
|
|
121
|
+
getTargeting
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Find all bids for a specific ad unit
|
|
125
|
+
function findBidsForAdUnit(auction, code) {
|
|
126
|
+
return auction?.bidsReceived?.filter(bid => bid.adUnitCode === code) || [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Find rejected bids for a specific ad unit
|
|
130
|
+
function findRejectedBidsForAdUnit(auction, code) {
|
|
131
|
+
if (!auction?.bidsRejected) return [];
|
|
132
|
+
|
|
133
|
+
// If bidsRejected is an array
|
|
134
|
+
if (Array.isArray(auction.bidsRejected)) {
|
|
135
|
+
return auction.bidsRejected.filter(bid => bid.adUnitCode === code);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// If bidsRejected is an object mapping bidders to their rejected bids
|
|
139
|
+
if (typeof auction.bidsRejected === 'object') {
|
|
140
|
+
return Object.values(auction.bidsRejected)
|
|
141
|
+
.filter(Array.isArray)
|
|
142
|
+
.flatMap(bidderBids => bidderBids.filter(bid => bid.adUnitCode === code));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Find a rejected bid due to price floor
|
|
149
|
+
function findRejectedFloorBid(rejectedBids) {
|
|
150
|
+
return rejectedBids.find(bid => {
|
|
151
|
+
return bid.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET &&
|
|
152
|
+
(bid.floorData?.floorValue && bid.cpm < bid.floorData.floorValue);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Find the winning or highest bid for an ad unit
|
|
157
|
+
function findWinningBid(adUnitCode) {
|
|
158
|
+
try {
|
|
159
|
+
const pbjs = getGlobal();
|
|
160
|
+
if (!pbjs?.getHighestCpmBids) return null;
|
|
161
|
+
|
|
162
|
+
const highestCpmBids = pbjs.getHighestCpmBids(adUnitCode);
|
|
163
|
+
if (!highestCpmBids?.length) {
|
|
164
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, `No highest CPM bids found for ad unit: ${adUnitCode}`);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const highestCpmBid = highestCpmBids[0];
|
|
169
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, `Found highest CPM bid using pbjs.getHighestCpmBids() for ad unit: ${adUnitCode}, CPM: ${highestCpmBid.cpm}`);
|
|
170
|
+
return highestCpmBid;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
logError(CONSTANTS.LOG_PRE_FIX, `Error finding highest CPM bid: ${error}`);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Find floor value from bidder requests
|
|
178
|
+
function findFloorValueFromBidderRequests(auction, code) {
|
|
179
|
+
if (!auction?.bidderRequests?.length) return 0;
|
|
180
|
+
|
|
181
|
+
// Find all bids in bidder requests for this ad unit
|
|
182
|
+
const bidsFromRequests = auction.bidderRequests
|
|
183
|
+
.flatMap(request => request.bids || [])
|
|
184
|
+
.filter(bid => bid.adUnitCode === code);
|
|
185
|
+
|
|
186
|
+
if (!bidsFromRequests.length) {
|
|
187
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, `No bids found for ad unit: ${code}`);
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const bidWithGetFloor = bidsFromRequests.find(bid => bid.getFloor);
|
|
192
|
+
if (!bidWithGetFloor) {
|
|
193
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, `No bid with getFloor method found for ad unit: ${code}`);
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Helper function to extract sizes with their media types from a source object
|
|
198
|
+
const extractSizes = (source) => {
|
|
199
|
+
if (!source) return null;
|
|
200
|
+
|
|
201
|
+
const result = [];
|
|
202
|
+
|
|
203
|
+
// Extract banner sizes
|
|
204
|
+
if (source.mediaTypes?.banner?.sizes) {
|
|
205
|
+
source.mediaTypes.banner.sizes.forEach(size => {
|
|
206
|
+
result.push({
|
|
207
|
+
size,
|
|
208
|
+
mediaType: 'banner'
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Extract video sizes
|
|
214
|
+
if (source.mediaTypes?.video?.playerSize) {
|
|
215
|
+
const playerSize = source.mediaTypes.video.playerSize;
|
|
216
|
+
// Handle both formats: [[w, h]] and [w, h]
|
|
217
|
+
const videoSizes = Array.isArray(playerSize[0]) ? playerSize : [playerSize];
|
|
218
|
+
|
|
219
|
+
videoSizes.forEach(size => {
|
|
220
|
+
result.push({
|
|
221
|
+
size,
|
|
222
|
+
mediaType: 'video'
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Use general sizes as fallback if no specific media types found
|
|
228
|
+
if (result.length === 0 && source.sizes) {
|
|
229
|
+
source.sizes.forEach(size => {
|
|
230
|
+
result.push({
|
|
231
|
+
size,
|
|
232
|
+
mediaType: 'banner' // Default to banner for general sizes
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result.length > 0 ? result : null;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Try to get sizes from different sources in order of preference
|
|
241
|
+
const adUnit = auction.adUnits?.find(unit => unit.code === code);
|
|
242
|
+
let sizes = extractSizes(adUnit) || extractSizes(bidWithGetFloor);
|
|
243
|
+
|
|
244
|
+
// Handle fallback to wildcard size if no sizes found
|
|
245
|
+
if (!sizes) {
|
|
246
|
+
sizes = [{ size: ['*', '*'], mediaType: 'banner' }];
|
|
247
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, `No sizes found, using wildcard size for ad unit: ${code}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Try to get floor values for each size
|
|
251
|
+
let minFloor = -1;
|
|
252
|
+
|
|
253
|
+
for (const sizeObj of sizes) {
|
|
254
|
+
// Extract size and mediaType from the object
|
|
255
|
+
const { size, mediaType } = sizeObj;
|
|
256
|
+
|
|
257
|
+
// Call getFloor with the appropriate media type
|
|
258
|
+
const floorInfo = bidWithGetFloor.getFloor({
|
|
259
|
+
currency: 'USD', // Default currency
|
|
260
|
+
mediaType: mediaType, // Use the media type we extracted
|
|
261
|
+
size: size
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (floorInfo?.floor && !isNaN(parseFloat(floorInfo.floor))) {
|
|
265
|
+
const floorValue = parseFloat(floorInfo.floor);
|
|
266
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, `Floor value for ${mediaType} size ${size}: ${floorValue}`);
|
|
267
|
+
|
|
268
|
+
// Update minimum floor value
|
|
269
|
+
minFloor = minFloor === -1 ? floorValue : Math.min(minFloor, floorValue);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (minFloor !== -1) {
|
|
274
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, `Calculated minimum floor value ${minFloor} for ad unit: ${code}`);
|
|
275
|
+
return minFloor;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, `No floor data found for ad unit: ${code}`);
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Select multiplier based on priority order: floors.json → config.json → default
|
|
283
|
+
function selectMultiplier(multiplierKey, profileConfigs) {
|
|
284
|
+
// Define sources in priority order
|
|
285
|
+
const multiplierSources = [
|
|
286
|
+
{
|
|
287
|
+
name: 'config.json',
|
|
288
|
+
getValue: () => {
|
|
289
|
+
const configPath = profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.multiplier;
|
|
290
|
+
const lowerKey = multiplierKey.toLowerCase();
|
|
291
|
+
return configPath && lowerKey in configPath ? configPath[lowerKey] : null;
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: 'floor.json',
|
|
296
|
+
getValue: () => {
|
|
297
|
+
const configPath = profileConfigs?.plugins?.dynamicFloors?.data?.multiplier;
|
|
298
|
+
const lowerKey = multiplierKey.toLowerCase();
|
|
299
|
+
return configPath && lowerKey in configPath ? configPath[lowerKey] : null;
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'default',
|
|
304
|
+
getValue: () => CONSTANTS.MULTIPLIERS[multiplierKey]
|
|
305
|
+
}
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
// Find the first source with a non-null value
|
|
309
|
+
for (const source of multiplierSources) {
|
|
310
|
+
const value = source.getValue();
|
|
311
|
+
if (value != null) {
|
|
312
|
+
return { value, source: source.name };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Fallback (shouldn't happen due to default source)
|
|
317
|
+
return { value: CONSTANTS.MULTIPLIERS[multiplierKey], source: 'default' };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Identify winning bid scenario and return scenario data
|
|
321
|
+
function handleWinningBidScenario(winningBid, code) {
|
|
322
|
+
return {
|
|
323
|
+
scenario: 'winning',
|
|
324
|
+
bidStatus: CONSTANTS.BID_STATUS.WON,
|
|
325
|
+
baseValue: winningBid.cpm,
|
|
326
|
+
multiplierKey: 'WIN',
|
|
327
|
+
logMessage: `Bid won for ad unit: ${code}, CPM: ${winningBid.cpm}`
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Identify rejected floor bid scenario and return scenario data
|
|
332
|
+
function handleRejectedFloorBidScenario(rejectedFloorBid, code) {
|
|
333
|
+
const baseValue = rejectedFloorBid.floorData?.floorValue || 0;
|
|
334
|
+
return {
|
|
335
|
+
scenario: 'rejected',
|
|
336
|
+
bidStatus: CONSTANTS.BID_STATUS.FLOORED,
|
|
337
|
+
baseValue,
|
|
338
|
+
multiplierKey: 'FLOORED',
|
|
339
|
+
logMessage: `Bid rejected due to price floor for ad unit: ${code}, Floor value: ${baseValue}, Bid CPM: ${rejectedFloorBid.cpm}`
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Identify no bid scenario and return scenario data
|
|
344
|
+
function handleNoBidScenario(auction, code) {
|
|
345
|
+
const baseValue = findFloorValueFromBidderRequests(auction, code);
|
|
346
|
+
return {
|
|
347
|
+
scenario: 'nobid',
|
|
348
|
+
bidStatus: CONSTANTS.BID_STATUS.NOBID,
|
|
349
|
+
baseValue,
|
|
350
|
+
multiplierKey: 'NOBID',
|
|
351
|
+
logMessage: `No bids for ad unit: ${code}, Floor value: ${baseValue}`
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Determine which scenario applies based on bid conditions
|
|
356
|
+
function determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) {
|
|
357
|
+
return winningBid ? handleWinningBidScenario(winningBid, code)
|
|
358
|
+
: rejectedFloorBid ? handleRejectedFloorBidScenario(rejectedFloorBid, code)
|
|
359
|
+
: handleNoBidScenario(auction, code);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Main function that determines bid status and calculates values
|
|
363
|
+
function determineBidStatusAndValues(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) {
|
|
364
|
+
const profileConfigs = getProfileConfigs();
|
|
365
|
+
|
|
366
|
+
// Determine the scenario based on bid conditions
|
|
367
|
+
const { bidStatus, baseValue, multiplierKey, logMessage } =
|
|
368
|
+
determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code);
|
|
369
|
+
|
|
370
|
+
// Select the appropriate multiplier
|
|
371
|
+
const { value: multiplier, source } = selectMultiplier(multiplierKey, profileConfigs);
|
|
372
|
+
logInfo(CONSTANTS.LOG_PRE_FIX, logMessage + ` (Using ${source} multiplier: ${multiplier})`);
|
|
373
|
+
|
|
374
|
+
return { bidStatus, baseValue, multiplier };
|
|
375
|
+
}
|