diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 09697b6f90..ab9a561aed 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -91,6 +91,11 @@ export function getModuleConfig(customModuleConfig) { acBidders: [], overwrites: {}, enforceVendorConsent: false, + bidders: { + msft: { + customCohorts: { source: 'ls', key: '_papns' } + }, + }, }, }, permutiveModuleConfig, @@ -108,6 +113,7 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { const acBidders = deepAccess(moduleConfig, 'params.acBidders') const maxSegs = deepAccess(moduleConfig, 'params.maxSegs') const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || [] + const biddersConfig = deepAccess(moduleConfig, 'params.bidders') || {} const ssps = segmentData?.ssp?.ssps ?? [] const sspCohorts = segmentData?.ssp?.cohorts ?? [] @@ -115,6 +121,7 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { const bidders = new Set([...acBidders, ...ssps]) bidders.forEach(function (bidder) { + const bidderConfig = biddersConfig[bidder] || {}; const currConfig = { ortb2: bidderOrtb2[bidder] || {} } let cohorts = [] @@ -129,11 +136,29 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) } - const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, topics, transformationConfigs, segmentData) + const customCohortsData = getCustomCohortsData(bidderConfig, bidder, segmentData, maxSegs) + + const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, topics, transformationConfigs, customCohortsData) bidderOrtb2[bidder] = nextConfig.ortb2 }) } +/** + * Resolves custom cohorts data for a bidder, reading from localStorage if configured. + * @param {Object} bidderCfg - Bidder-specific configuration from params.bidders + * @param {string} bidder - The bidder identifier + * @param {Object} segmentData - Segment data grouped by bidder or type + * @param {number} maxSegs - Maximum number of segments + * @return {string[]} Custom cohort IDs + */ +function getCustomCohortsData (bidderCfg, bidder, segmentData, maxSegs) { + const customCohorts = bidderCfg?.customCohorts + if (customCohorts?.source === 'ls' && customCohorts?.key) { + return makeSafe(() => readSegments(customCohorts.key, []).map(String).slice(0, maxSegs)) || [] + } + return deepAccess(segmentData, bidder) || [] +} + /** * Updates `user.data` object in existing bidder config with Permutive segments * @param {string} bidder - The bidder identifier @@ -143,14 +168,12 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { * @param {Object} topics - Privacy Sandbox Topics, keyed by IAB taxonomy version (600, 601, etc.) * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine * the transformations on user data to include the ORTB2 object - * @param {Object} segmentData - The segments available for targeting + * @param {string[]} customCohortsData - Custom cohort IDs for this bidder * @return {Object} Merged ortb2 object */ -function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, topics, transformationConfigs, segmentData) { +function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, topics, transformationConfigs, customCohortsData) { logger.logInfo(`Current ortb2 config`, { bidder, config: currConfig }) - const customCohortsData = deepAccess(segmentData, bidder) || [] - const name = 'permutive.com' const permutiveUserData = { diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 8ef632c75f..e8094142bd 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -48,6 +48,10 @@ as well as enabling settings for specific use cases mentioned above (e.g. acbidd | params.acBidders | String[] | An array of bidder codes to share cohorts with in certain versions of Prebid, see below | `[]` | | params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | | params.enforceVendorConsent | Boolean | When `true`, require TCF vendor consent for Permutive (vendor 361). See note below. | `false` | +| params.bidders | Object | Per-bidder configuration for custom cohort sources. Keys are bidder codes. | `{ msft: { customCohorts: { source: 'ls', key: '_papns' } } }` | +| params.bidders.\.customCohorts | Object | Custom cohorts source configuration for a specific bidder. | - | +| params.bidders.\.customCohorts.source | String | Storage type to read custom cohorts from. Currently only `'ls'` (localStorage) is supported. | - | +| params.bidders.\.customCohorts.key | String | The localStorage key to read custom cohorts from. | - | #### Consent @@ -81,11 +85,14 @@ Currently, bidders with known support for custom cohort targeting are: - Xandr - Magnite +- Microsoft (msft) When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. Permutive cohorts will be sent in the permutive key-value. +**Note:** Publishers migrating from the `appnexus` bidder to `msft` should add `msft` to the `acBidders` array (or via the Permutive platform). The module automatically reads custom cohorts for `msft` from the same localStorage key used by `appnexus`. + ### _Enabling Advertiser Cohorts_ diff --git a/test/spec/modules/permutiveCombined_spec.js b/test/spec/modules/permutiveCombined_spec.js index 1870c2161d..bb124260c4 100644 --- a/test/spec/modules/permutiveCombined_spec.js +++ b/test/spec/modules/permutiveCombined_spec.js @@ -128,6 +128,11 @@ describe('permutiveRtdProvider', function () { acBidders: [], overwrites: {}, enforceVendorConsent: false, + bidders: { + msft: { + customCohorts: { source: 'ls', key: '_papns' } + }, + }, }, }) @@ -689,6 +694,66 @@ describe('permutiveRtdProvider', function () { }) }) }) + + describe('bidders config with customCohorts', function () { + it('should read custom cohorts from localStorage for msft bidder using customCohorts config', function () { + const segmentsData = transformedTargeting() + const expectedAppnexusCohorts = segmentsData.appnexus + + const moduleConfig = { + name: 'permutive', + waitForIt: true, + params: { + acBidders: ['msft'], + maxSegs: 500, + bidders: { + msft: { + customCohorts: { source: 'ls', key: '_papns' } + } + } + } + } + const bidderConfig = {} + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + expect(bidderConfig['msft'].user.data).to.deep.include.members([ + { + name: 'permutive', + segment: expectedAppnexusCohorts.map(id => ({ id })), + }, + ]) + + expectedAppnexusCohorts.forEach(id => { + expect(bidderConfig['msft'].user.keywords).to.include(`permutive=${id}`) + }) + }) + + it('should fall back to segmentData lookup when customCohorts is not configured', function () { + const segmentsData = transformedTargeting() + + const moduleConfig = { + name: 'permutive', + waitForIt: true, + params: { + acBidders: ['appnexus'], + maxSegs: 500, + bidders: {} + } + } + const bidderConfig = {} + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + const expectedAppnexusCohorts = segmentsData.appnexus + expect(bidderConfig['appnexus'].user.data).to.deep.include.members([ + { + name: 'permutive', + segment: expectedAppnexusCohorts.map(id => ({ id })), + }, + ]) + }) + }) }) describe('Getting segments', function () {