Skip to content

Commit

Permalink
feat: multiformat support in single request implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Corbo committed Jun 14, 2023
1 parent 108a1dd commit ae40a93
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 52 deletions.
121 changes: 71 additions & 50 deletions modules/ixBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export const FEATURE_TOGGLES = {
let siteID = 0;
let gdprConsent = '';
let usPrivacy = '';
let defaultVideoPlacement = false;

// Possible values for bidResponse.seatBid[].bid[].mtype which indicates the type of the creative markup so that it can properly be associated with the right sub-object of the BidRequest.Imp.
const MEDIA_TYPES = {
Expand Down Expand Up @@ -227,7 +228,8 @@ export function bidToVideoImp(bid) {
if (deepAccess(videoParamRef, 'playerConfig.floatOnScroll')) {
imp.video.placement = 5;
} else {
imp.video.placement = 4;
imp.video.placement = 3;
defaultVideoPlacement = true;
}
} else {
logWarn(`IX Bid Adapter: Video context '${context}' is not supported`);
Expand Down Expand Up @@ -666,7 +668,8 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
r = applyRegulations(r, bidderRequest);

let payload = {};
createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload);
siteID = validBidRequests[0].params.siteId;
payload.s = siteID;

const transactionIds = Object.keys(impressions);
let isFpdAdded = false;
Expand All @@ -680,15 +683,13 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {

const fpd = deepAccess(bidderRequest, 'ortb2') || {};
const site = { ...(fpd.site || fpd.context) };

// update page URL with IX FPD KVs if they exist
site.page = getIxFirstPartyDataPageUrl(bidderRequest);

const user = { ...fpd.user };
if (!isEmpty(fpd) && !isFpdAdded) {
r = addFPD(bidderRequest, r, fpd, site, user);

const clonedRObject = deepClone(r);

clonedRObject.site = mergeDeep({}, clonedRObject.site, site);
clonedRObject.user = mergeDeep({}, clonedRObject.user, user);

r.site = mergeDeep({}, r.site, site);
r.user = mergeDeep({}, r.user, user);
isFpdAdded = true;
Expand Down Expand Up @@ -890,46 +891,6 @@ function applyRegulations(r, bidderRequest) {
return r
}

/**
* createPayload creates the payload to be sent with the request.
*
* @param {array} validBidRequests A list of valid bid request config objects.
* @param {object} bidderRequest An object containing other info like gdprConsent.
* @param {object} r Reuqest object.
* @param {string} baseUrl Base exchagne URL.
* @param {array} requests List of request obejcts.
* @param {object} payload Request payload object.
*/
function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload) {
// Use the siteId in the first bid request as the main siteId.
siteID = validBidRequests[0].params.siteId;
payload.s = siteID;

// Parse additional runtime configs.
const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix';
const otherIxConfig = config.getConfig(bidderCode);

if (otherIxConfig) {
// Append firstPartyData to r.site.page if firstPartyData exists.
if (typeof otherIxConfig.firstPartyData === 'object') {
const firstPartyData = otherIxConfig.firstPartyData;
let firstPartyString = '?';
for (const key in firstPartyData) {
if (firstPartyData.hasOwnProperty(key)) {
firstPartyString += `${encodeURIComponent(key)}=${encodeURIComponent(firstPartyData[key])}&`;
}
}
firstPartyString = firstPartyString.slice(0, -1);

if ('page' in r.site) {
r.site.page += firstPartyString;
} else {
r.site.page = firstPartyString;
}
}
}
}

/**
* addImpressions adds impressions to request object
*
Expand Down Expand Up @@ -1067,6 +1028,65 @@ function addImpressions(impressions, transactionIds, r, adUnitIndex) {
return r;
}

/**
This function retrieves the page URL and appends first party data query parameters
to it without adding duplicate query parameters. Returns original referer URL if no IX FPD exists.
@param {Object} bidderRequest - The bidder request object containing information about the bid and the page.
@returns {string} - The modified page URL with first party data query parameters appended.
*/
function getIxFirstPartyDataPageUrl (bidderRequest) {
// Parse additional runtime configs.
const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix';
const otherIxConfig = config.getConfig(bidderCode);

let pageUrl = '';
if (deepAccess(bidderRequest, 'ortb2.site.page')) {
pageUrl = bidderRequest.ortb2.site.page;
} else {
pageUrl = deepAccess(bidderRequest, 'refererInfo.page');
}

if (otherIxConfig) {
// Append firstPartyData to r.site.page if firstPartyData exists.
if (typeof otherIxConfig.firstPartyData === 'object') {
const firstPartyData = otherIxConfig.firstPartyData;
return appendIXQueryParams(bidderRequest, pageUrl, firstPartyData);
}
}

return pageUrl
}

/**
This function appends the provided query parameters to the given URL without adding duplicate query parameters.
@param {Object} bidderRequest - The bidder request object containing information about the bid and the page to be used as fallback in case url is not valid.
@param {string} url - The base URL to which query parameters will be appended.
@param {Object} params - An object containing key-value pairs of query parameters to append.
@returns {string} - The modified URL with the provided query parameters appended.
*/
function appendIXQueryParams(bidderRequest, url, params) {
let urlObj;
try {
urlObj = new URL(url);
} catch (error) {
logWarn(`IX Bid Adapter: Invalid URL set in ortb2.site.page: ${url}. Using referer URL instead.`);
urlObj = new URL(deepAccess(bidderRequest, 'refererInfo.page'));
}

const searchParams = new URLSearchParams(urlObj.search);

// Loop through the provided query parameters and append them
for (const [key, value] of Object.entries(params)) {
if (!searchParams.has(key)) {
searchParams.append(key, value);
}
}

// Construct the final URL with the updated query parameters
urlObj.search = searchParams.toString();
return urlObj.toString();
}

/**
* addFPD adds ortb2 first party data to request object.
*
Expand Down Expand Up @@ -1183,7 +1203,8 @@ function buildIXDiag(validBidRequests) {
ren: false,
version: '$prebid.version$',
userIds: _getUserIds(validBidRequests[0]),
url: window.location.href.split('?')[0]
url: window.location.href.split('?')[0],
vpd: defaultVideoPlacement
};

// create ad unit map and collect the required diag properties
Expand Down Expand Up @@ -1945,7 +1966,7 @@ export function removeSiteIDs(r) {
r.imp.forEach((imp, index) => {
const impExt = imp.ext;
if (impExt == undefined) {
return;
return r;
}
if (getFormatCount(imp) < 2) {
return;
Expand Down
70 changes: 68 additions & 2 deletions test/spec/modules/ixBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,11 @@ describe('IndexexchangeAdapter', function () {
refererInfo: {
page: 'https://www.prebid.org',
canonicalUrl: 'https://www.prebid.org/the/link/to/the/page'
},
ortb2: {
site: {
page: 'https://www.prebid.org'
}
}
};

Expand Down Expand Up @@ -2135,7 +2140,7 @@ describe('IndexexchangeAdapter', function () {

const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0];
const pageUrl = extractPayload(requestWithFirstPartyData).site.page;
const expectedPageUrl = DEFAULT_OPTION.refererInfo.page + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd';
const expectedPageUrl = DEFAULT_OPTION.ortb2.site.page + '/?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd';
expect(pageUrl).to.equal(expectedPageUrl);
});

Expand Down Expand Up @@ -2233,6 +2238,66 @@ describe('IndexexchangeAdapter', function () {
});
});

describe('getIxFirstPartyDataPageUrl', () => {
beforeEach(() => {
config.setConfig({ ix: { firstPartyData: { key1: 'value1', key2: 'value2' } } });
});

afterEach(() => {
config.resetConfig();
});

it('should return the modified URL with first party data query parameters appended', () => {
const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0];
const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page;
expect(pageUrl).to.equal('https://www.prebid.org/?key1=value1&key2=value2');
});

it('should return the modified URL with first party data query parameters appended but not duplicated', () => {
const bidderRequest = deepClone(DEFAULT_OPTION);
bidderRequest.ortb2.site.page = 'https://www.prebid.org/?key1=value1'
const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0];
const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page;
expect(pageUrl).to.equal('https://www.prebid.org/?key1=value1&key2=value2');
});

it('should not overwrite existing query parameters with first party data', () => {
config.setConfig({ ix: { firstPartyData: { key1: 'value1', key2: 'value2' } } });
const bidderRequest = deepClone(DEFAULT_OPTION);
bidderRequest.ortb2.site.page = 'https://www.prebid.org/?key1=existingValue1'
const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, bidderRequest)[0];
const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page;
expect(pageUrl).to.equal('https://www.prebid.org/?key1=existingValue1&key2=value2');
});

it('should return the original URL if no first party data is available', () => {
config.setConfig({ ix: {} });
const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0];
const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page;
expect(pageUrl).to.equal('https://www.prebid.org');
});

it('should return the original URL referer page url if ortb2 does not exist', () => {
config.setConfig({ ix: {} });
const bidderRequest = deepClone(DEFAULT_OPTION);
delete bidderRequest.ortb2;
bidderRequest.refererInfo.page = 'https://example.com';
const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, bidderRequest)[0];
const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page;
expect(pageUrl).to.equal('https://example.com');
});

it('should use referer URL if the provided ortb2.site.page URL is not valid', () => {
config.setConfig({ ix: { firstPartyData: { key1: 'value1', key2: 'value2' } } });
const bidderRequest = deepClone(DEFAULT_OPTION);
bidderRequest.ortb2.site.page = 'www.invalid-url*&?.com';
bidderRequest.refererInfo.page = 'https://www.prebid.org';
const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, bidderRequest)[0];
const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page;
expect(pageUrl).to.equal('https://www.prebid.org/?key1=value1&key2=value2');
});
});

describe('request should contain both banner and video requests', function () {
const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]], {});
it('should have banner request', () => {
Expand Down Expand Up @@ -2561,7 +2626,8 @@ describe('IndexexchangeAdapter', function () {
const impression = extractPayload(request).imp[0];

expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId);
expect(impression.video.placement).to.equal(4);
expect(impression.video.placement).to.equal(3);
expect(extractPayload(request).ext.ixdiag.vpd).to.equal(true);
});

it('should handle unexpected context', function () {
Expand Down

0 comments on commit ae40a93

Please sign in to comment.