diff --git a/integrationExamples/gpt/audigentSegments_example.html b/integrationExamples/gpt/audigentSegments_example.html new file mode 100644 index 00000000000..9b72da76d23 --- /dev/null +++ b/integrationExamples/gpt/audigentSegments_example.html @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + +

Audigent Segments Prebid

+ +
+ +
+TDID: +
+
+ +Audigent Segments: +
+
+ + diff --git a/modules/audigentRtdProvider.js b/modules/audigentRtdProvider.js new file mode 100644 index 00000000000..8b45287bddc --- /dev/null +++ b/modules/audigentRtdProvider.js @@ -0,0 +1,138 @@ +/** + * This module adds audigent provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch segments from audigent server + * @module modules/audigentRtdProvider + * @requires module:modules/realTimeData + */ + +/** + * @typedef {Object} ModuleParams + * @property {string} siteKey + * @property {string} pubKey + * @property {string} url + * @property {?string} keyName + * @property {number} auctionDelay + */ + +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import * as utils from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; + +/** @type {ModuleParams} */ +let _moduleParams = {}; + +/** + * XMLHttpRequest to get data form audigent server + * @param {string} url server url with query params + */ + +export function setData(data) { + utils.setDataInLocalStorage('__adgntseg', JSON.stringify(data)); +} + +function getSegments(adUnits, onDone) { + try { + let jsonData = utils.getDataFromLocalStorage('__adgntseg'); + if (jsonData) { + let data = JSON.parse(jsonData); + if (data.audigent_segments) { + let dataToReturn = adUnits.reduce((rp, cau) => { + const adUnitCode = cau && cau.code; + if (!adUnitCode) { return rp } + rp[adUnitCode] = data; + return rp; + }, {}); + + onDone(dataToReturn); + return; + } + } + getSegmentsAsync(adUnits, onDone); + } catch (e) { + getSegmentsAsync(adUnits, onDone); + } +} + +function getSegmentsAsync(adUnits, onDone) { + const userIds = (getGlobal()).getUserIds(); + let tdid = null; + + if (userIds && userIds['tdid']) { + tdid = userIds['tdid']; + } else { + onDone({}); + } + + const url = `https://seg.ad.gt/api/v1/rtb_segments?tdid=${tdid}`; + + ajax(url, { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.audigent_segments) { + setData(data); + let dataToReturn = adUnits.reduce((rp, cau) => { + const adUnitCode = cau && cau.code; + if (!adUnitCode) { return rp } + rp[adUnitCode] = data; + return rp; + }, {}); + + onDone(dataToReturn); + } else { + onDone({}); + } + } catch (err) { + utils.logError('unable to parse audigent segment data'); + onDone({}) + } + } else if (req.status === 204) { + // unrecognized site key + onDone({}); + } + }, + error: function () { + onDone({}); + utils.logError('unable to get audigent segment data'); + } + } + ); +} + +/** @type {RtdSubmodule} */ +export const audigentSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: 'audigent', + /** + * get data and send back to realTimeData module + * @function + * @param {adUnit[]} adUnits + * @param {function} onDone + */ + getData: getSegments +}; + +export function init(config) { + const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { + try { + _moduleParams = realTimeData.dataProviders && realTimeData.dataProviders.filter(pr => pr.name && pr.name.toLowerCase() === 'audigent')[0].params; + _moduleParams.auctionDelay = realTimeData.auctionDelay; + } catch (e) { + _moduleParams = {}; + } + confListener(); + }); +} + +submodule('realTimeData', audigentSubmodule); +init(config); diff --git a/modules/audigentRtdProvider.md b/modules/audigentRtdProvider.md new file mode 100644 index 00000000000..47bcbbbf951 --- /dev/null +++ b/modules/audigentRtdProvider.md @@ -0,0 +1,52 @@ +Audigent is a next-generation data management platform and a first-of-a-kind +"data agency" containing some of the most exclusive content-consuming audiences +across desktop, mobile and social platforms. + +This real-time data module provides first-party Audigent segments that can be +attached to bid request objects destined for different SSPs in order to optimize +targeting. Audigent maintains a large database of first-party Tradedesk Unified +ID to third party segment mappings that can now be queried at bid-time. + +Usage: + +Compile the audigent RTD module into your Prebid build: + +`gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` + +Audigent segments will then be attached to each bid request objects in +`bid.realTimeData.audigent_segments` + +The format of the segments is a per-SSP mapping: + +``` +{ + 'appnexus': ['anseg1', 'anseg2'], + 'google': ['gseg1', 'gseg2'] +} +``` + +If a given SSP's API backend supports segment fields, they can then be +attached prior to the bid request being sent: + +``` +pbjs.requestBids({bidsBackHandler: addAudigentSegments}); + +function addAudigentSegments() { + for (i = 0; i < adUnits.length; i++) { + let adUnit = adUnits[i]; + for (j = 0; j < adUnit.bids.length; j++) { + adUnit.bids[j].userId.lipb.segments = adUnit.bids[j].realTimeData.audigent_segments['rubicon']; + } + } +} +``` + +To view an example of the segments returned by Audigent's backends: + +`gulp serve --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/audigentSegments_example.html` + + diff --git a/test/spec/modules/realTimeModule_spec.js b/test/spec/modules/realTimeModule_spec.js index bf25b3f0a04..89b44333636 100644 --- a/test/spec/modules/realTimeModule_spec.js +++ b/test/spec/modules/realTimeModule_spec.js @@ -10,6 +10,10 @@ import { isIdMatchingAdUnit, setData } from 'modules/browsiRtdProvider.js'; +import { + init as audigentInit, + setData as setAudigentData +} from 'modules/audigentRtdProvider.js'; import {config} from 'src/config.js'; import {makeSlot} from '../integration/faker/googletag.js'; @@ -27,8 +31,9 @@ describe('Real time module', function() { 'pubKey': 'testPub', 'keyName': 'bv' } + }, { + 'name': 'audigent' }] - } }; @@ -58,6 +63,10 @@ describe('Real time module', function() { } }; + const audigentSegments = { + audigent_segments: {'a': 1, 'b': 2} + } + function getAdUnitMock(code = 'adUnit-code') { return { code, @@ -189,4 +198,46 @@ describe('Real time module', function() { expect(test4).to.equal(true); }) }); + + describe('Real time module with Audigent provider', function() { + afterEach(function () { + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + let targeting = []; + init(config); + audigentInit(config); + config.setConfig(conf); + setAudigentData(audigentSegments); + + it('check module using requestBidsHook', function () { + let adUnits1 = [getAdUnitMock('audigentAd_1')]; + let targeting = []; + let dataReceived = null; + + // set slot + const slotsB = createSlots(); + window.googletag.pubads().setSlots(slotsB); + + function afterBidHook(data) { + dataReceived = data; + slotsB.map(s => { + targeting = []; + s.getTargeting().map(value => { + targeting.push(Object.keys(value).toString()); + }); + }); + } + + requestBidsHook(afterBidHook, {adUnits: adUnits1}); + setTimeout(() => { + dataReceived.adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid.realTimeData).to.have.property('audigent_segments'); + expect(bid.realTimeData.audigent_segments).to.deep.equal(audigentSegments.audigent_segments); + }); + }); + }, 200); + }); + }); });