Skip to content

Commit

Permalink
IX Bid Adapter: implement support for exchangeId and externalId [PB-2…
Browse files Browse the repository at this point in the history
…050] (#10704)
  • Loading branch information
oronno authored Nov 9, 2023
1 parent a3c64d4 commit b389581
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 24 deletions.
107 changes: 83 additions & 24 deletions modules/ixBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,10 @@ export function bidToVideoImp(bid) {

imp.video = videoParamRef ? deepClone(bid.params.video) : {};
// populate imp level transactionId
imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid');
let tid = deepAccess(bid, 'ortb2Imp.ext.tid');
if (tid) {
deepSetValue(imp, 'ext.tid', tid);
}

setDisplayManager(imp, bid);

Expand Down Expand Up @@ -328,7 +331,10 @@ export function bidToNativeImp(bid) {
};

// populate imp level transactionId
imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid');
let tid = deepAccess(bid, 'ortb2Imp.ext.tid');
if (tid) {
deepSetValue(imp, 'ext.tid', tid);
}

// AdUnit-Specific First Party Data
addAdUnitFPD(imp, bid)
Expand All @@ -348,26 +354,30 @@ function bidToImp(bid, mediaType) {

imp.id = bid.bidId;

imp.ext = {};
if (isExchangeIdConfigured() && deepAccess(bid, `params.externalId`)) {
deepSetValue(imp, 'ext.externalID', bid.params.externalId);
}
if (deepAccess(bid, `params.${mediaType}.siteId`) && !isNaN(Number(bid.params[mediaType].siteId))) {
switch (mediaType) {
case BANNER:
imp.ext.siteID = bid.params.banner.siteId.toString();
deepSetValue(imp, 'ext.siteID', bid.params.banner.siteId.toString());
break;
case VIDEO:
imp.ext.siteID = bid.params.video.siteId.toString();
deepSetValue(imp, 'ext.siteID', bid.params.video.siteId.toString());
break;
case NATIVE:
imp.ext.siteID = bid.params.native.siteId.toString();
deepSetValue(imp, 'ext.siteID', bid.params.native.siteId.toString());
break;
}
} else {
imp.ext.siteID = bid.params.siteId.toString();
if (bid.params.siteId) {
deepSetValue(imp, 'ext.siteID', bid.params.siteId.toString());
}
}

// populate imp level sid
if (bid.params.hasOwnProperty('id') && (typeof bid.params.id === 'string' || typeof bid.params.id === 'number')) {
imp.ext.sid = String(bid.params.id);
deepSetValue(imp, 'ext.sid', String(bid.params.id));
}

return imp;
Expand Down Expand Up @@ -413,12 +423,12 @@ function _applyFloor(bid, imp, mediaType) {
if (moduleFloor) {
imp.bidfloor = moduleFloor.floor;
imp.bidfloorcur = moduleFloor.currency;
imp.ext.fl = FLOOR_SOURCE.PBJS;
deepSetValue(imp, 'ext.fl', FLOOR_SOURCE.PBJS);
setFloor = true;
} else if (adapterFloor) {
imp.bidfloor = adapterFloor.floor;
imp.bidfloorcur = adapterFloor.currency;
imp.ext.fl = FLOOR_SOURCE.IX;
deepSetValue(imp, 'ext.fl', FLOOR_SOURCE.IX);
setFloor = true;
}

Expand Down Expand Up @@ -709,8 +719,10 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
r = applyRegulations(r, bidderRequest);

let payload = {};
siteID = validBidRequests[0].params.siteId;
payload.s = siteID;
if (validBidRequests[0].params.siteId) {
siteID = validBidRequests[0].params.siteId;
payload.s = siteID;
}

const impKeys = Object.keys(impressions);
let isFpdAdded = false;
Expand Down Expand Up @@ -746,9 +758,19 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
r = removeSiteIDs(r);

if (isLastAdUnit) {
let exchangeUrl = `${baseUrl}?`;

if (siteID !== 0) {
exchangeUrl += `s=${siteID}`;
}

if (isExchangeIdConfigured()) {
exchangeUrl += siteID !== 0 ? '&' : '';
exchangeUrl += `p=${config.getConfig('exchangeId')}`;
}
requests.push({
method: 'POST',
url: baseUrl + '?s=' + siteID,
url: exchangeUrl,
data: deepClone(r),
option: {
contentType: 'text/plain',
Expand Down Expand Up @@ -970,6 +992,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) {
for (const impId in bannerImpsKeyed) {
const bannerImps = bannerImpsKeyed[impId];
const { id, banner: { topframe } } = bannerImps[0];
let externalID = deepAccess(bannerImps[0], 'ext.externalID');
const _bannerImpression = {
id,
banner: {
Expand All @@ -979,29 +1002,39 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) {
};

for (let i = 0; i < _bannerImpression.banner.format.length; i++) {
// We add sid in imp.ext.sid therefore, remove from banner.format[].ext
if (_bannerImpression.banner.format[i].ext != null && _bannerImpression.banner.format[i].ext.sid != null) {
delete _bannerImpression.banner.format[i].ext.sid;
// We add sid and externalID in imp.ext therefore, remove from banner.format[].ext
if (_bannerImpression.banner.format[i].ext != null) {
if (_bannerImpression.banner.format[i].ext.sid != null) {
delete _bannerImpression.banner.format[i].ext.sid;
}
if (_bannerImpression.banner.format[i].ext.externalID != null) {
delete _bannerImpression.banner.format[i].ext.externalID;
}
}

// add floor per size
if ('bidfloor' in bannerImps[i]) {
_bannerImpression.banner.format[i].ext.bidfloor = bannerImps[i].bidfloor;
}

if (JSON.stringify(_bannerImpression.banner.format[i].ext) === '{}') {
delete _bannerImpression.banner.format[i].ext;
}
}

const position = impressions[impKeys[adUnitIndex]].pos;
if (isInteger(position)) {
_bannerImpression.banner.pos = position;
}

if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment) {
if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment || externalID) {
_bannerImpression.ext = {};

_bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode;
_bannerImpression.ext.gpid = gpid;
_bannerImpression.ext.tid = tid;
_bannerImpression.ext.sid = sid;
_bannerImpression.ext.externalID = externalID;

// enable fledge auction
if (auctionEnvironment == 1) {
Expand Down Expand Up @@ -1030,7 +1063,9 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) {
// Removes imp.ext.bidfloor
// Sets imp.ext.siteID to one of the other [video/native].ext.siteid if imp.ext.siteID doesnt exist
otherImpressions.forEach(imp => {
deepSetValue(imp, 'ext.gpid', gpid);
if (gpid) {
deepSetValue(imp, 'ext.gpid', gpid);
}
if (r.imp.length > 0) {
let matchFound = false;
r.imp.forEach((rImp, index) => {
Expand Down Expand Up @@ -1628,6 +1663,17 @@ function isIndexRendererPreferred(bid) {
return !isValid || renderer.backupOnly;
}

function isExchangeIdConfigured() {
let exchangeId = config.getConfig('exchangeId');
if (typeof exchangeId === 'number' && isFinite(exchangeId)) {
return true;
}
if (typeof exchangeId === 'string' && exchangeId.trim() !== '' && isFinite(Number(exchangeId))) {
return true;
}
return false;
}

export const spec = {

code: BIDDER_CODE,
Expand Down Expand Up @@ -1685,14 +1731,21 @@ export const spec = {
}
}

if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') {
logError('IX Bid Adapter: siteId must be string or number type.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE });
if (!isExchangeIdConfigured() && bid.params.siteId == undefined) {
logError('IX Bid Adapter: Invalid configuration - either siteId or exchangeId must be configured.');
return false;
}

if (typeof bid.params.siteId !== 'string' && isNaN(Number(bid.params.siteId))) {
logError('IX Bid Adapter: siteId must valid value', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE });
return false;
if (bid.params.siteId !== undefined) {
if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') {
logError('IX Bid Adapter: siteId must be string or number type.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE });
return false;
}

if (typeof bid.params.siteId !== 'string' && isNaN(Number(bid.params.siteId))) {
logError('IX Bid Adapter: siteId must valid value', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE });
return false;
}
}

if (hasBidFloor || hasBidFloorCur) {
Expand Down Expand Up @@ -1725,6 +1778,11 @@ export const spec = {
return nativeMediaTypeValid(bid);
},

// For testing only - resets the siteID to 0 so that it can be set again
resetSiteID: function () {
siteID = 0;
},

/**
* Make a server request from the list of BidRequests.
*
Expand Down Expand Up @@ -1964,8 +2022,9 @@ function buildImgSyncUrl(syncsPerBidder, index) {
if (gdprConsent && gdprConsent.hasOwnProperty('consentString')) {
consentString = gdprConsent.consentString || '';
}
let siteIdParam = siteID !== 0 ? '&site_id=' + siteID.toString() : '';

return IMG_USER_SYNC_URL + '&site_id=' + siteID.toString() + '&p=' + syncsPerBidder.toString() + '&i=' + index.toString() + '&gdpr=' + gdprApplies + '&gdpr_consent=' + consentString + '&us_privacy=' + (usPrivacy || '');
return IMG_USER_SYNC_URL + siteIdParam + '&p=' + syncsPerBidder.toString() + '&i=' + index.toString() + '&gdpr=' + gdprApplies + '&gdpr_consent=' + consentString + '&us_privacy=' + (usPrivacy || '');
}

/**
Expand Down
78 changes: 78 additions & 0 deletions test/spec/modules/ixBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3295,6 +3295,84 @@ describe('IndexexchangeAdapter', function () {
});
});

describe('integration through exchangeId and externalId', function () {
const expectedExchangeId = 123456;
// create banner bids with externalId but no siteId as bidder param
const bannerBids = utils.deepClone(DEFAULT_BANNER_VALID_BID);
delete bannerBids[0].params.siteId;
bannerBids[0].params.externalId = 'exteranl_id_1';

beforeEach(() => {
config.setConfig({ exchangeId: expectedExchangeId });
spec.resetSiteID();
});

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

it('when exchangeId and externalId set but no siteId, isBidRequestValid should return true', function () {
const bid = utils.deepClone(bannerBids[0]);
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('when neither exchangeId nor siteId set, isBidRequestValid should return false', function () {
config.resetConfig();
const bid = utils.deepClone(bannerBids[0]);
expect(spec.isBidRequestValid(bid)).to.equal(false);
});

it('when exchangeId and externalId set with banner impression but no siteId, bidrequest sent to endpoint with p param and externalID inside imp.ext', function () {
const requests = spec.buildRequests(bannerBids, DEFAULT_OPTION);
const payload = extractPayload(requests[0]);

const expectedURL = IX_SECURE_ENDPOINT + '?p=' + expectedExchangeId;
expect(requests[0].url).to.equal(expectedURL);
expect(payload.imp[0].ext.externalID).to.equal(bannerBids[0].params.externalId);
expect(payload.imp[0].banner.format[0].ext).to.be.undefined;
expect(payload.imp[0].ext.siteID).to.be.undefined;
});

it('when exchangeId and externalId set with video impression, bidrequest sent to endpoint with p param and externalID inside imp.ext', function () {
const validBids = utils.deepClone(DEFAULT_VIDEO_VALID_BID);
delete validBids[0].params.siteId;
validBids[0].params.externalId = 'exteranl_id_1';

const requests = spec.buildRequests(validBids, DEFAULT_OPTION);
const payload = extractPayload(requests[0]);

const expectedURL = IX_SECURE_ENDPOINT + '?p=' + expectedExchangeId;
expect(requests[0].url).to.equal(expectedURL);
expect(payload.imp[0].ext.externalID).to.equal(validBids[0].params.externalId);
expect(payload.imp[0].ext.siteID).to.be.undefined;
});

it('when exchangeId and externalId set beside siteId, bidrequest sent to endpoint with both p param and s param and externalID inside imp.ext and siteID inside imp.banner.format.ext', function () {
bannerBids[0].params.siteId = '1234';
const requests = spec.buildRequests(bannerBids, DEFAULT_OPTION);
const payload = extractPayload(requests[0]);

const expectedURL = IX_SECURE_ENDPOINT + '?s=' + bannerBids[0].params.siteId + '&p=' + expectedExchangeId;
expect(requests[0].url).to.equal(expectedURL);
expect(payload.imp[0].ext.externalID).to.equal(bannerBids[0].params.externalId);
expect(payload.imp[0].banner.format[0].ext.externalID).to.be.undefined;
expect(payload.imp[0].ext.siteID).to.be.undefined;
expect(payload.imp[0].banner.format[0].ext.siteID).to.equal(bannerBids[0].params.siteId);
});

it('when exchangeId and siteId set, but no externalId, bidrequest sent to exchange', function () {
bannerBids[0].params.siteId = '1234';
delete bannerBids[0].params.externalId;
const requests = spec.buildRequests(bannerBids, DEFAULT_OPTION);
const payload = extractPayload(requests[0]);

const expectedURL = IX_SECURE_ENDPOINT + '?s=' + bannerBids[0].params.siteId + '&p=' + expectedExchangeId;
expect(requests[0].url).to.equal(expectedURL);
expect(payload.imp[0].ext.externalID).to.be.undefined;
expect(payload.imp[0].banner.format[0].ext.siteID).to.equal(bannerBids[0].params.siteId);
});
});

describe('interpretResponse', function () {
// generate bidderRequest with real buildRequest logic for intepretResponse testing
let bannerBidderRequest
Expand Down

0 comments on commit b389581

Please sign in to comment.