-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Permutive RTD Module: migrate magnite to ortb2 #9555
Changes from 7 commits
6822cee
177c2c0
87f0170
5880b11
f65b3ac
a9c609f
983f807
7aa5fa2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,12 +8,16 @@ | |
import {getGlobal} from '../src/prebidGlobal.js'; | ||
import {submodule} from '../src/hook.js'; | ||
import {getStorageManager} from '../src/storageManager.js'; | ||
import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse} from '../src/utils.js'; | ||
import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse, prefixLog} from '../src/utils.js'; | ||
import {includes} from '../src/polyfill.js'; | ||
|
||
const MODULE_NAME = 'permutive' | ||
|
||
const logger = prefixLog('[PermutiveRTD]') | ||
|
||
export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd' | ||
export const PERMUTIVE_STANDARD_KEYWORD = 'p_standard' | ||
export const PERMUTIVE_CUSTOM_COHORTS_KEYWORD = 'permutive' | ||
export const PERMUTIVE_STANDARD_AUD_KEYWORD = 'p_standard_aud' | ||
|
||
export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) | ||
|
@@ -24,30 +28,6 @@ function init(moduleConfig, userConsent) { | |
return true | ||
} | ||
|
||
/** | ||
* Set segment targeting from cache and then try to wait for Permutive | ||
* to initialise to get realtime segment targeting | ||
* @param {Object} reqBidsConfigObj | ||
* @param {function} callback - Called when submodule is done | ||
* @param {customModuleConfig} reqBidsConfigObj - Publisher config for module | ||
*/ | ||
export function initSegments (reqBidsConfigObj, callback, customModuleConfig) { | ||
const permutiveOnPage = isPermutiveOnPage() | ||
const moduleConfig = getModuleConfig(customModuleConfig) | ||
const segmentData = getSegments(moduleConfig.params.maxSegs) | ||
|
||
setSegments(reqBidsConfigObj, moduleConfig, segmentData) | ||
|
||
if (moduleConfig.waitForIt && permutiveOnPage) { | ||
window.permutive.ready(function () { | ||
setSegments(reqBidsConfigObj, moduleConfig, segmentData) | ||
callback() | ||
}, 'realtime') | ||
} else { | ||
callback() | ||
} | ||
} | ||
|
||
function liftIntoParams(params) { | ||
return isPlainObject(params) ? { params } : {} | ||
} | ||
|
@@ -109,15 +89,13 @@ export function getModuleConfig(customModuleConfig) { | |
|
||
/** | ||
* Sets ortb2 config for ac bidders | ||
* @param {Object} bidderOrtb2 | ||
* @param {Object} bidderOrtb2 - The ortb2 object for the all bidders | ||
* @param {Object} customModuleConfig - Publisher config for module | ||
*/ | ||
export function setBidderRtb (bidderOrtb2, customModuleConfig) { | ||
const moduleConfig = getModuleConfig(customModuleConfig) | ||
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 segmentData = getSegments(maxSegs) | ||
|
||
const ssps = segmentData?.ssp?.ssps ?? [] | ||
const sspCohorts = segmentData?.ssp?.cohorts ?? [] | ||
|
@@ -126,28 +104,35 @@ export function setBidderRtb (bidderOrtb2, customModuleConfig) { | |
bidders.forEach(function (bidder) { | ||
const currConfig = { ortb2: bidderOrtb2[bidder] || {} } | ||
|
||
let cohorts = [] | ||
|
||
const isAcBidder = acBidders.indexOf(bidder) > -1 | ||
const isSspBidder = ssps.indexOf(bidder) > -1 | ||
if (isAcBidder) { | ||
cohorts = segmentData.ac | ||
} | ||
|
||
let cohorts = [] | ||
if (isAcBidder) cohorts = segmentData.ac | ||
if (isSspBidder) cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) | ||
const isSspBidder = ssps.indexOf(bidder) > -1 | ||
if (isSspBidder) { | ||
cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) | ||
} | ||
|
||
const nextConfig = updateOrtbConfig(currConfig, cohorts, sspCohorts, transformationConfigs) | ||
bidderOrtb2[bidder] = nextConfig.ortb2; | ||
const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, transformationConfigs, segmentData) | ||
bidderOrtb2[bidder] = nextConfig.ortb2 | ||
}) | ||
} | ||
|
||
/** | ||
* Updates `user.data` object in existing bidder config with Permutive segments | ||
* @param string bidder - The bidder | ||
* @param {Object} currConfig - Current bidder config | ||
* @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 {string[]} segmentIDs - Permutive segment IDs | ||
* @param {string[]} sspSegmentIDs - Permutive SSP segment IDs | ||
* @param {Object} segmentData - The segments available for targeting | ||
* @return {Object} Merged ortb2 object | ||
*/ | ||
function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformationConfigs) { | ||
function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transformationConfigs, segmentData) { | ||
const name = 'permutive.com' | ||
|
||
const permutiveUserData = { | ||
|
@@ -174,6 +159,21 @@ function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformation | |
const updatedUserKeywords = (currentUserKeywords === '') ? keywords : `${currentUserKeywords},${keywords}` | ||
deepSetValue(ortbConfig, 'ortb2.user.keywords', updatedUserKeywords) | ||
|
||
// Set bidder specific extensions | ||
if (bidder === 'rubicon') { | ||
if (segmentIDs.length > 0) { | ||
deepSetValue(ortbConfig, 'ortb2.user.ext.data.' + PERMUTIVE_STANDARD_KEYWORD, segmentIDs) | ||
} | ||
|
||
if (segmentData?.rubicon?.length > 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question - why isn't this such that each There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the above comment should answer this, it's a specific extension for Rubicon. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a rules violation; you are not allowed to have a "specific extension for Rubicon." |
||
deepSetValue(ortbConfig, 'ortb2.user.ext.data.' + PERMUTIVE_CUSTOM_COHORTS_KEYWORD, segmentData.rubicon.map(String)) | ||
} | ||
|
||
logger.logInfo(`Extending ortb2.user.ext.data for ${bidder}`, deepAccess(ortbConfig, 'ortb2.user.ext.data')) | ||
} | ||
|
||
logger.logInfo(`Updating ortb2 config for ${bidder}`, ortbConfig) | ||
|
||
return ortbConfig | ||
} | ||
|
||
|
@@ -262,17 +262,6 @@ function getDefaultBidderFn (bidder) { | |
|
||
return bid | ||
}, | ||
rubicon: function (bid, data, acEnabled) { | ||
if (isPStandardTargetingEnabled(data, acEnabled)) { | ||
const segments = pStandardTargeting(data, acEnabled) | ||
deepSetValue(bid, 'params.visitor.p_standard', segments) | ||
} | ||
if (data.rubicon && data.rubicon.length) { | ||
deepSetValue(bid, 'params.visitor.permutive', data.rubicon.map(String)) | ||
} | ||
|
||
return bid | ||
}, | ||
Comment on lines
-265
to
-275
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the first special handling removed. The others are planned in follow up PRs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. awesome ty! |
||
ozone: function (bid, data, acEnabled) { | ||
if (isPStandardTargetingEnabled(data, acEnabled)) { | ||
const segments = pStandardTargeting(data, acEnabled) | ||
|
@@ -283,7 +272,12 @@ function getDefaultBidderFn (bidder) { | |
} | ||
} | ||
|
||
return bidderMap[bidder] | ||
// On no default bidder just return the same bid as passed in | ||
function bidIdentity(bid) { | ||
return bid | ||
} | ||
|
||
return bidderMap[bidder] || bidIdentity | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does this need to change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we don't break the public API of our module. |
||
} | ||
|
||
/** | ||
|
@@ -383,17 +377,55 @@ function iabSegmentId(permutiveSegmentId, iabIds) { | |
return iabIds[permutiveSegmentId] || unknownIabSegmentId | ||
} | ||
|
||
/** | ||
* Pull the latest configuration and cohort information and update accordingly. | ||
* | ||
* @param reqBidsConfigObj - Bidder provided config for request | ||
* @param customModuleConfig - Publisher provide config | ||
*/ | ||
export function readAndSetCohorts(reqBidsConfigObj, moduleConfig) { | ||
const segmentData = getSegments(deepAccess(moduleConfig, 'params.maxSegs')) | ||
|
||
makeSafe(function () { | ||
// Legacy route with custom parameters | ||
// ACK policy violation, in process of removing | ||
setSegments(reqBidsConfigObj, moduleConfig, segmentData) | ||
}); | ||
|
||
makeSafe(function () { | ||
// Route for bidders supporting ORTB2 | ||
setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, moduleConfig, segmentData) | ||
}) | ||
} | ||
|
||
let permutiveSDKInRealTime = false | ||
|
||
/** @type {RtdSubmodule} */ | ||
export const permutiveSubmodule = { | ||
name: MODULE_NAME, | ||
getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { | ||
const completeBidRequestData = () => { | ||
logger.logInfo(`Request data updated`) | ||
callback() | ||
} | ||
|
||
const moduleConfig = getModuleConfig(customModuleConfig) | ||
|
||
readAndSetCohorts(reqBidsConfigObj, moduleConfig) | ||
|
||
makeSafe(function () { | ||
// Legacy route with custom parameters | ||
initSegments(reqBidsConfigObj, callback, customModuleConfig) | ||
}); | ||
makeSafe(function () { | ||
// Route for bidders supporting ORTB2 | ||
setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, customModuleConfig) | ||
if (permutiveSDKInRealTime || !(moduleConfig.waitForIt && isPermutiveOnPage())) { | ||
return completeBidRequestData() | ||
} | ||
|
||
window.permutive.ready(function () { | ||
logger.logInfo(`SDK is realtime, updating cohorts`) | ||
permutiveSDKInRealTime = true | ||
readAndSetCohorts(reqBidsConfigObj, getModuleConfig(customModuleConfig)) | ||
completeBidRequestData() | ||
}, 'realtime') | ||
|
||
logger.logInfo(`Registered cohort update when SDK is realtime`) | ||
}) | ||
}, | ||
init: init | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this may be a dumb question - why is this set only for rubicon? with my naivete I'd expect it to be standardized and useful for everyone, or superfluous and the responsibility of rubicon to deduce from
user.data
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's specific for Rubicon because this is how they read from the ortb2 object and it falls under
ortb2.user.ext.data
, where the.ext
being they key part of the path. Following the OpenRTB 2.5 specification, we must place bidder-specific extensions in.ext
properties. Since it's bidder specific, it doesn't make sense to add it for anyone else as their contracts with the ortb2 object may be different under the.ext
paths.I hope that makes sense 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also worth pointing out, this data already exists under
ortb2.user.data
. We're just adding it into additional places in the ortb2 object for a bidder. There are follow up PRs coming that will be placing data in more places in ortb2 that will be standardised and available to all specified bidders.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal is, the word 'rubicon' should not appear in your code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks both, I updated it to be available for all bidders in this case.