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

Insticator Bid Adaptor: add support for different privacy platforms, ortb site first party data, fix video validation bug #11279

Merged
164 changes: 137 additions & 27 deletions modules/insticatorBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {config} from '../src/config.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums} from '../src/utils.js';
import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue} from '../src/utils.js';
import {getStorageManager} from '../src/storageManager.js';
import {find} from '../src/polyfill.js';

Expand All @@ -12,35 +12,37 @@ const USER_ID_COOKIE_EXP = 2592000000; // 30 days
const BID_TTL = 300; // 5 minutes
const GVLID = 910;

const isSubarray = (arr, target) => {
if (!isArrayOfNums(arr) || arr.length === 0) {
return false;
}
const targetSet = new Set(target);
return arr.every(el => targetSet.has(el));
};

export const OPTIONAL_VIDEO_PARAMS = {
'minduration': (value) => isInteger(value),
'maxduration': (value) => isInteger(value),
'protocols': (value) => isSubarray(value, [2, 3, 5, 6, 7, 8]), // protocols values supported by Inticator, according to the OpenRTB spec
'protocols': (value) => isArrayOfNums(value), // protocols values supported by Inticator, according to the OpenRTB spec
'startdelay': (value) => isInteger(value),
'linearity': (value) => isInteger(value) && [1].includes(value),
'skip': (value) => isInteger(value) && [1, 0].includes(value),
'skipmin': (value) => isInteger(value),
'skipafter': (value) => isInteger(value),
'sequence': (value) => isInteger(value),
'battr': (value) => isSubarray(value, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]),
'battr': (value) => isArrayOfNums(value),
'maxextended': (value) => isInteger(value),
'minbitrate': (value) => isInteger(value),
'maxbitrate': (value) => isInteger(value),
'playbackmethod': (value) => isSubarray(value, [1, 2, 3, 4]),
'playbackmethod': (value) => isArrayOfNums(value),
'playbackend': (value) => isInteger(value) && [1, 2, 3].includes(value),
'delivery': (value) => isSubarray(value, [1, 2, 3]),
'delivery': (value) => isArrayOfNums(value),
'pos': (value) => isInteger(value) && [0, 1, 2, 3, 4, 5, 6, 7].includes(value),
'api': (value) => isSubarray(value, [1, 2, 3, 4, 5, 6, 7]),
'api': (value) => isArrayOfNums(value),
};

const ORTB_SITE_FIRST_PARTY_DATA = {
'cat': v => Array.isArray(v) && v.every(c => typeof c === 'string'),
'sectioncat': v => Array.isArray(v) && v.every(c => typeof c === 'string'),
'pagecat': v => Array.isArray(v) && v.every(c => typeof c === 'string'),
'search': v => typeof v === 'string',
'mobile': v => isInteger(),
'content': v => typeof v === 'object',
'keywords': v => typeof v === 'string',
}

export const storage = getStorageManager({bidderCode: BIDDER_CODE});

