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

VidoomyBidAdapter: refactor cookie sync #7601

Merged
merged 11 commits into from
Nov 2, 2021
151 changes: 51 additions & 100 deletions modules/vidoomyBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@ const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`;
const BIDDER_CODE = 'vidoomy';
const GVLID = 380;

const COOKIE_SYNC_JSON = 'https://vpaid.vidoomy.com/sync/urls.json';
const COOKIE_SYNC_FALLBACK_URLS = [
FilipStamenkovic marked this conversation as resolved.
Show resolved Hide resolved
'https://x.bidswitch.net/sync?ssp=vidoomy',
'https://ib.adnxs.com/getuid?https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID',
'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D',
'https://sync.1rx.io/usersync2/vidoomy?redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DUN%26uid%3D%5BRX_UUID%5D',
'https://rtb.openx.net/sync/prebid?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&r=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dopenx%26uid%3D$%7BUID%7D',
'https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=&predirect=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D',
'https://cm.adform.net/cookie?redirect_url=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID',
'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}',
'https://ap.lijit.com/pixel?redir=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID'
];

const isBidRequestValid = bid => {
if (!bid.params) {
Expand All @@ -36,7 +46,7 @@ const isBidRequestValid = bid => {
};

const isBidResponseValid = bid => {
if (!bid.requestId || !bid.cpm || !bid.ttl || !bid.currency) {
if (!bid || !bid.requestId || !bid.cpm || !bid.ttl || !bid.currency) {
return false;
}
switch (bid.mediaType) {
Expand Down Expand Up @@ -67,35 +77,33 @@ const buildRequests = (validBidRequests, bidderRequest) => {

const videoContext = deepAccess(bid, 'mediaTypes.video.context');

const queryParams = [];
queryParams.push(['id', bid.params.id]);
queryParams.push(['adtype', adType]);
queryParams.push(['w', w]);
queryParams.push(['h', h]);
queryParams.push(['pos', parseInt(bid.params.position) || 1]);
queryParams.push(['ua', navigator.userAgent]);
queryParams.push(['l', navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '']);
queryParams.push(['dt', /Mobi/.test(navigator.userAgent) ? 2 : 1]);
queryParams.push(['pid', bid.params.pid]);
queryParams.push(['requestId', bid.bidId]);
queryParams.push(['d', getDomainWithoutSubdomain(hostname)]);
queryParams.push(['sp', encodeURIComponent(aElement.href)]);
const queryParams = {
id: bid.params.id,
adtype: adType,
w,
h,
pos: parseInt(bid.params.position) || 1,
ua: navigator.userAgent,
l: navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '',
dt: /Mobi/.test(navigator.userAgent) ? 2 : 1,
pid: bid.params.pid,
requestId: bid.bidId,
d: getDomainWithoutSubdomain(hostname),
sp: encodeURIComponent(aElement.href),
usp: bidderRequest.uspConsent || '',
coppa: !!config.getConfig('coppa'),
videoContext: videoContext || ''
};

if (bidderRequest.gdprConsent) {
queryParams.push(['gdpr', bidderRequest.gdprConsent.gdprApplies]);
queryParams.push(['gdprcs', bidderRequest.gdprConsent.consentString]);
queryParams.gdpr = bidderRequest.gdprConsent.gdprApplies;
queryParams.gdprcs = bidderRequest.gdprConsent.consentString;
}
queryParams.push(['usp', bidderRequest.uspConsent || '']);
queryParams.push(['coppa', !!config.getConfig('coppa')]);

const rawQueryParams = queryParams.map(qp => qp.join('=')).join('&');

cookieSync(bidderRequest)

sasanfarokh marked this conversation as resolved.
Show resolved Hide resolved
const url = `${ENDPOINT}?${rawQueryParams}`;
return {
method: 'GET',
url: url,
data: {videoContext}
url: ENDPOINT,
data: queryParams
}
});
return serverRequests;
Expand All @@ -117,6 +125,7 @@ const render = (bid) => {
const interpretResponse = (serverResponse, bidRequest) => {
try {
let responseBody = serverResponse.body;
if (!responseBody) return;
if (responseBody.mediaType === 'video') {
responseBody.ad = responseBody.vastUrl;
const videoContext = bidRequest.data.videoContext;
Expand Down Expand Up @@ -185,91 +194,33 @@ const interpretResponse = (serverResponse, bidRequest) => {
}
};

function getUserSyncs (syncOptions, responses, gdprConsent, uspConsent) {
sasanfarokh marked this conversation as resolved.
Show resolved Hide resolved
if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) {
const pixelType = syncOptions.pixelEnabled ? 'image' : 'iframe';
const urls = deepAccess(responses, '0.body.pixels') || COOKIE_SYNC_FALLBACK_URLS;

return [].concat(urls).map(url => ({
sasanfarokh marked this conversation as resolved.
Show resolved Hide resolved
type: pixelType,
url: url
.replace('{{GDPR}}', gdprConsent ? gdprConsent.gdprApplies : '0')
.replace('{{GDPR_CONSENT}}', gdprConsent ? encodeURIComponent(gdprConsent.consentString) : '')
.replace('{{USP_CONSENT}}', uspConsent ? encodeURIComponent(uspConsent) : '')
}));
}
};

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],
isBidRequestValid,
buildRequests,
interpretResponse,
gvlid: GVLID,
getUserSyncs,
};

registerBidder(spec);

let cookieSynced = false;
function cookieSync(bidderRequest) {
if (cookieSynced) return;
const xhr = new XMLHttpRequest();
xhr.open('GET', COOKIE_SYNC_JSON)
xhr.addEventListener('load', function () {
const macro = Macro({
gpdr: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : '0',
gpdr_consent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : '',
});
JSON.parse(this.responseText).filter(Boolean).forEach(url => {
firePixel(macro.replace(url))
})
})
xhr.send()
cookieSynced = true;
}

function firePixel(url) {
const img = document.createElement('img');
img.width = 1;
img.height = 1;
img.src = url;
document.body.appendChild(img);
setTimeout(() => {
img.remove();
}, 10000)
}

function normalizeKey (x) {
return x.replace(/_/g, '').toLowerCase();
}

function Macro (obj) {
const macros = {};
for (const key in obj) {
macros[normalizeKey(key)] = obj[key];
}

const set = (key, value) => {
macros[normalizeKey(key)] = typeof value === 'function' ? value : String(value);
};

return {
set,
setAll (obj) {
for (const key in obj) {
macros[normalizeKey(key)] = set(obj[key]);
}
},
replace (string, extraMacros = {}) {
const allMacros = {
...macros,
...extraMacros,
};
const regexes = [
/{{\s*([a-zA-Z0-9_]+)\s*}}/g,
/\$\$\s*([a-zA-Z0-9_]+)\s*\$\$/g,
/\[\s*([a-zA-Z0-9_]+)\s*\]/g,
/\{\s*([a-zA-Z0-9_]+)\s*\}/g,
];
regexes.forEach(regex => {
string = string.replace(regex, (str, x) => {
x = normalizeKey(x);
let value = allMacros[x];
value = typeof value === 'function' ? value(allMacros) : value;
return !value && value !== 0 ? '' : value;
});
});
return string;
},
};
}

function getDomainWithoutSubdomain (hostname) {
const parts = hostname.split('.');
const newParts = [];
Expand Down
45 changes: 32 additions & 13 deletions test/spec/modules/vidoomyBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js';
import { INSTREAM } from '../../../src/video';

const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`;
const PIXELS = ['/test.png', '/test2.png?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}']

