From 4d6b3dbfc323570444b6a4748ab422e3e459bf14 Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Mon, 4 Apr 2022 22:03:38 -0700 Subject: [PATCH 1/6] adding PAF userId and RTD modules. test paf bid adapter --- modules/pafBidAdapter.js | 329 ++++++++++++++++++++++++++++++++++++++ modules/pafIdSystem.js | 43 +++++ modules/pafRtdProvider.js | 111 +++++++++++++ 3 files changed, 483 insertions(+) create mode 100644 modules/pafBidAdapter.js create mode 100644 modules/pafIdSystem.js create mode 100644 modules/pafRtdProvider.js diff --git a/modules/pafBidAdapter.js b/modules/pafBidAdapter.js new file mode 100644 index 00000000000..4a30a530d44 --- /dev/null +++ b/modules/pafBidAdapter.js @@ -0,0 +1,329 @@ +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import includes from 'core-js-pure/features/array/includes.js' + +const bidderConfig = 'hb_pb_ortb'; +const bidderVersion = '1.0'; +const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', + 'startdelay', 'skippable', 'playbackmethod', 'api', 'protocols', 'boxingallowed', + 'linearity', 'delivery', 'protocol', 'placement', 'minbitrate', 'maxbitrate', 'ext']; +export const REQUEST_URL = 'https://rtb.openx.net/openrtbb/prebidjs'; +export const spec = { + code: 'paf', + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + transformBidParams +}; + +registerBidder(spec); + +function transformBidParams(params, isOpenRtb) { + return utils.convertTypes({ + 'unit': 'string', + 'customFloor': 'number' + }, params); +} + +function isBidRequestValid(bidRequest) { + return true; +} + +function buildRequests(bids, bidderRequest) { + let videoBids = bids.filter(bid => isVideoBid(bid)); + let bannerBids = bids.filter(bid => isBannerBid(bid)); + let requests = bannerBids.length ? [createBannerRequest(bannerBids, bidderRequest)] : []; + videoBids.forEach(bid => { + requests.push(createVideoRequest(bid, bidderRequest)); + }); + return requests; +} + +function createBannerRequest(bids, bidderRequest) { + let data = getBaseRequest(bids[0], bidderRequest); + data.imp = bids.map(bid => { + const floor = getFloor(bid, BANNER); + let imp = { + id: bid.bidId, + tagid: bid.params.unit, + banner: { + format: toFormat(bid.mediaTypes.banner.sizes), + topframe: utils.inIframe() ? 0 : 1 + }, + ext: {divid: bid.adUnitCode} + }; + enrichImp(imp, bid, floor); + return imp; + }); + return { + method: 'POST', + url: REQUEST_URL, + data: data + } +} + +function toFormat(sizes) { + return sizes.map((s) => { + return { w: s[0], h: s[1] }; + }); +} + +function enrichImp(imp, bid, floor) { + if (bid.params.customParams) { + utils.deepSetValue(imp, 'ext.customParams', bid.params.customParams); + } + if (floor > 0) { + imp.bidfloor = floor; + imp.bidfloorcur = 'USD'; + } else if (bid.params.customFloor) { + imp.bidfloor = bid.params.customFloor; + } + if (bid.ortb2Imp && bid.ortb2Imp.ext && bid.ortb2Imp.ext.data) { + imp.ext.data = bid.ortb2Imp.ext.data; + } +} + +function enrichFloc(req, bid) { + if (bid.userId && bid.userId.flocId) { + const flocObject = { + id: 'chrome', + segment: [{ + id: 'floc', + value: bid.userId.flocId.id.toString(), + ext: { + ver: bid.userId.flocId.version + } + }] + } + if (!req.user) { + req.user = {}; + } + if (!req.user.data) { + req.user.data = []; + } + req.user.data.push(flocObject); + } +} + +function createVideoRequest(bid, bidderRequest) { + let width; + let height; + const videoMediaType = utils.deepAccess(bid, `mediaTypes.video`); + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + const floor = getFloor(bid, VIDEO); + + // normalize config for video size + if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { + width = parseInt(bid.sizes[0], 10); + height = parseInt(bid.sizes[1], 10); + } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { + width = parseInt(bid.sizes[0][0], 10); + height = parseInt(bid.sizes[0][1], 10); + } else if (utils.isArray(playerSize) && playerSize.length === 2) { + width = parseInt(playerSize[0], 10); + height = parseInt(playerSize[1], 10); + } + + let data = getBaseRequest(bid, bidderRequest); + data.imp = [{ + id: bid.bidId, + tagid: bid.params.unit, + video: { + w: width, + h: height, + topframe: utils.inIframe() ? 0 : 1 + }, + ext: {divid: bid.adUnitCode} + }]; + + enrichImp(data.imp[0], bid, floor); + + if (context) { + if (context === 'instream') { + data.imp[0].video.placement = 1; + } else if (context === 'outstream') { + data.imp[0].video.placement = 4; + } + } + + let videoParams = bid.params.video || bid.params.openrtb || {}; + if (utils.isArray(videoParams.imp)) { + videoParams = videoParams[0].video; + } + + Object.keys(videoParams) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => data.imp[0].video[param] = videoParams[param]); + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => data.imp[0].video[param] = videoMediaType[param]); + + return { + method: 'POST', + url: REQUEST_URL, + data: data + } +} + +function getBaseRequest(bid, bidderRequest) { + let req = { + id: bidderRequest.auctionId, + cur: [config.getConfig('currency.adServerCurrency') || 'USD'], + at: 1, + tmax: config.getConfig('bidderTimeout'), + site: { + page: config.getConfig('pageUrl') || bidderRequest.refererInfo.referer + }, + regs: { + coppa: (config.getConfig('coppa') === true || bid.params.coppa) ? 1 : 0, + }, + device: { + dnt: (utils.getDNT() || bid.params.doNotTrack) ? 1 : 0, + h: screen.height, + w: screen.width, + ua: window.navigator.userAgent, + language: window.navigator.language.split('-').shift() + }, + ext: { + bc: bid.params.bc || `${bidderConfig}_${bidderVersion}` + } + }; + + if (bid.params.test) { + req.test = 1 + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + utils.deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0); + } + if (bidderRequest.gdprConsent.consentString !== undefined) { + utils.deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + if (bidderRequest.gdprConsent.addtlConsent !== undefined) { + utils.deepSetValue(req, 'user.ext.ConsentedProvidersSettings.consented_providers', bidderRequest.gdprConsent.addtlConsent); + } + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + if (bid.schain) { + utils.deepSetValue(req, 'source.ext.schain', bid.schain); + } + if (bid.userIdAsEids) { + utils.deepSetValue(req, 'user.ext.eids', bid.userIdAsEids); + } + const commonFpd = config.getConfig('ortb2') || {}; + if (commonFpd.site) { + utils.mergeDeep(req, {site: commonFpd.site}); + } + if (commonFpd.user) { + utils.mergeDeep(req, {user: commonFpd.user}); + } + enrichFloc(req, bid) + return req; +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function getFloor(bid, mediaType) { + let floor = 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + +function interpretResponse(resp, req) { + + const respBody = resp.body; + let bids = []; + req.data.imp.forEach(imp => { + let transmissions = [ + { + "version": "0.1", + "receiver": "ssp1.com", + "status": "success", + "details": "", + "source": { + "domain": "ssp1.com", + "timestamp": 1639589531, + "signature": "12345_signature" + } + }, + { + "version": "0.1", + "receiver": "ssp2.com", + "status": "success", + "details": "", + "source": { + "domain": "ssp2.com", + "timestamp": 1639589531, + "signature": "12345_signature" + } + }, + { + "version": "0.1", + "receiver": "dsp.com", + "status": "success", + "details": "", + "source": { + "domain": "dsp.com", + "timestamp": 1639589531, + "signature": "12345_signature" + } + } + ]; + bids.push({ + ad: `

