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

NextMillennium Bid Adapter: support for GPP consent string, site.pagecat, site.content.cat and site.content.language #10761

Merged
merged 13 commits into from
Dec 4, 2023
245 changes: 134 additions & 111 deletions modules/nextMillenniumBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
_each,
createTrackPixelHtml,
deepAccess, getBidIdParameter,
deepAccess,
deepSetValue,
getBidIdParameter,
getDefinedParams,
getWindowTop,
isArray,
Expand All @@ -20,17 +22,32 @@ import * as events from '../src/events.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {getRefererInfo} from '../src/refererDetection.js';

const NM_VERSION = '3.0.0';
const GVLID = 1060;
const BIDDER_CODE = 'nextMillennium';
const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction';
const TEST_ENDPOINT = 'https://test.pbs.nextmillmedia.com/openrtb2/auction';
const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?';
const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&type={{.TYPE_PIXEL}}';
const REPORT_ENDPOINT = 'https://report2.hb.brainlyads.com/statistics/metric';
const TIME_TO_LIVE = 360;

const VIDEO_PARAMS = [
'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement',
'playbackmethod', 'protocols', 'startdelay'
'api',
'linearity',
'maxduration',
'mimes',
'minduration',
'placement',
'playbackmethod',
'protocols',
'startdelay',
];

const ALLOWED_ORTB2_PARAMETERS = [
'site.pagecat',
'site.content.cat',
'site.content.language',
];
const GVLID = 1060;

