Skip to content

Commit

Permalink
RTB House Bid Adapter: Process FLEDGE request/response (prebid#9215)
Browse files Browse the repository at this point in the history
* 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 <leandro.otani@rtbhouse.com>
Co-authored-by: rtbh-lotani <83652735+rtbh-lotani@users.noreply.github.com>
Co-authored-by: Tomasz Swirski <tomasz.swirski@rtbhouse.com>
  • Loading branch information
4 people authored Nov 21, 2022
1 parent f5fdcf0 commit d6418a0
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 29 deletions.
119 changes: 90 additions & 29 deletions modules/rtbhouseBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {deepAccess, isArray, logError} from '../src/utils.js';
import {deepAccess, mergeDeep, isArray, logError, logInfo} from '../src/utils.js';
import { getOrigin } from '../libraries/getOrigin/index.js';
import {BANNER, NATIVE} from '../src/mediaTypes.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {includes} from '../src/polyfill.js';
import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
import { config } from '../src/config.js';

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

const request = {
id: validBidRequests[0].auctionId,
imp: validBidRequests.map(slot => mapImpression(slot)),
imp: validBidRequests.map(slot => mapImpression(slot, bidderRequest)),
site: mapSite(validBidRequests, bidderRequest),
cur: DEFAULT_CURRENCY_ARR,
test: validBidRequests[0].params.test || 0,
source: mapSource(validBidRequests[0]),
};

if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) {
const consentStr = (bidderRequest.gdprConsent.consentString)
? bidderRequest.gdprConsent.consentString.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : '';
Expand All @@ -81,55 +84,105 @@ export const spec = {
}
}