Test Ad

`, + requestId: imp.id, + cpm: 1, + width: imp.banner.format[0].w, + height: imp.banner.format[0].h, + creativeId: "test-crid", + currency: 'USD', + netRevenue: true, + ttl: 300, + mediaType: 'banner', + meta: { + paf: { + transmissions: transmissions + } + }}); + }); + + return bids; +} + +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + return undefined; +} \ No newline at end of file diff --git a/modules/pafIdSystem.js b/modules/pafIdSystem.js new file mode 100644 index 00000000000..4b271cb3be1 --- /dev/null +++ b/modules/pafIdSystem.js @@ -0,0 +1,43 @@ +/** + * This module adds pafId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/swanIdSystem + * @requires module:modules/userId + */ + + import {submodule} from '../src/hook.js'; + + /** @type {Submodule} */ + export const pafIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'pafData', + /** + * decode the stored data value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ + decode(data) { + return { pafData: data }; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @param {(Object|undefined)} cacheIdObj + * @returns {IdResponse|undefined} + */ + getId(config, consentData) { + if (window.PAF) { + return {id: window.PAF.getIdsAndPreferences()}; + } else { + return undefined; + } + } + }; + + submodule('userId', pafIdSubmodule); \ No newline at end of file diff --git a/modules/pafRtdProvider.js b/modules/pafRtdProvider.js new file mode 100644 index 00000000000..b35fa180579 --- /dev/null +++ b/modules/pafRtdProvider.js @@ -0,0 +1,111 @@ + +import { submodule } from '../src/hook.js'; +import { mergeDeep, isPlainObject, logMessage, deepSetValue } from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import {config} from '../src/config.js'; + +const SUBMODULE_NAME = 'paf'; + +/** + * + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {Object} rtdConfig + * @param {Object} userConsent + */ +function getBidRequestData(reqBidsConfigObj, callback, rtdConfig, userConsent) { + + let idsAndPreferences; + let seed; + const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits); + + if (window.PAF) { + idsAndPreferences = window.PAF.getIdsAndPreferences(); + if (!idsAndPreferences) { + callback(); + return; + } + logMessage('DEBUG(idsAndPreferences):', idsAndPreferences); + seed = rtdConfig.multipleTransactions ? window.PAF.getSeed() : window.PAF.getSeed(adUnits.length); + logMessage('DEBUG(seed):', seed); + } else { + callback(); + return; + } + + if (seed.transaction_ids) { + for (var i=0; i < adUnits.length; i++) { + deepSetValue(adUnits[i], `ortb2Imp.ext.data.paf_transaction_id`, seed.transaction_ids[i]) + } + } + + const pafOrtb2 = { + ortb2: { + user: { + ext: { + paf: { + seed: seed, + identifiers: idsAndPreferences.identifiers, + preferences: idsAndPreferences.preferences + } + } + } + } + } + + if (rtdConfig.params && rtdConfig.params.bidders) { + let bidderConfig = config.getBidderConfig(); + logMessage(`set ortb2 for: ${rtdConfig.params.bidders}`, pafOrtb2); + rtdConfig.params.bidders.forEach(bidder => { + let bidderOptions = {}; + if (isPlainObject(bidderConfig[bidder])) { + bidderOptions = bidderConfig[bidder]; + } + + config.setBidderConfig({ + bidders: [bidder], + config: mergeLazy(bidderOptions, pafOrtb2) + }); + }); + } else { + let ortb2 = config.getConfig('ortb2') || {}; + logMessage('DEBUG(set ortb2):', pafOrtb2); + config.setConfig({ortb2: mergeLazy(ortb2, pafOrtb2.ortb2)}); + } + + callback(); +} + +/** + * Lazy merge objects. + * @param {Object} target + * @param {Object} source + */ + function mergeLazy(target, source) { + if (!isPlainObject(target)) { + target = {}; + } + + if (!isPlainObject(source)) { + source = {}; + } + + return mergeDeep(target, source); +} + +/** @type {RtdSubmodule} */ +export const pafDataSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: SUBMODULE_NAME, + init: () => true, + getBidRequestData, +}; + +function registerSubModule() { + submodule('realTimeData', pafDataSubmodule); +} + +registerSubModule(); From bd63aa4662b66523994f3261401bda11e13ad0ae Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Mon, 11 Apr 2022 15:13:36 -0700 Subject: [PATCH 2/6] add PAF readmes and small fixes --- modules/pafIdSystem.md | 102 ++++++++++++++++++++++++++++++++++++++ modules/pafRtdProvider.js | 72 ++++++++++++++------------- modules/pafRtdProvider.md | 93 ++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+), 35 deletions(-) create mode 100644 modules/pafIdSystem.md create mode 100644 modules/pafRtdProvider.md diff --git a/modules/pafIdSystem.md b/modules/pafIdSystem.md new file mode 100644 index 00000000000..ccd593030b3 --- /dev/null +++ b/modules/pafIdSystem.md @@ -0,0 +1,102 @@ +# Prebid Addreaability Framework (OneKey) + +Insert description here +https://github.com/prebid/addressability-framework +https://github.com/prebid/paf-mvp-implementation + +## PAF Registration + +insert info here + +## PAF Configuration + +The pafData module depends on paf-lib.js existing in the page. + +Compile the pafData module into your Prebid build. +You will also want to add the pafRtdProvider module as well. + +`gulp build --modules=userId,pafIdSystem,rtdModule,pafRtdProvider,appnexusBidAdapter` + +There are no custom configuration parameters for PAF. The module +will retrieve the PAF data from the page if available and pass the +information to bidders. Here is a configuration example: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: "pafData", + params: {} + }] + }], + auctionDelay: 50 // example auction delay, applies to all userId modules + } +}); +``` + +Bidders will receive the data in the following format: + +```json +{ + "identifiers": [{ + "version": "0.1", + "type": "paf_browser_id", + "value": "da135b3a-7d04-44bf-a0af-c4709f10420b", + "source": { + "domain": "crto-poc-1.onekey.network", + "timestamp": 1648836556881, + "signature": "+NF27bBvPM54z103YPExXuS834+ggAQe6JV0jPeGo764vRYiiBl5OmEXlnB7UZgxNe3KBU7rN2jk0SkI4uL0bg==" + } + }], + "preferences": { + "version": "0.1", + "data": { + "use_browsing_for_personalization": true + }, + "source": { + "domain": "cmp.pafdemopublisher.com", + "timestamp": 1648836566468, + "signature": "ipbYhU8IbSFm2tCqAVYI2d5w4DnGF7Xa2AaiZScx2nmBPLfMmIT/FkBYGitR8Mi791DHtcy5MXr4+bs1aeZFqw==" + } + } +} +``` + + +If the bidder elects to use pbjs.getUserIdsAsEids() then the format will be: + +```json +"user": { + "ext": { + "eids": [{ + "source": "paf", + "uids": [{ + "id": "da135b3a-7d04-44bf-a0af-c4709f10420b", + "atype": 1, + "ext": { + "version": "0.1", + "type": "paf_browser_id", + "source": { + "domain": "crto-poc-1.onekey.network", + "timestamp": 1648836556881, + "signature": "+NF27bBvPM54z103YPExXuS834+ggAQe6JV0jPeGo764vRYiiBl5OmEXlnB7UZgxNe3KBU7rN2jk0SkI4uL0bg==" + } + } + }], + "ext": { + "preferences": { + "version": "0.1", + "data": { + "use_browsing_for_personalization": true + }, + "source": { + "domain": "cmp.pafdemopublisher.com", + "timestamp": 1648836566468, + "signature": "ipbYhU8IbSFm2tCqAVYI2d5w4DnGF7Xa2AaiZScx2nmBPLfMmIT/FkBYGitR8Mi791DHtcy5MXr4+bs1aeZFqw==" + } + } + } + }] + } +} +``` \ No newline at end of file diff --git a/modules/pafRtdProvider.js b/modules/pafRtdProvider.js index b35fa180579..4ed0a12ad9c 100644 --- a/modules/pafRtdProvider.js +++ b/modules/pafRtdProvider.js @@ -1,6 +1,6 @@ import { submodule } from '../src/hook.js'; -import { mergeDeep, isPlainObject, logMessage, deepSetValue } from '../src/utils.js'; +import { mergeDeep, isPlainObject, logMessage, deepSetValue, generateUUID } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import {config} from '../src/config.js'; @@ -13,40 +13,60 @@ const SUBMODULE_NAME = 'paf'; * @param {Object} rtdConfig * @param {Object} userConsent */ -function getBidRequestData(reqBidsConfigObj, callback, rtdConfig, userConsent) { - +function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { + logMessage('DEBUG(paf):', rtdConfig); let idsAndPreferences; - let seed; const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits); - if (window.PAF) { + if (rtdConfig.params && rtdConfig.params.proxyHostName && window.PAF) { idsAndPreferences = window.PAF.getIdsAndPreferences(); if (!idsAndPreferences) { - callback(); + onDone(); return; } + + let transaction_ids = []; + for (var i=0; i < adUnits.length; i++) { + const uuid = generateUUID(); + transaction_ids.push(uuid) + deepSetValue(adUnits[i], `ortb2Imp.ext.data.paf.transaction_id`, uuid) + } + logMessage('DEBUG(idsAndPreferences):', idsAndPreferences); - seed = rtdConfig.multipleTransactions ? window.PAF.getSeed() : window.PAF.getSeed(adUnits.length); - logMessage('DEBUG(seed):', seed); + window.PAF.createSeed({proxyHostName: rtdConfig.params.proxyHostName, callback: function (seed) {setData(seed, rtdConfig, onDone);}}, transaction_ids) } else { - callback(); + onDone(); return; } +} - if (seed.transaction_ids) { - for (var i=0; i < adUnits.length; i++) { - deepSetValue(adUnits[i], `ortb2Imp.ext.data.paf_transaction_id`, seed.transaction_ids[i]) - } +/** + * Lazy merge objects. + * @param {Object} target + * @param {Object} source + */ + function mergeLazy(target, source) { + if (!isPlainObject(target)) { + target = {}; } + if (!isPlainObject(source)) { + source = {}; + } + + return mergeDeep(target, source); +} + +function setData(seed, rtdConfig, onDone) { + logMessage('DEBUG(seed):', seed); const pafOrtb2 = { ortb2: { user: { ext: { paf: { - seed: seed, - identifiers: idsAndPreferences.identifiers, - preferences: idsAndPreferences.preferences + transmission: { + seed: seed + } } } } @@ -72,25 +92,7 @@ function getBidRequestData(reqBidsConfigObj, callback, rtdConfig, userConsent) { logMessage('DEBUG(set ortb2):', pafOrtb2); config.setConfig({ortb2: mergeLazy(ortb2, pafOrtb2.ortb2)}); } - - callback(); -} - -/** - * Lazy merge objects. - * @param {Object} target - * @param {Object} source - */ - function mergeLazy(target, source) { - if (!isPlainObject(target)) { - target = {}; - } - - if (!isPlainObject(source)) { - source = {}; - } - - return mergeDeep(target, source); + onDone(); } /** @type {RtdSubmodule} */ diff --git a/modules/pafRtdProvider.md b/modules/pafRtdProvider.md new file mode 100644 index 00000000000..ece95d39b24 --- /dev/null +++ b/modules/pafRtdProvider.md @@ -0,0 +1,93 @@ +## Prebid Addressability Framework Real-time Data Submodule + +The PAF real-time data module in Prebid has been built so that publishers +can quickly and easily setup the Prebid Addressability Framework and utilize OneKey. +This module is used along with the pafIdSysytem to pass PAF data to your partners. +Both modules are required. This module will pass transmission requests to your partners +while the pafIdSystem will pass the pafData. + +Background information: +https://github.com/prebid/addressability-framework +https://github.com/prebid/paf-mvp-implementation + + + +### Publisher Usage + +The paf RTD module depends on paf-lib.js existing in the page. + +Compile the paf RTD module into your Prebid build: + +`gulp build --modules=userId,pafIdSystem,rtdModule,pafRtdProvider,appnexusBidAdapter` + +Add the PAF RTD provider to your Prebid config. In this example we will configure +a sample proxyHostName. See the "Parameter Descriptions" below for more detailed information +of the configuration parameters. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 5000, + dataProviders: [ + { + name: "paf", + waitForIt: true, + params: { + proxyHostName: "cmp.pafdemopublisher.com" + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the PAF Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Always 'paf' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.proxyHostName | String | URL of the PAF Proxy which will generate seeds. | Required | +| params.bidders | Array | List of bidders to restrict the data to. | Optional | + +### Data for bidders + +The data will provided to the bidders using the `ortb2` object. You can find the +format of the data at https://github.com/prebid/addressability-framework. +The following is an example of the format of the data: + +```json +"user": { + "ext": { + "paf": { + "transmission": { + "seed": { + "version": "0.1", + "transaction_ids": ["06df6992-691c-4342-bbb0-66d2a005d5b1", "d2cd0aa7-8810-478c-bd15-fb5bfa8138b8"], + "publisher": "cmp.pafdemopublisher.com", + "source": { + "domain": "cmp.pafdemopublisher.com", + "timestamp": 1649712888, + "signature": "turzZlXh9IqD5Rjwh4vWR78pKLrVsmwQrGr6fgw8TPgQVJSC8K3HvkypTV7lm3UaCi+Zzjl+9sd7Hrv87gdI8w==" + } + } + } + } + } +} + + +```json +"ortb2Imp": { + "ext": { + "data": { + "paf": { + "transaction_id": "52d23fed-4f50-4c17-b07a-c458143e9d09" + } + } + } +} +``` \ No newline at end of file From 95a704b2644335f7dbe806bb5000c5286a884cf6 Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Thu, 14 Apr 2022 15:27:05 -0700 Subject: [PATCH 3/6] address PR comments --- modules/pafIdSystem.js | 4 ++-- modules/pafRtdProvider.js | 18 +++++++++++------- modules/pafRtdProvider.md | 7 ++++--- modules/userId/eids.js | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/modules/pafIdSystem.js b/modules/pafIdSystem.js index 4b271cb3be1..73c26670d0e 100644 --- a/modules/pafIdSystem.js +++ b/modules/pafIdSystem.js @@ -32,8 +32,8 @@ * @returns {IdResponse|undefined} */ getId(config, consentData) { - if (window.PAF) { - return {id: window.PAF.getIdsAndPreferences()}; + if (window.PAF && window.PAF.getIdsAndPreferences()) { + return {id: window.PAF.getIdsAndPreferences()}; } else { return undefined; } diff --git a/modules/pafRtdProvider.js b/modules/pafRtdProvider.js index 4ed0a12ad9c..e0203a97f8a 100644 --- a/modules/pafRtdProvider.js +++ b/modules/pafRtdProvider.js @@ -1,6 +1,6 @@ import { submodule } from '../src/hook.js'; -import { mergeDeep, isPlainObject, logMessage, deepSetValue, generateUUID } from '../src/utils.js'; +import { mergeDeep, isPlainObject, logError, logMessage, deepSetValue, generateUUID } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import {config} from '../src/config.js'; @@ -14,7 +14,6 @@ const SUBMODULE_NAME = 'paf'; * @param {Object} userConsent */ function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { - logMessage('DEBUG(paf):', rtdConfig); let idsAndPreferences; const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits); @@ -22,6 +21,7 @@ function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { idsAndPreferences = window.PAF.getIdsAndPreferences(); if (!idsAndPreferences) { onDone(); + logMessage(SUBMODULE_NAME, 'No id and preferences. Not creating Seed.'); return; } @@ -32,7 +32,6 @@ function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { deepSetValue(adUnits[i], `ortb2Imp.ext.data.paf.transaction_id`, uuid) } - logMessage('DEBUG(idsAndPreferences):', idsAndPreferences); window.PAF.createSeed({proxyHostName: rtdConfig.params.proxyHostName, callback: function (seed) {setData(seed, rtdConfig, onDone);}}, transaction_ids) } else { onDone(); @@ -58,14 +57,19 @@ function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { } function setData(seed, rtdConfig, onDone) { - logMessage('DEBUG(seed):', seed); + if (!seed) { + logError(SUBMODULE_NAME, 'Could not createSeed'); + onDone() + return; + } + logMessage(SUBMODULE_NAME, 'Created Seed:', seed); const pafOrtb2 = { ortb2: { user: { ext: { paf: { transmission: { - seed: seed + seed } } } @@ -75,7 +79,7 @@ function setData(seed, rtdConfig, onDone) { if (rtdConfig.params && rtdConfig.params.bidders) { let bidderConfig = config.getBidderConfig(); - logMessage(`set ortb2 for: ${rtdConfig.params.bidders}`, pafOrtb2); + logMessage(SUBMODULE_NAME, `set ortb2 for: ${rtdConfig.params.bidders}`, pafOrtb2); rtdConfig.params.bidders.forEach(bidder => { let bidderOptions = {}; if (isPlainObject(bidderConfig[bidder])) { @@ -89,7 +93,7 @@ function setData(seed, rtdConfig, onDone) { }); } else { let ortb2 = config.getConfig('ortb2') || {}; - logMessage('DEBUG(set ortb2):', pafOrtb2); + logMessage(SUBMODULE_NAME, 'set ortb2:', pafOrtb2); config.setConfig({ortb2: mergeLazy(ortb2, pafOrtb2.ortb2)}); } onDone(); diff --git a/modules/pafRtdProvider.md b/modules/pafRtdProvider.md index ece95d39b24..c879a04b020 100644 --- a/modules/pafRtdProvider.md +++ b/modules/pafRtdProvider.md @@ -7,8 +7,8 @@ Both modules are required. This module will pass transmission requests to your p while the pafIdSystem will pass the pafData. Background information: -https://github.com/prebid/addressability-framework -https://github.com/prebid/paf-mvp-implementation +- [prebid/addressability-framework](https://github.com/prebid/addressability-framework) +- [prebid/paf-mvp-implementation](https://github.com/prebid/paf-mvp-implementation) @@ -50,7 +50,7 @@ pbjs.setConfig( | name | String | Real time data module name | Always 'paf' | | waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | | params | Object | | | -| params.proxyHostName | String | URL of the PAF Proxy which will generate seeds. | Required | +| params.proxyHostName | String | servername of the PAF Proxy which will generate seeds. | Required | | params.bidders | Array | List of bidders to restrict the data to. | Optional | ### Data for bidders @@ -78,6 +78,7 @@ The following is an example of the format of the data: } } } +``` ```json diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 9c995a52fe3..54669c687c1 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -314,6 +314,25 @@ export const USER_IDS_CONFIG = { return data.envelope; } }, + + 'pafData': { + getValue: function(data) { + return data.identifiers[0].value; + }, + source: 'paf', + atype: 1, + getEidExt: function(data) { + return {preferences: data.preferences}; + }, + getUidExt: function(data) { + const id = data.identifiers[0]; + return { + version: id.version, + type: id.type, + source: id.source + }; + } + } }; // this function will create an eid object for the given UserId sub-module From fb626773eb9126aa3c2b70e4b4c063f613b4d96a Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Fri, 15 Apr 2022 11:17:31 -0700 Subject: [PATCH 4/6] revert to OpenX serving test ads --- ...pafBidAdapter.js => openxRtbBidAdapter.js} | 143 +++++++++++------- modules/pafIdSystem.md | 2 +- 2 files changed, 91 insertions(+), 54 deletions(-) rename modules/{pafBidAdapter.js => openxRtbBidAdapter.js} (71%) diff --git a/modules/pafBidAdapter.js b/modules/openxRtbBidAdapter.js similarity index 71% rename from modules/pafBidAdapter.js rename to modules/openxRtbBidAdapter.js index 4a30a530d44..ca06e883237 100644 --- a/modules/pafBidAdapter.js +++ b/modules/openxRtbBidAdapter.js @@ -10,8 +10,10 @@ const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', 'startdelay', 'skippable', 'playbackmethod', 'api', 'protocols', 'boxingallowed', 'linearity', 'delivery', 'protocol', 'placement', 'minbitrate', 'maxbitrate', 'ext']; export const REQUEST_URL = 'https://rtb.openx.net/openrtbb/prebidjs'; +export const SYNC_URL = 'https://u.openx.net/w/1.0/pd'; +export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; export const spec = { - code: 'paf', + code: 'openx2', supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, @@ -30,7 +32,16 @@ function transformBidParams(params, isOpenRtb) { } function isBidRequestValid(bidRequest) { - return true; + const hasDelDomainOrPlatform = bidRequest.params.delDomain || + bidRequest.params.platform; + + if (utils.deepAccess(bidRequest, 'mediaTypes.banner') && + hasDelDomainOrPlatform) { + return !!bidRequest.params.unit || + utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; + } + + return !!(bidRequest.params.unit && hasDelDomainOrPlatform); } function buildRequests(bids, bidderRequest) { @@ -190,10 +201,17 @@ function getBaseRequest(bid, bidderRequest) { language: window.navigator.language.split('-').shift() }, ext: { - bc: bid.params.bc || `${bidderConfig}_${bidderVersion}` + bc: bid.params.bc || `${bidderConfig}_${bidderVersion}`, + frontier_flags: {is_filtered: true} } }; + if (bid.params.platform) { + utils.deepSetValue(req, 'ext.platform', bid.params.platform); + } + if (bid.params.delDomain) { + utils.deepSetValue(req, 'ext.delDomain', bid.params.delDomain); + } if (bid.params.test) { req.test = 1 } @@ -257,61 +275,55 @@ function getFloor(bid, mediaType) { } function interpretResponse(resp, req) { + // pass these from request to the responses for use in userSync + if (req.data.ext) { + if (req.data.ext.delDomain) { + utils.deepSetValue(resp, 'body.ext.delDomain', req.data.ext.delDomain); + } + if (req.data.ext.platform) { + utils.deepSetValue(resp, 'body.ext.platform', req.data.ext.platform); + } + } const respBody = resp.body; + if ('nbr' in respBody) { + return []; + } + let bids = []; - req.data.imp.forEach(imp => { - let transmissions = [ - { - "version": "0.1", - "receiver": "ssp1.com", - "status": "success", - "details": "", - "source": { - "domain": "ssp1.com", - "timestamp": 1639589531, - "signature": "12345_signature" - } - }, - { - "version": "0.1", - "receiver": "ssp2.com", - "status": "success", - "details": "", - "source": { - "domain": "ssp2.com", - "timestamp": 1639589531, - "signature": "12345_signature" - } - }, - { - "version": "0.1", - "receiver": "dsp.com", - "status": "success", - "details": "", - "source": { - "domain": "dsp.com", - "timestamp": 1639589531, - "signature": "12345_signature" - } - } - ]; - bids.push({ - ad: `

Test Ad

`, - requestId: imp.id, - cpm: 1, - width: imp.banner.format[0].w, - height: imp.banner.format[0].h, - creativeId: "test-crid", - currency: 'USD', + respBody.seatbid.forEach(seatbid => { + bids = [...bids, ...seatbid.bid.map(bid => { + let response = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + dealId: bid.dealid, + currency: respBody.cur || 'USD', netRevenue: true, ttl: 300, - mediaType: 'banner', - meta: { - paf: { - transmissions: transmissions + mediaType: 'banner' in req.data.imp[0] ? BANNER : VIDEO, + meta: { advertiserDomains: bid.adomain } + }; + + if (response.mediaType === VIDEO) { + if (bid.nurl) { + response.vastUrl = bid.nurl; + } else { + response.vastXml = bid.adm; } - }}); + } else { + response.ad = bid.adm; + } + + if (bid.ext) { + response.meta.networkId = bid.ext.dsp_id; + response.meta.advertiserId = bid.ext.buyer_id; + response.meta.brandId = bid.ext.brand_id; + } + return response + })]; }); return bids; @@ -325,5 +337,30 @@ function interpretResponse(resp, req) { * @return {{type: (string), url: (*|string)}[]} */ function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { - return undefined; + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let queryParamStrings = []; + let syncUrl = SYNC_URL; + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + if (responses.length > 0 && responses[0].body && responses[0].body.ext) { + const ext = responses[0].body.ext; + if (ext.delDomain) { + syncUrl = `https://${ext.delDomain}/w/1.0/pd` + } else if (ext.platform) { + queryParamStrings.push('ph=' + ext.platform) + } + } else { + queryParamStrings.push('ph=' + DEFAULT_PH) + } + return [{ + type: pixelType, + url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` + }]; + } } \ No newline at end of file diff --git a/modules/pafIdSystem.md b/modules/pafIdSystem.md index ccd593030b3..54fbfdf0a1b 100644 --- a/modules/pafIdSystem.md +++ b/modules/pafIdSystem.md @@ -1,4 +1,4 @@ -# Prebid Addreaability Framework (OneKey) +# Prebid Addressability Framework (OneKey) Insert description here https://github.com/prebid/addressability-framework From ba048936c2e3fbfa4106fad4101096f2f75557df Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Mon, 25 Apr 2022 14:24:55 -0700 Subject: [PATCH 5/6] update pafRtdProvider to use generateSeed --- modules/pafRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pafRtdProvider.js b/modules/pafRtdProvider.js index e0203a97f8a..d729facc13d 100644 --- a/modules/pafRtdProvider.js +++ b/modules/pafRtdProvider.js @@ -32,7 +32,7 @@ function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { deepSetValue(adUnits[i], `ortb2Imp.ext.data.paf.transaction_id`, uuid) } - window.PAF.createSeed({proxyHostName: rtdConfig.params.proxyHostName, callback: function (seed) {setData(seed, rtdConfig, onDone);}}, transaction_ids) + window.PAF.generateSeed({proxyHostName: rtdConfig.params.proxyHostName, callback: function (seed) {setData(seed, rtdConfig, onDone);}}, transaction_ids) } else { onDone(); return; From dcb2e4ff35097196da90cec959fceddc320152c6 Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Fri, 29 Apr 2022 13:36:28 -0700 Subject: [PATCH 6/6] add PAF unit tests --- modules/.submodules.json | 2 + modules/openxRtbBidAdapter.js | 4 +- modules/pafIdSystem.js | 42 ++--- modules/pafIdSystem.md | 14 +- modules/pafRtdProvider.js | 15 +- modules/pafRtdProvider.md | 38 +++- modules/userId/eids.js | 23 ++- test/spec/modules/pafIdSystem_spec.js | 114 ++++++++++++ test/spec/modules/pafRtdProvider_spec.js | 213 +++++++++++++++++++++++ 9 files changed, 417 insertions(+), 48 deletions(-) create mode 100644 test/spec/modules/pafIdSystem_spec.js create mode 100644 test/spec/modules/pafRtdProvider_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 59bae2013d1..1ca72def9f3 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -32,6 +32,7 @@ "netIdSystem", "nextrollIdSystem", "novatiqIdSystem", + "pafIdSystem", "parrableIdSystem", "pubProvidedIdSystem", "publinkIdSystem", @@ -60,6 +61,7 @@ "jwplayerRtdProvider", "medianetRtdProvider", "optimeraRtdProvider", + "pafRtdProvider", "permutiveRtdProvider", "reconciliationRtdProvider", "sirdataRtdProvider", diff --git a/modules/openxRtbBidAdapter.js b/modules/openxRtbBidAdapter.js index ca06e883237..1ec375ae928 100644 --- a/modules/openxRtbBidAdapter.js +++ b/modules/openxRtbBidAdapter.js @@ -2,7 +2,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import includes from 'core-js-pure/features/array/includes.js' +import {includes} from '../src/polyfill.js'; const bidderConfig = 'hb_pb_ortb'; const bidderVersion = '1.0'; @@ -363,4 +363,4 @@ function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` }]; } -} \ No newline at end of file +} diff --git a/modules/pafIdSystem.js b/modules/pafIdSystem.js index 73c26670d0e..3de42ea2c94 100644 --- a/modules/pafIdSystem.js +++ b/modules/pafIdSystem.js @@ -1,29 +1,29 @@ /** - * This module adds pafId to the User ID module + * This module adds pafData to the User ID module * The {@link module:modules/userId} module is required * @module modules/swanIdSystem * @requires module:modules/userId */ - import {submodule} from '../src/hook.js'; +import {submodule} from '../src/hook.js'; - /** @type {Submodule} */ - export const pafIdSubmodule = { - /** +/** @type {Submodule} */ +export const pafIdSubmodule = { + /** * used to link submodule with config * @type {string} */ - name: 'pafData', - /** + name: 'pafData', + /** * decode the stored data value for passing to bid requests * @function decode * @param {(Object|string)} value * @returns {(Object|undefined)} */ - decode(data) { - return { pafData: data }; - }, - /** + decode(data) { + return { pafData: data }; + }, + /** * performs action to obtain id and return a value in the callback's response argument * @function * @param {SubmoduleConfig} [config] @@ -31,13 +31,13 @@ * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ - getId(config, consentData) { - if (window.PAF && window.PAF.getIdsAndPreferences()) { - return {id: window.PAF.getIdsAndPreferences()}; - } else { - return undefined; - } - } - }; - - submodule('userId', pafIdSubmodule); \ No newline at end of file + getId(config, consentData) { + if (window.PAF && window.PAF.getIdsAndPreferences()) { + return {id: window.PAF.getIdsAndPreferences()}; + } else { + return undefined; + } + } +}; + +submodule('userId', pafIdSubmodule); diff --git a/modules/pafIdSystem.md b/modules/pafIdSystem.md index 54fbfdf0a1b..3c9a3c16aa8 100644 --- a/modules/pafIdSystem.md +++ b/modules/pafIdSystem.md @@ -1,12 +1,14 @@ # Prebid Addressability Framework (OneKey) -Insert description here -https://github.com/prebid/addressability-framework -https://github.com/prebid/paf-mvp-implementation +The PAF real-time data module in Prebid has been built so that publishers +can quickly and easily setup the Prebid Addressability Framework and utilize OneKey. +This module is used along with the pafRtdProvider to pass PAF data to your partners. +Both modules are required. This module will pass paData to your partners +while the pafRtdProvider will pass the transmission requests. -## PAF Registration - -insert info here +Background information: +- [prebid/addressability-framework](https://github.com/prebid/addressability-framework) +- [prebid/paf-mvp-implementation](https://github.com/prebid/paf-mvp-implementation) ## PAF Configuration diff --git a/modules/pafRtdProvider.js b/modules/pafRtdProvider.js index d729facc13d..07cc322842b 100644 --- a/modules/pafRtdProvider.js +++ b/modules/pafRtdProvider.js @@ -13,7 +13,7 @@ const SUBMODULE_NAME = 'paf'; * @param {Object} rtdConfig * @param {Object} userConsent */ -function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { +export function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { let idsAndPreferences; const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits); @@ -25,17 +25,16 @@ function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { return; } - let transaction_ids = []; - for (var i=0; i < adUnits.length; i++) { + let transactionIds = []; + for (var i = 0; i < adUnits.length; i++) { const uuid = generateUUID(); - transaction_ids.push(uuid) + transactionIds.push(uuid) deepSetValue(adUnits[i], `ortb2Imp.ext.data.paf.transaction_id`, uuid) } - window.PAF.generateSeed({proxyHostName: rtdConfig.params.proxyHostName, callback: function (seed) {setData(seed, rtdConfig, onDone);}}, transaction_ids) + window.PAF.generateSeed({proxyHostName: rtdConfig.params.proxyHostName, callback: function (seed) { setData(seed, rtdConfig, onDone); }}, transactionIds) } else { onDone(); - return; } } @@ -44,7 +43,7 @@ function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { * @param {Object} target * @param {Object} source */ - function mergeLazy(target, source) { +function mergeLazy(target, source) { if (!isPlainObject(target)) { target = {}; } @@ -56,7 +55,7 @@ function getBidRequestData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { return mergeDeep(target, source); } -function setData(seed, rtdConfig, onDone) { +export function setData(seed, rtdConfig, onDone) { if (!seed) { logError(SUBMODULE_NAME, 'Could not createSeed'); onDone() diff --git a/modules/pafRtdProvider.md b/modules/pafRtdProvider.md index c879a04b020..a685413aa77 100644 --- a/modules/pafRtdProvider.md +++ b/modules/pafRtdProvider.md @@ -10,8 +10,6 @@ Background information: - [prebid/addressability-framework](https://github.com/prebid/addressability-framework) - [prebid/paf-mvp-implementation](https://github.com/prebid/paf-mvp-implementation) - - ### Publisher Usage The paf RTD module depends on paf-lib.js existing in the page. @@ -91,4 +89,38 @@ The following is an example of the format of the data: } } } -``` \ No newline at end of file +``` + +### Bidder Responses + +Bidders who are part of the Prebid Addressability Framework and receive PAF +transmissions are required to return transmission responses as outlined in +[prebid/addressability-framework](https://github.com/prebid/addressability-framework/blob/main/mvp-spec/ad-auction.md). Transmission responses should be appended to bids +along with the releveant content_id using the meta.paf field. The paf-lib will +be responsible for collecting all of the transmission responses. + +Below is an example of setting a transmission response: +```javascript +bid.meta.paf = { + "content_id": "90141190-26fe-497c-acee-4d2b649c2112", + "transmission": { + "version": "0.1", + "contents": [ + { + "transaction_id": "f55a401d-e8bb-4de1-a3d2-fa95619393e8", + "content_id": "90141190-26fe-497c-acee-4d2b649c2112" + } + ], + "status": "success", + "details": "", + "receiver": "dsp1.com", + "source": { + "domain": "dsp1.com", + "timestamp": 1639589531, + "signature": "d01c6e83f14b4f057c2a2a86d320e2454fc0c60df4645518d993b5f40019d24c" + }, + "children": [] + } +} +``` + diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 54669c687c1..d49af8fbad8 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -315,22 +315,29 @@ export const USER_IDS_CONFIG = { } }, + // PAF Data 'pafData': { getValue: function(data) { - return data.identifiers[0].value; + if (data && Array.isArray(data.identifiers) && data.identifiers[0]) { + return data.identifiers[0].value; + } }, source: 'paf', atype: 1, getEidExt: function(data) { - return {preferences: data.preferences}; + if (data && data.preferences) { + return {preferences: data.preferences}; + } }, getUidExt: function(data) { - const id = data.identifiers[0]; - return { - version: id.version, - type: id.type, - source: id.source - }; + if (data && Array.isArray(data.identifiers) && data.identifiers[0]) { + const id = data.identifiers[0]; + return { + version: id.version, + type: id.type, + source: id.source + }; + } } } }; diff --git a/test/spec/modules/pafIdSystem_spec.js b/test/spec/modules/pafIdSystem_spec.js new file mode 100644 index 00000000000..22825e2922f --- /dev/null +++ b/test/spec/modules/pafIdSystem_spec.js @@ -0,0 +1,114 @@ +import { pafIdSubmodule } from 'modules/pafIdSystem' +import { config } from 'src/config.js'; +import {find} from 'src/polyfill.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; + +const idsAndPrefs = { + 'identifiers': [ + { + 'version': '0.1', + 'type': 'paf_browser_id', + 'value': 'da135b3a-7d04-44bf-a0af-c4709f10420b', + 'source': { + 'domain': 'crto-poc-1.onekey.network', + 'timestamp': 1648836556881, + 'signature': '+NF27bBvPM54z103YPExXuS834+ggAQe6JV0jPeGo764vRYiiBl5OmEXlnB7UZgxNe3KBU7rN2jk0SkI4uL0bg==' + } + } + ], + 'preferences': { + 'version': '0.1', + 'data': { + 'use_browsing_for_personalization': true + }, + 'source': { + 'domain': 'cmp.pafdemopublisher.com', + 'timestamp': 1648836566468, + 'signature': 'ipbYhU8IbSFm2tCqAVYI2d5w4DnGF7Xa2AaiZScx2nmBPLfMmIT/FkBYGitR8Mi791DHtcy5MXr4+bs1aeZFqw==' + } + } +}; + +function getConfigMock() { + return { + userSync: { + syncDelay: 0, + userIds: [{ + name: 'pafData' + }] + } + } +} + +function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [ + [300, 200], + [300, 600] + ], + bids: [{ + bidder: 'sampleBidder', + params: { placementId: 'banner-only-bidder' } + }] + }; +} + +describe('pafData module', function () { + it('returns undefined if paf-lib is not found', function () { + const moduleIdResponse = pafIdSubmodule.getId(); + expect(moduleIdResponse).to.be.undefined; + }) + it('returns undefined if no Data', function () { + window.PAF = { + getIdsAndPreferences() { + return undefined; + } + } + const moduleIdResponse = pafIdSubmodule.getId(); + expect(moduleIdResponse).to.be.undefined; + }) + it('gets pafData from page context', function () { + window.PAF = { + getIdsAndPreferences() { + return idsAndPrefs; + } + } + const moduleIdResponse = pafIdSubmodule.getId(); + expect(moduleIdResponse).to.deep.equal({id: idsAndPrefs}); + }) + + // this test format was copied from other id module tests + // but it is failing on the hook and im not sure why, if someone + // knows why and can help i will fix, otherwise i will remove it + // describe('requestBids hook', function() { + // let adUnits; + + // beforeEach(function() { + // adUnits = [getAdUnitMock()]; + // window.PAF = { + // getIdsAndPreferences() { + // return idsAndPrefs; + // } + // } + // init(config); + // setSubmoduleRegistry([pafIdSubmodule]); + // config.setConfig(getConfigMock()); + // }); + + // it('when pafData exists it is added to bids', function(done) { + // requestBidsHook(function() { + // adUnits.forEach(unit => { + // unit.bids.forEach(bid => { + // expect(bid).to.have.deep.nested.property('userId.pafData'); + // expect(bid.userId.pafData).to.equal(idsAndPrefs); + // const pafDataAsEid = find(bid.userIdAsEids, e => e.source == 'paf'); + // expect(pafDataAsEid.uids[0].id).to.equal('da135b3a-7d04-44bf-a0af-c4709f10420b'); + // }); + // }); + // done(); + // }, { adUnits }); + // }); + // }); +}) diff --git a/test/spec/modules/pafRtdProvider_spec.js b/test/spec/modules/pafRtdProvider_spec.js new file mode 100644 index 00000000000..7d719fb4610 --- /dev/null +++ b/test/spec/modules/pafRtdProvider_spec.js @@ -0,0 +1,213 @@ +import {config} from 'src/config.js'; +import {setData, getBidRequestData, pafDataSubmodule} from 'modules/pafRtdProvider.js'; +import {getAdUnits} from '../../fixtures/fixtures.js'; + +describe('pafRtdProvider', function() { + beforeEach(function() { + config.resetConfig(); + }); + + describe('pafDataSubmodule', function() { + it('successfully instantiates', function () { + expect(pafDataSubmodule.init()).to.equal(true); + }); + }); + + describe('setData', function() { + it('merges global ortb2 data', function() { + let rtdConfig = {params: {proxyHostName: 'host'}}; + let seed = 'seed_placeholder'; + + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + config.setConfig({ + ortb2: { + user: { + data: [setConfigUserObj1], + ext: {other: 'data'} + } + } + }); + + setData(seed, rtdConfig, () => {}); + + let ortb2Config = config.getConfig().ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1]); + expect(ortb2Config.user.ext.paf.transmission.seed).to.equal(seed); + expect(ortb2Config.user.ext.other).to.equal('data'); + }); + + it('merges bidder-specific ortb2 data', function() { + let rtdConfig = {params: {proxyHostName: 'host', bidders: ['openx']}}; + let seed = 'seed_placeholder'; + + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + config.setBidderConfig({ + bidders: ['bidder1'], + config: { + ortb2: { + user: { + data: [setConfigUserObj1], + ext: {other: 'data'} + } + } + } + }); + + config.setBidderConfig({ + bidders: ['openx'], + config: { + ortb2: { + user: { + data: [setConfigUserObj1], + ext: {other: 'data'} + } + } + } + }); + + setData(seed, rtdConfig, () => {}); + + let ortb2Config = config.getBidderConfig().bidder1.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1]); + expect(ortb2Config.user.ext.paf).to.be.undefined; + expect(ortb2Config.user.ext.other).to.equal('data'); + + ortb2Config = config.getBidderConfig().openx.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1]); + expect(ortb2Config.user.ext.paf.transmission.seed).to.equal(seed); + expect(ortb2Config.user.ext.other).to.equal('data'); + }); + }); + + describe('getBidRequestData', function() { + it('gets seed from paf-lib and sets data and transaction_ids', function() { + const adUnits = getAdUnits(); + window.PAF = { + getIdsAndPreferences() { + return true; + }, + generateSeed(options, ids) { + options.callback({ + transaction_ids: ids + }) + } + } + let bidConfig = {adUnits}; + let rtdConfig = {params: {proxyHostName: 'host'}}; + + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + config.setConfig({ + ortb2: { + user: { + data: [setConfigUserObj1], + ext: {other: 'data'} + } + } + }); + + getBidRequestData(bidConfig, () => {}, rtdConfig, {}); + let ortb2Config = config.getConfig().ortb2; + + adUnits.forEach(adUnit => { + const transaction_id = adUnit.ortb2Imp.ext.data.paf.transaction_id; + expect(transaction_id).to.not.be.undefined; + expect(ortb2Config.user.ext.paf.transmission.seed.transaction_ids).contain(transaction_id) + }); + + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1]); + expect(ortb2Config.user.ext.paf.transmission.seed).to.have.property('transaction_ids'); + expect(ortb2Config.user.ext.other).to.equal('data'); + }); + }); + + it('does nothing if paf-lib doesnt exist', function() { + const adUnits = getAdUnits(); + window.PAF = undefined; + let bidConfig = {adUnits}; + let rtdConfig = {params: {proxyHostName: 'host'}}; + + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + config.setConfig({ + ortb2: { + user: { + data: [setConfigUserObj1], + ext: {other: 'data'} + } + } + }); + + getBidRequestData(bidConfig, () => {}, rtdConfig, {}); + let ortb2Config = config.getConfig().ortb2; + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1]); + expect(ortb2Config.user.ext.other).to.equal('data'); + }); + + it('requires proxyHostName', function() { + const adUnits = getAdUnits(); + window.PAF = { + getIdsAndPreferences() { + return true; + }, + generateSeed(options, ids) { + options.callback({ + transaction_ids: ids + }) + } + } + let bidConfig = {adUnits}; + let rtdConfig = {params: {}}; + + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + config.setConfig({ + ortb2: { + user: { + data: [setConfigUserObj1], + ext: {other: 'data'} + } + } + }); + + getBidRequestData(bidConfig, () => {}, rtdConfig, {}); + let ortb2Config = config.getConfig().ortb2; + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1]); + expect(ortb2Config.user.ext.other).to.equal('data'); + }); +});