const sendingDataStatistic = initSendingDataStatistic();
events.on(CONSTANTS.EVENTS.AUCTION_INIT, auctionInitHandler);
Expand Down Expand Up @@ -61,85 +78,35 @@ export const spec = {
const id = getPlacementId(bid);
const auctionId = bid.auctionId;
const bidId = bid.bidId;
let sizes = bid.sizes;
if (sizes && !Array.isArray(sizes[0])) sizes = [sizes];

const site = getSiteObj();
const device = getDeviceObj();

const postBody = {
'id': bidderRequest?.bidderRequestId,
'ext': {
'prebid': {
'storedrequest': {
'id': id
}
id: bidderRequest?.bidderRequestId,
ext: {
prebid: {
storedrequest: {
id,
},
},

'nextMillennium': {
'refresh_count': window.nmmRefreshCounts[bid.adUnitCode]++,
'elOffsets': getBoundingClient(bid),
'scrollTop': window.pageYOffset || document.documentElement.scrollTop
}
nextMillennium: {
nm_version: NM_VERSION,
refresh_count: window.nmmRefreshCounts[bid.adUnitCode]++,
elOffsets: getBoundingClient(bid),
scrollTop: window.pageYOffset || document.documentElement.scrollTop,
},
},

device,
site,
imp: []
};

const imp = {
id: bid.adUnitCode,
ext: {
prebid: {
storedrequest: {id}
}
}
};

if (deepAccess(bid, 'mediaTypes.banner')) {
imp.banner = {
format: (sizes || []).map(s => { return {w: s[0], h: s[1]} })
};
imp: [],
};

const video = deepAccess(bid, 'mediaTypes.video');
if (video) {
imp.video = getDefinedParams(video, VIDEO_PARAMS);
if (video.playerSize) {
imp.video = Object.assign(
imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize[0]) || {}
);
} else if (video.w && video.h) {
imp.video.w = video.w;
imp.video.h = video.h;
};
};

postBody.imp.push(imp);

const gdprConsent = bidderRequest && bidderRequest.gdprConsent;
const uspConsent = bidderRequest && bidderRequest.uspConsent;

if (gdprConsent || uspConsent) {
postBody.regs = { ext: {} };

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

if (gdprConsent) {
if (typeof gdprConsent.gdprApplies !== 'undefined') {
postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0;
};

if (typeof gdprConsent.consentString !== 'undefined') {
postBody.user = {
ext: { consent: gdprConsent.consentString }
};
};
};
};
postBody.imp.push(getImp(bid, id));
setConsentStrings(postBody, bidderRequest);
setOrtb2Parameters(postBody, bidderRequest?.ortb2);

const urlParameters = parseUrl(getWindowTop().location.href).search;
const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test';
Expand All @@ -151,7 +118,7 @@ export const spec = {
data: JSON.stringify(postBody),
options: {
contentType: 'text/plain',
withCredentials: true
withCredentials: true,
},

bidId,
Expand All @@ -173,6 +140,7 @@ export const spec = {
const params = bidRequest.params;
const auctionId = bidRequest.auctionId;
const wurl = deepAccess(bid, 'ext.prebid.events.win');

// TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781
addWurl({auctionId, requestId, wurl});

Expand All @@ -189,8 +157,8 @@ export const spec = {
netRevenue: true,
ttl: TIME_TO_LIVE,
meta: {
advertiserDomains: bid.adomain || []
}
advertiserDomains: bid.adomain || [],
},
};

if (vastUrl || vastXml) {
Expand All @@ -210,38 +178,29 @@ export const spec = {
return bidResponses;
},

getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) {
getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) {
if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) return [];

const pixels = [];
const getSetPixelFunc = type => url => { pixels.push({type, url: replaceUsersyncMacros(url, gdprConsent, uspConsent, gppConsent, type)}) };
const getSetPixelsFunc = type => response => { deepAccess(response, `body.ext.sync.${type}`, []).forEach(getSetPixelFunc(type)) };

const setPixel = (type, url) => { (getSetPixelFunc(type))(url) };
const setPixelImages = getSetPixelsFunc('image');
const setPixelIframes = getSetPixelsFunc('iframe');

if (isArray(responses)) {
responses.forEach(response => {
if (syncOptions.pixelEnabled) {
deepAccess(response, 'body.ext.sync.image', []).forEach(imgUrl => {
pixels.push({
type: 'image',
url: replaceUsersyncMacros(imgUrl, gdprConsent, uspConsent)
});
})
}

if (syncOptions.iframeEnabled) {
deepAccess(response, 'body.ext.sync.iframe', []).forEach(iframeUrl => {
pixels.push({
type: 'iframe',
url: replaceUsersyncMacros(iframeUrl, gdprConsent, uspConsent)
});
})
}
if (syncOptions.pixelEnabled) setPixelImages(response);
if (syncOptions.iframeEnabled) setPixelIframes(response);
})
}

if (!pixels.length) {
let syncUrl = SYNC_ENDPOINT;
if (gdprConsent && gdprConsent.gdprApplies) syncUrl += 'gdpr=1&gdpr_consent=' + gdprConsent.consentString + '&';
if (uspConsent) syncUrl += 'us_privacy=' + uspConsent + '&';
if (syncOptions.iframeEnabled) pixels.push({type: 'iframe', url: syncUrl + 'type=iframe'});
if (syncOptions.pixelEnabled) pixels.push({type: 'image', url: syncUrl + 'type=image'});
if (syncOptions.pixelEnabled) setPixel('image', SYNC_ENDPOINT);
if (syncOptions.iframeEnabled) setPixel('iframe', SYNC_ENDPOINT);
}

return pixels;
},

Expand Down Expand Up @@ -281,24 +240,88 @@ export const spec = {
},
};

function replaceUsersyncMacros(url, gdprConsent, uspConsent) {
const { consentString, gdprApplies } = gdprConsent || {};
export function getImp(bid, id) {
const imp = {
id: bid.adUnitCode,
ext: {
prebid: {
storedrequest: {
id,
},
},
},
};

const banner = deepAccess(bid, 'mediaTypes.banner');
if (banner) {
imp.banner = {
format: (banner?.sizes || []).map(s => { return {w: s[0], h: s[1]} }),
};
};

const video = deepAccess(bid, 'mediaTypes.video');
if (video) {
imp.video = getDefinedParams(video, VIDEO_PARAMS);
if (video.playerSize) {
imp.video = Object.assign(imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize) || {});
} else if (video.w && video.h) {
imp.video.w = video.w;
imp.video.h = video.h;
};
};

if (gdprApplies) {
const gdpr = Number(gdprApplies);
url = url.replace('{{.GDPR}}', gdpr);
return imp;
};

if (gdpr == 1 && consentString && consentString.length > 0) {
url = url.replace('{{.GDPRConsent}}', consentString);
}
} else {
url = url.replace('{{.GDPR}}', 0);
url = url.replace('{{.GDPRConsent}}', '');
}
export function setConsentStrings(postBody = {}, bidderRequest) {
const gdprConsent = bidderRequest?.gdprConsent;
const uspConsent = bidderRequest?.uspConsent;
let gppConsent = bidderRequest?.gppConsent?.gppString && bidderRequest?.gppConsent;
if (!gppConsent && bidderRequest?.ortb2?.regs?.gpp) gppConsent = bidderRequest?.ortb2?.regs;

if (uspConsent) {
url = url.replace('{{.USPrivacy}}', uspConsent);
if (gdprConsent || uspConsent || gppConsent) {
postBody.regs = { ext: {} };

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

if (gppConsent) {
postBody.regs.gpp = gppConsent?.gppString || gppConsent?.gpp;
postBody.regs.gpp_sid = bidderRequest.gppConsent?.applicableSections || gppConsent?.gpp_sid;
};

if (gdprConsent) {
if (typeof gdprConsent.gdprApplies !== 'undefined') {
postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0;
};

if (typeof gdprConsent.consentString !== 'undefined') {
postBody.user = {
ext: { consent: gdprConsent.consentString },
};
};
};
};
};

export function setOrtb2Parameters(postBody, ortb2 = {}) {
for (let parameter of ALLOWED_ORTB2_PARAMETERS) {
const value = deepAccess(ortb2, parameter);
if (value) deepSetValue(postBody, parameter, value);
}
}

export function replaceUsersyncMacros(url, gdprConsent = {}, uspConsent = '', gppConsent = {}, type = '') {
const { consentString = '', gdprApplies = false } = gdprConsent;
const gdpr = Number(gdprApplies);
url = url
.replace('{{.GDPR}}', gdpr)
.replace('{{.GDPRConsent}}', consentString)
.replace('{{.USPrivacy}}', uspConsent)
.replace('{{.GPP}}', gppConsent.gppString || '')
.replace('{{.GPPSID}}', (gppConsent.applicableSections || []).join(','))
.replace('{{.TYPE_PIXEL}}', type);

return url;
};
Expand Down
Loading