config.setDefaults({
Expand Down Expand Up @@ -103,6 +105,7 @@ function buildVideo(bidRequest) {
const placement = deepAccess(bidRequest, 'mediaTypes.video.placement') || 3;
const plcmt = deepAccess(bidRequest, 'mediaTypes.video.plcmt') || undefined;
const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize');
const context = deepAccess(bidRequest, 'mediaTypes.video.context');

if (!w && playerSize) {
if (Array.isArray(playerSize[0])) {
Expand All @@ -121,17 +124,26 @@ function buildVideo(bidRequest) {

const bidRequestVideo = deepAccess(bidRequest, 'mediaTypes.video');
const videoBidderParams = deepAccess(bidRequest, 'params.video', {});

let optionalParams = {};
for (const param in OPTIONAL_VIDEO_PARAMS) {
if (bidRequestVideo[param]) {
if (bidRequestVideo[param] && OPTIONAL_VIDEO_PARAMS[param](bidRequestVideo[param])) {
optionalParams[param] = bidRequestVideo[param];
}
// remove invalid optional params from bidder specific overrides
if (videoBidderParams[param] && !OPTIONAL_VIDEO_PARAMS[param](videoBidderParams[param])) {
delete videoBidderParams[param];
}
}

if (plcmt) {
optionalParams['plcmt'] = plcmt;
}

if (context !== undefined) {
optionalParams['context'] = context;
}

let videoObj = {
placement,
mimes,
Expand Down Expand Up @@ -190,31 +202,102 @@ function buildDevice(bidRequest) {
return device;
}

function _getCoppa(bidderRequest) {
const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa');

// If coppa is defined in the request, use it
if (coppa !== undefined) {
return coppa;
}
return config.getConfig('coppa') === true ? 1 : 0;
shubhamc-ins marked this conversation as resolved.
Show resolved Hide resolved
}

function _getGppConsent(bidderRequest) {
let gpp = deepAccess(bidderRequest, 'gppConsent.gppString')
let gppSid = deepAccess(bidderRequest, 'gppConsent.applicableSections')

if (!gpp || !gppSid) {
gpp = deepAccess(bidderRequest, 'ortb2.regs.gpp', '')
gppSid = deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', [])
}
return { gpp, gppSid }
}

function _getUspConsent(bidderRequest) {
return (deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false;
}

function buildRegs(bidderRequest) {
let regs = {
ext: {},
};
if (bidderRequest.gdprConsent) {
return {
ext: {
gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0,
gdprConsentString: bidderRequest.gdprConsent.consentString,
},
};
regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0;
regs.ext.gdprConsentString = bidderRequest.gdprConsent.consentString;
}

regs.coppa = _getCoppa(bidderRequest);

const { gpp, gppSid } = _getGppConsent(bidderRequest);

if (gpp) {
regs.ext.gpp = gpp;
}

if (gppSid) {
regs.ext.gppSid = gppSid;
}

const usp = _getUspConsent(bidderRequest);

if (usp) {
regs.ext.us_privacy = usp.uspConsent;
regs.ext.ccpa = usp.uspConsent
}

const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa');
if (dsa) {
regs.ext.dsa = dsa;
}

return {};
return regs;
}

function buildUser(bid) {
const userId = getUserId() || generateUUID();
const yob = deepAccess(bid, 'params.user.yob')
const gender = deepAccess(bid, 'params.user.gender')
const keywords = deepAccess(bid, 'params.user.keywords')
const data = deepAccess(bid, 'params.user.data')
const ext = deepAccess(bid, 'params.user.ext')

setUserId(userId);

return {
const userData = {
id: userId,
yob,
gender,
};
}

if (yob) {
userData.yob = yob;
}

if (gender) {
userData.gender = gender;
}

if (keywords) {
userData.keywords = keywords;
}

if (data) {
userData.data = data;
}

if (ext) {
userData.ext = ext;
}

return userData
}

function extractSchain(bids, requestId) {
Expand Down Expand Up @@ -283,6 +366,20 @@ function buildRequest(validBidRequests, bidderRequest) {
req.user.ext = { eids };
}

const ortb2SiteData = deepAccess(bidderRequest, 'ortb2.site');
if (ortb2SiteData) {
for (const key in ORTB_SITE_FIRST_PARTY_DATA) {
const value = ortb2SiteData[key];
if (value && ORTB_SITE_FIRST_PARTY_DATA[key](value)) {
req.site[key] = value;
}
}
}

if (bidderRequest.gdprConsent) {
deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString);
}

return req;
}

Expand Down Expand Up @@ -326,6 +423,13 @@ function buildBid(bid, bidderRequest) {
bidResponse.vastUrl = 'data:text/xml;charset=utf-8;base64,' + window.btoa(bidResponse.vastXml.replace(/\\"/g, '"'));
}

if (bid.ext && bid.ext.dsa) {
bidResponse.ext = {
...bidResponse.ext,
dsa: bid.ext.dsa,
}
}

return bidResponse;
}

Expand Down Expand Up @@ -453,7 +557,6 @@ function validateVideo(bid) {
if (video[param]) {
if (!OPTIONAL_VIDEO_PARAMS[param](video[param])) {
logError(`insticator: video ${param} is invalid or not supported by insticator`);
return false
}
}
}
Expand Down Expand Up @@ -485,6 +588,13 @@ export const spec = {
let endpointUrl = config.getConfig('insticator.endpointUrl') || ENDPOINT;
endpointUrl = endpointUrl.replace(/^http:/, 'https:');

// Use the first bid request's bid_request_url if it exists ( for updating server url)
if (validBidRequests.length > 0) {
if (deepAccess(validBidRequests[0], 'params.bid_endpoint_request_url')) {
endpointUrl = deepAccess(validBidRequests[0], 'params.bid_endpoint_request_url').replace(/^http:/, 'https:');
}
}

if (validBidRequests.length > 0) {
requests.push({
method: 'POST',
Expand Down
Loading