describe('vidoomyBidAdapter', function() {
const adapter = newBidder(spec);
Expand Down Expand Up @@ -101,24 +102,24 @@ describe('vidoomyBidAdapter', function() {
});

it('attaches source and version to endpoint URL as query params', function () {
expect(request[0].url).to.include(ENDPOINT);
expect(request[1].url).to.include(ENDPOINT);
expect(request[0].url).to.equal(ENDPOINT);
expect(request[1].url).to.equal(ENDPOINT);
});

it('only accepts first width and height sizes', function () {
expect(request[0].url).to.include('w=300');
expect(request[0].url).to.include('h=250');
expect(request[0].url).to.not.include('w=200');
expect(request[0].url).to.not.include('h=100');
expect(request[1].url).to.include('w=400');
expect(request[1].url).to.include('h=225');
expect('' + request[0].data.w).to.equal('300');
expect('' + request[0].data.h).to.equal('250');
expect('' + request[0].data.w).to.not.equal('200');
expect('' + request[0].data.h).to.not.equal('100');
expect('' + request[1].data.w).to.equal('400');
expect('' + request[1].data.h).to.equal('225');
});

it('should send id and pid parameters', function () {
expect(request[0].url).to.include('id=123123');
expect(request[0].url).to.include('pid=123123');
expect(request[1].url).to.include('id=456456');
expect(request[1].url).to.include('pid=456456');
expect('' + request[0].data.id).to.equal('123123');
expect('' + request[0].data.pid).to.equal('123123');
expect('' + request[1].data.id).to.equal('456456');
expect('' + request[1].data.pid).to.equal('456456');
});
});

Expand Down Expand Up @@ -182,7 +183,8 @@ describe('vidoomyBidAdapter', function() {
'networkName': null,
'primaryCatId': 'IAB3-1',
'secondaryCatIds': null
}
},
'pixels': PIXELS
}
}

Expand All @@ -206,5 +208,22 @@ describe('vidoomyBidAdapter', function() {

expect(result[0].requestId).to.equal(serverResponseBanner.body.requestId);
});

it('should sync user cookies', function () {
const GDPR_CONSENT = 'GDPR_TEST'
const result = spec.getUserSyncs({
pixelEnabled: true
}, [serverResponseBanner], { consentString: GDPR_CONSENT, gdprApplies: 1 }, null)
expect(result).to.eql([
{
type: 'image',
url: PIXELS[0]
},
{
type: 'image',
url: `/test2.png?gdpr=1&gdpr_consent=${GDPR_CONSENT}`
}
])
});
});
});