Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IX Bid Adapter : fix missing IX First Party Data #9920

Merged
merged 5 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 65 additions & 47 deletions modules/ixBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,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 @@ -649,15 +650,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 @@ -856,46 +855,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 @@ -977,6 +936,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
67 changes: 66 additions & 1 deletion 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