const ortb2Params = bidderRequest && bidderRequest.ortb2;
if (ortb2Params?.user) {
request.user = {
...request.user,
...(ortb2Params.user.data && {
data: { ...request.user?.data, ...ortb2Params.user.data },
}),
...(ortb2Params.user.ext && {
ext: { ...request.user?.ext, ...ortb2Params.user.ext },
}),
};
const ortb2Params = bidderRequest?.ortb2 || {};
if (ortb2Params.site) {
mergeDeep(request, { site: ortb2Params.site });
}
if (ortb2Params.user) {
mergeDeep(request, { user: ortb2Params.user });
}
if (ortb2Params.device) {
mergeDeep(request, { device: ortb2Params.device });
}
if (ortb2Params?.site) {
request.site = {
...request.site,
...(ortb2Params.site.content && {
content: { ...request.site?.content, ...ortb2Params.site.content },
}),
...(ortb2Params.site.ext && {
ext: { ...request.site?.ext, ...ortb2Params.site.ext },
}),
};

let computedEndpointUrl = ENDPOINT_URL;

const fledgeConfig = config.getConfig('fledgeConfig');
if (bidderRequest.fledgeEnabled && fledgeConfig) {
mergeDeep(request, { ext: { fledge_config: fledgeConfig } });
computedEndpointUrl = FLEDGE_ENDPOINT_URL;
}

return {
method: 'POST',
url: 'https://' + validBidRequests[0].params.region + '.' + ENDPOINT_URL,
url: 'https://' + validBidRequests[0].params.region + '.' + computedEndpointUrl,
data: JSON.stringify(request)
};
},
interpretResponse: function (serverResponse, originalRequest) {
interpretOrtbResponse: function (serverResponse, originalRequest) {
const responseBody = serverResponse.body;
if (!isArray(responseBody)) {
return [];
}

const bids = [];
responseBody.forEach(serverBid => {
if (serverBid.price === 0) {
if (!serverBid.price) { // price may exist and is === 0 or there's no price prop at all (fledge req case)
return;
}

let interpretedBid;

// try...catch would be risky cause JSON.parse throws SyntaxError
if (serverBid.adm.indexOf('{') === 0) {
bids.push(interpretNativeBid(serverBid));
interpretedBid = interpretNativeBid(serverBid);
} else {
bids.push(interpretBannerBid(serverBid));
interpretedBid = interpretBannerBid(serverBid);
}
if (serverBid.ext) interpretedBid.ext = serverBid.ext;

bids.push(interpretedBid);
});
return bids;
},
interpretResponse: function (serverResponse, originalRequest) {
let bids;

const responseBody = serverResponse.body;
let fledgeAuctionConfigs = null;

if (responseBody.bidid && isArray(responseBody?.ext?.igbid)) {
// we have fledge response
// mimic the original response ([{},...])
bids = this.interpretOrtbResponse({ body: responseBody.seatbid[0]?.bid }, originalRequest);

const seller = responseBody.ext.seller;
const decisionLogicUrl = responseBody.ext.decisionLogicUrl;
const sellerTimeout = 'sellerTimeout' in responseBody.ext ? { sellerTimeout: responseBody.ext.sellerTimeout } : {};
responseBody.ext.igbid.forEach((igbid) => {
const perBuyerSignals = {};
igbid.igbuyer.forEach(buyerItem => {
perBuyerSignals[buyerItem.igdomain] = buyerItem.buyersignal
});
fledgeAuctionConfigs = fledgeAuctionConfigs || {};
fledgeAuctionConfigs[igbid.impid] = mergeDeep(
{
seller,
decisionLogicUrl,
interestGroupBuyers: Object.keys(perBuyerSignals),
perBuyerSignals,
},
sellerTimeout
);
});
} else {
bids = this.interpretOrtbResponse(serverResponse, originalRequest);
}

if (fledgeAuctionConfigs) {
fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => {
return Object.assign({
bidId,
auctionSignals: {}
}, cfg);
});
logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs });
return {
bids,
fledgeAuctionConfigs,
}
}
return bids;
}
};
registerBidder(spec);
Expand All @@ -154,7 +207,7 @@ function applyFloor(slot) {
* @param {object} slot Ad Unit Params by Prebid
* @returns {object} Imp by OpenRTB 2.5 §3.2.4
*/
function mapImpression(slot) {
function mapImpression(slot, bidderRequest) {
const imp = {
id: slot.bidId,
banner: mapBanner(slot),
Expand All @@ -167,6 +220,14 @@ function mapImpression(slot) {
imp.bidfloor = bidfloor;
}

if (bidderRequest.fledgeEnabled) {
imp.ext = imp.ext || {};
imp.ext.ae = slot?.ortb2Imp?.ext?.ae
} else {
if (imp.ext?.ae) {
delete imp.ext.ae;
}
}
return imp;
}

Expand Down
79 changes: 79 additions & 0 deletions test/spec/modules/rtbhouseBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect } from 'chai';
import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter.js';
import { newBidder } from 'src/adapters/bidderFactory.js';
import { config } from 'src/config.js';

describe('RTBHouseAdapter', () => {
const adapter = newBidder(spec);
Expand Down Expand Up @@ -97,6 +98,10 @@ describe('RTBHouseAdapter', () => {
];
});

afterEach(function () {
config.resetConfig();
});

it('should build test param into the request', () => {
let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data;
expect(JSON.parse(builtTestRequest).test).to.equal(1);
Expand Down Expand Up @@ -263,6 +268,45 @@ describe('RTBHouseAdapter', () => {
expect(data.source).to.not.have.property('ext');
});

context('FLEDGE', function() {
afterEach(function () {
config.resetConfig();
});

it('sends bid request to FLEDGE ENDPOINT via POST', function () {
let bidRequest = Object.assign([], bidRequests);
delete bidRequest[0].params.test;
config.setConfig({ fledgeConfig: true });
const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true });
expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebidfledge/bids');
expect(request.method).to.equal('POST');
});

it('when FLEDGE is disabled, should not send imp.ext.ae', function () {
let bidRequest = Object.assign([], bidRequests);
delete bidRequest[0].params.test;
bidRequest[0].ortb2Imp = {
ext: { ae: 2 }
};
const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: false });
let data = JSON.parse(request.data);
if (data.imp[0].ext) {
expect(data.imp[0].ext).to.not.have.property('ae');
}
});

it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () {
let bidRequest = Object.assign([], bidRequests);
delete bidRequest[0].params.test;
bidRequest[0].ortb2Imp = {
ext: { ae: 2 }
};
const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true });
let data = JSON.parse(request.data);
expect(data.imp[0].ext.ae).to.equal(2);
});
});

describe('native imp', () => {
function basicRequest(extension) {
return Object.assign({
Expand Down Expand Up @@ -460,6 +504,29 @@ describe('RTBHouseAdapter', () => {
'h': 250
}];

let fledgeResponse = {
'id': 'bid-identifier',
'ext': {
'igbid': [{
'impid': 'test-bid-id',
'igbuyer': [{
'igdomain': 'https://buyer-domain.com',
'buyersignal': {}
}]
}],
'sellerTimeout': 500,
'seller': 'https://seller-domain.com',
'decisionLogicUrl': 'https://seller-domain.com/decision-logic.js'
},
'bidid': 'bid-identifier',
'seatbid': [{
'bid': [{
'id': 'bid-response-id',
'impid': 'test-bid-id'
}]
}]
};

it('should get correct bid response', function () {
let expectedResponse = [
{
Expand Down Expand Up @@ -488,6 +555,18 @@ describe('RTBHouseAdapter', () => {
expect(result.length).to.equal(0);
});

context('when the response contains FLEDGE interest groups config', function () {
let bidderRequest;
let response = spec.interpretResponse({body: fledgeResponse}, {bidderRequest});

it('should return FLEDGE auction_configs alongside bids', function () {
expect(response).to.have.property('bids');
expect(response).to.have.property('fledgeAuctionConfigs');
expect(response.fledgeAuctionConfigs.length).to.equal(1);
expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id');
});
});

describe('native', () => {
const adm = {
native: {
Expand Down

0 comments on commit d6418a0

Please sign in to comment.