Skip to content

Commit

Permalink
Brandmetrics RTD Module: add new RTD module (#7756)
Browse files Browse the repository at this point in the history
* First version of brandmetrics RTD- module

* Implement brandmetricsRtdProvider

* Add gdpr and usp consent- checks

* Add user- consent related tests

* Set targeting keys in a more generic way

* Test- logic updates
  • Loading branch information
johanbrandmetrics authored Jan 14, 2022
1 parent d48e7fa commit 55e7cdb
Show file tree
Hide file tree
Showing 3 changed files with 399 additions and 0 deletions.
168 changes: 168 additions & 0 deletions modules/brandmetricsRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* This module adds brandmetrics provider to the real time data module
* The {@link module:modules/realTimeData} module is required
* The module will load load the brandmetrics script and set survey- targeting to ad units of specific bidders.
* @module modules/brandmetricsRtdProvider
* @requires module:modules/realTimeData
*/
import { config } from '../src/config.js'
import { submodule } from '../src/hook.js'
import { deepSetValue, mergeDeep, logError, deepAccess } from '../src/utils.js'
import {loadExternalScript} from '../src/adloader.js'
const MODULE_NAME = 'brandmetrics'
const MODULE_CODE = MODULE_NAME
const RECEIVED_EVENTS = []
const GVL_ID = 422
const TCF_PURPOSES = [1, 7]

function init (config, userConsent) {
const hasConsent = checkConsent(userConsent)

if (hasConsent) {
const moduleConfig = getMergedConfig(config)
initializeBrandmetrics(moduleConfig.params.scriptId)
}
return hasConsent
}

/**
* Checks TCF and USP consents
* @param {Object} userConsent
* @returns {boolean}
*/
function checkConsent (userConsent) {
let consent = false

if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) {
const gdpr = userConsent.gdpr

if (gdpr.vendorData) {
const vendor = gdpr.vendorData.vendor
const purpose = gdpr.vendorData.purpose

let vendorConsent = false
if (vendor.consents) {
vendorConsent = vendor.consents[GVL_ID]
}

if (vendor.legitimateInterests) {
vendorConsent = vendorConsent || vendor.legitimateInterests[GVL_ID]
}

const purposes = TCF_PURPOSES.map(id => {
return (purpose.consents && purpose.consents[id]) || (purpose.legitimateInterests && purpose.legitimateInterests[id])
})
const purposesValid = purposes.filter(p => p === true).length === TCF_PURPOSES.length
consent = vendorConsent && purposesValid
}
} else if (userConsent.usp) {
const usp = userConsent.usp
consent = usp[1] !== 'N' && usp[2] !== 'Y'
}

return consent
}

/**
* Add event- listeners to hook in to brandmetrics events
* @param {Object} reqBidsConfigObj
* @param {function} callback
*/
function processBrandmetricsEvents (reqBidsConfigObj, moduleConfig, callback) {
const callBidTargeting = (event) => {
if (event.available && event.conf) {
const targetingConf = event.conf.displayOption || {}
if (targetingConf.type === 'pbjs') {
setBidderTargeting(reqBidsConfigObj, moduleConfig, targetingConf.targetKey || 'brandmetrics_survey', event.survey.measurementId)
}
}
callback()
}

if (RECEIVED_EVENTS.length > 0) {
callBidTargeting(RECEIVED_EVENTS[RECEIVED_EVENTS.length - 1])
} else {
window._brandmetrics = window._brandmetrics || []
window._brandmetrics.push({
cmd: '_addeventlistener',
val: {
event: 'surveyloaded',
reEmitLast: true,
handler: (ev) => {
RECEIVED_EVENTS.push(ev)
if (RECEIVED_EVENTS.length === 1) {
// Call bid targeting only for the first received event, if called subsequently, last event from the RECEIVED_EVENTS array is used
callBidTargeting(ev)
}
},
}
})
}
}

/**
* Sets bid targeting of specific bidders
* @param {Object} reqBidsConfigObj
* @param {string} key Targeting key
* @param {string} val Targeting value
*/
function setBidderTargeting (reqBidsConfigObj, moduleConfig, key, val) {
const bidders = deepAccess(moduleConfig, 'params.bidders')
if (bidders && bidders.length > 0) {
const ortb2 = {}
deepSetValue(ortb2, 'ortb2.user.ext.data.' + key, val)
config.setBidderConfig({
bidders: bidders,
config: ortb2
})
}
}

/**
* Add the brandmetrics script to the page.
* @param {string} scriptId - The script- id provided by brandmetrics or brandmetrics partner
*/
function initializeBrandmetrics(scriptId) {
if (scriptId) {
const path = 'https://cdn.brandmetrics.com/survey/script/'
const file = scriptId + '.js'
const url = path + file

loadExternalScript(url, MODULE_CODE)
}
}

/**
* Merges a provided config with default values
* @param {Object} customConfig
* @returns
*/
function getMergedConfig(customConfig) {
return mergeDeep({
waitForIt: false,
params: {
bidders: [],
scriptId: undefined,
}
}, customConfig)
}

/** @type {RtdSubmodule} */
export const brandmetricsSubmodule = {
name: MODULE_NAME,
getBidRequestData: function (reqBidsConfigObj, callback, customConfig) {
try {
const moduleConfig = getMergedConfig(customConfig)
if (moduleConfig.waitForIt) {
processBrandmetricsEvents(reqBidsConfigObj, moduleConfig, callback)
} else {
callback()
}
} catch (e) {
logError(e)
}
},
init: init
}

submodule('realTimeData', brandmetricsSubmodule)
40 changes: 40 additions & 0 deletions modules/brandmetricsRtdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Brandmetrics Real-time Data Submodule
This module is intended to be used by brandmetrics (https://brandmetrics.com) partners and sets targeting keywords to bids if the browser is eligeble to see a brandmetrics survey.
The module hooks in to brandmetrics events and requires a brandmetrics script to be running. The module can optionally load and initialize brandmetrics by providing the 'scriptId'- parameter.

## Usage
Compile the Brandmetrics RTD module into your Prebid build:
```
gulp build --modules=rtdModule,brandmetricsRtdProvider
```

> Note that the global RTD module, `rtdModule`, is a prerequisite of the Brandmetrics RTD module.
Enable the Brandmetrics RTD in your Prebid configuration, using the below format:

```javascript
pbjs.setConfig({
...,
realTimeData: {
auctionDelay: 500, // auction delay
dataProviders: [{
name: 'brandmetrics',
waitForIt: true // should be true if there's an `auctionDelay`,
params: {
scriptId: '00000000-0000-0000-0000-000000000000',
bidders: ['ozone']
}
}]
},
...
})
```

## Parameters
| Name | Type | Description | Default |
| ----------------- | -------------------- | ------------------ | ------------------ |
| name | String | This should always be `brandmetrics` | - |
| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (recommended) | `false` |
| params | Object | | - |
| params.bidders | String[] | An array of bidders which should receive targeting keys. | `[]` |
| params.scriptId | String | A script- id GUID if the brandmetrics- script should be initialized. | `undefined` |
Loading

0 comments on commit 55e7cdb

Please sign in to comment.