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

Transmitting targeting data to bidder adapters #6

Merged
merged 12 commits into from
Jun 28, 2022
1 change: 1 addition & 0 deletions integrationExamples/gpt/1plusXRtdProviderExample.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
waitForIt: true,
params: {
customerId: 'acme',
bidders: ['appnexus'],
timeout: 1000
}

Expand Down
149 changes: 134 additions & 15 deletions modules/1plusXRtdProvider.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,64 @@
import { submodule } from '../src/hook.js'
import { ajax } from '../src/ajax.js'
import { logMessage, logError, deepAccess, isNumber } from '../src/utils.js';
import { submodule } from '../src/hook.js';
import { config } from '../src/config.js';
import { ajax } from '../src/ajax.js';
import {
logMessage, logError,
deepAccess, mergeDeep,
isNumber, isArray, deepSetValue
} from '../src/utils.js';

// Constants
const REAL_TIME_MODULE = 'realTimeData';
const MODULE_NAME = '1plusX';
const PAPI_VERSION = 'v1.0';
const SUPPORTED_BIDDERS = ['appnexus', 'rubicon']

// Functions
const extractConfig = (config) => {
/**
* Extracts the parameters for 1plusX RTD module from the config object passed at instanciation
* @param {Object} moduleConfig Config object passed to the module
* @param {Object} reqBidsConfigObj Config object for the bidders; each adapter has its own entry
* @returns
*/
const extractConfig = (moduleConfig, reqBidsConfigObj) => {
// CustomerId
const customerId = deepAccess(config, 'params.customerId');
const customerId = deepAccess(moduleConfig, 'params.customerId');
if (!customerId) {
throw new Error('REQUIRED CUSTOMER ID');
}
// Timeout
const tempTimeout = deepAccess(config, 'params.timeout');
const tempTimeout = deepAccess(moduleConfig, 'params.timeout');
const timeout = isNumber(tempTimeout) && tempTimeout > 300 ? tempTimeout : 1000;

return { customerId, timeout };
// Bidders
const biddersTemp = deepAccess(moduleConfig, 'params.bidders');
if (!isArray(biddersTemp) || !biddersTemp.length) {
throw new Error('REQUIRED BIDDERS IN SUBMODULE CONFIG');
}

const adUnitBidders = reqBidsConfigObj.adUnits
.flatMap(({ bids }) => bids.map(({ bidder }) => bidder))
.filter((e, i, a) => a.indexOf(e) === i);
if (!isArray(adUnitBidders) || !adUnitBidders.length) {
throw new Error('REQUIRED BIDDERS IN BID REQUEST CONFIG');
}

const bidders = biddersTemp.filter(
bidder =>
SUPPORTED_BIDDERS.includes(bidder) && adUnitBidders.includes(bidder)
);
if (!bidders.length) {
throw new Error('NO SUPPORTED BIDDER FOUND IN SUBMODULE/ BID REQUEST CONFIG');
}

return { customerId, timeout, bidders };
}

/**
* Gets the URL of Profile Api from which targeting data will be fetched
* @param {*} param0
* @returns
*/
const getPapiUrl = ({ customerId }) => {
logMessage('GET PAPI URL');
// https://[yourClientId].profiles.tagger.opecloud.com/[VERSION]/targeting?url=
Expand All @@ -29,6 +67,11 @@ const getPapiUrl = ({ customerId }) => {
return papiUrl;
}

/**
* Fetches targeting data. It contains the audience segments & the contextual topics
* @param {string} papiUrl URL of profile API
* @returns
*/
const getTargetingDataFromPapi = (papiUrl) => {
return new Promise((resolve, reject) => {
const requestOptions = {
Expand All @@ -38,7 +81,7 @@ const getTargetingDataFromPapi = (papiUrl) => {
}
const callbacks = {
success(responseText, response) {
logMessage("Say it has been successful");
logMessage('Say it has been successful');
resolve(JSON.parse(response.response));
},
error(errorText, error) {
Expand All @@ -51,26 +94,102 @@ const getTargetingDataFromPapi = (papiUrl) => {
})
}

/**
* Prepares the update for the ORTB2 object
* @param {*} param0
* @returns
*/
export const buildOrtb2Updates = ({ segments = [], topics = [] }) => {
const userData = {
name: '1plusX.com',
segment: segments.map((segmentId) => ({ id: segmentId }))
};
const site = {
keywords: topics.join(',')
};
return { userData, site };
}

/**
* Merges the targeting data with the existing config for bidder and updates
* @param {string} bidder Bidder for which to set config
* @param {Object} ortb2
* @param {Object} bidderConfigs
* @returns
*/
export const updateBidderConfig = (bidder, ortb2Updates, bidderConfigs) => {
if (!SUPPORTED_BIDDERS.includes(bidder)) {
return null;
}
const { site, userData } = ortb2Updates;
const bidderConfigCopy = mergeDeep({}, bidderConfigs[bidder]);

const currentSite = deepAccess(bidderConfigCopy, 'ortb2.site')
const updatedSite = mergeDeep(currentSite, site);

const currentUserData = deepAccess(bidderConfigCopy, 'ortb2.user.data') || [];
const updatedUserData = [
...currentUserData.filter(({ name }) => name != userData.name),
userData
];

deepSetValue(bidderConfigCopy, 'ortb2.site', updatedSite);
deepSetValue(bidderConfigCopy, 'ortb2.user.data', updatedUserData);

return bidderConfigCopy
};

/**
* Updates bidder configs with the targeting data retreived from Profile API
* @param {*} papiResponse
* @param {*} param1
*/
export const setTargetingDataToConfig = (papiResponse, { bidders }) => {
const bidderConfigs = config.getBidderConfig();
const { s: segments, t: topics } = papiResponse;
const ortb2Updates = buildOrtb2Updates({ segments, topics });

for (const bidder of bidders) {
const updatedBidderConfig = updateBidderConfig(bidder, ortb2Updates, bidderConfigs);
if (updatedBidderConfig) {
config.setBidderConfig({
bidders: [bidder],
config: updatedBidderConfig
});
}
}
}

// Functions exported in submodule object
/**
* Init
* @param {*} config
* @param {*} userConsent
* @returns
*/
const init = (config, userConsent) => {
// We prolly get the config again in getBidRequestData
return true;
}

const getBidRequestData = (reqBidsConfigObj, callback, config, userConsent) => {
/**
*
* @param {*} reqBidsConfigObj
* @param {*} callback
* @param {*} moduleConfig
* @param {*} userConsent
*/
const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent) => {
try {
// Get the required config
const { customerId } = extractConfig(config);
const { customerId, bidders } = extractConfig(moduleConfig, reqBidsConfigObj);
// Get PAPI URL
const papiUrl = getPapiUrl({ customerId })
// Call PAPI
getTargetingDataFromPapi(papiUrl)
.then((response) => {
// -- Then :
// ---- extract relevant data
// ---- set the data to the bid
.then((papiResponse) => {
logMessage('REQUEST TO PAPI SUCCESS');
const { s: segments, t: targeting } = response;
setTargetingDataToConfig(papiResponse, { bidders });
callback();
})
.catch((error) => {
Expand Down
Loading