Skip to content

Commit

Permalink
Refactor: Consolidate shared adapter methods into dspxUtils, reduce r…
Browse files Browse the repository at this point in the history
…edundant code (#12140)

* Refactor: Consolidate shared adapter methods into dspxUtils, reduce redundant code

* Update bidderUtils.js

* Update bidderUtils.js

---------

Co-authored-by: avj <[email protected]>
Co-authored-by: Patrick McCann <[email protected]>
  • Loading branch information
3 people authored Sep 9, 2024
1 parent f728178 commit 1d2b583
Show file tree
Hide file tree
Showing 5 changed files with 453 additions and 609 deletions.
390 changes: 390 additions & 0 deletions libraries/dspxUtils/bidderUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,390 @@
import { BANNER, VIDEO } from '../../src/mediaTypes.js';
import {deepAccess, isArray, isEmptyStr, isFn, logError} from '../../src/utils.js';
/**
* @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
* @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
* @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse
*/
/**
* Adds userIds to payload
*
* @param bidRequest
* @param payload
*/
export function fillUsersIds(bidRequest, payload) {
if (bidRequest.hasOwnProperty('userId')) {
let didMapping = {
did_netid: 'userId.netId',
did_id5: 'userId.id5id.uid',
did_id5_linktype: 'userId.id5id.ext.linkType',
did_uid2: 'userId.uid2',
did_sharedid: 'userId.sharedid',
did_pubcid: 'userId.pubcid',
did_uqid: 'userId.utiq',
did_cruid: 'userId.criteoid',
did_euid: 'userId.euid',
// did_tdid: 'unifiedId',
did_tdid: 'userId.tdid',
did_ppuid: function() {
let path = 'userId.pubProvidedId';
let value = deepAccess(bidRequest, path);
if (isArray(value)) {
for (const rec of value) {
if (rec.uids && rec.uids.length > 0) {
for (let i = 0; i < rec.uids.length; i++) {
if ('id' in rec.uids[i] && deepAccess(rec.uids[i], 'ext.stype') === 'ppuid') {
return (rec.uids[i].atype ?? '') + ':' + rec.source + ':' + rec.uids[i].id;
}
}
}
}
}
return undefined;
},
did_cpubcid: 'crumbs.pubcid'
};
for (let paramName in didMapping) {
let path = didMapping[paramName];

// handle function
if (typeof path == 'function') {
let value = path(paramName);
if (value) {
payload[paramName] = value;
}
continue;
}
// direct access
let value = deepAccess(bidRequest, path);
if (typeof value == 'string' || typeof value == 'number') {
payload[paramName] = value;
} else if (typeof value == 'object') {
// trying to find string ID value
if (typeof deepAccess(bidRequest, path + '.id') == 'string') {
payload[paramName] = deepAccess(bidRequest, path + '.id');
} else {
if (Object.keys(value).length > 0) {
logError(`WARNING: fillUserIds had to use first key in user object to get value for bid.userId key: ${path}.`);
payload[paramName] = value[Object.keys(value)[0]];
}
}
}
}
}
}

export function appendToUrl(url, what) {
if (!what) {
return url;
}
return url + (url.indexOf('?') !== -1 ? '&' : '?') + what;
}

export function objectToQueryString(obj, prefix) {
let str = [];
let p;
for (p in obj) {
if (obj.hasOwnProperty(p)) {
let k = prefix ? prefix + '[' + p + ']' : p;
let v = obj[p];
str.push((v !== null && typeof v === 'object')
? objectToQueryString(v, k)
: encodeURIComponent(k) + '=' + encodeURIComponent(v));
}
}
return str.filter(n => n).join('&');
}

/**
* Check if it's a banner bid request
*
* @param {BidRequest} bid - Bid request generated from ad slots
* @returns {boolean} True if it's a banner bid
*/
export function isBannerRequest(bid) {
return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid);
}

/**
* Check if it's a video bid request
*
* @param {BidRequest} bid - Bid request generated from ad slots
* @returns {boolean} True if it's a video bid
*/
export function isVideoRequest(bid) {
return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video');
}

/**
* Get video sizes
*
* @param {BidRequest} bid - Bid request generated from ad slots
* @returns {object}
*/
export function getVideoSizes(bid) {
return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes);
}

/**
* Get video context
*
* @param {BidRequest} bid - Bid request generated from ad slots
* @returns {object}
*/
export function getVideoContext(bid) {
return deepAccess(bid, 'mediaTypes.video.context') || 'unknown';
}

/**
* Get banner sizes
*
* @param {BidRequest} bid - Bid request generated from ad slots
* @returns {object} True if it's a video bid
*/
export function getBannerSizes(bid) {
return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes);
}

/**
* Parse size
* @param size
* @returns {object} sizeObj
*/
export function parseSize(size) {
let sizeObj = {}
sizeObj.width = parseInt(size[0], 10);
sizeObj.height = parseInt(size[1], 10);
return sizeObj;
}

/**
* Parse sizes
* @param sizes
* @returns {{width: number , height: number }[]}
*/
export function parseSizes(sizes) {
if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]])
return sizes.map(size => parseSize(size));
}
return [parseSize(sizes)]; // or a single one ? (ie. [728,90])
}

/**
* Get MediaInfo object for server request
*
* @param mediaTypesInfo
* @returns {*}
*/
export function convertMediaInfoForRequest(mediaTypesInfo) {
let requestData = {};
Object.keys(mediaTypesInfo).forEach(mediaType => {
requestData[mediaType] = mediaTypesInfo[mediaType].map(size => {
return size.width + 'x' + size.height;
}).join(',');
});
return requestData;
}

