From a0bfc6b022068debab2e0cbb971c8cd58dac04cf Mon Sep 17 00:00:00 2001 From: antoin Date: Mon, 26 Sep 2022 10:28:23 -0400 Subject: [PATCH 01/10] collect EIDs for bid request --- modules/concertBidAdapter.js | 29 +++++++++++++++++++++ test/spec/modules/concertBidAdapter_spec.js | 21 +++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index fc6fc23c26d..0e448f66b55 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -34,6 +34,9 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { logMessage(validBidRequests); logMessage(bidderRequest); + + const eids = []; + let payload = { meta: { prebidVersion: '$prebid.version$', @@ -49,6 +52,8 @@ export const spec = { }; payload.slots = validBidRequests.map(bidRequest => { + collectEid(eids, bidRequest); + let slot = { name: bidRequest.adUnitCode, bidId: bidRequest.bidId, @@ -65,6 +70,8 @@ export const spec = { return slot; }); + payload.meta.eids = eids.filter(Boolean); + logMessage(payload); return { @@ -216,3 +223,25 @@ function consentAllowsPpid(bidderRequest) { return (uspConsent || gdprConsent); } + +function collectEid(eids, bid) { + if (bid.userId) { + const eid = getUserId(bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com', undefined, 3) + eids.push(eid) + } +} + +function getUserId(id, source, uidExt, atype) { + if (id) { + const uid = { id, atype }; + + if (uidExt) { + uid.ext = uidExt; + } + + return { + source, + uids: [ uid ] + }; + } +} diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 2f9eda0ca7c..abb032ab154 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -119,6 +119,27 @@ describe('ConcertAdapter', function () { expect(payload.meta.uid).to.equal('foo'); }); + + it('should add uid2 to eids list if available', function() { + bidRequests[0].userId = { uid2: { id: 'uid123' } } + + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + const meta = payload.meta + + expect(meta.eids.length).to.equal(1); + expect(meta.eids[0].uids[0].id).to.equal('uid123') + expect(meta.eids[0].uids[0].atype).to.equal(3) + }) + + it('should return empty eids list if none are available', function() { + bidRequests[0].userId = { testId: { id: 'uid123' } } + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + const meta = payload.meta + + expect(meta.eids.length).to.equal(0); + }) }); describe('spec.interpretResponse', function() { From 0be448c2e5b3b20a19528aa3b5ae6181dbc43ac8 Mon Sep 17 00:00:00 2001 From: antoin Date: Tue, 27 Sep 2022 15:02:28 -0400 Subject: [PATCH 02/10] add ad slot positioning to payload --- modules/concertBidAdapter.js | 17 ++++++++-- test/spec/modules/concertBidAdapter_spec.js | 35 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 0e448f66b55..620e77a5a08 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -53,6 +53,8 @@ export const spec = { payload.slots = validBidRequests.map(bidRequest => { collectEid(eids, bidRequest); + const adUnitElement = document.getElementById(bidRequest.adUnitCode) + const coordinates = getOffset(adUnitElement) let slot = { name: bidRequest.adUnitCode, @@ -64,8 +66,9 @@ export const spec = { adSlot: bidRequest.params.slot || bidRequest.adUnitCode, placementId: bidRequest.params.placementId || '', site: bidRequest.params.site || bidderRequest.refererInfo.page, - ref: bidderRequest.refererInfo.ref - }; + ref: bidderRequest.refererInfo.ref, + offsetCoordinates: { x: coordinates?.left, y: coordinates?.top } + } return slot; }); @@ -245,3 +248,13 @@ function getUserId(id, source, uidExt, atype) { }; } } + +function getOffset(el) { + if (el) { + const rect = el.getBoundingClientRect(); + return { + left: rect.left + window.scrollX, + top: rect.top + window.scrollY + }; + } +} diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index abb032ab154..10ea997b304 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -7,11 +7,33 @@ describe('ConcertAdapter', function () { let bidRequests; let bidRequest; let bidResponse; + let element; + let sandbox; afterEach(function () { $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); }); + beforeEach(function () { + element = { + x: 0, + y: 0, + width: 0, + height: 0, + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + $$PREBID_GLOBAL$$.bidderSettings = { concert: { storageAllowed: true @@ -56,6 +78,9 @@ describe('ConcertAdapter', function () { ] } } + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('desktop_leaderboard_variable').returns(element) }); describe('spec.isBidRequestValid', function() { @@ -139,6 +164,16 @@ describe('ConcertAdapter', function () { const meta = payload.meta expect(meta.eids.length).to.equal(0); + }); + + it('should return x/y offset coordiantes when element is present', function() { + Object.assign(element, { x: 100, y: 0, width: 400, height: 400 }) + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + const slot = payload.slots[0]; + + expect(slot.offsetCoordinates.x).to.equal(100) + expect(slot.offsetCoordinates.y).to.equal(0) }) }); From c54b0117528cea607dd10e6211abd0cd0448b27a Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Thu, 27 Oct 2022 13:32:09 -0600 Subject: [PATCH 03/10] RPO-2012: Update local storage name-spacing for c_uid (#8) * Updates c_uid namespacing to be more specific for concert * fixes unit tests * remove console.log --- modules/concertBidAdapter.js | 12 ++++++++-- test/spec/modules/concertBidAdapter_spec.js | 25 +++++++++------------ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 620e77a5a08..515798ca192 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -178,7 +178,7 @@ export const spec = { registerBidder(spec); -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * Check or generate a UID for the current user. @@ -188,10 +188,18 @@ function getUid(bidderRequest) { return false; } - const CONCERT_UID_KEY = 'c_uid'; + const LEGACY_CONCERT_UID_KEY = 'c_uid'; + const CONCERT_UID_KEY = 'vmconcert_uid'; + const legacyUid = storage.getDataFromLocalStorage(LEGACY_CONCERT_UID_KEY); let uid = storage.getDataFromLocalStorage(CONCERT_UID_KEY); + if (legacyUid) { + uid = legacyUid; + storage.setDataInLocalStorage(CONCERT_UID_KEY, uid); + storage.removeDataFromLocalStorage(LEGACY_CONCERT_UID_KEY); + } + if (!uid) { uid = generateUUID(); storage.setDataInLocalStorage(CONCERT_UID_KEY, uid); diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 10ea997b304..16625be24b7 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; -import { spec } from 'modules/concertBidAdapter.js'; -import { getStorageManager } from '../../../src/storageManager.js' +import { spec, storage } from 'modules/concertBidAdapter.js'; +import { hook } from 'src/hook.js'; describe('ConcertAdapter', function () { let bidRequests; @@ -10,9 +10,8 @@ describe('ConcertAdapter', function () { let element; let sandbox; - afterEach(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - sandbox.restore(); + before(function () { + hook.ready(); }); beforeEach(function () { @@ -39,6 +38,7 @@ describe('ConcertAdapter', function () { storageAllowed: true } }; + bidRequests = [ { bidder: 'concert', @@ -83,6 +83,11 @@ describe('ConcertAdapter', function () { sandbox.stub(document, 'getElementById').withArgs('desktop_leaderboard_variable').returns(element) }); + afterEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); + }); + describe('spec.isBidRequestValid', function() { it('should return when it recieved all the required params', function() { const bid = bidRequests[0]; @@ -118,7 +123,6 @@ describe('ConcertAdapter', function () { }); it('should not generate uid if the user has opted out', function() { - const storage = getStorageManager(); storage.setDataInLocalStorage('c_nap', 'true'); const request = spec.buildRequests(bidRequests, bidRequest); const payload = JSON.parse(request.data); @@ -127,7 +131,6 @@ describe('ConcertAdapter', function () { }); it('should generate uid if the user has not opted out', function() { - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); const request = spec.buildRequests(bidRequests, bidRequest); const payload = JSON.parse(request.data); @@ -136,8 +139,7 @@ describe('ConcertAdapter', function () { }); it('should grab uid from local storage if it exists', function() { - const storage = getStorageManager(); - storage.setDataInLocalStorage('c_uid', 'foo'); + storage.setDataInLocalStorage('vmconcert_uid', 'foo'); storage.removeDataFromLocalStorage('c_nap'); const request = spec.buildRequests(bidRequests, bidRequest); const payload = JSON.parse(request.data); @@ -211,7 +213,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.setDataInLocalStorage('c_nap', 'true'); const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); @@ -222,7 +223,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); bidRequest.gdprConsent = { @@ -237,7 +237,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); bidRequest.gdprConsent = { @@ -252,7 +251,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); bidRequest.gdprConsent = { @@ -268,7 +266,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); bidRequest.gdprConsent = { From 2bb71431208f1363fc5bed27d670f1e410a6920e Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Fri, 4 Nov 2022 12:12:46 -0600 Subject: [PATCH 04/10] RPO-2012: Add check for shared id (#9) * Adds check for sharedId * Updates cookie name * remove trailing comma --- modules/concertBidAdapter.js | 8 +++++++- test/spec/modules/concertBidAdapter_spec.js | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 515798ca192..dcea60d5231 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -1,4 +1,4 @@ -import { logWarn, logMessage, debugTurnedOn, generateUUID } from '../src/utils.js'; +import { logWarn, logMessage, debugTurnedOn, generateUUID, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { hasPurpose1Consent } from '../src/utils/gpdr.js'; @@ -188,6 +188,12 @@ function getUid(bidderRequest) { return false; } + const sharedId = deepAccess(bidderRequest, 'userId._sharedid.id'); + + if (sharedId) { + return sharedId; + } + const LEGACY_CONCERT_UID_KEY = 'c_uid'; const CONCERT_UID_KEY = 'vmconcert_uid'; diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 16625be24b7..00626472ecb 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -138,7 +138,21 @@ describe('ConcertAdapter', function () { expect(payload.meta.uid).to.not.equal(false); }); - it('should grab uid from local storage if it exists', function() { + it('should use sharedid if it exists', function() { + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, { + ...bidRequest, + userId: { + _sharedid: { + id: '123abc' + } + } + }); + const payload = JSON.parse(request.data); + expect(payload.meta.uid).to.equal('123abc'); + }) + + it('should grab uid from local storage if it exists and sharedid does not', function() { storage.setDataInLocalStorage('vmconcert_uid', 'foo'); storage.removeDataFromLocalStorage('c_nap'); const request = spec.buildRequests(bidRequests, bidRequest); From 99b06643b34c7af3a8d85bc4a3548f5447404457 Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Wed, 22 Mar 2023 10:02:30 -0600 Subject: [PATCH 05/10] [RPO-3152] Enable Support for GPP Consent (#12) * Adds gpp consent integration to concert bid adapter * Update tests to check for gpp consent string param * removes user sync endpoint and tests * updates comment * cleans up consentAllowsPpid function * comment fix * rename variables for clarity --- modules/concertBidAdapter.js | 59 ++++--------- test/spec/modules/concertBidAdapter_spec.js | 93 +++------------------ 2 files changed, 30 insertions(+), 122 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index dcea60d5231..cbf86aed782 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -5,7 +5,6 @@ import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const BIDDER_CODE = 'concert'; const CONCERT_ENDPOINT = 'https://bids.concert.io'; -const USER_SYNC_URL = 'https://cdn.concert.io/lib/bids/sync.html'; export const spec = { code: BIDDER_CODE, @@ -47,10 +46,18 @@ export const spec = { optedOut: hasOptedOutOfPersonalization(), adapterVersion: '1.1.1', uspConsent: bidderRequest.uspConsent, - gdprConsent: bidderRequest.gdprConsent + gdprConsent: bidderRequest.gdprConsent, + gppConsent: bidderRequest.gppConsent, } }; + if (!payload.meta.gppConsent && bidderRequest.ortb2?.regs?.gpp) { + payload.meta.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + applicableSections: bidderRequest.ortb2.regs.gpp_sid + } + } + payload.slots = validBidRequests.map(bidRequest => { collectEid(eids, bidRequest); const adUnitElement = document.getElementById(bidRequest.adUnitCode) @@ -124,38 +131,6 @@ export const spec = { return bidResponses; }, - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @param {gdprConsent} object GDPR consent object. - * @param {uspConsent} string US Privacy String. - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncs = []; - if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) { - let params = []; - - if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { - params.push(`gdpr_applies=${gdprConsent.gdprApplies ? '1' : '0'}`); - } - if (gdprConsent && (typeof gdprConsent.consentString === 'string')) { - params.push(`gdpr_consent=${gdprConsent.consentString}`); - } - if (uspConsent && (typeof uspConsent === 'string')) { - params.push(`usp_consent=${uspConsent}`); - } - - syncs.push({ - type: 'iframe', - url: USER_SYNC_URL + (params.length > 0 ? `?${params.join('&')}` : '') - }); - } - return syncs; - }, - /** * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data @@ -229,16 +204,18 @@ function hasOptedOutOfPersonalization() { * @param {BidderRequest} bidderRequest Object which contains any data consent signals */ function consentAllowsPpid(bidderRequest) { - /* NOTE: We can't easily test GDPR consent, without the - * `consent-string` npm module; so will have to rely on that - * happening on the bid-server. */ - const uspConsent = !(bidderRequest?.uspConsent === 'string' && + const uspConsentAllows = + typeof bidderRequest?.uspConsent === 'string' && bidderRequest?.uspConsent[0] === '1' && - bidderRequest?.uspConsent[2].toUpperCase() === 'Y'); + bidderRequest?.uspConsent[2].toUpperCase() === 'N'; - const gdprConsent = bidderRequest?.gdprConsent && hasPurpose1Consent(bidderRequest?.gdprConsent); + /* + * True if the gdprConsent is null-y; or GDPR does not apply; or if purpose 1 consent was given. + * Much more nuanced GDPR requirements are tested on the bid server using the @iabtcf/core npm module; + */ + const gdprConsentAllows = hasPurpose1Consent(bidderRequest?.gdprConsent); - return (uspConsent || gdprConsent); + return (uspConsentAllows && gdprConsentAllows); } function collectEid(eids, bid) { diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 00626472ecb..d5e7140a9f7 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -57,8 +57,9 @@ describe('ConcertAdapter', function () { refererInfo: { page: 'https://www.google.com' }, - uspConsent: '1YYY', - gdprConsent: {} + uspConsent: '1YN-', + gdprConsent: {}, + gppConsent: {} }; bidResponse = { @@ -111,7 +112,7 @@ describe('ConcertAdapter', function () { expect(payload).to.have.property('meta'); expect(payload).to.have.property('slots'); - const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent']; + const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent']; const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType']; metaRequiredFields.forEach(function(field) { @@ -138,6 +139,14 @@ describe('ConcertAdapter', function () { expect(payload.meta.uid).to.not.equal(false); }); + it('should not generate uid if USP consent disallows', function() { + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, { ...bidRequest, uspConsent: '1YY' }); + const payload = JSON.parse(request.data); + + expect(payload.meta.uid).to.equal(false); + }); + it('should use sharedid if it exists', function() { storage.removeDataFromLocalStorage('c_nap'); const request = spec.buildRequests(bidRequests, { @@ -213,82 +222,4 @@ describe('ConcertAdapter', function () { expect(bids).to.have.lengthOf(0); }); }); - - describe('spec.getUserSyncs', function() { - it('should not register syncs when iframe is not enabled', function() { - const opts = { - iframeEnabled: false - } - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should not register syncs when the user has opted out', function() { - const opts = { - iframeEnabled: true - }; - storage.setDataInLocalStorage('c_nap', 'true'); - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: true - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=1'); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=0'); - }); - - it('should set gdpr consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - }); - - it('should set ccpa consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - uspConsent: '1YYY' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('usp_consent=1YY'); - }); - }); }); From 105802d481d29bdf91bfe6d8d4356c66f5426028 Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Wed, 22 Mar 2023 10:44:37 -0600 Subject: [PATCH 06/10] fixes conditional logic for consent allows function (#13) --- modules/concertBidAdapter.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index cbf86aed782..176729dd607 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -204,10 +204,16 @@ function hasOptedOutOfPersonalization() { * @param {BidderRequest} bidderRequest Object which contains any data consent signals */ function consentAllowsPpid(bidderRequest) { - const uspConsentAllows = + let uspConsentAllows = true; + + // if a us privacy string was provided, but they explicitly opted out + if ( typeof bidderRequest?.uspConsent === 'string' && bidderRequest?.uspConsent[0] === '1' && - bidderRequest?.uspConsent[2].toUpperCase() === 'N'; + bidderRequest?.uspConsent[2].toUpperCase() === 'Y' // user has opted-out + ) { + uspConsentAllows = false; + } /* * True if the gdprConsent is null-y; or GDPR does not apply; or if purpose 1 consent was given. From 0ee3b28be58e9b044c11a39b930f8e20ab79dd90 Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Thu, 6 Apr 2023 11:55:12 -0600 Subject: [PATCH 07/10] [RPO-3262] Update getUid function to check for pubcid and sharedid (#14) * Update getUid function to check for pubcid and sharedid * updates adapter version --- modules/concertBidAdapter.js | 21 ++++++++++++++------- test/spec/modules/concertBidAdapter_spec.js | 11 +++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 176729dd607..a25d9086446 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -42,9 +42,9 @@ export const spec = { pageUrl: bidderRequest.refererInfo.page, screen: [window.screen.width, window.screen.height].join('x'), debug: debugTurnedOn(), - uid: getUid(bidderRequest), + uid: getUid(bidderRequest, validBidRequests), optedOut: hasOptedOutOfPersonalization(), - adapterVersion: '1.1.1', + adapterVersion: '1.2.0', uspConsent: bidderRequest.uspConsent, gdprConsent: bidderRequest.gdprConsent, gppConsent: bidderRequest.gppConsent, @@ -158,16 +158,23 @@ export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * Check or generate a UID for the current user. */ -function getUid(bidderRequest) { +function getUid(bidderRequest, validBidRequests) { if (hasOptedOutOfPersonalization() || !consentAllowsPpid(bidderRequest)) { return false; } - const sharedId = deepAccess(bidderRequest, 'userId._sharedid.id'); + /** + * check for shareId or pubCommonId before generating a new one + * sharedId: @see https://docs.prebid.org/dev-docs/modules/userId.html + * pubCid (no longer supported): @see https://docs.prebid.org/dev-docs/modules/pubCommonId.html#adapter-integration + */ + const sharedId = + deepAccess(validBidRequests[0], 'userId.sharedid.id') || + deepAccess(validBidRequests[0], 'userId.pubcid') + const pubCid = deepAccess(validBidRequests[0], 'crumbs.pubcid'); - if (sharedId) { - return sharedId; - } + if (sharedId) return sharedId; + if (pubCid) return pubCid; const LEGACY_CONCERT_UID_KEY = 'c_uid'; const CONCERT_UID_KEY = 'vmconcert_uid'; diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index d5e7140a9f7..f5c807b4703 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -149,15 +149,10 @@ describe('ConcertAdapter', function () { it('should use sharedid if it exists', function() { storage.removeDataFromLocalStorage('c_nap'); - const request = spec.buildRequests(bidRequests, { - ...bidRequest, - userId: { - _sharedid: { - id: '123abc' - } - } - }); + const bidRequestsWithSharedId = [{ ...bidRequests[0], userId: { sharedid: { id: '123abc' } } }] + const request = spec.buildRequests(bidRequestsWithSharedId, bidRequest); const payload = JSON.parse(request.data); + expect(payload.meta.uid).to.equal('123abc'); }) From d46d1028e25caeb653e9f99dd8439e7608d7b114 Mon Sep 17 00:00:00 2001 From: Sam Ghitelman Date: Thu, 10 Aug 2023 10:56:42 -0400 Subject: [PATCH 08/10] [RPO-3405] Add browserLanguage to request meta object --- modules/concertBidAdapter.js | 1 + test/spec/modules/concertBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index a25d9086446..411ab54c2bb 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -41,6 +41,7 @@ export const spec = { prebidVersion: '$prebid.version$', pageUrl: bidderRequest.refererInfo.page, screen: [window.screen.width, window.screen.height].join('x'), + browserLanguage: window.navigator.language, debug: debugTurnedOn(), uid: getUid(bidderRequest, validBidRequests), optedOut: hasOptedOutOfPersonalization(), diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index f5c807b4703..ba14fdb0388 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -112,7 +112,7 @@ describe('ConcertAdapter', function () { expect(payload).to.have.property('meta'); expect(payload).to.have.property('slots'); - const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent']; + const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent', 'browserLanguage']; const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType']; metaRequiredFields.forEach(function(field) { From aa9f8b0933459287956274e45811bad873b87478 Mon Sep 17 00:00:00 2001 From: Sam Ghitelman Date: Thu, 28 Sep 2023 13:25:27 -0400 Subject: [PATCH 09/10] ConcertBidAdapter: Add TDID (#20) * Add tdid to meta object * Fix null handling and add tests --- modules/concertBidAdapter.js | 9 +++++ test/spec/modules/concertBidAdapter_spec.js | 40 ++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 7042c895bfb..bc2c4555c54 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -49,6 +49,7 @@ export const spec = { uspConsent: bidderRequest.uspConsent, gdprConsent: bidderRequest.gdprConsent, gppConsent: bidderRequest.gppConsent, + tdid: getTdid(bidderRequest, validBidRequests), } }; @@ -263,3 +264,11 @@ function getOffset(el) { }; } } + +function getTdid(bidderRequest, validBidRequests) { + if (hasOptedOutOfPersonalization() || !consentAllowsPpid(bidderRequest)) { + return null; + } + + return deepAccess(validBidRequests[0], 'userId.tdid') || null; +} diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 4a6a4f2ba60..40294deb26d 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -116,7 +116,20 @@ describe('ConcertAdapter', function () { expect(payload).to.have.property('meta'); expect(payload).to.have.property('slots'); - const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent', 'browserLanguage']; + const metaRequiredFields = [ + 'prebidVersion', + 'pageUrl', + 'screen', + 'debug', + 'uid', + 'optedOut', + 'adapterVersion', + 'uspConsent', + 'gdprConsent', + 'gppConsent', + 'browserLanguage', + 'tdid' + ]; const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType']; metaRequiredFields.forEach(function(field) { @@ -199,6 +212,31 @@ describe('ConcertAdapter', function () { expect(slot.offsetCoordinates.x).to.equal(100) expect(slot.offsetCoordinates.y).to.equal(0) }) + + it('should not pass along tdid if the user has opted out', function() { + storage.setDataInLocalStorage('c_nap', 'true'); + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + + expect(payload.meta.tdid).to.be.null; + }); + + it('should not pass along tdid if USP consent disallows', function() { + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, { ...bidRequest, uspConsent: '1YY' }); + const payload = JSON.parse(request.data); + + expect(payload.meta.tdid).to.be.null; + }); + + it('should pass along tdid if the user has not opted out', function() { + storage.removeDataFromLocalStorage('c_nap', 'true'); + const tdid = '123abc'; + const bidRequestsWithTdid = [{ ...bidRequests[0], userId: { tdid } }] + const request = spec.buildRequests(bidRequestsWithTdid, bidRequest); + const payload = JSON.parse(request.data); + expect(payload.meta.tdid).to.equal(tdid); + }); }); describe('spec.interpretResponse', function() { From e197133f54bd8e6abf631645abc962581a309eef Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Tue, 28 May 2024 09:35:00 -0600 Subject: [PATCH 10/10] Concert Bid Adapter: Add dealId Property to Bid Responses (#22) * adds dealid property to bid responses * updates tests * use first bid for tests * adds dealid at the correct level --- modules/concertBidAdapter.js | 3 ++- test/spec/modules/concertBidAdapter_spec.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index bd738a39bba..12dba194844 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -128,7 +128,8 @@ export const spec = { meta: { advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, creativeId: bid.creativeId, netRevenue: bid.netRevenue, - currency: bid.currency + currency: bid.currency, + ...(bid.dealid && { dealId: bid.dealid }), }; }); diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 0a76ed3e62d..2fb43236081 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -249,6 +249,22 @@ describe('ConcertAdapter', function () { }); }); + it('should include dealId when present in bidResponse', function() { + const bids = spec.interpretResponse({ + body: { + bids: [ + { ...bidResponse.body.bids[0], dealid: 'CON-123' } + ] + } + }, bidRequest); + expect(bids[0]).to.have.property('dealId'); + }); + + it('should exclude dealId when absent in bidResponse', function() { + const bids = spec.interpretResponse(bidResponse, bidRequest); + expect(bids[0]).to.not.have.property('dealId'); + }); + it('should return empty bids if there is no response from server', function() { const bids = spec.interpretResponse({ body: null }, bidRequest); expect(bids).to.have.lengthOf(0);