UNPKG

mk9-prebid

Version:

Header Bidding Management Library

281 lines (247 loc) 8.16 kB
/** * This module adds permutive provider to the real time data module * The {@link module:modules/realTimeData} module is required * The module will add custom segment targeting to ad units of specific bidders * @module modules/permutiveRtdProvider * @requires module:modules/realTimeData */ import { getGlobal } from '../src/prebidGlobal.js' import { submodule } from '../src/hook.js' import { getStorageManager } from '../src/storageManager.js' import { deepSetValue, deepAccess, isFn, mergeDeep, logError } from '../src/utils.js' import { config } from '../src/config.js' import includes from 'core-js-pure/features/array/includes.js' const MODULE_NAME = 'permutive' export const storage = getStorageManager(null, MODULE_NAME) 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() } } /** * Merges segments into existing bidder config * @param {Object} customModuleConfig - Publisher config for module * @return {Object} Merged defatul and custom config */ function getModuleConfig (customModuleConfig) { return mergeDeep({ waitForIt: false, params: { maxSegs: 500, acBidders: [], overwrites: {} } }, customModuleConfig) } /** * Sets ortb2 config for ac bidders * @param {Object} auctionDetails * @param {Object} customModuleConfig - Publisher config for module */ export function setBidderRtb (auctionDetails, customModuleConfig) { const bidderConfig = config.getBidderConfig() const moduleConfig = getModuleConfig(customModuleConfig) const acBidders = deepAccess(moduleConfig, 'params.acBidders') const maxSegs = deepAccess(moduleConfig, 'params.maxSegs') const segmentData = getSegments(maxSegs) acBidders.forEach(function (bidder) { const currConfig = bidderConfig[bidder] || {} const nextConfig = mergeOrtbConfig(currConfig, segmentData) config.setBidderConfig({ bidders: [bidder], config: nextConfig }) }) } /** * Merges segments into existing bidder config * @param {Object} currConfig - Current bidder config * @param {Object} segmentData - Segment data * @return {Object} Merged ortb2 object */ function mergeOrtbConfig (currConfig, segmentData) { const segment = segmentData.ac.map(seg => { return { id: seg } }) const name = 'permutive.com' const ortbConfig = mergeDeep({}, currConfig) const currSegments = deepAccess(ortbConfig, 'ortb2.user.data') || [] const userSegment = currSegments .filter(el => el.name !== name) .concat({ name, segment }) deepSetValue(ortbConfig, 'ortb2.user.data', userSegment) return ortbConfig } /** * Set segments on bid request object * @param {Object} reqBidsConfigObj - Bid request object * @param {Object} moduleConfig - Module configuration * @param {Object} segmentData - Segment object */ function setSegments (reqBidsConfigObj, moduleConfig, segmentData) { const adUnits = (reqBidsConfigObj && reqBidsConfigObj.adUnits) || getGlobal().adUnits const utils = { deepSetValue, deepAccess, isFn, mergeDeep } const aliasMap = { appnexusAst: 'appnexus' } if (!adUnits) { return } adUnits.forEach(adUnit => { adUnit.bids.forEach(bid => { let { bidder } = bid if (typeof aliasMap[bidder] !== 'undefined') { bidder = aliasMap[bidder] } const acEnabled = isAcEnabled(moduleConfig, bidder) const customFn = getCustomBidderFn(moduleConfig, bidder) const defaultFn = getDefaultBidderFn(bidder) if (customFn) { customFn(bid, segmentData, acEnabled, utils, defaultFn) } else if (defaultFn) { defaultFn(bid, segmentData, acEnabled) } }) }) } /** * Catch and log errors * @param {function} fn - Function to safely evaluate */ function makeSafe (fn) { try { fn() } catch (e) { logError(e) } } function getCustomBidderFn (moduleConfig, bidder) { const overwriteFn = deepAccess(moduleConfig, `params.overwrites.${bidder}`) if (overwriteFn && isFn(overwriteFn)) { return overwriteFn } else { return null } } /** * Returns a function that receives a `bid` object, a `data` object and a `acEnabled` boolean * and which will set the right segment targeting keys for `bid` based on `data` and `acEnabled` * @param {string} bidder - Bidder name * @return {Object} Bidder function */ function getDefaultBidderFn (bidder) { const bidderMap = { appnexus: function (bid, data, acEnabled) { if (acEnabled && data.ac && data.ac.length) { deepSetValue(bid, 'params.keywords.p_standard', data.ac) } if (data.appnexus && data.appnexus.length) { deepSetValue(bid, 'params.keywords.permutive', data.appnexus) } return bid }, rubicon: function (bid, data, acEnabled) { if (acEnabled && data.ac && data.ac.length) { deepSetValue(bid, 'params.visitor.p_standard', data.ac) } if (data.rubicon && data.rubicon.length) { deepSetValue(bid, 'params.visitor.permutive', data.rubicon) } return bid }, ozone: function (bid, data, acEnabled) { if (acEnabled && data.ac && data.ac.length) { deepSetValue(bid, 'params.customData.0.targeting.p_standard', data.ac) } return bid } } return bidderMap[bidder] } /** * Check whether ac is enabled for bidder * @param {Object} moduleConfig - Module configuration * @param {string} bidder - Bidder name * @return {boolean} */ export function isAcEnabled (moduleConfig, bidder) { const acBidders = deepAccess(moduleConfig, 'params.acBidders') || [] return includes(acBidders, bidder) } /** * Check whether Permutive is on page * @return {boolean} */ export function isPermutiveOnPage () { return typeof window.permutive !== 'undefined' && typeof window.permutive.ready === 'function' } /** * Get all relevant segment IDs in an object * @param {number} maxSegs - Maximum number of segments to be included * @return {Object} */ export function getSegments (maxSegs) { const legacySegs = readSegments('_psegs').map(Number).filter(seg => seg >= 1000000).map(String) const _ppam = readSegments('_ppam') const _pcrprs = readSegments('_pcrprs') const segments = { ac: [..._pcrprs, ..._ppam, ...legacySegs], rubicon: readSegments('_prubicons'), appnexus: readSegments('_papns'), gam: readSegments('_pdfps') } for (const type in segments) { segments[type] = segments[type].slice(0, maxSegs) } return segments } /** * Gets an array of segment IDs from LocalStorage * or returns an empty array * @param {string} key * @return {string[]|number[]} */ function readSegments (key) { try { return JSON.parse(storage.getDataFromLocalStorage(key) || '[]') } catch (e) { return [] } } /** @type {RtdSubmodule} */ export const permutiveSubmodule = { name: MODULE_NAME, getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { makeSafe(function () { // Legacy route with custom parameters initSegments(reqBidsConfigObj, callback, customModuleConfig) }) }, onAuctionInitEvent: function (auctionDetails, customModuleConfig) { makeSafe(function () { // Route for bidders supporting ORTB2 setBidderRtb(auctionDetails, customModuleConfig) }) }, init: init } submodule('realTimeData', permutiveSubmodule)