Skip to content

Commit

Permalink
PAAPI: add top level auction example (prebid#11259)
Browse files Browse the repository at this point in the history
* bidderFactory fledge API: accept single fledge configs

* PAAPI: add top level auction example

* update example formatting

* provide bidfloor in top level auctionSignals

* update example decisionLogic to enforce auction floor, remove reporting

* PR feedback

* add reportResult
  • Loading branch information
dgirardi authored and DecayConstant committed Jul 18, 2024
1 parent c8724cd commit 2801922
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 9 deletions.
10 changes: 9 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,15 @@ function startLocalServer(options = {}) {
port: port,
host: INTEG_SERVER_HOST,
root: './',
livereload: options.livereload
livereload: options.livereload,
middleware: function () {
return [
function (req, res, next) {
res.setHeader('Ad-Auction-Allowed', 'True');
next();
}
];
}
});
}

Expand Down
57 changes: 57 additions & 0 deletions integrationExamples/gpt/top-level-paapi/decisionLogic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
function logPrefix(scope) {
return [
`%c PAAPI %c ${scope} %c`,
'color: green; background-color:yellow; border: 1px solid black',
'color: blue; border:1px solid black',
'',
];
}

function scoreAd(
adMetadata,
bid,
auctionConfig,
trustedScoringSignals,
browserSignals,
directFromSellerSignals
) {
console.group(...logPrefix('scoreAd'), 'Buyer:', browserSignals.interestGroupOwner);
console.log('Context:', JSON.stringify({
adMetadata,
bid,
auctionConfig: {
...auctionConfig,
componentAuctions: '[omitted]'
},
trustedScoringSignals,
browserSignals,
directFromSellerSignals
}, ' ', ' '));

const result = {
desirability: bid,
allowComponentAuction: true,
};
const {bidfloor, bidfloorcur} = auctionConfig.auctionSignals?.prebid || {};
if (bidfloor) {
if (browserSignals.bidCurrency !== '???' && browserSignals.bidCurrency !== bidfloorcur) {
console.log(`Floor currency (${bidfloorcur}) does not match bid currency (${browserSignals.bidCurrency}), and currency conversion is not yet implemented. Rejecting bid.`);
result.desirability = -1;
} else if (bid < bidfloor) {
console.log(`Bid (${bid}) lower than contextual winner/floor (${bidfloor}). Rejecting bid.`);
result.desirability = -1;
result.rejectReason = 'bid-below-auction-floor';
}
}
console.log('Result:', result);
console.groupEnd();
return result;
}

function reportResult(auctionConfig, browserSignals) {
console.group(...logPrefix('reportResult'));
console.log('Context', JSON.stringify({auctionConfig, browserSignals}, ' ', ' '));
console.groupEnd();
sendReportTo(`${auctionConfig.seller}/report/win?${Object.entries(browserSignals).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&')}`);
return {};
}
188 changes: 188 additions & 0 deletions integrationExamples/gpt/top-level-paapi/tl_paapi_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<html>
<head>
<script async src="../../../../build/dev/prebid.js"></script>
<script>
// intercept navigator.runAdAuction and print parameters to console
(() => {
var originalRunAdAuction = navigator.runAdAuction;
navigator.runAdAuction = function (...args) {
console.log('%c runAdAuction', 'background: cyan; border: 2px; border-radius: 3px', ...args);
return originalRunAdAuction.apply(navigator, args);
};
})();
</script>

<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>

<script>
var FAILSAFE_TIMEOUT = 3300;
var PREBID_TIMEOUT = 3000;
var adUnits = [{
code: 'div-1',
mediaTypes: {
banner: {
sizes: [[300, 250]],
}
},
ortb2Imp: {
ext: {
ae: 1
}
},
bids: [
{
bidder: 'openx',
params: {
unit: '538703464',
response_template_name: 'test_banner_ad',
test: true,
delDomain: 'sademo-d.openx.net'
}
},

],
}
]
;

var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];

var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function () {
googletag.pubads().disableInitialLoad();
});

pbjs.que.push(function () {
pbjs.setConfig({
debug: true,
paapi: {
enabled: true,
gpt: {
autoconfig: false
}
},
debugging: {
enabled: true,
intercept: [
{
when: {
bidder: 'openx',
},
then: {
cpm: 0.1
},
paapi() {
return [
{
'seller': 'https://privacysandbox.openx.net',
'decisionLogicURL': 'https://privacysandbox.openx.net/fledge/decision-logic-component.js',
'sellerSignals': {
'floor': 0.01,
'currency': 'USD',
'auctionTimestamp': new Date().getTime(),
'publisherId': '537143056',
'adUnitId': '538703464'
},
'interestGroupBuyers': [
'https://privacysandbox.openx.net'
],
'perBuyerSignals': {
'https://privacysandbox.openx.net': {
'bid': 1.5
}
},
'sellerCurrency': 'USD'
}
];
}
}
]
},
});

pbjs.addAdUnits(adUnits);
pbjs.requestBids({
bidsBackHandler: sendAdserverRequest,
timeout: PREBID_TIMEOUT
});
});

function raa() {
return Promise.all(
Object.entries(pbjs.getPAAPIConfig())
.map(([adUnitCode, auctionConfig]) => {
return navigator.runAdAuction({
seller: window.location.origin,
decisionLogicURL: new URL('decisionLogic.js', window.location).toString(),
resolveToConfig: false,
...auctionConfig
}).then(urn => {
if (urn) {
// if we have a paapi winner, replace the adunit div
// with an iframe that renders it
const iframe = document.createElement('iframe');
Object.assign(iframe, {
src: urn,
frameBorder: 0,
scrolling: 'no',
}, auctionConfig.requestedSize);
const div = document.getElementById(adUnitCode);
div.parentElement.insertBefore(iframe, div);
div.remove();
return true;
}
});
})
).then(won => won.every(el => el));
}

