Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Userid module: propagate ortb2.user.ext.eids to userIdsAsEids even if no UserId submodules #12477

Merged
merged 10 commits into from
Nov 23, 2024
30 changes: 25 additions & 5 deletions modules/userId/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,6 @@ export const dep = {
isAllowed: isActivityAllowed
}

/** @type {boolean} */
let addedUserIdHook = false;

/** @type {SubmoduleContainer[]} */
let submodules = [];

Expand Down Expand Up @@ -693,6 +690,25 @@ export const startAuctionHook = timedAuctionHook('userId', function requestBidsH
});
});

/**
* 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 addUserIdsHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj) {
addIdData(reqBidsConfigObj);
// calling fn allows prebid to continue processing
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
Expand Down Expand Up @@ -1110,11 +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));
}
Expand Down Expand Up @@ -1221,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
Expand Down
56 changes: 56 additions & 0 deletions test/spec/modules/userId_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
init,
PBJS_USER_ID_OPTOUT_NAME,
startAuctionHook,
addUserIdsHook,
requestDataDeletion,
setStoredValue,
setSubmoduleRegistry,
Expand All @@ -27,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';
Expand Down Expand Up @@ -175,6 +177,8 @@ describe('User ID', function () {
afterEach(() => {
sandbox.restore();
config.resetConfig();
startAuction.getHooks({hook: startAuctionHook}).remove();
startAuction.getHooks({hook: addUserIdsHook}).remove();
});

after(() => {
Expand Down Expand Up @@ -2423,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() {
Expand Down