diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index f606194e132..b834bf0dc57 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -389,13 +389,36 @@ function getPageDescription() { } /** - * Gets an id from the userId object if it exists + * EID source for each legacy `bid.userId` key this adapter reads, so those ids + * can be recovered from `userIdAsEids` on Prebid 10+, where the `bid.userId` + * object was removed (prebid/Prebid.js#13253, first shipped in 10.0.0). Without + * this the tdid/pubcid/cri_prebid request params silently stop being sent. + */ +const EID_SOURCE_BY_USER_ID = { + pubcid: 'pubcid.org', + tdid: 'adserver.org', + criteoId: 'criteo.com', +}; + +/** + * Gets an id previously read from the (now-removed) `bid.userId` object. + * Prefers the legacy object when present (pre-Prebid 10); otherwise falls back + * to the matching source in `userIdAsEids`. * @param {*} request * @param {*} idType * @returns an id if there is one, or undefined */ function getId(request, idType) { - return (typeof deepAccess(request, 'userId') === 'object') ? request.userId[idType] : undefined; + const legacyUserId = deepAccess(request, 'userId'); + if (typeof legacyUserId === 'object' && legacyUserId != null && legacyUserId[idType] != null) { + return legacyUserId[idType]; + } + const source = EID_SOURCE_BY_USER_ID[idType]; + if (source) { + const eid = (getEids(request) || []).find(e => e && e.source === source); + return deepAccess(eid, 'uids.0.id'); + } + return undefined; } /** diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 4f4454c17ae..4f30dc1f5f3 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -267,6 +267,78 @@ describe('YieldmoAdapter', function () { expect(buildAndGetData([criteoIdBid]).cri_prebid).to.deep.equal(criteoId); }); + // Prebid 10+ removed the legacy bid.userId object (prebid/Prebid.js#13253), + // leaving only userIdAsEids. tdid/pubcid/cri_prebid must be recovered from + // there so they keep being sent. + describe('userIdAsEids fallback (Prebid 10+, no bid.userId)', function () { + const eidsOnlyBid = (extraEids = []) => mockBannerBid({ + crumbs: undefined, + userId: undefined, + userIdAsEids: [ + { source: 'pubcid.org', uids: [{ id: 'eid_pubcid', atype: 1 }] }, + { source: 'adserver.org', uids: [{ id: 'eid_tdid', atype: 1, ext: { rtiPartner: 'TDID' } }] }, + { source: 'criteo.com', uids: [{ id: 'eid_criteo', atype: 1 }] }, + ...extraEids, + ], + }); + + it('should read tdid from userIdAsEids adserver.org', function () { + expect(buildAndGetData([eidsOnlyBid()]).tdid).to.equal('eid_tdid'); + }); + + it('should read pubcid from userIdAsEids pubcid.org', function () { + expect(buildAndGetData([eidsOnlyBid()]).pubcid).to.equal('eid_pubcid'); + }); + + it('should read criteoId from userIdAsEids criteo.com', function () { + expect(buildAndGetData([eidsOnlyBid()]).cri_prebid).to.equal('eid_criteo'); + }); + + it('should prefer the legacy bid.userId object when both are present', function () { + const bid = eidsOnlyBid(); + bid.userId = { tdid: 'legacy_tdid' }; + expect(buildAndGetData([bid]).tdid).to.equal('legacy_tdid'); + }); + + it('should omit an id whose source is absent from userIdAsEids', function () { + const bid = mockBannerBid({ + crumbs: undefined, + userId: undefined, + userIdAsEids: [{ source: 'pubcid.org', uids: [{ id: 'eid_pubcid', atype: 1 }] }], + }); + expect(buildAndGetData([bid])).to.not.have.property('tdid'); + }); + + // Real getUserIdsAsEids() capture from www.yahoo.com (Prebid 9.53.2): a + // 13-source blob with a single adserver.org entry tagged rtiPartner TDID, a + // pubcid.org, no criteo, plus 11 sources we never read as flat params. + // Verifies flat tdid/pubcid are recovered on 10+ and every source still ships + // via the `eids` param. + it('should recover pubcid/tdid from a real Yahoo userIdAsEids blob and forward all sources', function () { + const yahooEids = [ + { source: 'yahoo.com', uids: [{ id: 'ceKGMBMyiD_y9Hiy3yEezGpqPWhG9vckE9uQ9dXvzZ7YH5B3OcU3GkuAGulWrJrGMqumCS4bkIWCl_HKMsb2eQ', atype: 3 }] }, + { source: 'liveramp.com', uids: [{ id: 'ApoINPkmDal1SZ-1eFryc322FuBMuiTIfa0n83qkZ9d4TmmF2yjDwWNFt0rOB2DBaH5YnHs', atype: 3 }] }, + { source: 'liveintent.com', uids: [{ id: '31-AEQ3i4A7U6Q/VmkQW8HMRDb69Hc4Dmb3d9Vw5OujdEhuKayNFArmrWkjwcXZsaWltfVm1zsQVPR6lhgSdD4H6PLANZWX8wE7ctQxbQuxYgvwBQXrbQW4', atype: 3 }] }, + { source: 'liveintent.triplelift.com', uids: [{ id: '3039651338209079102359', atype: 3, ext: { provider: 'liveintent.com' } }] }, + { source: 'rubiconproject.com', uids: [{ id: 'MEDJJCH8-O-IKN8', atype: 3, ext: { provider: 'liveintent.com' } }] }, + { source: 'liveintent.indexexchange.com', uids: [{ id: 'aJ-V4sAoIlYAKl2pAwiuIwAA&2037', atype: 3, ext: { provider: 'liveintent.com' } }] }, + { source: 'openx.net', uids: [{ id: '7a105d7c-4708-4c2d-a3ba-1e795e46983e', atype: 3, ext: { provider: 'liveintent.com' } }] }, + { source: 'pubmatic.com', uids: [{ id: '4235B560-BD45-4F77-9695-19B7846C1E31', atype: 3, ext: { provider: 'liveintent.com' } }] }, + { source: 'liveintent.sovrn.com', uids: [{ id: 'LLFyABZHZ8flvu7cQ-Giva3O', atype: 3, ext: { provider: 'liveintent.com' } }] }, + { source: 'liveintent.sonobi.com', uids: [{ id: '9706c68f-27f2-4424-af37-ab315995f8b0', atype: 3, ext: { provider: 'liveintent.com' } }] }, + { source: 'liveintent.unrulymedia.com', uids: [{ id: 'RX-1927b623-5cc4-4212-b089-32380b081397-005', atype: 3, ext: { provider: 'liveintent.com' } }] }, + { source: 'pubcid.org', uids: [{ id: '41137366-b187-4188-bd07-7e858ac15a9b', atype: 1 }] }, + { source: 'adserver.org', uids: [{ id: '44d89fb7-c8f6-4c5d-913f-d41707bd4511', atype: 1, ext: { rtiPartner: 'TDID' } }], inserter: 'adserver.org', matcher: 'adserver.org', mm: 4 }, + ]; + const data = buildAndGetData([mockBannerBid({ crumbs: undefined, userId: undefined, userIdAsEids: yahooEids })]); + expect(data.pubcid).to.equal('41137366-b187-4188-bd07-7e858ac15a9b'); + expect(data.tdid).to.equal('44d89fb7-c8f6-4c5d-913f-d41707bd4511'); + expect(data).to.not.have.property('cri_prebid'); + // every EID source is still forwarded intact via the eids param + expect(JSON.parse(data.eids).map(e => e.source)).to.have.members(yahooEids.map(e => e.source)); + }); + }); + it('should add gdpr information to request if available', () => { const gdprConsent = { consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==',