function sendAdserverRequest() {
if (pbjs.adserverRequestSent) return;
pbjs.adserverRequestSent = true;
raa().then((allPaapi) => {
if (!allPaapi) {
googletag.cmd.push(function () {
pbjs.que.push(function () {
pbjs.setTargetingForGPTAsync();
googletag.pubads().refresh();
});
});
}
});
}

setTimeout(function () {
sendAdserverRequest();
}, FAILSAFE_TIMEOUT);

googletag.cmd.push(function () {
googletag.defineSlot('/19968336/header-bid-tag-0', [[300, 250], [300, 600]], 'div-1').addService(googletag.pubads());

googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
</script>
</head>

<body>
<h2>Standalone PAAPI Prebid.js Example</h2>
<p>Start local server with:</p>
<code>gulp serve-fast --https</code>
<p>Chrome flags:</p>
<code>--enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true
--privacy-sandbox-enrollment-overrides=https://localhost:9999</code>
<p>Join interest group at <a href="https://privacysandbox.openx.net/fledge/advertiser">https://privacysandbox.openx.net/fledge/advertiser</a>
</p>
<h5>Div-1</h5>
<div id='div-1' style='min-width: 300px; min-height: 250px;'>
<script type='text/javascript'>
googletag.cmd.push(function () {
googletag.display('div-1');
});
</script>
</div>

</body>
</html>
2 changes: 1 addition & 1 deletion libraries/creative-renderer-native/renderer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 18 additions & 4 deletions modules/paapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import {config} from '../src/config.js';
import {getHook, module} from '../src/hook.js';
import {deepSetValue, logInfo, logWarn, mergeDeep} from '../src/utils.js';
import {deepSetValue, logInfo, logWarn, mergeDeep, parseSizesInput} from '../src/utils.js';
import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js';
import * as events from '../src/events.js';
import {EVENTS} from '../src/constants.js';
Expand Down Expand Up @@ -89,19 +89,33 @@ function getSlotSignals(bidsReceived = [], bidRequests = []) {
return cfg;
}

function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes}) {
function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adUnits}) {
const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || [])
const allReqs = bidderRequests?.flatMap(br => br.bids);
const paapiConfigs = {};
(adUnitCodes || []).forEach(au => {
paapiConfigs[au] = null;
!latestAuctionForAdUnit.hasOwnProperty(au) && (latestAuctionForAdUnit[au] = null);
})
});
Object.entries(pendingForAuction(auctionId) || {}).forEach(([adUnitCode, auctionConfigs]) => {
const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode;
const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit));
paapiConfigs[adUnitCode] = {
...slotSignals,
componentAuctions: auctionConfigs.map(cfg => mergeDeep({}, slotSignals, cfg))
};
// TODO: need to flesh out size treatment:
// - which size should the paapi auction pick? (this uses the first one defined)
// - should we signal it to SSPs, and how?
// - what should we do if adapters pick a different one?
// - what does size mean for video and native?
const size = parseSizesInput(adUnitsByCode[adUnitCode]?.mediaTypes?.banner?.sizes)?.[0]?.split('x');
if (size) {
paapiConfigs[adUnitCode].requestedSize = {
width: size[0],
height: size[1],
};
}
latestAuctionForAdUnit[adUnitCode] = auctionId;
});
configsForAuction(auctionId, paapiConfigs);
Expand Down Expand Up @@ -159,7 +173,7 @@ export function getPAAPIConfig({auctionId, adUnitCode} = {}, includeBlanks = fal
output[au] = null;
}
}
})
});
return output;
}

Expand Down
32 changes: 29 additions & 3 deletions test/spec/modules/paapi_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,30 @@ describe('paapi module', () => {
expect(getPAAPIConfig({auctionId})).to.eql({});
});

it('should use first size as requestedSize', () => {
addComponentAuctionHook(nextFnSpy, {
auctionId,
adUnitCode: 'au1',
}, fledgeAuctionConfig);
events.emit(EVENTS.AUCTION_END, {
auctionId,
adUnits: [
{
code: 'au1',
mediaTypes: {
banner: {
sizes: [[200, 100], [300, 200]]
}
}
}
]
});
expect(getPAAPIConfig({auctionId}).au1.requestedSize).to.eql({
width: '200',
height: '100'
})
})

it('should augment auctionSignals with FPD', () => {
addComponentAuctionHook(nextFnSpy, {
auctionId,
Expand Down Expand Up @@ -295,9 +319,11 @@ describe('paapi module', () => {
it('should populate bidfloor/bidfloorcur', () => {
addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig);
events.emit(EVENTS.AUCTION_END, payload);
const signals = getPAAPIConfig({auctionId}).au.componentAuctions[0].auctionSignals;
expect(signals.prebid?.bidfloor).to.eql(bidfloor);
expect(signals.prebid?.bidfloorcur).to.eql(bidfloorcur);
const cfg = getPAAPIConfig({auctionId}).au;
const signals = cfg.auctionSignals;
sinon.assert.match(cfg.componentAuctions[0].auctionSignals, signals || {});
expect(signals?.prebid?.bidfloor).to.eql(bidfloor);
expect(signals?.prebid?.bidfloorcur).to.eql(bidfloorcur);
});
});
});
Expand Down

0 comments on commit 2801922

Please sign in to comment.