Skip to content
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 submodule #6290

Merged
merged 6 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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']
jdwieland8282 marked this conversation as resolved.
Show resolved Hide resolved
}
}
],
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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW - while this is ok for Feb 10, it hoped/expected that most adapters will soon be able to start reading ortb2.user.data to get segments. When rubicon supports this, will want to update the example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good shout - please see comment below

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