/**
* Get media types info
*
* @param bid
*/
export function getMediaTypesInfo(bid) {
let mediaTypesInfo = {};

if (bid.mediaTypes) {
Object.keys(bid.mediaTypes).forEach(mediaType => {
if (mediaType === BANNER) {
mediaTypesInfo[mediaType] = getBannerSizes(bid);
}
if (mediaType === VIDEO) {
mediaTypesInfo[mediaType] = getVideoSizes(bid);
}
});
} else {
mediaTypesInfo[BANNER] = getBannerSizes(bid);
}
return mediaTypesInfo;
}

/**
* Get Bid Floor
* @param bid
* @returns {number|*}
*/
export function getBidFloor(bid) {
if (!isFn(bid.getFloor)) {
return deepAccess(bid, 'params.bidfloor', 0);
}

try {
const bidFloor = bid.getFloor({
currency: 'EUR',
mediaType: '*',
size: '*',
});
return bidFloor.floor;
} catch (_) {
return 0
}
}

/**
* Convert site.content to string
* @param content
*/
export function siteContentToString(content) {
if (!content) {
return '';
}
let stringKeys = ['id', 'title', 'series', 'season', 'artist', 'genre', 'isrc', 'url', 'keywords'];
let intKeys = ['episode', 'context', 'livestream'];
let arrKeys = ['cat'];
let retArr = [];
arrKeys.forEach(k => {
let val = deepAccess(content, k);
if (val && Array.isArray(val)) {
retArr.push(k + ':' + val.join('|'));
}
});
intKeys.forEach(k => {
let val = deepAccess(content, k);
if (val && typeof val === 'number') {
retArr.push(k + ':' + val);
}
});
stringKeys.forEach(k => {
let val = deepAccess(content, k);
if (val && typeof val === 'string') {
retArr.push(k + ':' + encodeURIComponent(val));
}
});
return retArr.join(',');
}

/**
* Assigns multiple values to the specified keys on an object if the values are not undefined.
* @param {Object} target - The object to which the values will be assigned.
* @param {Object} values - An object containing key-value pairs to be assigned.
*/
export function assignDefinedValues(target, values) {
for (const key in values) {
if (values[key] !== undefined) {
target[key] = values[key];
}
}
}

/**
* Extracts user segments/topics from the bid request object
* @param {Object} bid - The bid request object
* @returns {{segclass: *, segtax: *, segments: *}|undefined} - User segments/topics or undefined if not found
*/
export function extractUserSegments(bid) {
const userData = deepAccess(bid, 'ortb2.user.data') || [];
for (const dataObj of userData) {
if (dataObj.segment && isArray(dataObj.segment) && dataObj.segment.length > 0) {
const segments = dataObj.segment
.filter(seg => seg.id && !isEmptyStr(seg.id) && isFinite(seg.id))
.map(seg => Number(seg.id));
if (segments.length > 0) {
return {
segtax: deepAccess(dataObj, 'ext.segtax'),
segclass: deepAccess(dataObj, 'ext.segclass'),
segments: segments.join(',')
};
}
}
}
return undefined;
}

export function handleSyncUrls(syncOptions, serverResponses, gdprConsent, uspConsent) {
if (!serverResponses || serverResponses.length === 0) {
return [];
}

const syncs = [];
let gdprParams = '';
if (gdprConsent) {
if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') {
gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
} else {
gdprParams = `gdpr_consent=${gdprConsent.consentString}`;
}
}

if (serverResponses.length > 0 && serverResponses[0].body.userSync) {
if (syncOptions.iframeEnabled) {
serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({
type: 'iframe',
url: appendToUrl(url, gdprParams)
}));
}
if (syncOptions.pixelEnabled) {
serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({
type: 'image',
url: appendToUrl(url, gdprParams)
}));
}
}
return syncs;
}

export function interpretResponse(serverResponse, bidRequest, rendererFunc) {
const bidResponses = [];
const response = serverResponse.body;
const crid = response.crid || 0;
const cpm = response.cpm / 1000000 || 0;
if (cpm !== 0 && crid !== 0) {
const dealId = response.dealid || '';
const currency = response.currency || 'EUR';
const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue;
const bidResponse = {
requestId: response.bid_id,
cpm: cpm,
width: response.width,
height: response.height,
creativeId: crid,
dealId: dealId,
currency: currency,
netRevenue: netRevenue,
type: response.type,
ttl: 60,
meta: {
advertiserDomains: response.adomain || []
}
};

if (response.vastUrl) {
bidResponse.vastUrl = response.vastUrl;
bidResponse.mediaType = 'video';
}
if (response.vastXml) {
bidResponse.vastXml = response.vastXml;
bidResponse.mediaType = 'video';
}
if (response.renderer) {
bidResponse.renderer = rendererFunc(bidRequest, response);
}

if (response.videoCacheKey) {
bidResponse.videoCacheKey = response.videoCacheKey;
}

if (response.adTag) {
bidResponse.ad = response.adTag;
}

if (response.bid_appendix) {
Object.keys(response.bid_appendix).forEach(fieldName => {
bidResponse[fieldName] = response.bid_appendix[fieldName];
});
}

bidResponses.push(bidResponse);
}
return bidResponses;
}
Loading

0 comments on commit 1d2b583

Please sign in to comment.