Skip to content

Commit

Permalink
Prebid Server adapter: fledge support (prebid#9342)
Browse files Browse the repository at this point in the history
* Move fledge logic to fledge module

* Change fledge interpretResponse API: from {bidId, ...fledgeAuctionConfig} to {bidId, config: fledgeAuctionConfig}

* Fledge ORTB processors

* PBS adapter fledge impl

* Update modules/fledgeForGpt.js

Co-authored-by: Laurentiu Badea <laurb9@users.noreply.github.com>

* fix fledge tests to pass adUnitCode

* Update text

* Fix test case to reflect check on `navigator.joinAdInterestGroup`

* Adjust warnings

* Name change

---------

Co-authored-by: Laurentiu Badea <laurb9@users.noreply.github.com>
  • Loading branch information
2 people authored and jorgeluisrocha committed May 18, 2023
1 parent 5478c34 commit 490b832
Show file tree
Hide file tree
Showing 12 changed files with 385 additions and 117 deletions.
5 changes: 3 additions & 2 deletions libraries/ortbConverter/converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,13 @@ export function ortbConverter({
req: Object.assign({bidRequests}, defaultContext, context),
imp: {}
}
ctx.req.impContext = ctx.imp;
const imps = bidRequests.map(bidRequest => {
const impContext = Object.assign({bidderRequest, reqContext: ctx.req}, defaultContext, context);
const result = buildImp(bidRequest, impContext);
if (result != null) {
if (result.hasOwnProperty('id')) {
impContext.bidRequest = bidRequest;
Object.assign(impContext, {bidRequest, imp: result});
ctx.imp[result.id] = impContext;
return result;
}
Expand All @@ -116,7 +117,7 @@ export function ortbConverter({
throw new Error('ortbRequest passed to `fromORTB` must be the same object returned by `toORTB`')
}
function augmentContext(ctx, extraParams = {}) {
return Object.assign({ortbRequest: request}, extraParams, ctx);
return Object.assign(ctx, {ortbRequest: request}, extraParams, ctx);
}
const impsById = Object.fromEntries((request.imp || []).map(imp => [imp.id, imp]));
const bidResponses = (response.seatbid || []).flatMap(seatbid =>
Expand Down
60 changes: 54 additions & 6 deletions modules/fledgeForGpt.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { config } from '../src/config.js';
import { getHook } from '../src/hook.js';
import { getGptSlotForAdUnitCode, logInfo, logWarn } from '../src/utils.js';
import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js';

const MODULE = 'fledgeForGpt'

Expand All @@ -21,22 +22,20 @@ export function init(cfg) {
getHook('addComponentAuction').before(addComponentAuctionHook);
isEnabled = true;
}
logInfo(MODULE, `isEnabled`, cfg);
logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} fledge)`, cfg);
} else {
if (isEnabled) {
getHook('addComponentAuction').getHooks({hook: addComponentAuctionHook}).remove();
isEnabled = false;
}
logInfo(MODULE, `isDisabled`, cfg);
logInfo(`${MODULE} disabled`, cfg);
}
}

export function addComponentAuctionHook(next, bidRequest, componentAuctionConfig) {
export function addComponentAuctionHook(next, adUnitCode, componentAuctionConfig) {
const seller = componentAuctionConfig.seller;
const adUnitCode = bidRequest.adUnitCode;
const gptSlot = getGptSlotForAdUnitCode(adUnitCode);
if (gptSlot && gptSlot.setConfig) {
delete componentAuctionConfig.bidId;
gptSlot.setConfig({
componentAuction: [{
configKey: seller,
Expand All @@ -48,5 +47,54 @@ export function addComponentAuctionHook(next, bidRequest, componentAuctionConfig
logWarn(MODULE, `unable to register component auction config for: ${adUnitCode} x ${seller}.`);
}

next(bidRequest, componentAuctionConfig);
next(adUnitCode, componentAuctionConfig);
}

function isFledgeSupported() {
return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator
}

export function markForFledge(next, bidderRequests) {
if (isFledgeSupported()) {
bidderRequests.forEach((req) => {
req.fledgeEnabled = config.runWithBidder(req.bidderCode, () => config.getConfig('fledgeEnabled'))
})
}
next(bidderRequests);
}
getHook('makeBidRequests').after(markForFledge);

export function setImpExtAe(imp, bidRequest, context) {
if (!context.bidderRequest.fledgeEnabled) {
delete imp.ext?.ae;
}
}
registerOrtbProcessor({type: IMP, name: 'impExtAe', fn: setImpExtAe});

// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up
// fledge response processing in two steps: first aggregate all the auction configs by their imp...

export function parseExtPrebidFledge(response, ortbResponse, context) {
(ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []).forEach((cfg) => {
const impCtx = context.impContext[cfg.impid];
if (!impCtx?.imp?.ext?.ae) {
logWarn('Received fledge auction configuration for an impression that was not in the request or did not ask for it', cfg, impCtx?.imp);
} else {
impCtx.fledgeConfigs = impCtx.fledgeConfigs || [];
impCtx.fledgeConfigs.push(cfg);
}
})
}
registerOrtbProcessor({type: RESPONSE, name: 'extPrebidFledge', fn: parseExtPrebidFledge, dialects: [PBS]});

// ...then, make them available in the adapter's response. This is the client side version, for which the
// interpretResponse api is {fledgeAuctionConfigs: [{bidId, config}]}

export function setResponseFledgeConfigs(response, ortbResponse, context) {
const configs = Object.values(context.impContext)
.flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({bidId: impCtx.bidRequest.bidId, config: cfg.config})));
if (configs.length > 0) {
response.fledgeAuctionConfigs = configs;
}
}
registerOrtbProcessor({type: RESPONSE, name: 'fledgeAuctionConfigs', priority: -1, fn: setResponseFledgeConfigs, dialects: [PBS]})
12 changes: 5 additions & 7 deletions modules/openxOrtbBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ const converter = ortbConverter({
if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') {
imp.video.placement = imp.video.placement || 4;
}
if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) {
// TODO: we may want to standardize this and move fledge logic to ortbConverter
delete imp.ext.ae;
}
mergeDeep(imp, {
tagid: bidRequest.params.unit,
ext: {
Expand Down Expand Up @@ -106,10 +102,12 @@ const converter = ortbConverter({
let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs');
if (fledgeAuctionConfigs) {
fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => {
return Object.assign({
return {
bidId,
auctionSignals: {}
}, cfg);
config: Object.assign({
auctionSignals: {},
}, cfg)
}
});
return {
bids: response.bids,
Expand Down
12 changes: 9 additions & 3 deletions modules/prebidServerBidAdapter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import CONSTANTS from '../../src/constants.json';
import adapterManager from '../../src/adapterManager.js';
import {config} from '../../src/config.js';
import {isValid} from '../../src/adapters/bidderFactory.js';
import {addComponentAuction, isValid} from '../../src/adapters/bidderFactory.js';
import * as events from '../../src/events.js';
import {includes} from '../../src/polyfill.js';
import {S2S_VENDORS} from './config.js';
Expand Down Expand Up @@ -496,6 +496,9 @@ export function PrebidServer() {
addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.INVALID);
}
}
},
onFledge: ({adUnitCode, config}) => {
addComponentAuction(adUnitCode, config);
}
})
}
Expand All @@ -521,7 +524,7 @@ export function PrebidServer() {
* @param onError {function(String, {})} invoked on HTTP failure - with status message and XHR error
* @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse
*/
export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}) {
export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid, onFledge}) {
let { gdprConsent } = getConsentData(bidRequests);
const adUnits = deepClone(s2sBidRequest.ad_units);

Expand All @@ -545,8 +548,11 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques
let result;
try {
result = JSON.parse(response);
const bids = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request).bids);
const {bids, fledgeAuctionConfigs} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request));
bids.forEach(onBid);
if (fledgeAuctionConfigs) {
fledgeAuctionConfigs.forEach(onFledge);
}
} catch (error) {
logError(error);
}
Expand Down
14 changes: 12 additions & 2 deletions modules/prebidServerBidAdapter/ortbConverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ const PBS_CONVERTER = ortbConverter({
serverSideStats(orig, response, ortbResponse, context) {
// override to process each request
context.actualBidderRequests.forEach(req => orig(response, ortbResponse, {...context, bidderRequest: req, bidRequests: req.bids}));
},
fledgeAuctionConfigs(orig, response, ortbResponse, context) {
const configs = Object.values(context.impContext)
.flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({adUnitCode: impCtx.adUnit.code, config: cfg.config})));
if (configs.length > 0) {
response.fledgeAuctionConfigs = configs;
}
}
}
},
Expand Down Expand Up @@ -254,11 +261,14 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste
...adUnit,
adUnitCode: adUnit.code,
...getDefinedParams(actualBidRequests.values().next().value || {}, ['userId', 'userIdAsEids', 'schain']),
pbsData: {impId, actualBidRequests, adUnit}
pbsData: {impId, actualBidRequests, adUnit},
});
});

const proxyBidderRequest = Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k)))
const proxyBidderRequest = {
...Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))),
fledgeEnabled: bidderRequests.some(req => req.fledgeEnabled)
}

return PBS_CONVERTER.toORTB({
bidderRequest: proxyBidderRequest,
Expand Down
8 changes: 5 additions & 3 deletions modules/rtbhouseBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,12 @@ export const spec = {

if (fledgeAuctionConfigs) {
fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => {
return Object.assign({
return {
bidId,
auctionSignals: {}
}, cfg);
config: Object.assign({
auctionSignals: {}
}, cfg)
}
});
logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs });
return {
Expand Down
7 changes: 0 additions & 7 deletions src/adapterManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,13 +338,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a
}
});

bidRequests.forEach(bidRequest => {
config.runWithBidder(bidRequest.bidderCode, () => {
const fledgeEnabledFromConfig = config.getConfig('fledgeEnabled');
bidRequest['fledgeEnabled'] = navigator.runAdAuction && fledgeEnabledFromConfig
});
});

return bidRequests;
}, 'makeBidRequests');

Expand Down
43 changes: 28 additions & 15 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import Adapter from '../adapter.js';
import adapterManager from '../adapterManager.js';
import { config } from '../config.js';
import { createBid } from '../bidfactory.js';
import { userSync } from '../userSync.js';
import { nativeBidIsValid } from '../native.js';
import { isValidVideoBid } from '../video.js';
import {config} from '../config.js';
import {createBid} from '../bidfactory.js';
import {userSync} from '../userSync.js';
import {nativeBidIsValid} from '../native.js';
import {isValidVideoBid} from '../video.js';
import CONSTANTS from '../constants.json';
import * as events from '../events.js';
import {includes} from '../polyfill.js';
import { ajax } from '../ajax.js';
import { logWarn, logInfo, logError, parseQueryStringParameters, delayExecution, parseSizesInput, flatten, uniques, timestamp, deepAccess, isArray, isPlainObject } from '../utils.js';
import { ADPOD } from '../mediaTypes.js';
import { getHook, hook } from '../hook.js';
import { getCoreStorageManager } from '../storageManager.js';
import {ajax} from '../ajax.js';
import {
deepAccess,
delayExecution,
flatten,
isArray,
isPlainObject,
logError,
logWarn,
parseQueryStringParameters,
parseSizesInput,
timestamp,
uniques
} from '../utils.js';
import {ADPOD} from '../mediaTypes.js';
import {getHook, hook} from '../hook.js';
import {getCoreStorageManager} from '../storageManager.js';
import {auctionManager} from '../auctionManager.js';
import { bidderSettings } from '../bidderSettings.js';
import {bidderSettings} from '../bidderSettings.js';
import {useMetrics} from '../utils/perfMetrics.js';

export const storage = getCoreStorageManager('bidderFactory');
Expand Down Expand Up @@ -247,7 +259,9 @@ export function newBidder(spec) {
fledgeAuctionConfigs.forEach((fledgeAuctionConfig) => {
const bidRequest = bidRequestMap[fledgeAuctionConfig.bidId];
if (bidRequest) {
addComponentAuction(bidRequest, fledgeAuctionConfig);
addComponentAuction(bidRequest.adUnitCode, fledgeAuctionConfig.config);
} else {
logWarn('Received fledge auction configuration for an unknown bidId', fledgeAuctionConfig);
}
});
},
Expand Down Expand Up @@ -467,9 +481,8 @@ export const registerSyncInner = hook('async', function(spec, responses, gdprCon
}
}, 'registerSyncs')

export const addComponentAuction = hook('sync', (_bidRequest, fledgeAuctionConfig) => {
logInfo(`bidderFactory.addComponentAuction`, fledgeAuctionConfig);
}, 'addComponentAuction')
export const addComponentAuction = hook('sync', (adUnitCode, fledgeAuctionConfig) => {
}, 'addComponentAuction');

export function preloadBidderMappingFile(fn, adUnits) {
if (FEATURES.VIDEO) {
Expand Down
Loading

0 comments on commit 490b832

Please sign in to comment.