diff --git a/modules/userId/index.js b/modules/userId/index.js index ec0e5a16db2..7e7b48287f9 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -142,7 +142,8 @@ import { isPlainObject, logError, logInfo, - logWarn + logWarn, + deepEqual, } from '../../src/utils.js'; import {getPPID as coreGetPPID} from '../../src/adserver.js'; import {defer, PbPromise, delay} from '../../src/utils/promise.js'; @@ -1127,35 +1128,53 @@ function updateEIDConfig(submodules) { ).forEach(([key, submodules]) => EID_CONFIG.set(key, submodules[0].eids[key])) } +export function generateSubmoduleContainers(options, configs, prevSubmodules = submodules, registry = submoduleRegistry) { + const {autoRefresh, retainConfig} = options; + return registry + .reduce((acc, submodule) => { + const {name, aliasName} = submodule; + const matchesName = (query) => [name, aliasName].some(value => value?.toLowerCase() === query.toLowerCase()); + const submoduleConfig = configs.find((configItem) => matchesName(configItem.name)); + + if (!submoduleConfig) { + if (!retainConfig) return acc; + const previousSubmodule = prevSubmodules.find(prevSubmodules => matchesName(prevSubmodules.config.name)); + return previousSubmodule ? [...acc, previousSubmodule] : acc; + } + + const newSubmoduleContainer = { + submodule, + config: { + ...submoduleConfig, + name: submodule.name + }, + callback: undefined, + idObj: undefined, + storageMgr: getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: submoduleConfig.name}) + }; + + if (autoRefresh) { + const previousSubmodule = prevSubmodules.find(prevSubmodules => matchesName(prevSubmodules.config.name)); + newSubmoduleContainer.refreshIds = !previousSubmodule || !deepEqual(newSubmoduleContainer.config, previousSubmodule.config); + } + + return [...acc, newSubmoduleContainer]; + }, []); +} + /** * update submodules by validating against existing configs and storage types */ -function updateSubmodules() { +function updateSubmodules(options = {}) { updateEIDConfig(submoduleRegistry); const configs = getValidSubmoduleConfigs(configRegistry); if (!configs.length) { return; } - // do this to avoid reprocessing submodules - // TODO: the logic does not match the comment - addedSubmodules is always a copy of submoduleRegistry - // (if it did it would not be correct - it's not enough to find new modules, as others may have been removed or changed) - const addedSubmodules = submoduleRegistry.filter(i => !find(submodules, j => j.name === i.name)); + const updatedContainers = generateSubmoduleContainers(options, configs); submodules.splice(0, submodules.length); - // find submodule and the matching configuration, if found create and append a SubmoduleContainer - addedSubmodules.map(i => { - const submoduleConfig = find(configs, j => j.name && (j.name.toLowerCase() === i.name.toLowerCase() || - (i.aliasName && j.name.toLowerCase() === i.aliasName.toLowerCase()))); - if (submoduleConfig && i.name !== submoduleConfig.name) submoduleConfig.name = i.name; - return submoduleConfig ? { - submodule: i, - config: submoduleConfig, - callback: undefined, - idObj: undefined, - storageMgr: getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: submoduleConfig.name}), - } : null; - }).filter(submodule => submodule !== null) - .forEach((sm) => submodules.push(sm)); + submodules.push(...updatedContainers); if (submodules.length) { if (!addedStartAuctionHook()) { @@ -1251,12 +1270,17 @@ export function init(config, {mkDelay = delay} = {}) { if (userSync) { ppidSource = userSync.ppid; if (userSync.userIds) { + const {autoRefresh = false, retainConfig = true} = userSync; configRegistry = userSync.userIds; syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : USERSYNC_DEFAULT_CONFIG.syncDelay auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : USERSYNC_DEFAULT_CONFIG.auctionDelay; - updateSubmodules(); + updateSubmodules({retainConfig, autoRefresh}); updateIdPriority(userSync.idPriority, submoduleRegistry); initIdSystem({ready: true}); + const submodulesToRefresh = submodules.filter(item => item.refreshIds); + if (submodulesToRefresh.length) { + refreshUserIds({submoduleNames: submodulesToRefresh.map(item => item.name)}); + } } } }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index f423a1b064c..f4ddd438480 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -38,6 +38,7 @@ import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; import {ACTIVITY_ENRICH_EIDS} from '../../../src/activities/activities.js'; import {ACTIVITY_PARAM_COMPONENT_NAME, ACTIVITY_PARAM_COMPONENT_TYPE} from '../../../src/activities/params.js'; import {extractEids} from '../../../modules/prebidServerBidAdapter/bidderConfig.js'; +import {generateSubmoduleContainers} from '../../../modules/userId/index.js'; import { registerActivityControl } from '../../../src/activities/rules.js'; import { addIdData } from '../../../modules/userId/index.js'; @@ -3221,4 +3222,72 @@ describe('User ID', function () { }); }) }); + + describe('generateSubmoduleContainers', () => { + it('should properly map registry to submodule containers for empty previous submodule containers', () => { + const previousSubmoduleContainers = []; + const submoduleRegistry = [ + sharedIdSystemSubmodule, + createMockIdSubmodule('mockId1Module', { id: { uid2: { id: 'uid2_value' } } }, null, null), + createMockIdSubmodule('mockId2Module', { id: { uid2: { id: 'uid2_value' } } }, null, null), + ]; + const configRegistry = [{ name: 'sharedId' }]; + const result = generateSubmoduleContainers({}, configRegistry, previousSubmoduleContainers, submoduleRegistry); + expect(result).to.have.lengthOf(1); + expect(result[0].submodule.name).to.eql('sharedId'); + }); + + it('should properly map registry to submodule containers for non-empty previous submodule containers', () => { + const previousSubmoduleContainers = [ + {submodule: {name: 'notSharedId'}, config: {name: 'notSharedId'}}, + {submodule: {name: 'notSharedId2'}, config: {name: 'notSharedId2'}}, + ]; + const submoduleRegistry = [ + sharedIdSystemSubmodule, + createMockIdSubmodule('mockId1Module', { id: { uid2: { id: 'uid2_value' } } }, null, null), + createMockIdSubmodule('mockId2Module', { id: { uid2: { id: 'uid2_value' } } }, null, null), + ]; + const configRegistry = [{ name: 'sharedId' }]; + const result = generateSubmoduleContainers({}, configRegistry, previousSubmoduleContainers, submoduleRegistry); + expect(result).to.have.lengthOf(1); + expect(result[0].submodule.name).to.eql('sharedId'); + }); + + it('should properly map registry to submodule containers for retainConfig flag', () => { + const previousSubmoduleContainers = [ + {submodule: {name: 'shouldBeKept'}, config: {name: 'shouldBeKept'}}, + ]; + const submoduleRegistry = [ + sharedIdSystemSubmodule, + createMockIdSubmodule('shouldBeKept', { id: { uid2: { id: 'uid2_value' } } }, null, null), + ]; + const configRegistry = [{ name: 'sharedId' }]; + const result = generateSubmoduleContainers({retainConfig: true}, configRegistry, previousSubmoduleContainers, submoduleRegistry); + expect(result).to.have.lengthOf(2); + expect(result[0].submodule.name).to.eql('sharedId'); + expect(result[1].submodule.name).to.eql('shouldBeKept'); + }); + + it('should properly map registry to submodule containers for autoRefresh flag', () => { + const previousSubmoduleContainers = [ + {submodule: {name: 'modified'}, config: {name: 'modified', auctionDelay: 300}}, + {submodule: {name: 'unchanged'}, config: {name: 'unchanged', auctionDelay: 300}}, + ]; + const submoduleRegistry = [ + createMockIdSubmodule('modified', { id: { uid2: { id: 'uid2_value' } } }, null, null), + createMockIdSubmodule('new', { id: { uid2: { id: 'uid2_value' } } }, null, null), + createMockIdSubmodule('unchanged', { id: { uid2: { id: 'uid2_value' } } }, null, null), + ]; + const configRegistry = [ + {name: 'modified', auctionDelay: 200}, + {name: 'new'}, + {name: 'unchanged', auctionDelay: 300}, + ]; + const result = generateSubmoduleContainers({autoRefresh: true}, configRegistry, previousSubmoduleContainers, submoduleRegistry); + expect(result).to.have.lengthOf(3); + const itemsWithRefreshIds = result.filter(item => item.refreshIds); + const submoduleNames = itemsWithRefreshIds.map(item => item.submodule.name); + expect(submoduleNames).to.deep.eql(['modified', 'new']); + }); + }); });