Skip to content

Commit

Permalink
Permutive RTD submodule (#6290)
Browse files Browse the repository at this point in the history
* Add Permutive RTD module

* set demo data in LS for test page

* fix linter issues

* reduce timeout on example page

* decrease sample timeouts

* rename targeting to segments
  • Loading branch information
dreischer authored Feb 12, 2021
1 parent 2ea3f6f commit 6e51dcf
Show file tree
Hide file tree
Showing 4 changed files with 775 additions and 0 deletions.
223 changes: 223 additions & 0 deletions integrationExamples/gpt/permutiveRtdProvider_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
<html>

<head>
<link rel="icon" type="image/png" href="/favicon.png">
<script async src="//www.googletagservices.com/tag/js/gpt.js"></script>
<script src="../../build/dev/prebid.js" async></script>
<script>
window.permutive = {};
window.permutive.ready = () => {};

function setLocalStorageData () {
const data = {
_pdfps: ['gam1', 'gam2'],
_prubicons: ['rubicon1', 'rubicon2'],
_papns: ['appnexus1', 'appnexus2'],
_psegs: ['1234', '1000001', '1000002'],
_ppam: ['ppam1', 'ppam2'],
_pcrprs: ['pcrprs1', 'pcrprs2']
}

for (let key in data) {
window.localStorage[key] = JSON.stringify(data[key])
}
}

setLocalStorageData()

var div_1_sizes = [
[300, 250],
[300, 600]
];
var div_2_sizes = [
[728, 90],
[970, 250]
];
var PREBID_TIMEOUT = 1000;
var FAILSAFE_TIMEOUT = 1500;

var adUnits = [
{
code: '/19968336/header-bid-tag-0',
mediaTypes: {
banner: {
sizes: div_1_sizes
}
},
bids: [
{
bidder: 'appnexus',
params: {
placementId: 13144370,
keywords: {
inline_kvs: ['1']
}
}
},
{
bidder: 'rubicon',
params: {
accountId: '9840',
siteId: '123564',
zoneId: '583584',
inventory: {
area: ['home']
},
visitor: {
inline_kvs: ['1']
}
}
},
{
bidder: 'ozone',
params: {
publisherId: 'OZONEGMG0001',
siteId: '4204204209',
placementId: '0420420500',
customData: [
{
settings: {},
targeting: {
inline_kvs: ['1', '2', '3', '4']
}
}
],
ozoneData: {}
}
}
]
},
{
code: '/19968336/header-bid-tag-1',
mediaTypes: {
banner: {
sizes: div_2_sizes
}
},
bids: [
{
bidder: 'appnexus',
params: {
placementId: 13144370
}
},
{
bidder: 'ozone',
params: {
publisherId: 'OZONEGMG0001',
siteId: '4204204209',
placementId: '0420420500'
}
}
]
}
];


var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function() {
googletag.pubads().disableInitialLoad();
});

var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];

pbjs.que.push(function() {
pbjs.setConfig({
debug: true,
realTimeData: {
auctionDelay: 50, // maximum time for RTD modules to respond
dataProviders: [
{
name: 'permutive',
waitForIt: true,
params: {
acBidders: ['appnexus', 'rubicon', 'ozone'],
maxSegs: 500,
overwrites: {
rubicon: function (bid, data, acEnabled, utils, defaultFn) {
if (defaultFn){
bid = defaultFn(bid, data, acEnabled)
}
if (data.gam && data.gam.length) {
utils.deepSetValue(bid, 'params.visitor.permutive', data.gam)
}
}
}
}
}
]
}
});
pbjs.addAdUnits(adUnits);
requestBids();
});

function requestBids () {
pbjs.que.push(function() {
pbjs.requestBids({
bidsBackHandler: initAdserver,
timeout: PREBID_TIMEOUT
});
});
}

function initAdserver() {
if (pbjs.initAdserverSet) return;
pbjs.initAdserverSet = true;
googletag.cmd.push(function() {
pbjs.que.push(function() {
pbjs.setTargetingForGPTAsync();
googletag.pubads().refresh();
});
});
}
// in case PBJS doesn't load
setTimeout(function() {
initAdserver();
}, FAILSAFE_TIMEOUT);

googletag.cmd.push(function() {
googletag.defineSlot('/19968336/header-bid-tag-0', div_1_sizes, 'div-1').addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
googletag.cmd.push(function() {
googletag.defineSlot('/19968336/header-bid-tag-1', div_2_sizes, 'div-2').addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});

</script>

</head>

<body>
<p><button onclick="requestBids()">Refresh Ad Unit</button></p>
<h2>Basic Prebid.js Example</h2>
<h5>Div-1</h5>
<div id="div-1">
<script type="text/javascript">
googletag.cmd.push(function() {
googletag.display("div-1");
});

</script>
</div>

<br>

<h5>Div-2</h5>
<div id="div-2">
<script type="text/javascript">
googletag.cmd.push(function() {
googletag.display("div-2");
});

</script>
</div>

</body>

</html>
171 changes: 171 additions & 0 deletions modules/permutiveRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/**
* 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 } from '../src/utils.js'
import includes from 'core-js-pure/features/array/includes.js'

export const storage = getStorageManager()

function init (config, userConsent) {
return true
}

/**
* Set segment targeting from cache and then try to wait for Permutive
* to initialise to get realtime segment targeting
*/
export function initSegments (reqBidsConfigObj, callback, customConfig) {
const permutiveOnPage = isPermutiveOnPage()
const config = mergeDeep({
waitForIt: false,
params: {
maxSegs: 500,
acBidders: [],
overwrites: {}
}
}, customConfig)

setSegments(reqBidsConfigObj, config)

if (config.waitForIt && permutiveOnPage) {
window.permutive.ready(function () {
setSegments(reqBidsConfigObj, config)
callback()
}, 'realtime')
} else {
callback()
}
}

function setSegments (reqBidsConfigObj, config) {
const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits
const data = getSegments(config.params.maxSegs)
const utils = { deepSetValue, deepAccess, isFn, mergeDeep }

adUnits.forEach(adUnit => {
adUnit.bids.forEach(bid => {
const { bidder } = bid
const acEnabled = isAcEnabled(config, bidder)
const customFn = getCustomBidderFn(config, bidder)
const defaultFn = getDefaultBidderFn(bidder)

if (customFn) {
customFn(bid, data, acEnabled, utils, defaultFn)
} else if (defaultFn) {
defaultFn(bid, data, acEnabled)
} else {

}
})
})
}

function getCustomBidderFn (config, bidder) {
const overwriteFn = deepAccess(config, `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
* @param {object} data
*/
function getDefaultBidderFn (bidder) {
const bidderMapper = {
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 bidderMapper[bidder]
}

export function isAcEnabled (config, bidder) {
const acBidders = deepAccess(config, 'params.acBidders') || []
return includes(acBidders, bidder)
}

export function isPermutiveOnPage () {
return typeof window.permutive !== 'undefined' && typeof window.permutive.ready === 'function'
}

/**
* Returns all relevant segment IDs in an 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
*/
function readSegments (key) {
try {
return JSON.parse(storage.getDataFromLocalStorage(key) || '[]')
} catch (e) {
return []
}
}

/** @type {RtdSubmodule} */
export const permutiveSubmodule = {
name: 'permutive',
getBidRequestData: initSegments,
init: init
}

submodule('realTimeData', permutiveSubmodule)
Loading

0 comments on commit 6e51dcf

Please sign in to comment.