/** @module adaptermanger */
import {
deepClone,
flatten,
generateUUID,
getBidderCodes,
getDefinedParams,
getUniqueIdentifierStr,
getUserConfiguredParams,
groupBy,
internal,
isArray,
isPlainObject,
isValidMediaTypes,
logError,
logInfo,
logMessage,
logWarn,
mergeDeep,
shuffle,
timestamp,
uniques,
} from './utils.js';
import {decorateAdUnitsWithNativeParams, nativeAdapters} from './native.js';
import {newBidder} from './adapters/bidderFactory.js';
import {ajaxBuilder} from './ajax.js';
import {config, RANDOM} from './config.js';
import {hook} from './hook.js';
import {
type AdUnit,
type AdUnitBid,
type AdUnitBidderBid, type AdUnitModuleBid,
getAuctionsCounter,
getBidderRequestsCounter,
getBidderWinsCounter,
getRequestsCounter,
incrementAuctionsCounter,
incrementBidderRequestsCounter,
incrementBidderWinsCounter,
incrementRequestsCounter
} from './adUnits.js';
import {getRefererInfo, type RefererInfo} from './refererDetection.js';
import {GDPR_GVLIDS, gdprDataHandler, gppDataHandler, uspDataHandler,} from './consentHandler.js';
import * as events from './events.js';
import {EVENTS, S2S} from './constants.js';
import {type Metrics, useMetrics} from './utils/perfMetrics.js';
import {auctionManager} from './auctionManager.js';
import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER, MODULE_TYPE_PREBID} from './activities/modules.js';
import {isActivityAllowed} from './activities/rules.js';
import {ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS} from './activities/activities.js';
import {ACTIVITY_PARAM_ANL_CONFIG, ACTIVITY_PARAM_S2S_NAME, activityParamsBuilder} from './activities/params.js';
import {redactor} from './activities/redactor.js';
import {EVENT_TYPE_IMPRESSION, parseEventTrackers, TRACKER_METHOD_IMG} from './eventTrackers.js';
import type {
AdUnitCode,
BidderCode,
BidSource,
ContextIdentifiers,
Identifier,
ORTBFragments,
Size, StorageDisclosure
} from "./types/common.d.ts";
import type {DeepPartial} from "./types/objects.d.ts";
import type {ORTBRequest} from "./types/ortb/request.d.ts";
import type {
AnalyticsConfig,
AnalyticsProvider, AnalyticsProviderConfig,
} from "../libraries/analyticsAdapter/AnalyticsAdapter.ts";
export {gdprDataHandler, gppDataHandler, uspDataHandler, coppaDataHandler} from './consentHandler.js';
export const PBS_ADAPTER_NAME = 'pbsBidAdapter';
export const PARTITIONS = {
CLIENT: 'client',
SERVER: 'server'
}
export const dep = {
isAllowed: isActivityAllowed,
redact: redactor
}
const _bidderRegistry = {};
const _aliasRegistry: { [aliasCode: BidderCode]: BidderCode } = {};
const _analyticsRegistry: { [P in AnalyticsProvider]?: { adapter: AnalyticsAdapter
, gvlid?: number }} = {};
let _s2sConfigs = [];
config.getConfig('s2sConfig', config => {
if (config && config.s2sConfig) {
_s2sConfigs = isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig];
}
});
const activityParams = activityParamsBuilder((alias) => adapterManager.resolveAlias(alias));
function getConfigName(s2sConfig) {
// According to our docs, "module" bid (stored impressions)
// have params.configName referring to s2sConfig.name,
// but for a long while this was checking against s2sConfig.configName.
// Keep allowing s2sConfig.configName to avoid the breaking change
return s2sConfig.configName ?? s2sConfig.name;
}
export function s2sActivityParams(s2sConfig) {
return activityParams(MODULE_TYPE_PREBID, PBS_ADAPTER_NAME, {
[ACTIVITY_PARAM_S2S_NAME]: getConfigName(s2sConfig)
});
}
export interface BaseBidRequest extends ContextIdentifiers, Pick {
/**
* Unique ID for this request.
*/
bidId: Identifier;
/**
* ID of the BidderRequest containing this request.
*/
bidderRequestId: Identifier;
metrics: Metrics;
src: BidSource;
/**
* The code of the ad unit associated with this request.
*/
adUnitCode: AdUnitCode;
/**
* @deprecated - use mediaType specific size parameters instead.
*/
sizes: Size | Size[];
/**
* The number of auctions that took place involving the ad unit associated with this request.
*/
auctionsCount: number;
/**
* How many times the ad unit code associated with this request took part in an auction. This differs
* from `auctionsCount` when twin ad units are used.
*/
bidRequestsCount: number;
/**
* The number of client (but not s2s) requests that were generated for the combination of ad unit and bidder
* associated with this request.
*/
bidderRequestsCount: number;
/**
* The number of times a bid from the same bidder and for the same ad unit as this request has won.
*/
bidderWinsCount: number;
deferBilling: AdUnit['deferBilling'];
/**
* "Global" (not adUnit-specific) first party data for this request. This
* is an alias for the enclosing BidderRequest's `.ortb2`; adUnit-specific
* FPD is in `ortb2Imp`.
*/
ortb2: DeepPartial;
}
export interface StoredBidRequest extends BaseBidRequest, Omit<{[K in keyof AdUnitBidderBid]?: undefined}, keyof BaseBidRequest> {
bidder: null;
src: typeof S2S.SRC;
}
type BidderBidRequest = BaseBidRequest & AdUnitBidderBid;
export type BidRequest = BIDDER extends null ? StoredBidRequest : BidderBidRequest;
export interface BaseBidderRequest {
/**
* Unique ID for this request.
*/
bidderRequestId: Identifier;
auctionId: Identifier;
/**
* The bidder associated with this request, or null in the case of stored impressions.
*/
bidderCode: BIDDER;
/**
* Bid requests (one per ad unit) included in this request.
*/
bids: BidRequest[];
/**
* First party data for this request.
*/
ortb2: DeepPartial;
/**
* Auction start timestamp.
*/
auctionStart: number;
/**
* Request timeout in milliseconds.
*/
timeout: number;
refererInfo: RefererInfo;
metrics: Metrics;
gdprConsent?: ReturnType;
uspConsent?: ReturnType;
gppConsent?: ReturnType;
}
export interface S2SBidderRequest extends BaseBidderRequest {
src: typeof S2S.SRC;
uniquePbsTid: Identifier;
adUnitsS2SCopy: PBSAdUnit[];
}
export interface ClientBidderRequest extends BaseBidderRequest {
src: 'client';
}
export type BidderRequest = ClientBidderRequest | S2SBidderRequest;
const ADUNIT_BID_PROPERTIES = [
'nativeParams',
'nativeOrtbRequest',
'renderer',
] as const;
type GetBidsOptions = {
bidderCode: BIDDER;
auctionId: Identifier;
bidderRequestId: Identifier;
adUnits: (SRC extends typeof S2S.SRC ? PBSAdUnit : AdUnit)[]
src: SRC;
metrics: Metrics,
tids: { [bidderCode: BidderCode]: string };
}
export type AliasBidderOptions = {
/**
* IAB Global Vendor List ID for this alias for use with the TCF control module.
*/
gvlid?: number;
/**
* Flag determining if the GVL ID of the original adapter should be re-used.
*/
useBaseGvlid?: boolean;
/**
* If true, the alias will not be communicated to Prebid Server.
*/
skipPbsAliasing?: boolean
}
export type AnalyticsAdapter = StorageDisclosure & {
code?: P;
enableAnalytics(config: AnalyticsConfig
): void;
gvlid?: number | ((config: AnalyticsConfig
) => number);
}
function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics, tids}: GetBidsOptions): BidRequest[] {
return adUnits.reduce((result, adUnit) => {
const bids = adUnit.bids.filter(bid => bid.bidder === bidderCode);
if (bidderCode == null && bids.length === 0 && (adUnit as PBSAdUnit).s2sBid != null) {
bids.push({bidder: null});
}
result.push(
bids.reduce((bids: BidRequest[], bid: BidRequest) => {
if (!tids.hasOwnProperty(adUnit.transactionId)) {
tids[adUnit.transactionId] = generateUUID();
}
bid = Object.assign({}, bid,
{ortb2Imp: mergeDeep({}, adUnit.ortb2Imp, bid.ortb2Imp, {ext: {tid: tids[adUnit.transactionId]}})},
getDefinedParams(adUnit, ADUNIT_BID_PROPERTIES),
);
const mediaTypes = bid.mediaTypes == null ? adUnit.mediaTypes : bid.mediaTypes
if (isValidMediaTypes(mediaTypes)) {
bid = Object.assign({}, bid, {
mediaTypes
});
} else {
logError(
`mediaTypes is not correctly configured for adunit ${adUnit.code}`
);
}
if (src === 'client') {
incrementBidderRequestsCounter(adUnit.code, bidderCode);
}
bids.push(Object.assign({}, bid, {
adUnitCode: adUnit.code,
transactionId: adUnit.transactionId,
adUnitId: adUnit.adUnitId,
sizes: mediaTypes?.banner?.sizes || mediaTypes?.video?.playerSize || [],
bidId: (bid as any).bid_id || generateUUID(),
bidderRequestId,
auctionId,
src,
metrics,
auctionsCount: getAuctionsCounter(adUnit.code),
bidRequestsCount: getRequestsCounter(adUnit.code),
bidderRequestsCount: getBidderRequestsCounter(adUnit.code, bid.bidder),
bidderWinsCount: getBidderWinsCounter(adUnit.code, bid.bidder),
deferBilling: !!adUnit.deferBilling
}));
return bids;
}, [])
);
return result;
}, []).reduce(flatten, []).filter(val => val !== '');
}
/**
* Filter an adUnit's bids for building client and/or server requests
*
* @param bids an array of bids as defined in an adUnit
* @param s2sConfig null if the adUnit is being routed to a client adapter; otherwise the s2s adapter's config
* @returns the subset of `bids` that are pertinent for the given `s2sConfig`
*/
export const filterBidsForAdUnit = hook('sync', function(bids, s2sConfig, {getS2SBidders = getS2SBidderSet} = {}) {
if (s2sConfig == null) {
return bids;
} else {
const serverBidders = getS2SBidders(s2sConfig);
return bids.filter((bid: BidRequest) => {
if (!serverBidders.has(bid.bidder)) return false;
if (bid.s2sConfigName == null) return true;
const configName = getConfigName(s2sConfig);
const allowedS2SConfigs = Array.isArray(bid.s2sConfigName) ? bid.s2sConfigName : [bid.s2sConfigName];
return allowedS2SConfigs.includes(configName);
})
}
}, 'filterBidsForAdUnit');
type PBSAdUnitBid = AdUnitBid & {
bid_id?: Identifier;
};
type PBSAdUnit = Omit & {
s2sBid?: PBSAdUnitBid;
bids: PBSAdUnitBid[];
};
function getAdUnitCopyForPrebidServer(adUnits: AdUnit[], s2sConfig) {
let adUnitsCopy = deepClone(adUnits) as PBSAdUnit[];
let hasModuleBids = false;
adUnitsCopy.forEach((adUnit) => {
// filter out client side bids
const s2sBids = adUnit.bids.filter((b) => b.module === PBS_ADAPTER_NAME && (
(b as AdUnitModuleBid).params?.configName === getConfigName(s2sConfig)
));
if (s2sBids.length === 1) {
adUnit.s2sBid = s2sBids[0];
hasModuleBids = true;
adUnit.ortb2Imp = mergeDeep({}, adUnit.s2sBid.ortb2Imp, adUnit.ortb2Imp);
} else if (s2sBids.length > 1) {
logWarn('Multiple "module" bids for the same s2s configuration; all will be ignored', s2sBids);
}
adUnit.bids = filterBidsForAdUnit(adUnit.bids, s2sConfig)
.map((bid) => {
bid.bid_id = getUniqueIdentifierStr();
return bid;
});
});
adUnitsCopy = adUnitsCopy.filter(adUnit => {
if (s2sConfig.filterBidderlessCalls) {
if (adUnit.bids.length === 1 && adUnit.bids[0].bidder == null) return false;
}
return adUnit.bids.length !== 0 || adUnit.s2sBid != null;
});
// don't send empty requests
return {adUnits: adUnitsCopy, hasModuleBids};
}
function getAdUnitCopyForClientAdapters(adUnits: AdUnit[]) {
let adUnitsClientCopy = deepClone(adUnits);
adUnitsClientCopy.forEach((adUnit) => {
adUnit.bids = filterBidsForAdUnit(adUnit.bids, null);
});
// don't send empty requests
adUnitsClientCopy = adUnitsClientCopy.filter(adUnit => {
return adUnit.bids.length !== 0;
});
return adUnitsClientCopy;
}
/**
* Filter and/or modify media types for ad units based on the given labels.
*
* This should return adUnits that are active for the given labels, modified to have their `mediaTypes`
* conform to size mapping configuration. If different bids for the same adUnit should use different `mediaTypes`,
* they should be exposed under `adUnit.bids[].mediaTypes`.
*/
export const setupAdUnitMediaTypes = hook('sync', (adUnits, labels) => {
return adUnits;
}, 'setupAdUnitMediaTypes')
/**
* @param {{}|Array<{}>} s2sConfigs
* @returns {Set} a set of all the bidder codes that should be routed through the S2S adapter(s)
* as defined in `s2sConfigs`
*/
export function getS2SBidderSet(s2sConfigs) {
if (!isArray(s2sConfigs)) s2sConfigs = [s2sConfigs];
// `null` represents the "no bid bidder" - when an ad unit is meant only for S2S adapters, like stored impressions
const serverBidders = new Set([null]);
s2sConfigs.filter((s2s) => s2s && s2s.enabled)
.flatMap((s2s) => s2s.bidders)
.forEach((bidder) => serverBidders.add(bidder));
return serverBidders;
}
/**
* @param {Array} adUnits - The ad units to be processed.
* @param {Object} s2sConfigs - The server-to-server configurations.
* @returns {Object} - An object containing arrays of bidder codes for client and server.
* @returns {Object} return.client - Array of bidder codes that should be routed to client adapters.
* @returns {Object} return.server - Array of bidder codes that should be routed to server adapters.
*/
export function _partitionBidders (adUnits, s2sConfigs, {getS2SBidders = getS2SBidderSet} = {}) {
const serverBidders = getS2SBidders(s2sConfigs);
return getBidderCodes(adUnits).reduce((memo, bidder) => {
const partition = serverBidders.has(bidder) ? PARTITIONS.SERVER : PARTITIONS.CLIENT;
memo[partition].push(bidder);
return memo;
}, {[PARTITIONS.CLIENT]: [], [PARTITIONS.SERVER]: []})
}
export const partitionBidders = hook('sync', _partitionBidders, 'partitionBidders');
declare module './events' {
interface Events {
/**
* Fired once per auction, just before bid requests are generated.
*/
[EVENTS.BEFORE_REQUEST_BIDS]: [AdUnit[]];
/**
* Fired once for each bidder in each auction (or twice for bidders that are configured for both client and s2s).
*/
[EVENTS.BID_REQUESTED]: [BidderRequest];
}
}
declare module './hook' {
interface NamedHooks {
makeBidRequests: typeof adapterManager.makeBidRequests;
}
}
const adapterManager = {
bidderRegistry: _bidderRegistry,
analyticsRegistry: _analyticsRegistry,
/**
* Map from alias codes to the bidder code they alias.
*/
aliasRegistry: _aliasRegistry,
makeBidRequests: hook('sync', function (
adUnits: AdUnit[],
auctionStart: number,
auctionId: Identifier,
cbTimeout: number,
labels: string[],
ortb2Fragments: ORTBFragments = {},
auctionMetrics: Metrics
): BidderRequest[] {
auctionMetrics = useMetrics(auctionMetrics);
/**
* emit and pass adunits for external modification
* @see {@link https://github.com/prebid/Prebid.js/issues/4149|Issue}
*/
events.emit(EVENTS.BEFORE_REQUEST_BIDS, adUnits);
if (FEATURES.NATIVE) {
decorateAdUnitsWithNativeParams(adUnits);
}
adUnits
.map(adUnit => adUnit.code)
.filter(uniques)
.forEach(incrementAuctionsCounter);
adUnits.forEach(au => {
if (!isPlainObject(au.mediaTypes)) {
au.mediaTypes = {};
}
// filter out bidders that cannot participate in the auction
au.bids = au.bids.filter((bid) => !bid.bidder || dep.isAllowed(ACTIVITY_FETCH_BIDS, activityParams(MODULE_TYPE_BIDDER, bid.bidder)))
incrementRequestsCounter(au.code);
});
adUnits = setupAdUnitMediaTypes(adUnits, labels);
let {[PARTITIONS.CLIENT]: clientBidders, [PARTITIONS.SERVER]: serverBidders} = partitionBidders(adUnits, _s2sConfigs);
if (config.getConfig('bidderSequence') === RANDOM) {
clientBidders = shuffle(clientBidders);
}
const refererInfo = getRefererInfo();
const bidRequests: BidderRequest[] = [];
const ortb2 = ortb2Fragments.global || {};
const bidderOrtb2 = ortb2Fragments.bidder || {};
const sourceTids: any = {};
const extTids: any = {};
function tidFor(tids, bidderCode, makeTid) {
const tid = tids.hasOwnProperty(bidderCode) ? tids[bidderCode] : makeTid();
if (bidderCode != null) {
tids[bidderCode] = tid;
}
return tid;
}
function addOrtb2>(bidderRequest: Partial, s2sActivityParams?): T {
const redact = dep.redact(
s2sActivityParams != null
? s2sActivityParams
: activityParams(MODULE_TYPE_BIDDER, bidderRequest.bidderCode)
);
const tid = tidFor(sourceTids, bidderRequest.bidderCode, generateUUID);
const fpd = Object.freeze(redact.ortb2(mergeDeep(
{},
ortb2,
bidderOrtb2[bidderRequest.bidderCode],
{source: {tid}}
)));
bidderRequest.ortb2 = fpd;
bidderRequest.bids = bidderRequest.bids.map((bid) => {
bid.ortb2 = fpd;
return redact.bidRequest(bid);
})
return bidderRequest as T;
}
_s2sConfigs.forEach(s2sConfig => {
const s2sParams = s2sActivityParams(s2sConfig);
if (s2sConfig && s2sConfig.enabled && dep.isAllowed(ACTIVITY_FETCH_BIDS, s2sParams)) {
const {adUnits: adUnitsS2SCopy, hasModuleBids} = getAdUnitCopyForPrebidServer(adUnits, s2sConfig);
// uniquePbsTid is so we know which server to send which bids to during the callBids function
const uniquePbsTid = generateUUID();
(serverBidders.length === 0 && hasModuleBids ? [null] : serverBidders).forEach(bidderCode => {
const tids = tidFor(extTids, bidderCode, () => ({}));
const bidderRequestId = generateUUID();
const metrics = auctionMetrics.fork();
const bidderRequest = addOrtb2({
bidderCode,
auctionId,
bidderRequestId,
uniquePbsTid,
bids: getBids({
bidderCode,
auctionId,
bidderRequestId,
'adUnits': deepClone(adUnitsS2SCopy),
src: S2S.SRC,
metrics,
tids
}),
auctionStart: auctionStart,
timeout: s2sConfig.timeout,
src: S2S.SRC,
refererInfo,
metrics,
}, s2sParams);
if (bidderRequest.bids.length !== 0) {
bidRequests.push(bidderRequest);
}
});
// update the s2sAdUnits object and remove all bids that didn't pass sizeConfig/label checks from getBids()
// this is to keep consistency and only allow bids/adunits that passed the checks to go to pbs
adUnitsS2SCopy.forEach((adUnitCopy) => {
const validBids = adUnitCopy.bids.filter((adUnitBid) =>
bidRequests.find(request =>
request.bids.find((reqBid) => reqBid.bidId === adUnitBid.bid_id)));
adUnitCopy.bids = validBids;
});
bidRequests.forEach((request: S2SBidderRequest) => {
if (request.adUnitsS2SCopy === undefined) {
request.adUnitsS2SCopy = adUnitsS2SCopy.filter(au => au.bids.length > 0 || au.s2sBid != null);
}
});
}
});
// client adapters
const adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits);
clientBidders.forEach(bidderCode => {
const tids = tidFor(extTids, bidderCode, () => ({}));
const bidderRequestId = generateUUID();
const metrics = auctionMetrics.fork();
const bidderRequest = addOrtb2({
bidderCode,
auctionId,
bidderRequestId,
bids: getBids({
bidderCode,
auctionId,
bidderRequestId,
'adUnits': deepClone(adUnitsClientCopy),
src: 'client',
metrics,
tids
}),
auctionStart: auctionStart,
timeout: cbTimeout,
refererInfo,
metrics,
});
const adapter = _bidderRegistry[bidderCode];
if (!adapter) {
logError(`Trying to make a request for bidder that does not exist: ${bidderCode}`);
}
if (adapter && bidderRequest.bids && bidderRequest.bids.length !== 0) {
bidRequests.push(bidderRequest);
}
});
bidRequests.forEach(bidRequest => {
if (gdprDataHandler.getConsentData()) {
bidRequest['gdprConsent'] = gdprDataHandler.getConsentData();
}
if (uspDataHandler.getConsentData()) {
bidRequest['uspConsent'] = uspDataHandler.getConsentData();
}
if (gppDataHandler.getConsentData()) {
bidRequest['gppConsent'] = gppDataHandler.getConsentData();
}
});
return bidRequests;
}, 'makeBidRequests'),
callBids(adUnits, bidRequests, addBidResponse, doneCb, requestCallbacks, requestBidsTimeout, onTimelyResponse, ortb2Fragments = {}) {
if (!bidRequests.length) {
logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?');
return;
}
const [clientBidderRequests, serverBidderRequests] = bidRequests.reduce((partitions, bidRequest) => {
partitions[Number(typeof bidRequest.src !== 'undefined' && bidRequest.src === S2S.SRC)].push(bidRequest);
return partitions;
}, [[], []]);
var uniqueServerBidRequests = [];
serverBidderRequests.forEach(serverBidRequest => {
var index = -1;
for (var i = 0; i < uniqueServerBidRequests.length; ++i) {
if (serverBidRequest.uniquePbsTid === uniqueServerBidRequests[i].uniquePbsTid) {
index = i;
break;
}
}
if (index <= -1) {
uniqueServerBidRequests.push(serverBidRequest);
}
});
let counter = 0;
_s2sConfigs.forEach((s2sConfig) => {
if (s2sConfig && uniqueServerBidRequests[counter] && getS2SBidderSet(s2sConfig).has(uniqueServerBidRequests[counter].bidderCode)) {
// s2s should get the same client side timeout as other client side requests.
const s2sAjax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? {
request: requestCallbacks.request.bind(null, 's2s'),
done: requestCallbacks.done
} : undefined);
const adaptersServerSide = s2sConfig.bidders;
const s2sAdapter = _bidderRegistry[s2sConfig.adapter];
const uniquePbsTid = uniqueServerBidRequests[counter].uniquePbsTid;
const adUnitsS2SCopy = uniqueServerBidRequests[counter].adUnitsS2SCopy;
const uniqueServerRequests = serverBidderRequests.filter(serverBidRequest => serverBidRequest.uniquePbsTid === uniquePbsTid);
if (s2sAdapter) {
const s2sBidRequest = {'ad_units': adUnitsS2SCopy, s2sConfig, ortb2Fragments, requestBidsTimeout};
if (s2sBidRequest.ad_units.length) {
const doneCbs = uniqueServerRequests.map(bidRequest => {
bidRequest.start = timestamp();
return function (timedOut, ...args) {
if (!timedOut) {
onTimelyResponse(bidRequest.bidderRequestId);
}
doneCb.apply(bidRequest, [timedOut, ...args]);
}
});
const bidders = getBidderCodes(s2sBidRequest.ad_units).filter((bidder) => adaptersServerSide.includes(bidder));
logMessage(`CALLING S2S HEADER BIDDERS ==== ${bidders.length > 0 ? bidders.join(', ') : 'No bidder specified, using "ortb2Imp" definition(s) only'}`);
// fire BID_REQUESTED event for each s2s bidRequest
uniqueServerRequests.forEach(bidRequest => {
// add the new sourceTid
events.emit(EVENTS.BID_REQUESTED, { ...bidRequest, tid: bidRequest.auctionId });
});
// make bid requests
s2sAdapter.callBids(
s2sBidRequest,
serverBidderRequests,
addBidResponse,
(timedOut) => doneCbs.forEach(done => done(timedOut)),
s2sAjax
);
}
} else {
logError('missing ' + s2sConfig.adapter);
}
counter++;
}
});
// handle client adapter requests
clientBidderRequests.forEach(bidderRequest => {
bidderRequest.start = timestamp();
const adapter = _bidderRegistry[bidderRequest.bidderCode];
config.runWithBidder(bidderRequest.bidderCode, () => {
logMessage(`CALLING BIDDER`);
events.emit(EVENTS.BID_REQUESTED, bidderRequest);
});
const ajax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? {
request: requestCallbacks.request.bind(null, bidderRequest.bidderCode),
done: requestCallbacks.done
} : undefined);
const adapterDone = doneCb.bind(bidderRequest);
try {
config.runWithBidder(
bidderRequest.bidderCode,
adapter.callBids.bind(
adapter,
bidderRequest,
addBidResponse,
adapterDone,
ajax,
() => onTimelyResponse(bidderRequest.bidderRequestId),
config.callbackWithBidder(bidderRequest.bidderCode)
)
);
} catch (e) {
logError(`${bidderRequest.bidderCode} Bid Adapter emitted an uncaught error when parsing their bidRequest`, {e, bidRequest: bidderRequest});
adapterDone();
}
})
},
videoAdapters: [],
registerBidAdapter(bidAdapter, bidderCode, {supportedMediaTypes = []} = {}) {
if (bidAdapter && bidderCode) {
if (typeof bidAdapter.callBids === 'function') {
_bidderRegistry[bidderCode] = bidAdapter;
GDPR_GVLIDS.register(MODULE_TYPE_BIDDER, bidderCode, bidAdapter.getSpec?.().gvlid);
if (FEATURES.VIDEO && supportedMediaTypes.includes('video')) {
adapterManager.videoAdapters.push(bidderCode);
}
if (FEATURES.NATIVE && supportedMediaTypes.includes('native')) {
nativeAdapters.push(bidderCode);
}
} else {
logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function');
}
} else {
logError('bidAdapter or bidderCode not specified');
}
},
aliasBidAdapter(bidderCode: BidderCode, alias: BidderCode, options?: AliasBidderOptions) {
const existingAlias = _bidderRegistry[alias];
if (typeof existingAlias === 'undefined') {
const bidAdapter = _bidderRegistry[bidderCode];
if (typeof bidAdapter === 'undefined') {
// check if alias is part of s2sConfig and allow them to register if so (as base bidder may be s2s-only)
const nonS2SAlias = [];
_s2sConfigs.forEach(s2sConfig => {
if (s2sConfig.bidders && s2sConfig.bidders.length) {
const s2sBidders = s2sConfig && s2sConfig.bidders;
if (!(s2sConfig && s2sBidders.includes(alias))) {
nonS2SAlias.push(bidderCode);
} else {
_aliasRegistry[alias] = bidderCode;
}
}
});
nonS2SAlias.forEach(bidderCode => {
logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adapterManager.aliasBidAdapter');
});
} else {
try {
let newAdapter;
const supportedMediaTypes = getSupportedMediaTypes(bidderCode);
// Have kept old code to support backward compatibilitiy.
// Remove this if loop when all adapters are supporting bidderFactory. i.e When Prebid.js is 1.0
if (bidAdapter.constructor.prototype !== Object.prototype) {
newAdapter = new bidAdapter.constructor();
newAdapter.setBidderCode(alias);
} else {
const { useBaseGvlid = false } = options || {};
const spec = bidAdapter.getSpec();
const gvlid = useBaseGvlid ? spec.gvlid : options?.gvlid;
if (gvlid == null && spec.gvlid != null) {
logWarn(`Alias '${alias}' will NOT re-use the GVL ID of the original adapter ('${spec.code}', gvlid: ${spec.gvlid}). Functionality that requires TCF consent may not work as expected.`)
}
const skipPbsAliasing = options && options.skipPbsAliasing;
newAdapter = newBidder(Object.assign({}, spec, { code: alias, gvlid, skipPbsAliasing }));
_aliasRegistry[alias] = bidderCode;
}
adapterManager.registerBidAdapter(newAdapter, alias, {
supportedMediaTypes
});
} catch (e) {
logError(bidderCode + ' bidder does not currently support aliasing.', 'adapterManager.aliasBidAdapter');
}
}
} else {
logMessage('alias name "' + alias + '" has been already specified.');
}
},
resolveAlias(alias) {
let code = alias;
let visited;
while (_aliasRegistry[code] && (!visited || !visited.has(code))) {
code = _aliasRegistry[code];
(visited = visited || new Set()).add(code);
}
return code;
},
registerAnalyticsAdapter({adapter, code, gvlid}: {
adapter: AnalyticsAdapter
,
code: P,
gvlid?: number
}) {
if (adapter && code) {
if (typeof adapter.enableAnalytics === 'function') {
adapter.code = code;
_analyticsRegistry[code] = { adapter, gvlid };
GDPR_GVLIDS.register(MODULE_TYPE_ANALYTICS, code, gvlid);
} else {
logError(`Prebid Error: Analytics adaptor error for analytics "${code}"
analytics adapter must implement an enableAnalytics() function`);
}
} else {
logError('Prebid Error: analyticsAdapter or analyticsCode not specified');
}
},
enableAnalytics(
config: AnalyticsConfig
| AnalyticsConfig
| AnalyticsConfig[]
) {
if (!isArray(config)) {
config = [config];
}
config.forEach(adapterConfig => {
const entry = _analyticsRegistry[adapterConfig.provider];
if (entry && entry.adapter) {
if (dep.isAllowed(ACTIVITY_REPORT_ANALYTICS, activityParams(MODULE_TYPE_ANALYTICS, adapterConfig.provider, {[ACTIVITY_PARAM_ANL_CONFIG]: adapterConfig}))) {
entry.adapter.enableAnalytics(adapterConfig);
}
} else {
logError(`Prebid Error: no analytics adapter found in registry for '${adapterConfig.provider}'.`);
}
});
},
getBidAdapter(bidder) {
return _bidderRegistry[bidder];
},
getAnalyticsAdapter(code) {
return _analyticsRegistry[code];
},
callTimedOutBidders(adUnits, timedOutBidders, cbTimeout) {
timedOutBidders = timedOutBidders.map((timedOutBidder) => {
// Adding user configured params & timeout to timeout event data
timedOutBidder.params = getUserConfiguredParams(adUnits, timedOutBidder.adUnitCode, timedOutBidder.bidder);
timedOutBidder.timeout = cbTimeout;
return timedOutBidder;
});
timedOutBidders = groupBy(timedOutBidders, 'bidder');
Object.keys(timedOutBidders).forEach((bidder) => {
tryCallBidderMethod(bidder, 'onTimeout', timedOutBidders[bidder]);
});
},
callBidWonBidder(bidder, bid, adUnits) {
// Adding user configured params to bidWon event data
bid.params = getUserConfiguredParams(adUnits, bid.adUnitCode, bid.bidder);
incrementBidderWinsCounter(bid.adUnitCode, bid.bidder);
tryCallBidderMethod(bidder, 'onBidWon', bid);
},
triggerBilling: (() => {
const BILLED = new WeakSet();
return (bid) => {
if (!BILLED.has(bid)) {
BILLED.add(bid);
(parseEventTrackers(bid.eventtrackers)[EVENT_TYPE_IMPRESSION]?.[TRACKER_METHOD_IMG] || [])
.forEach((url) => internal.triggerPixel(url));
tryCallBidderMethod(bid.bidder, 'onBidBillable', bid);
}
}
})(),
callSetTargetingBidder(bidder, bid) {
tryCallBidderMethod(bidder, 'onSetTargeting', bid);
},
callBidViewableBidder(bidder, bid) {
tryCallBidderMethod(bidder, 'onBidViewable', bid);
},
callBidderError(bidder, error, bidderRequest) {
const param = { error, bidderRequest };
tryCallBidderMethod(bidder, 'onBidderError', param);
},
callAdRenderSucceededBidder(bidder, bid) {
tryCallBidderMethod(bidder, 'onAdRenderSucceeded', bid);
},
callOnInterventionBidder(bidder, bid, intervention) {
const param = { bid, intervention }
tryCallBidderMethod(bidder, 'onIntervention', param);
},
/**
* Ask every adapter to delete PII.
* See https://github.com/prebid/Prebid.js/issues/9081
*/
callDataDeletionRequest: hook('sync', function (...args) {
const method = 'onDataDeletionRequest';
Object.keys(_bidderRegistry)
.filter((bidder) => !_aliasRegistry.hasOwnProperty(bidder))
.forEach(bidder => {
const target = getBidderMethod(bidder, method);
if (target != null) {
const bidderRequests = auctionManager.getBidsRequested().filter((br) =>
resolveAlias(br.bidderCode) === bidder
);
invokeBidderMethod(bidder, method, ...target, bidderRequests, ...args);
}
});
Object.entries(_analyticsRegistry).forEach(([name, entry]: any) => {
const fn = entry?.adapter?.[method];
if (typeof fn === 'function') {
try {
fn.apply(entry.adapter, args);
} catch (e) {
logError(`error calling ${method} of ${name}`, e);
}
}
});
})
}
function getSupportedMediaTypes(bidderCode) {
const supportedMediaTypes = [];
if (FEATURES.VIDEO && adapterManager.videoAdapters.includes(bidderCode)) supportedMediaTypes.push('video');
if (FEATURES.NATIVE && nativeAdapters.includes(bidderCode)) supportedMediaTypes.push('native');
return supportedMediaTypes;
}
function getBidderMethod(bidder, method): [string, string] {
const adapter = _bidderRegistry[bidder];
const spec = adapter?.getSpec && adapter.getSpec();
if (spec && spec[method] && typeof spec[method] === 'function') {
return [spec, spec[method]]
}
}
function invokeBidderMethod(bidder, method, spec, fn, ...params) {
try {
logInfo(`Invoking ${bidder}.${method}`);
config.runWithBidder(bidder, fn.bind(spec, ...params));
} catch (e) {
logWarn(`Error calling ${method} of ${bidder}`);
}
}
function tryCallBidderMethod(bidder, method, param) {
if (param?.source !== S2S.SRC) {
const target = getBidderMethod(bidder, method);
if (target != null) {
invokeBidderMethod(bidder, method, ...target, param);
}
}
}
function resolveAlias(alias) {
const seen = new Set();
while (_aliasRegistry.hasOwnProperty(alias) && !seen.has(alias)) {
seen.add(alias);
alias = _aliasRegistry[alias];
}
return alias;
}
export default adapterManager;