From 2ef5991ae982a65af03e3770d6d34cb3f1833b72 Mon Sep 17 00:00:00 2001 From: Konstantin Mikhalyov Date: Fri, 1 Nov 2024 14:08:48 +0200 Subject: [PATCH 1/7] userId module: temp fix empty bid.userIdAsEids --- modules/userId/index.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index d0299427603..e70df2dfc2a 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -1086,6 +1086,7 @@ function updateSubmodules() { updateEIDConfig(submoduleRegistry); const configs = getValidSubmoduleConfigs(configRegistry); if (!configs.length) { + addUserIdHook(); return; } // do this to avoid reprocessing submodules @@ -1110,16 +1111,20 @@ function updateSubmodules() { .forEach((sm) => submodules.push(sm)); if (submodules.length) { - if (!addedUserIdHook) { - startAuction.before(startAuctionHook, 100) // use higher priority than dataController / rtd - adapterManager.callDataDeletionRequest.before(requestDataDeletion); - coreGetPPID.after((next) => next(getPPID())); - addedUserIdHook = true; - } + addUserIdHook(); logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); } } +function addUserIdHook() { + if (!addedUserIdHook) { + startAuction.before(startAuctionHook, 100); // use higher priority than dataController / rtd + adapterManager.callDataDeletionRequest.before(requestDataDeletion); + coreGetPPID.after((next) => next(getPPID())); + addedUserIdHook = true; + } +} + /** * This function will update the idPriority according to the provided configuration * @param {Object} idPriorityConfig From ffe0b9d28f8f2162aaeef6eedf562220320bb6a5 Mon Sep 17 00:00:00 2001 From: Konstantin Mikhalyov Date: Fri, 1 Nov 2024 18:27:48 +0200 Subject: [PATCH 2/7] userId module: fix empty bid.userIdAsEids --- modules/userId/index.js | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index e70df2dfc2a..e103851f4c6 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -676,23 +676,34 @@ function getPPID(eids = getUserIdsAsEids() || []) { * Hook is executed before adapters, but after consentManagement. Consent data is requied because * this module requires GDPR consent with Purpose #1 to save data locally. * The two main actions handled by the hook are: - * 1. check gdpr consentData and handle submodule initialization. - * 2. append user id data (loaded from cookied/html or from the getId method) to bids to be accessed in adapters. + * 1. check gdpr consentData. + * 2. handle submodule initialization. * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export const startAuctionHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout, getIds = getUserIdsAsync} = {}) { +export const initUserIdHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout, getIds = getUserIdsAsync} = {}) { GreedyPromise.race([ getIds().catch(() => null), delay(auctionDelay) ]).then(() => { - addIdData(reqBidsConfigObj); - uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); }); }); +/** + * Hook is executed before adapters, but after initUserIdHook. + * Append user id data (loaded from cookies/html or from the getId method) to bids to be accessed in adapters. + * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js + */ +export const addUserIdHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj) { + addIdData(reqBidsConfigObj); + uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); + // calling fn allows prebid to continue processing + fn.call(this, reqBidsConfigObj); +}); + /** * This function will be exposed in global-name-space so that userIds stored by Prebid UserId module can be used by external codes as well. * Simple use case will be passing these UserIds to A9 wrapper solution @@ -1085,8 +1096,10 @@ function updateEIDConfig(submodules) { function updateSubmodules() { updateEIDConfig(submoduleRegistry); const configs = getValidSubmoduleConfigs(configRegistry); + if (!startAuction.getHooks({type: 'before', hook: addUserIdHook}).length) { + startAuction.before(addUserIdHook, 100); // use lower priority than initUserIdHook + } if (!configs.length) { - addUserIdHook(); return; } // do this to avoid reprocessing submodules @@ -1111,20 +1124,16 @@ function updateSubmodules() { .forEach((sm) => submodules.push(sm)); if (submodules.length) { - addUserIdHook(); + if (!addedUserIdHook) { + startAuction.before(initUserIdHook, 101); // use higher priority than dataController / rtd + adapterManager.callDataDeletionRequest.before(requestDataDeletion); + coreGetPPID.after((next) => next(getPPID())); + addedUserIdHook = true; + } logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); } } -function addUserIdHook() { - if (!addedUserIdHook) { - startAuction.before(startAuctionHook, 100); // use higher priority than dataController / rtd - adapterManager.callDataDeletionRequest.before(requestDataDeletion); - coreGetPPID.after((next) => next(getPPID())); - addedUserIdHook = true; - } -} - /** * This function will update the idPriority according to the provided configuration * @param {Object} idPriorityConfig From c9fa222277820270690608686f16aa2bee798216 Mon Sep 17 00:00:00 2001 From: Konstantin Mikhalyov Date: Thu, 7 Nov 2024 16:23:46 +0200 Subject: [PATCH 3/7] userId module: fix empty bid.userIdAsEids --- modules/userId/index.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index e103851f4c6..ce288df333b 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -676,30 +676,30 @@ function getPPID(eids = getUserIdsAsEids() || []) { * Hook is executed before adapters, but after consentManagement. Consent data is requied because * this module requires GDPR consent with Purpose #1 to save data locally. * The two main actions handled by the hook are: - * 1. check gdpr consentData. - * 2. handle submodule initialization. + * 1. check gdpr consentData and handle submodule initialization. + * 2. append user id data (loaded from cookied/html or from the getId method) to bids to be accessed in adapters. * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export const initUserIdHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout, getIds = getUserIdsAsync} = {}) { +export const startAuctionHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout, getIds = getUserIdsAsync} = {}) { GreedyPromise.race([ getIds().catch(() => null), delay(auctionDelay) ]).then(() => { + addIdData(reqBidsConfigObj); + uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); }); }); /** - * Hook is executed before adapters, but after initUserIdHook. - * Append user id data (loaded from cookies/html or from the getId method) to bids to be accessed in adapters. - * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * Append user id data from config to bids to be accessed in adapters when there are no submodules. * @param {function} fn required; The next function in the chain, used by hook.js + * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. */ -export const addUserIdHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj) { +export const baseStartAuctionHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj) { addIdData(reqBidsConfigObj); - uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); }); @@ -1096,10 +1096,10 @@ function updateEIDConfig(submodules) { function updateSubmodules() { updateEIDConfig(submoduleRegistry); const configs = getValidSubmoduleConfigs(configRegistry); - if (!startAuction.getHooks({type: 'before', hook: addUserIdHook}).length) { - startAuction.before(addUserIdHook, 100); // use lower priority than initUserIdHook - } if (!configs.length) { + if (!startAuction.getHooks({hook: baseStartAuctionHook}).length) { + startAuction.before(baseStartAuctionHook, 100); + } return; } // do this to avoid reprocessing submodules @@ -1125,7 +1125,8 @@ function updateSubmodules() { if (submodules.length) { if (!addedUserIdHook) { - startAuction.before(initUserIdHook, 101); // use higher priority than dataController / rtd + startAuction.getHooks({hook: baseStartAuctionHook}).remove(); + startAuction.before(startAuctionHook, 100) // use higher priority than dataController / rtd adapterManager.callDataDeletionRequest.before(requestDataDeletion); coreGetPPID.after((next) => next(getPPID())); addedUserIdHook = true; From 467b0d6aa215956fc15e9ba562aa0bc861d68960 Mon Sep 17 00:00:00 2001 From: Konstantin Mikhalyov Date: Tue, 19 Nov 2024 18:29:52 +0200 Subject: [PATCH 4/7] userId module: fix empty bid.userIdAsEids --- modules/userId/index.js | 11 ++++++----- test/spec/modules/userId_spec.js | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index ce288df333b..e25d02c5f8e 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -698,8 +698,9 @@ export const startAuctionHook = timedAuctionHook('userId', function requestBidsH * @param {function} fn required; The next function in the chain, used by hook.js * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. */ -export const baseStartAuctionHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj) { +export const addUserIdsHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj) { addIdData(reqBidsConfigObj); + uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); }); @@ -1097,9 +1098,6 @@ function updateSubmodules() { updateEIDConfig(submoduleRegistry); const configs = getValidSubmoduleConfigs(configRegistry); if (!configs.length) { - if (!startAuction.getHooks({hook: baseStartAuctionHook}).length) { - startAuction.before(baseStartAuctionHook, 100); - } return; } // do this to avoid reprocessing submodules @@ -1125,7 +1123,7 @@ function updateSubmodules() { if (submodules.length) { if (!addedUserIdHook) { - startAuction.getHooks({hook: baseStartAuctionHook}).remove(); + startAuction.getHooks({hook: addUserIdsHook}).remove(); startAuction.before(startAuctionHook, 100) // use higher priority than dataController / rtd adapterManager.callDataDeletionRequest.before(requestDataDeletion); coreGetPPID.after((next) => next(getPPID())); @@ -1224,8 +1222,11 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { updateSubmodules(); updateIdPriority(userSync.idPriority, submoduleRegistry); initIdSystem({ready: true}); + return; } } + // Add ortb2.user.ext.eids even if 0 submodules are added + startAuction.before(addUserIdsHook, 100); // use higher priority than dataController / rtd }); // exposing getUserIds function in global-name-space so that userIds stored in Prebid can be used by external codes. diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 6ebe533e260..70a3ba46ab7 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -8,6 +8,7 @@ import { init, PBJS_USER_ID_OPTOUT_NAME, startAuctionHook, + addUserIdsHook, requestDataDeletion, setStoredValue, setSubmoduleRegistry, @@ -2067,6 +2068,28 @@ describe('User ID', function () { }, {adUnits}); }); + it('should add global user id', function (done) { + addUserIdsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userIdAsEids.0.source'); + expect(bid).to.have.deep.nested.property('userIdAsEids.0.uids.0.id'); + expect(bid.userIdAsEids[0].source).to.equal('example.com'); + expect(bid.userIdAsEids[0].uids[0].id).to.equal('1234'); + }); + }); + done(); + }, { + adUnits, + ortb2Fragments: { + global: { + user: {ext: {eids: [{source: 'example.com', uids: [{id: '1234', atype: 3}]}]}} + }, + bidder: {} + } + }); + }); + describe('activity controls', () => { let isAllowed; const MOCK_IDS = ['mockId1', 'mockId2'] From 8adb84ff0b99e2a34a268d63d71f52a0074b9bf2 Mon Sep 17 00:00:00 2001 From: Konstantin Mikhalyov Date: Thu, 21 Nov 2024 12:52:07 +0200 Subject: [PATCH 5/7] userId module: fix tests --- modules/userId/index.js | 1 - test/spec/modules/userId_spec.js | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index e25d02c5f8e..c280a6bbbd1 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -700,7 +700,6 @@ export const startAuctionHook = timedAuctionHook('userId', function requestBidsH */ export const addUserIdsHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj) { addIdData(reqBidsConfigObj); - uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 70a3ba46ab7..ce4a9d268b6 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -28,6 +28,7 @@ import {sharedIdSystemSubmodule} from 'modules/sharedIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import * as mockGpt from '../integration/faker/googletag.js'; import 'src/prebid.js'; +import {startAuction} from 'src/prebid'; import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import {getPPID} from '../../../src/adserver.js'; @@ -176,6 +177,7 @@ describe('User ID', function () { afterEach(() => { sandbox.restore(); config.resetConfig(); + startAuction.getHooks({hook: addUserIdsHook}).remove(); }); after(() => { From 621cb1da15413d787a98e5f3f9cf8b1b5acca248 Mon Sep 17 00:00:00 2001 From: Konstantin Mikhalyov Date: Thu, 21 Nov 2024 13:51:38 +0200 Subject: [PATCH 6/7] userId module: prevent re-adding addUserIdsHook hook --- modules/userId/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index c280a6bbbd1..33b9b043a8c 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -1221,11 +1221,12 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { updateSubmodules(); updateIdPriority(userSync.idPriority, submoduleRegistry); initIdSystem({ready: true}); - return; } } - // Add ortb2.user.ext.eids even if 0 submodules are added - startAuction.before(addUserIdsHook, 100); // use higher priority than dataController / rtd + if (!addedUserIdHook && !startAuction.getHooks({hook: addUserIdsHook}).length) { + // Add ortb2.user.ext.eids even if 0 submodules are added + startAuction.before(addUserIdsHook, 100); // use higher priority than dataController / rtd + } }); // exposing getUserIds function in global-name-space so that userIds stored in Prebid can be used by external codes. From 5fd5daa9ec435a867dc789dba2c4f017a843eddb Mon Sep 17 00:00:00 2001 From: Konstantin Mikhalyov Date: Fri, 22 Nov 2024 16:02:51 +0200 Subject: [PATCH 7/7] userId module: mode adding addUserIdsHook, added test --- modules/userId/index.js | 22 ++++++---- test/spec/modules/userId_spec.js | 75 ++++++++++++++++++++++---------- 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 33b9b043a8c..2a9d1f27072 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -165,9 +165,6 @@ export const dep = { isAllowed: isActivityAllowed } -/** @type {boolean} */ -let addedUserIdHook = false; - /** @type {SubmoduleContainer[]} */ let submodules = []; @@ -704,6 +701,14 @@ export const addUserIdsHook = timedAuctionHook('userId', function requestBidsHoo fn.call(this, reqBidsConfigObj); }); +/** + * Is startAuctionHook added + * @returns {boolean} + */ +function addedStartAuctionHook() { + return !!startAuction.getHooks({hook: startAuctionHook}).length; +} + /** * This function will be exposed in global-name-space so that userIds stored by Prebid UserId module can be used by external codes as well. * Simple use case will be passing these UserIds to A9 wrapper solution @@ -1121,12 +1126,11 @@ function updateSubmodules() { .forEach((sm) => submodules.push(sm)); if (submodules.length) { - if (!addedUserIdHook) { + if (!addedStartAuctionHook()) { startAuction.getHooks({hook: addUserIdsHook}).remove(); startAuction.before(startAuctionHook, 100) // use higher priority than dataController / rtd adapterManager.callDataDeletionRequest.before(requestDataDeletion); coreGetPPID.after((next) => next(getPPID())); - addedUserIdHook = true; } logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); } @@ -1223,10 +1227,6 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { initIdSystem({ready: true}); } } - if (!addedUserIdHook && !startAuction.getHooks({hook: addUserIdsHook}).length) { - // Add ortb2.user.ext.eids even if 0 submodules are added - startAuction.before(addUserIdsHook, 100); // use higher priority than dataController / rtd - } }); // exposing getUserIds function in global-name-space so that userIds stored in Prebid can be used by external codes. @@ -1237,6 +1237,10 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { (getGlobal()).refreshUserIds = normalizePromise(refreshUserIds); (getGlobal()).getUserIdsAsync = normalizePromise(getUserIdsAsync); (getGlobal()).getUserIdsAsEidBySource = getUserIdsAsEidBySource; + if (!addedStartAuctionHook()) { + // Add ortb2.user.ext.eids even if 0 submodules are added + startAuction.before(addUserIdsHook, 100); // use higher priority than dataController / rtd + } } // init config update listener to start the application diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index ce4a9d268b6..c4f333e56ac 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -177,6 +177,7 @@ describe('User ID', function () { afterEach(() => { sandbox.restore(); config.resetConfig(); + startAuction.getHooks({hook: startAuctionHook}).remove(); startAuction.getHooks({hook: addUserIdsHook}).remove(); }); @@ -2070,28 +2071,6 @@ describe('User ID', function () { }, {adUnits}); }); - it('should add global user id', function (done) { - addUserIdsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userIdAsEids.0.source'); - expect(bid).to.have.deep.nested.property('userIdAsEids.0.uids.0.id'); - expect(bid.userIdAsEids[0].source).to.equal('example.com'); - expect(bid.userIdAsEids[0].uids[0].id).to.equal('1234'); - }); - }); - done(); - }, { - adUnits, - ortb2Fragments: { - global: { - user: {ext: {eids: [{source: 'example.com', uids: [{id: '1234', atype: 3}]}]}} - }, - bidder: {} - } - }); - }); - describe('activity controls', () => { let isAllowed; const MOCK_IDS = ['mockId1', 'mockId2'] @@ -2448,6 +2427,58 @@ describe('User ID', function () { }) }) }); + + describe('submodules not added', () => { + const eid = { + source: 'example.com', + uids: [{id: '1234', atype: 3}] + }; + let adUnits; + let startAuctionStub; + function saHook(fn, ...args) { + return startAuctionStub(...args); + } + beforeEach(() => { + adUnits = [{code: 'au1', bids: [{bidder: 'sampleBidder'}]}]; + startAuctionStub = sinon.stub(); + startAuction.before(saHook); + config.resetConfig(); + }); + afterEach(() => { + startAuction.getHooks({hook: saHook}).remove(); + }) + + it('addUserIdsHook', function (done) { + addUserIdsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userIdAsEids.0.source'); + expect(bid).to.have.deep.nested.property('userIdAsEids.0.uids.0.id'); + expect(bid.userIdAsEids[0].source).to.equal('example.com'); + expect(bid.userIdAsEids[0].uids[0].id).to.equal('1234'); + }); + }); + done(); + }, { + adUnits, + ortb2Fragments: { + global: {user: {ext: {eids: [eid]}}}, + bidder: {} + } + }); + }); + + it('should add userIdAsEids and merge ortb2.user.ext.eids even if no User ID submodules', () => { + init(config); + config.setConfig({ + ortb2: {user: {ext: {eids: [eid]}}} + }) + expect(startAuction.getHooks({hook: startAuctionHook}).length).equal(0); + expect(startAuction.getHooks({hook: addUserIdsHook}).length).equal(1); + $$PREBID_GLOBAL$$.requestBids({adUnits}); + sinon.assert.calledWith(startAuctionStub, sinon.match.hasNested('adUnits[0].bids[0].userIdAsEids[0]', eid)); + }); + }); }); describe('handles config with ESP configuration in user sync object', function() {