diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index c62876b9605..c9a81e152b6 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -3,7 +3,6 @@ import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { find } from '../src/polyfill.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { Renderer } from '../src/Renderer.js'; import { @@ -499,6 +498,8 @@ export const spec = { 'x_imp.ext.tid': bidRequest.ortb2Imp?.ext?.tid, 'l_pb_bid_id': bidRequest.bidId, 'o_cdep': bidRequest.ortb2?.device?.ext?.cdep, + 'ip': bidRequest.ortb2?.device?.ip, + 'ipv6': bidRequest.ortb2?.device?.ipv6, 'p_screen_res': _getScreenResolution(), 'tk_user_key': params.userId, 'p_geo.latitude': isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4), @@ -542,42 +543,47 @@ export const spec = { if (bidRequest?.ortb2Imp?.ext?.ae) { data['o_ae'] = 1; } + // If the bid request contains a 'mobile' property under 'ortb2.site', add it to 'data' as 'p_site.mobile'. + if (typeof bidRequest?.ortb2?.site?.mobile === 'number') { + data['p_site.mobile'] = bidRequest.ortb2.site.mobile + } addDesiredSegtaxes(bidderRequest, data); // loop through userIds and add to request - if (bidRequest.userIdAsEids) { - bidRequest.userIdAsEids.forEach(eid => { + if (bidRequest?.ortb2?.user?.ext?.eids) { + bidRequest.ortb2.user.ext.eids.forEach(({ source, uids = [], inserter, matcher, mm, ext = {} }) => { try { - // special cases - if (eid.source === 'adserver.org') { - data['tpid_tdid'] = eid.uids[0].id; - data['eid_adserver.org'] = eid.uids[0].id; - } else if (eid.source === 'liveintent.com') { - data['tpid_liveintent.com'] = eid.uids[0].id; - data['eid_liveintent.com'] = eid.uids[0].id; - if (eid.ext && Array.isArray(eid.ext.segments) && eid.ext.segments.length) { - data['tg_v.LIseg'] = eid.ext.segments.join(','); - } - } else if (eid.source === 'liveramp.com') { - data['x_liverampidl'] = eid.uids[0].id; - } else if (eid.source === 'id5-sync.com') { - data['eid_id5-sync.com'] = `${eid.uids[0].id}^${eid.uids[0].atype}^${(eid.uids[0].ext && eid.uids[0].ext.linkType) || ''}`; - } else { - // add anything else with this generic format - // if rubicon drop ^ - const id = eid.source === 'rubiconproject.com' ? eid.uids[0].id : `${eid.uids[0].id}^${eid.uids[0].atype || ''}` - data[`eid_${eid.source}`] = id; - } - // send AE "ppuid" signal if exists, and hasn't already been sent + // Ensure there is at least one valid UID in the 'uids' array + const uidData = uids[0]; + if (!uidData) return; // Skip processing if no valid UID exists + + // Function to build the EID value in the required format + const buildEidValue = (uidData) => [ + uidData.id, // uid: The user ID + uidData.atype || '', + '', // third: Always empty, as specified in the requirement + inserter || '', + matcher || '', + mm || '', + uidData?.ext?.rtipartner || '' + ].join('^'); // Return a single string formatted with '^' delimiter + + const eidValue = buildEidValue(uidData); // Build the EID value string + + // Store the constructed EID value for the given source + data[`eid_${source}`] = eidValue; + + // Handle the "ppuid" signal, ensuring it is set only once if (!data['ppuid']) { - // get the first eid.uids[*].ext.stype === 'ppuid', if one exists - const ppId = find(eid.uids, uid => uid.ext && uid.ext.stype === 'ppuid'); - if (ppId && ppId.id) { - data['ppuid'] = ppId.id; + // Search for a UID with the 'stype' field equal to 'ppuid' in its extension + const ppId = uids.find(uid => uid.ext?.stype === 'ppuid'); + if (ppId?.id) { + data['ppuid'] = ppId.id; // Store the ppuid if found } } } catch (e) { - logWarn('Rubicon: error reading eid:', eid, e); + // Log any errors encountered during processing + logWarn('Rubicon: error reading eid:', { source, uids }, e); } }); } diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 5be3040ddd4..6cfdb664d27 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -710,6 +710,28 @@ describe('the rubicon adapter', function () { expect(data.get('o_cdep')).to.equal('3'); }); + it('should correctly send ip signal when ortb2.device.ip is provided', () => { + const ipRequest = utils.deepClone(bidderRequest); + ipRequest.bids[0].ortb2 = { device: { ip: '123.45.67.89' } }; + + let [request] = spec.buildRequests(ipRequest.bids, ipRequest); + let data = new URLSearchParams(request.data); + + // Verify if 'ip' is correctly added to the request data + expect(data.get('ip')).to.equal('123.45.67.89'); + }); + + it('should correctly send ipv6 signal when ortb2.device.ipv6 is provided', () => { + const ipv6Request = utils.deepClone(bidderRequest); + ipv6Request.bids[0].ortb2 = { device: { ipv6: '2001:db8::ff00:42:8329' } }; + + let [request] = spec.buildRequests(ipv6Request.bids, ipv6Request); + let data = new URLSearchParams(request.data); + + // Verify if 'ipv6' is correctly added to the request data + expect(data.get('ipv6')).to.equal('2001:db8::ff00:42:8329'); + }); + it('ad engine query params should be ordered correctly', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); @@ -1332,173 +1354,60 @@ describe('the rubicon adapter', function () { }); describe('user id config', function () { - it('should send tpid_tdid when userIdAsEids contains unifiedId', function () { - const clonedBid = utils.deepClone(bidderRequest.bids[0]); - clonedBid.userId = { - tdid: 'abcd-efgh-ijkl-mnop-1234' - }; - clonedBid.userIdAsEids = [ - { - 'source': 'adserver.org', - 'uids': [ - { - 'id': 'abcd-efgh-ijkl-mnop-1234', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - } - ] - } - ]; - let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = new URLSearchParams(request.data); - - expect(data.get('tpid_tdid')).to.equal('abcd-efgh-ijkl-mnop-1234'); - expect(data.get('eid_adserver.org')).to.equal('abcd-efgh-ijkl-mnop-1234'); - }); - - describe('LiveIntent support', function () { - it('should send tpid_liveintent.com when userIdAsEids contains liveintentId', function () { - const clonedBid = utils.deepClone(bidderRequest.bids[0]); - clonedBid.userId = { - lipb: { - lipbid: '0000-1111-2222-3333', - segments: ['segA', 'segB'] - } - }; - clonedBid.userIdAsEids = [ - { - 'source': 'liveintent.com', - 'uids': [ - { - 'id': '0000-1111-2222-3333', - 'atype': 3 - } - ], - 'ext': { - 'segments': [ - 'segA', - 'segB' - ] - } - } - ]; - let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = new URLSearchParams(request.data); - - expect(data.get('tpid_liveintent.com')).to.equal('0000-1111-2222-3333'); - expect(data.get('eid_liveintent.com')).to.equal('0000-1111-2222-3333'); - expect(data.get('tg_v.LIseg')).to.equal('segA,segB'); - }); - - it('should send tg_v.LIseg when userIdAsEids contains liveintentId with ext.segments as array', function () { - const clonedBid = utils.deepClone(bidderRequest.bids[0]); - clonedBid.userId = { - lipb: { - lipbid: '1111-2222-3333-4444', - segments: ['segD', 'segE'] - } - }; - clonedBid.userIdAsEids = [ - { - 'source': 'liveintent.com', - 'uids': [ - { - 'id': '1111-2222-3333-4444', - 'atype': 3 - } - ], - 'ext': { - 'segments': [ - 'segD', - 'segE' - ] - } - } - ] - let [request] = spec.buildRequests([clonedBid], bidderRequest); - const unescapedData = unescape(request.data); - - expect(unescapedData.indexOf('&tpid_liveintent.com=1111-2222-3333-4444&') !== -1).to.equal(true); - expect(unescapedData.indexOf('&tg_v.LIseg=segD,segE&') !== -1).to.equal(true); - }); - }); - - describe('LiveRamp support', function () { - it('should send x_liverampidl when userIdAsEids contains liverampId', function () { - const clonedBid = utils.deepClone(bidderRequest.bids[0]); - clonedBid.userId = { - idl_env: '1111-2222-3333-4444' - }; - clonedBid.userIdAsEids = [ - { - 'source': 'liveramp.com', - 'uids': [ - { - 'id': '1111-2222-3333-4444', - 'atype': 3 - } - ] - } - ] - let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = new URLSearchParams(request.data); - - expect(data.get('x_liverampidl')).to.equal('1111-2222-3333-4444'); - }); - }); - describe('pubcid support', function () { - it('should send eid_pubcid.org when userIdAsEids contains pubcid', function () { + it('should send eid_pubcid.org when ortb2.user.ext.eids contains pubcid', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { pubcid: '1111' }; - clonedBid.userIdAsEids = [ - { - 'source': 'pubcid.org', - 'uids': [ - { - 'id': '1111', - 'atype': 1 - } - ] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'pubcid.org', + 'uids': [{ + 'id': '1111', + 'atype': 1 + }] + }] + } } - ] + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = new URLSearchParams(request.data); - expect(data.get('eid_pubcid.org')).to.equal('1111^1'); + expect(data.get('eid_pubcid.org')).to.equal('1111^1^^^^^'); }); }); describe('Criteo support', function () { - it('should send eid_criteo.com when userIdAsEids contains criteo', function () { + it('should send eid_criteo.com when ortb2.user.ext.eids contains criteo', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { criteoId: '1111' }; - clonedBid.userIdAsEids = [ - { - 'source': 'criteo.com', - 'uids': [ - { - 'id': '1111', - 'atype': 1 - } - ] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': '1111', + 'atype': 1 + }] + }] + } } - ] + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = new URLSearchParams(request.data); - expect(data.get('eid_criteo.com')).to.equal('1111^1'); + expect(data.get('eid_criteo.com')).to.equal('1111^1^^^^^'); }); }); describe('pubProvidedId support', function () { - it('should send pubProvidedId when userIdAsEids contains pubProvidedId ids', function () { + it('should send pubProvidedId when ortb2.user.ext.eids contains pubProvidedId ids', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { pubProvidedId: [{ @@ -1516,27 +1425,27 @@ describe('the rubicon adapter', function () { }] }] }; - clonedBid.userIdAsEids = [ - { - 'source': 'example.com', - 'uids': [ - { - 'id': '11111', - 'ext': { - 'stype': 'ppuid' - } - } - ] - }, - { - 'source': 'id-partner.com', - 'uids': [ + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'example.com', + 'uids': [{ + 'id': '11111', + 'ext': { + 'stype': 'ppuid' + } + }] + }, { - 'id': '222222' - } - ] + 'source': 'id-partner.com', + 'uids': [{ + 'id': '222222' + }] + }] + } } - ]; + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = new URLSearchParams(request.data); @@ -1545,7 +1454,7 @@ describe('the rubicon adapter', function () { }); describe('ID5 support', function () { - it('should send ID5 id when userIdAsEids contains ID5', function () { + it('should send ID5 id when ortb2.user.ext.eids contains ID5', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { id5id: { @@ -1555,24 +1464,26 @@ describe('the rubicon adapter', function () { } } }; - clonedBid.userIdAsEids = [ - { - 'source': 'id5-sync.com', - 'uids': [ - { - 'id': '11111', - 'atype': 1, - 'ext': { - 'linkType': '22222' - } - } - ] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'id5-sync.com', + 'uids': [{ + 'id': '11111', + 'atype': 1, + 'ext': { + 'linkType': '22222' + } + }] + }] + } } - ]; + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = new URLSearchParams(request.data); - expect(data.get('eid_id5-sync.com')).to.equal('11111^1^22222'); + expect(data.get('eid_id5-sync.com')).to.equal('11111^1^^^^^'); }); }); @@ -1580,36 +1491,82 @@ describe('the rubicon adapter', function () { it('should send user id with generic format', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); // Hardcoding userIdAsEids since createEidsArray returns empty array if source not found in eids.js - clonedBid.userIdAsEids = [{ - source: 'catchall', - uids: [{ - id: '11111', - atype: 2 - }] - }] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'catchall', + 'uids': [{ + 'id': '11111', + 'atype': 2 + }] + }] + } + } + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = new URLSearchParams(request.data); - expect(data.get('eid_catchall')).to.equal('11111^2'); + expect(data.get('eid_catchall')).to.equal('11111^2^^^^^'); }); it('should send rubiconproject special case', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); // Hardcoding userIdAsEids since createEidsArray returns empty array if source not found in eids.js - clonedBid.userIdAsEids = [{ - source: 'rubiconproject.com', - uids: [{ - id: 'some-cool-id', - atype: 3 - }] - }] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + source: 'rubiconproject.com', + uids: [{ + id: 'some-cool-id', + atype: 3 + }] + }] + } + } + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = new URLSearchParams(request.data); - expect(data.get('eid_rubiconproject.com')).to.equal('some-cool-id'); + expect(data.get('eid_rubiconproject.com')).to.equal('some-cool-id^3^^^^^'); }); - }); + describe('Full eidValue format validation', function () { + it('should send complete eidValue in the format uid^atype^third^inserter^matcher^mm^rtipartner', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + // Simulating a full EID object with multiple fields + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + source: 'example.com', + uids: [{ + id: '11111', // UID + atype: 2, // atype + ext: { + rtipartner: 'rtipartner123', // rtipartner + stype: 'ppuid' // stype + } + }], + inserter: 'inserter123', // inserter + matcher: 'matcher123', // matcher + mm: 'mm123' // mm + }] + } + } + }; + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = new URLSearchParams(request.data); + + // Expected format: uid^atype^third^inserter^matcher^mm^rtipartner + const expectedEidValue = '11111^2^^inserter123^matcher123^mm123^rtipartner123'; + + // Check if the generated EID value matches the expected format + expect(data.get('eid_example.com')).to.equal(expectedEidValue); + }); + }); + }); describe('Config user.id support', function () { it('should send ppuid when config defines user.id', function () { config.setConfig({user: {id: '123'}}); @@ -2871,6 +2828,36 @@ describe('the rubicon adapter', function () { expect(slotParams['tg_i.tax10']).is.equal('2,3'); expect(slotParams['tg_v.tax404']).is.equal(undefined); }); + + it('should add p_site.mobile if mobile is a number in ortb2.site', function () { + // Set up a bidRequest with mobile property as a number + const localBidderRequest = Object.assign({}, bidderRequest); + localBidderRequest.bids[0].ortb2 = { + site: { + mobile: 1 // Valid mobile value (number) + } + }; + + // Call the function + const slotParams = spec.createSlotParams(localBidderRequest.bids[0], localBidderRequest); + // Check that p_site.mobile was added to the slotParams with the correct value + expect(slotParams['p_site.mobile']).to.equal(1); + }); + it('should not add p_site.mobile if mobile is not a number in ortb2.site', function () { + // Set up a bidRequest with mobile property as a string (invalid value) + const localBidderRequest = Object.assign({}, bidderRequest); + localBidderRequest.bids[0].ortb2 = { + site: { + mobile: 'not-a-number' // Invalid mobile value (string) + } + }; + + // Call the function + const slotParams = spec.createSlotParams(localBidderRequest.bids[0], localBidderRequest); + + // Check that p_site.mobile is not added to the slotParams + expect(slotParams['p_site.mobile']).to.be.undefined; + }); }); describe('classifiedAsVideo', function () {