Skip to content

Commit 1d2b583

Browse files
onlsolzav1517patmmccann
authored
Refactor: Consolidate shared adapter methods into dspxUtils, reduce redundant 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]>
1 parent f728178 commit 1d2b583

File tree

5 files changed

+453
-609
lines changed

5 files changed

+453
-609
lines changed

libraries/dspxUtils/bidderUtils.js

Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
import { BANNER, VIDEO } from '../../src/mediaTypes.js';
2+
import {deepAccess, isArray, isEmptyStr, isFn, logError} from '../../src/utils.js';
3+
/**
4+
* @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest
5+
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
6+
* @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
7+
* @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse
8+
*/
9+
/**
10+
* Adds userIds to payload
11+
*
12+
* @param bidRequest
13+
* @param payload
14+
*/
15+
export function fillUsersIds(bidRequest, payload) {
16+
if (bidRequest.hasOwnProperty('userId')) {
17+
let didMapping = {
18+
did_netid: 'userId.netId',
19+
did_id5: 'userId.id5id.uid',
20+
did_id5_linktype: 'userId.id5id.ext.linkType',
21+
did_uid2: 'userId.uid2',
22+
did_sharedid: 'userId.sharedid',
23+
did_pubcid: 'userId.pubcid',
24+
did_uqid: 'userId.utiq',
25+
did_cruid: 'userId.criteoid',
26+
did_euid: 'userId.euid',
27+
// did_tdid: 'unifiedId',
28+
did_tdid: 'userId.tdid',
29+
did_ppuid: function() {
30+
let path = 'userId.pubProvidedId';
31+
let value = deepAccess(bidRequest, path);
32+
if (isArray(value)) {
33+
for (const rec of value) {
34+
if (rec.uids && rec.uids.length > 0) {
35+
for (let i = 0; i < rec.uids.length; i++) {
36+
if ('id' in rec.uids[i] && deepAccess(rec.uids[i], 'ext.stype') === 'ppuid') {
37+
return (rec.uids[i].atype ?? '') + ':' + rec.source + ':' + rec.uids[i].id;
38+
}
39+
}
40+
}
41+
}
42+
}
43+
return undefined;
44+
},
45+
did_cpubcid: 'crumbs.pubcid'
46+
};
47+
for (let paramName in didMapping) {
48+
let path = didMapping[paramName];
49+
50+
// handle function
51+
if (typeof path == 'function') {
52+
let value = path(paramName);
53+
if (value) {
54+
payload[paramName] = value;
55+
}
56+
continue;
57+
}
58+
// direct access
59+
let value = deepAccess(bidRequest, path);
60+
if (typeof value == 'string' || typeof value == 'number') {
61+
payload[paramName] = value;
62+
} else if (typeof value == 'object') {
63+
// trying to find string ID value
64+
if (typeof deepAccess(bidRequest, path + '.id') == 'string') {
65+
payload[paramName] = deepAccess(bidRequest, path + '.id');
66+
} else {
67+
if (Object.keys(value).length > 0) {
68+
logError(`WARNING: fillUserIds had to use first key in user object to get value for bid.userId key: ${path}.`);
69+
payload[paramName] = value[Object.keys(value)[0]];
70+
}
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
export function appendToUrl(url, what) {
78+
if (!what) {
79+
return url;
80+
}
81+
return url + (url.indexOf('?') !== -1 ? '&' : '?') + what;
82+
}
83+
84+
export function objectToQueryString(obj, prefix) {
85+
let str = [];
86+
let p;
87+
for (p in obj) {
88+
if (obj.hasOwnProperty(p)) {
89+
let k = prefix ? prefix + '[' + p + ']' : p;
90+
let v = obj[p];
91+
str.push((v !== null && typeof v === 'object')
92+
? objectToQueryString(v, k)
93+
: encodeURIComponent(k) + '=' + encodeURIComponent(v));
94+
}
95+
}
96+
return str.filter(n => n).join('&');
97+
}
98+
99+
/**
100+
* Check if it's a banner bid request
101+
*
102+
* @param {BidRequest} bid - Bid request generated from ad slots
103+
* @returns {boolean} True if it's a banner bid
104+
*/
105+
export function isBannerRequest(bid) {
106+
return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid);
107+
}
108+
109+
/**
110+
* Check if it's a video bid request
111+
*
112+
* @param {BidRequest} bid - Bid request generated from ad slots
113+
* @returns {boolean} True if it's a video bid
114+
*/
115+
export function isVideoRequest(bid) {
116+
return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video');
117+
}
118+
119+
/**
120+
* Get video sizes
121+
*
122+
* @param {BidRequest} bid - Bid request generated from ad slots
123+
* @returns {object}
124+
*/
125+
export function getVideoSizes(bid) {
126+
return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes);
127+
}
128+
129+
/**
130+
* Get video context
131+
*
132+
* @param {BidRequest} bid - Bid request generated from ad slots
133+
* @returns {object}
134+
*/
135+
export function getVideoContext(bid) {
136+
return deepAccess(bid, 'mediaTypes.video.context') || 'unknown';
137+
}
138+
139+
/**
140+
* Get banner sizes
141+
*
142+
* @param {BidRequest} bid - Bid request generated from ad slots
143+
* @returns {object} True if it's a video bid
144+
*/
145+
export function getBannerSizes(bid) {
146+
return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes);
147+
}
148+
149+
/**
150+
* Parse size
151+
* @param size
152+
* @returns {object} sizeObj
153+
*/
154+
export function parseSize(size) {
155+
let sizeObj = {}
156+
sizeObj.width = parseInt(size[0], 10);
157+
sizeObj.height = parseInt(size[1], 10);
158+
return sizeObj;
159+
}
160+
161+
/**
162+
* Parse sizes
163+
* @param sizes
164+
* @returns {{width: number , height: number }[]}
165+
*/
166+
export function parseSizes(sizes) {
167+
if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]])
168+
return sizes.map(size => parseSize(size));
169+
}
170+
return [parseSize(sizes)]; // or a single one ? (ie. [728,90])
171+
}
172+
173+
/**
174+
* Get MediaInfo object for server request
175+
*
176+
* @param mediaTypesInfo
177+
* @returns {*}
178+
*/
179+
export function convertMediaInfoForRequest(mediaTypesInfo) {
180+
let requestData = {};
181+
Object.keys(mediaTypesInfo).forEach(mediaType => {
182+
requestData[mediaType] = mediaTypesInfo[mediaType].map(size => {
183+
return size.width + 'x' + size.height;
184+
}).join(',');
185+
});
186+
return requestData;
187+
}
188+
189+
/**
190+
* Get media types info
191+
*
192+
* @param bid
193+
*/
194+
export function getMediaTypesInfo(bid) {
195+
let mediaTypesInfo = {};
196+
197+
if (bid.mediaTypes) {
198+
Object.keys(bid.mediaTypes).forEach(mediaType => {
199+
if (mediaType === BANNER) {
200+
mediaTypesInfo[mediaType] = getBannerSizes(bid);
201+
}
202+
if (mediaType === VIDEO) {
203+
mediaTypesInfo[mediaType] = getVideoSizes(bid);
204+
}
205+
});
206+
} else {
207+
mediaTypesInfo[BANNER] = getBannerSizes(bid);
208+
}
209+
return mediaTypesInfo;
210+
}
211+
212+
/**
213+
* Get Bid Floor
214+
* @param bid
215+
* @returns {number|*}
216+
*/
217+
export function getBidFloor(bid) {
218+
if (!isFn(bid.getFloor)) {
219+
return deepAccess(bid, 'params.bidfloor', 0);
220+
}
221+
222+
try {
223+
const bidFloor = bid.getFloor({
224+
currency: 'EUR',
225+
mediaType: '*',
226+
size: '*',
227+
});
228+
return bidFloor.floor;
229+
} catch (_) {
230+
return 0
231+
}
232+
}
233+
234+
/**
235+
* Convert site.content to string
236+
* @param content
237+
*/
238+
export function siteContentToString(content) {
239+
if (!content) {
240+
return '';
241+
}
242+
let stringKeys = ['id', 'title', 'series', 'season', 'artist', 'genre', 'isrc', 'url', 'keywords'];
243+
let intKeys = ['episode', 'context', 'livestream'];
244+
let arrKeys = ['cat'];
245+
let retArr = [];
246+
arrKeys.forEach(k => {
247+
let val = deepAccess(content, k);
248+
if (val && Array.isArray(val)) {
249+
retArr.push(k + ':' + val.join('|'));
250+
}
251+
});
252+
intKeys.forEach(k => {
253+
let val = deepAccess(content, k);
254+
if (val && typeof val === 'number') {
255+
retArr.push(k + ':' + val);
256+
}
257+
});
258+
stringKeys.forEach(k => {
259+
let val = deepAccess(content, k);
260+
if (val && typeof val === 'string') {
261+
retArr.push(k + ':' + encodeURIComponent(val));
262+
}
263+
});
264+
return retArr.join(',');
265+
}
266+
267+
/**
268+
* Assigns multiple values to the specified keys on an object if the values are not undefined.
269+
* @param {Object} target - The object to which the values will be assigned.
270+
* @param {Object} values - An object containing key-value pairs to be assigned.
271+
*/
272+
export function assignDefinedValues(target, values) {
273+
for (const key in values) {
274+
if (values[key] !== undefined) {
275+
target[key] = values[key];
276+
}
277+
}
278+
}
279+
280+
/**
281+
* Extracts user segments/topics from the bid request object
282+
* @param {Object} bid - The bid request object
283+
* @returns {{segclass: *, segtax: *, segments: *}|undefined} - User segments/topics or undefined if not found
284+
*/
285+
export function extractUserSegments(bid) {
286+
const userData = deepAccess(bid, 'ortb2.user.data') || [];
287+
for (const dataObj of userData) {
288+
if (dataObj.segment && isArray(dataObj.segment) && dataObj.segment.length > 0) {
289+
const segments = dataObj.segment
290+
.filter(seg => seg.id && !isEmptyStr(seg.id) && isFinite(seg.id))
291+
.map(seg => Number(seg.id));
292+
if (segments.length > 0) {
293+
return {
294+
segtax: deepAccess(dataObj, 'ext.segtax'),
295+
segclass: deepAccess(dataObj, 'ext.segclass'),
296+
segments: segments.join(',')
297+
};
298+
}
299+
}
300+
}
301+
return undefined;
302+
}
303+
304+
export function handleSyncUrls(syncOptions, serverResponses, gdprConsent, uspConsent) {
305+
if (!serverResponses || serverResponses.length === 0) {
306+
return [];
307+
}
308+
309+
const syncs = [];
310+
let gdprParams = '';
311+
if (gdprConsent) {
312+
if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') {
313+
gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
314+
} else {
315+
gdprParams = `gdpr_consent=${gdprConsent.consentString}`;
316+
}
317+
}
318+
319+
if (serverResponses.length > 0 && serverResponses[0].body.userSync) {
320+
if (syncOptions.iframeEnabled) {
321+
serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({
322+
type: 'iframe',
323+
url: appendToUrl(url, gdprParams)
324+
}));
325+
}
326+
if (syncOptions.pixelEnabled) {
327+
serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({
328+
type: 'image',
329+
url: appendToUrl(url, gdprParams)
330+
}));
331+
}
332+
}
333+
return syncs;
334+
}
335+
336+
export function interpretResponse(serverResponse, bidRequest, rendererFunc) {
337+
const bidResponses = [];
338+
const response = serverResponse.body;
339+
const crid = response.crid || 0;
340+
const cpm = response.cpm / 1000000 || 0;
341+
if (cpm !== 0 && crid !== 0) {
342+
const dealId = response.dealid || '';
343+
const currency = response.currency || 'EUR';
344+
const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue;
345+
const bidResponse = {
346+
requestId: response.bid_id,
347+
cpm: cpm,
348+
width: response.width,
349+
height: response.height,
350+
creativeId: crid,
351+
dealId: dealId,
352+
currency: currency,
353+
netRevenue: netRevenue,
354+
type: response.type,
355+
ttl: 60,
356+
meta: {
357+
advertiserDomains: response.adomain || []
358+
}
359+
};
360+
361+
if (response.vastUrl) {
362+
bidResponse.vastUrl = response.vastUrl;
363+
bidResponse.mediaType = 'video';
364+
}
365+
if (response.vastXml) {
366+
bidResponse.vastXml = response.vastXml;
367+
bidResponse.mediaType = 'video';
368+
}
369+
if (response.renderer) {
370+
bidResponse.renderer = rendererFunc(bidRequest, response);
371+
}
372+
373+
if (response.videoCacheKey) {
374+
bidResponse.videoCacheKey = response.videoCacheKey;
375+
}
376+
377+
if (response.adTag) {
378+
bidResponse.ad = response.adTag;
379+
}
380+
381+
if (response.bid_appendix) {
382+
Object.keys(response.bid_appendix).forEach(fieldName => {
383+
bidResponse[fieldName] = response.bid_appendix[fieldName];
384+
});
385+
}
386+
387+
bidResponses.push(bidResponse);
388+
}
389+
return bidResponses;
390+
}

0 commit comments

Comments
 (0)