Skip to content

Commit d6418a0

Browse files
piotrj-rtbhrtbh-lotaniTomasz Swirski
authored
RTB House Bid Adapter: Process FLEDGE request/response (prebid#9215)
* RTBHouse Bid Adapter: add global vendor list id * structured user agent - browsers.brands * fix lint errors * Added sda into rtbhouse adapter * spreading ortb2: user & site props * examples reverted * init version * using mergedeep * removed wrong imp array augm.; slot imp augm. with addtl check * [SUA] merging ortb2.device into request * fledge auctionConfig adapted to our bid response structure * new bidder response structure for fledge * make sure bidderRequest has proper flag turned on * fledge endpoint hardcoded; code cleanups * remove obsolete function * obsolete function removed * [RTB House] Process FLEDGE request/response (#4) * [SDA & SUA] refactor using mergedeep * [FLEDGE] fledge auctionConfig adapted to our bid response structure * [FLEDGE] new bidder response structure for fledge * [FLEDGE] make sure bidderRequest has proper flag turned on * [FLEDGE] fledge endpoint hardcoded; code cleanups * [FLEDGE] remove obsolete functions * fixed lint errors * fledge test suites; adapter: delete imp.ext.ae when no fledge (#5) Co-authored-by: Leandro Otani <[email protected]> Co-authored-by: rtbh-lotani <[email protected]> Co-authored-by: Tomasz Swirski <[email protected]>
1 parent f5fdcf0 commit d6418a0

File tree

2 files changed

+169
-29
lines changed

2 files changed

+169
-29
lines changed

modules/rtbhouseBidAdapter.js

+90-29
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import {deepAccess, isArray, logError} from '../src/utils.js';
1+
import {deepAccess, mergeDeep, isArray, logError, logInfo} from '../src/utils.js';
22
import { getOrigin } from '../libraries/getOrigin/index.js';
33
import {BANNER, NATIVE} from '../src/mediaTypes.js';
44
import {registerBidder} from '../src/adapters/bidderFactory.js';
55
import {includes} from '../src/polyfill.js';
66
import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
7+
import { config } from '../src/config.js';
78

89
const BIDDER_CODE = 'rtbhouse';
910
const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia'];
1011
const ENDPOINT_URL = 'creativecdn.com/bidder/prebid/bids';
12+
const FLEDGE_ENDPOINT_URL = 'creativecdn.com/bidder/prebidfledge/bids';
1113
const DEFAULT_CURRENCY_ARR = ['USD']; // NOTE - USD is the only supported currency right now; Hardcoded for bids
1214
const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE];
1315
const TTL = 55;
@@ -50,12 +52,13 @@ export const spec = {
5052

5153
const request = {
5254
id: validBidRequests[0].auctionId,
53-
imp: validBidRequests.map(slot => mapImpression(slot)),
55+
imp: validBidRequests.map(slot => mapImpression(slot, bidderRequest)),
5456
site: mapSite(validBidRequests, bidderRequest),
5557
cur: DEFAULT_CURRENCY_ARR,
5658
test: validBidRequests[0].params.test || 0,
5759
source: mapSource(validBidRequests[0]),
5860
};
61+
5962
if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) {
6063
const consentStr = (bidderRequest.gdprConsent.consentString)
6164
? bidderRequest.gdprConsent.consentString.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : '';
@@ -81,55 +84,105 @@ export const spec = {
8184
}
8285
}
8386

84-
const ortb2Params = bidderRequest && bidderRequest.ortb2;
85-
if (ortb2Params?.user) {
86-
request.user = {
87-
...request.user,
88-
...(ortb2Params.user.data && {
89-
data: { ...request.user?.data, ...ortb2Params.user.data },
90-
}),
91-
...(ortb2Params.user.ext && {
92-
ext: { ...request.user?.ext, ...ortb2Params.user.ext },
93-
}),
94-
};
87+
const ortb2Params = bidderRequest?.ortb2 || {};
88+
if (ortb2Params.site) {
89+
mergeDeep(request, { site: ortb2Params.site });
90+
}
91+
if (ortb2Params.user) {
92+
mergeDeep(request, { user: ortb2Params.user });
93+
}
94+
if (ortb2Params.device) {
95+
mergeDeep(request, { device: ortb2Params.device });
9596
}
96-
if (ortb2Params?.site) {
97-
request.site = {
98-
...request.site,
99-
...(ortb2Params.site.content && {
100-
content: { ...request.site?.content, ...ortb2Params.site.content },
101-
}),
102-
...(ortb2Params.site.ext && {
103-
ext: { ...request.site?.ext, ...ortb2Params.site.ext },
104-
}),
105-
};
97+
98+
let computedEndpointUrl = ENDPOINT_URL;
99+
100+
const fledgeConfig = config.getConfig('fledgeConfig');
101+
if (bidderRequest.fledgeEnabled && fledgeConfig) {
102+
mergeDeep(request, { ext: { fledge_config: fledgeConfig } });
103+
computedEndpointUrl = FLEDGE_ENDPOINT_URL;
106104
}
107105

108106
return {
109107
method: 'POST',
110-
url: 'https://' + validBidRequests[0].params.region + '.' + ENDPOINT_URL,
108+
url: 'https://' + validBidRequests[0].params.region + '.' + computedEndpointUrl,
111109
data: JSON.stringify(request)
112110
};
113111
},
114-
interpretResponse: function (serverResponse, originalRequest) {
112+
interpretOrtbResponse: function (serverResponse, originalRequest) {
115113
const responseBody = serverResponse.body;
116114
if (!isArray(responseBody)) {
117115
return [];
118116
}
119117

120118
const bids = [];
121119
responseBody.forEach(serverBid => {
122-
if (serverBid.price === 0) {
120+
if (!serverBid.price) { // price may exist and is === 0 or there's no price prop at all (fledge req case)
123121
return;
124122
}
123+
124+
let interpretedBid;
125+
125126
// try...catch would be risky cause JSON.parse throws SyntaxError
126127
if (serverBid.adm.indexOf('{') === 0) {
127-
bids.push(interpretNativeBid(serverBid));
128+
interpretedBid = interpretNativeBid(serverBid);
128129
} else {
129-
bids.push(interpretBannerBid(serverBid));
130+
interpretedBid = interpretBannerBid(serverBid);
130131
}
132+
if (serverBid.ext) interpretedBid.ext = serverBid.ext;
133+
134+
bids.push(interpretedBid);
131135
});
132136
return bids;
137+
},
138+
interpretResponse: function (serverResponse, originalRequest) {
139+
let bids;
140+
141+
const responseBody = serverResponse.body;
142+
let fledgeAuctionConfigs = null;
143+
144+
if (responseBody.bidid && isArray(responseBody?.ext?.igbid)) {
145+
// we have fledge response
146+
// mimic the original response ([{},...])
147+
bids = this.interpretOrtbResponse({ body: responseBody.seatbid[0]?.bid }, originalRequest);
148+
149+
const seller = responseBody.ext.seller;
150+
const decisionLogicUrl = responseBody.ext.decisionLogicUrl;
151+
const sellerTimeout = 'sellerTimeout' in responseBody.ext ? { sellerTimeout: responseBody.ext.sellerTimeout } : {};
152+
responseBody.ext.igbid.forEach((igbid) => {
153+
const perBuyerSignals = {};
154+
igbid.igbuyer.forEach(buyerItem => {
155+
perBuyerSignals[buyerItem.igdomain] = buyerItem.buyersignal
156+
});
157+
fledgeAuctionConfigs = fledgeAuctionConfigs || {};
158+
fledgeAuctionConfigs[igbid.impid] = mergeDeep(
159+
{
160+
seller,
161+
decisionLogicUrl,
162+
interestGroupBuyers: Object.keys(perBuyerSignals),
163+
perBuyerSignals,
164+
},
165+
sellerTimeout
166+
);
167+
});
168+
} else {
169+
bids = this.interpretOrtbResponse(serverResponse, originalRequest);
170+
}
171+
172+
if (fledgeAuctionConfigs) {
173+
fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => {
174+
return Object.assign({
175+
bidId,
176+
auctionSignals: {}
177+
}, cfg);
178+
});
179+
logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs });
180+
return {
181+
bids,
182+
fledgeAuctionConfigs,
183+
}
184+
}
185+
return bids;
133186
}
134187
};
135188
registerBidder(spec);
@@ -154,7 +207,7 @@ function applyFloor(slot) {
154207
* @param {object} slot Ad Unit Params by Prebid
155208
* @returns {object} Imp by OpenRTB 2.5 §3.2.4
156209
*/
157-
function mapImpression(slot) {
210+
function mapImpression(slot, bidderRequest) {
158211
const imp = {
159212
id: slot.bidId,
160213
banner: mapBanner(slot),
@@ -167,6 +220,14 @@ function mapImpression(slot) {
167220
imp.bidfloor = bidfloor;
168221
}
169222

223+
if (bidderRequest.fledgeEnabled) {
224+
imp.ext = imp.ext || {};
225+
imp.ext.ae = slot?.ortb2Imp?.ext?.ae
226+
} else {
227+
if (imp.ext?.ae) {
228+
delete imp.ext.ae;
229+
}
230+
}
170231
return imp;
171232
}
172233

test/spec/modules/rtbhouseBidAdapter_spec.js

+79
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect } from 'chai';
22
import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter.js';
33
import { newBidder } from 'src/adapters/bidderFactory.js';
4+
import { config } from 'src/config.js';
45

56
describe('RTBHouseAdapter', () => {
67
const adapter = newBidder(spec);
@@ -97,6 +98,10 @@ describe('RTBHouseAdapter', () => {
9798
];
9899
});
99100

101+
afterEach(function () {
102+
config.resetConfig();
103+
});
104+
100105
it('should build test param into the request', () => {
101106
let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data;
102107
expect(JSON.parse(builtTestRequest).test).to.equal(1);
@@ -263,6 +268,45 @@ describe('RTBHouseAdapter', () => {
263268
expect(data.source).to.not.have.property('ext');
264269
});
265270

271+
context('FLEDGE', function() {
272+
afterEach(function () {
273+
config.resetConfig();
274+
});
275+
276+
it('sends bid request to FLEDGE ENDPOINT via POST', function () {
277+
let bidRequest = Object.assign([], bidRequests);
278+
delete bidRequest[0].params.test;
279+
config.setConfig({ fledgeConfig: true });
280+
const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true });
281+
expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebidfledge/bids');
282+
expect(request.method).to.equal('POST');
283+
});
284+
285+
it('when FLEDGE is disabled, should not send imp.ext.ae', function () {
286+
let bidRequest = Object.assign([], bidRequests);
287+
delete bidRequest[0].params.test;
288+
bidRequest[0].ortb2Imp = {
289+
ext: { ae: 2 }
290+
};
291+
const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: false });
292+
let data = JSON.parse(request.data);
293+
if (data.imp[0].ext) {
294+
expect(data.imp[0].ext).to.not.have.property('ae');
295+
}
296+
});
297+
298+
it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () {
299+
let bidRequest = Object.assign([], bidRequests);
300+
delete bidRequest[0].params.test;
301+
bidRequest[0].ortb2Imp = {
302+
ext: { ae: 2 }
303+
};
304+
const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true });
305+
let data = JSON.parse(request.data);
306+
expect(data.imp[0].ext.ae).to.equal(2);
307+
});
308+
});
309+
266310
describe('native imp', () => {
267311
function basicRequest(extension) {
268312
return Object.assign({
@@ -460,6 +504,29 @@ describe('RTBHouseAdapter', () => {
460504
'h': 250
461505
}];
462506

507+
let fledgeResponse = {
508+
'id': 'bid-identifier',
509+
'ext': {
510+
'igbid': [{
511+
'impid': 'test-bid-id',
512+
'igbuyer': [{
513+
'igdomain': 'https://buyer-domain.com',
514+
'buyersignal': {}
515+
}]
516+
}],
517+
'sellerTimeout': 500,
518+
'seller': 'https://seller-domain.com',
519+
'decisionLogicUrl': 'https://seller-domain.com/decision-logic.js'
520+
},
521+
'bidid': 'bid-identifier',
522+
'seatbid': [{
523+
'bid': [{
524+
'id': 'bid-response-id',
525+
'impid': 'test-bid-id'
526+
}]
527+
}]
528+
};
529+
463530
it('should get correct bid response', function () {
464531
let expectedResponse = [
465532
{
@@ -488,6 +555,18 @@ describe('RTBHouseAdapter', () => {
488555
expect(result.length).to.equal(0);
489556
});
490557

558+
context('when the response contains FLEDGE interest groups config', function () {
559+
let bidderRequest;
560+
let response = spec.interpretResponse({body: fledgeResponse}, {bidderRequest});
561+
562+
it('should return FLEDGE auction_configs alongside bids', function () {
563+
expect(response).to.have.property('bids');
564+
expect(response).to.have.property('fledgeAuctionConfigs');
565+
expect(response.fledgeAuctionConfigs.length).to.equal(1);
566+
expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id');
567+
});
568+
});
569+
491570
describe('native', () => {
492571
const adm = {
493572
native: {

0 commit comments

Comments
 (0)