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

DSPx Bid Adapter: add ortb2 content, topics support #11941

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 176 additions & 54 deletions modules/dspxBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {deepAccess, getBidIdParameter, isFn, logError, logMessage, logWarn} from '../src/utils.js';
import {deepAccess, getBidIdParameter, isFn, logError, logMessage, logWarn, isEmptyStr, isArray} from '../src/utils.js';

import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {Renderer} from '../src/Renderer.js';
Expand All @@ -7,7 +8,6 @@ import {includes} from '../src/polyfill.js';
/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
*/

const BIDDER_CODE = 'dspx';
const ENDPOINT_URL = 'https://buyer.dspx.tv/request/';
const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/';
Expand Down Expand Up @@ -63,24 +63,18 @@ export const spec = {
rnd: rnd,
ref: referrer,
bid_id: bidId,
pbver: '$prebid.version$'
pbver: '$prebid.version$',
};

payload.pfilter = {};
if (params.pfilter !== undefined) {
payload.pfilter = params.pfilter;
}

if (bidderRequest && bidderRequest.gdprConsent) {
if (payload.pfilter !== undefined) {
if (!payload.pfilter.gdpr_consent) {
payload.pfilter.gdpr_consent = bidderRequest.gdprConsent.consentString;
payload.pfilter.gdpr = bidderRequest.gdprConsent.gdprApplies;
}
} else {
payload.pfilter = {
'gdpr_consent': bidderRequest.gdprConsent.consentString,
'gdpr': bidderRequest.gdprConsent.gdprApplies
};
if (!payload.pfilter.gdpr_consent) {
payload.pfilter.gdpr_consent = bidderRequest.gdprConsent.consentString;
payload.pfilter.gdpr = bidderRequest.gdprConsent.gdprApplies;
}
}

Expand All @@ -94,48 +88,10 @@ export const spec = {
payload.prebidDevMode = 1;
}

// fill userId params
if (bidRequest.userId) {
if (bidRequest.userId.netId) {
payload.did_netid = bidRequest.userId.netId;
}
if (bidRequest.userId.id5id) {
payload.did_id5 = bidRequest.userId.id5id.uid || '0';
if (bidRequest.userId.id5id.ext.linkType !== undefined) {
payload.did_id5_linktype = bidRequest.userId.id5id.ext.linkType;
}
}
let uId2 = deepAccess(bidRequest, 'userId.uid2.id');
if (uId2) {
payload.did_uid2 = uId2;
}
let sharedId = deepAccess(bidRequest, 'userId.sharedid.id');
if (sharedId) {
payload.did_sharedid = sharedId;
}
let pubcId = deepAccess(bidRequest, 'userId.pubcid');
if (pubcId) {
payload.did_pubcid = pubcId;
}
let crumbsPubcid = deepAccess(bidRequest, 'crumbs.pubcid');
if (crumbsPubcid) {
payload.did_cpubcid = crumbsPubcid;
}
}

if (bidRequest.schain) {
payload.schain = bidRequest.schain;
}

if (payload.pfilter === undefined || !payload.pfilter.floorprice) {
if (!payload.pfilter.floorprice) {
let bidFloor = getBidFloor(bidRequest);
if (bidFloor > 0) {
if (payload.pfilter !== undefined) {
payload.pfilter.floorprice = bidFloor;
} else {
payload.pfilter = { 'floorprice': bidFloor };
}
// payload.bidFloor = bidFloor;
payload.pfilter.floorprice = bidFloor;
}
}

Expand All @@ -146,6 +102,7 @@ export const spec = {
payload.pbcode = pbcode;
}

// media types
payload.media_types = convertMediaInfoForRequest(mediaTypesInfo);
if (mediaTypesInfo[VIDEO] !== undefined) {
payload.vctx = getVideoContext(bidRequest);
Expand All @@ -159,6 +116,33 @@ export const spec = {
.forEach(key => payload.vpl[key] = videoParams[key]);
}

// iab content
let content = deepAccess(bidderRequest, 'ortb2.site.content');
if (content) {
let stringContent = siteContentToString(content);
if (stringContent) {
payload.pfilter.iab_content = stringContent;
}
}

// Google Topics
const segments = extractUserSegments(bidderRequest);
if (segments) {
assignDefinedValues(payload, {
segtx: segments.segtax,
segcl: segments.segclass,
segs: segments.segments
});
}

// schain
if (bidRequest.schain) {
payload.schain = bidRequest.schain;
}

// fill userId params
fillUsersIds(bidRequest, payload);

return {
method: 'GET',
url: endpoint,
Expand Down Expand Up @@ -257,6 +241,74 @@ export const spec = {
}
}

/**
* Adds userIds to payload
*
* @param bidRequest
* @param payload
*/
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(`DSPx: 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]];
}
}
}
}
}
}

function appendToUrl(url, what) {
if (!what) {
return url;
Expand All @@ -276,7 +328,7 @@ function objectToQueryString(obj, prefix) {
: encodeURIComponent(k) + '=' + encodeURIComponent(v));
}
}
return str.join('&');
return str.filter(n => n).join('&');
}

/**
Expand Down Expand Up @@ -508,4 +560,74 @@ function createOutstreamEmbedCode(bid) {
return fragment;
}

/**
* Convert site.content to string
* @param content
*/
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.
*/
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
*/
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;
}

registerBidder(spec);
Loading
Loading