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

CCPA modifications #9

Merged
merged 1 commit into from
Jun 22, 2020
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
144 changes: 68 additions & 76 deletions modules/nextrollBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ export const spec = {
*/
buildRequests: function (validBidRequests, bidderRequest) {
let topLocation = utils.parseUrl(utils.deepAccess(bidderRequest, 'refererInfo.referer'));
let consent = hasCCPAConsent(bidderRequest);
return validBidRequests.map((bidRequest, index) => {

return validBidRequests.map((bidRequest) => {
return {
method: 'POST',
options: {
withCredentials: consent,
withCredentials: true,
},
url: BIDDER_ENDPOINT,
data: {
Expand All @@ -59,9 +59,10 @@ export const spec = {
site: _getSite(bidRequest, topLocation),
seller: _getSeller(bidRequest),
device: _getDevice(bidRequest),
regs: _getRegs(bidderRequest)
}
}
})
};
});
},

/**
Expand All @@ -82,22 +83,22 @@ export const spec = {
}

function _getBanner(bidRequest) {
let sizes = _getSizes(bidRequest)
if (sizes === undefined) return undefined
return {format: sizes}
let sizes = _getSizes(bidRequest);
if (sizes === undefined) return undefined;
return {format: sizes};
}

function _getNative(mediaTypeNative) {
if (mediaTypeNative === undefined) return undefined
let assets = _getNativeAssets(mediaTypeNative)
if (assets === undefined || assets.length == 0) return undefined
if (mediaTypeNative === undefined) return undefined;
let assets = _getNativeAssets(mediaTypeNative);
if (assets === undefined || assets.length == 0) return undefined;
return {
request: {
native: {
assets: assets
}
}
}
};
}

/*
Expand All @@ -114,69 +115,71 @@ const NATIVE_ASSET_MAP = [
{id: 4, kind: 'img', key: 'logo', type: 2},
{id: 5, kind: 'data', key: 'sponsoredBy', type: 1},
{id: 6, kind: 'data', key: 'body', type: 2}
]
];

const ASSET_KIND_MAP = {
title: _getTitleAsset,
img: _getImageAsset,
data: _getDataAsset,
}
};

function _getAsset(mediaTypeNative, assetMap) {
let asset = mediaTypeNative[assetMap.key]
if (asset === undefined) return undefined
let assetFunc = ASSET_KIND_MAP[assetMap.kind]
const asset = mediaTypeNative[assetMap.key];
if (asset === undefined) return undefined;
const assetFunc = ASSET_KIND_MAP[assetMap.kind];
return {
id: assetMap.id,
required: (assetMap.required || !!asset.required) ? 1 : 0,
[assetMap.kind]: assetFunc(asset, assetMap)
}
};
}

function _getTitleAsset(title, _assetMap) {
return {len: title.len || 0}
return {len: title.len || 0};
}

function _getMinAspectRatio(aspectRatio, property) {
if (!utils.isPlainObject(aspectRatio)) return 1
if (!utils.isPlainObject(aspectRatio)) return 1;

let ratio = aspectRatio['ratio_' + property]
let min = aspectRatio['min_' + property]
const ratio = aspectRatio['ratio_' + property];
const min = aspectRatio['min_' + property];

if (utils.isNumber(ratio)) return ratio
if (utils.isNumber(min)) return min
if (utils.isNumber(ratio)) return ratio;
if (utils.isNumber(min)) return min;

return 1
return 1;
}

function _getImageAsset(image, assetMap) {
let sizes = image.sizes
let aspectRatio = image.aspect_ratios ? image.aspect_ratios[0] : undefined
const sizes = image.sizes;
const aspectRatio = image.aspect_ratios ? image.aspect_ratios[0] : undefined;

return {
type: assetMap.type,
w: (sizes ? sizes[0] : undefined),
h: (sizes ? sizes[1] : undefined),
wmin: _getMinAspectRatio(aspectRatio, 'width'),
hmin: _getMinAspectRatio(aspectRatio, 'height'),
}
};
}

function _getDataAsset(data, assetMap) {
return {
type: assetMap.type,
len: data.len || 0
}
};
}

function _getNativeAssets(mediaTypeNative) {
return NATIVE_ASSET_MAP.map(assetMap => _getAsset(mediaTypeNative, assetMap)).filter(asset => asset !== undefined)
return NATIVE_ASSET_MAP
.map(assetMap => _getAsset(mediaTypeNative, assetMap))
.filter(asset => asset !== undefined);
}

function _getUser(requests) {
let id = utils.deepAccess(requests, '0.userId.nextroll');
const id = utils.deepAccess(requests, '0.userId.nextroll');
if (id === undefined) {
return
return;
}

return {
Expand All @@ -186,7 +189,7 @@ function _getUser(requests) {
id
}]
}
}
};
}

function _buildResponse(bidResponse, bid) {
Expand All @@ -200,15 +203,15 @@ function _buildResponse(bidResponse, bid) {
currency: 'USD',
netRevenue: true,
ttl: 300
}
};
if (utils.isStr(bid.adm)) {
response.mediaType = BANNER
response.ad = utils.replaceAuctionPrice(bid.adm, bid.price)
response.mediaType = BANNER;
response.ad = utils.replaceAuctionPrice(bid.adm, bid.price);
} else {
response.mediaType = NATIVE
response.native = _getNativeResponse(bid.adm, bid.price)
response.mediaType = NATIVE;
response.native = _getNativeResponse(bid.adm, bid.price);
}
return response
return response;
}

const privacyLink = 'https://info.evidon.com/pub_info/573';
Expand All @@ -222,30 +225,30 @@ function _getNativeResponse(adm, price) {
impressionTrackers: adm.imptrackers.map(impTracker => utils.replaceAuctionPrice(impTracker, price)),
privacyLink: privacyLink,
privacyIcon: privacyIcon
}
};
return adm.assets.reduce((accResponse, asset) => {
let assetMaps = NATIVE_ASSET_MAP.filter(assetMap => assetMap.id === asset.id && asset[assetMap.kind] !== undefined)
if (assetMaps.length === 0) return accResponse
let assetMap = assetMaps[0]
accResponse[assetMap.key] = _getAssetResponse(asset, assetMap)
return accResponse
}, baseResponse)
const assetMaps = NATIVE_ASSET_MAP.filter(assetMap => assetMap.id === asset.id && asset[assetMap.kind] !== undefined);
if (assetMaps.length === 0) return accResponse;
const assetMap = assetMaps[0];
accResponse[assetMap.key] = _getAssetResponse(asset, assetMap);
return accResponse;
}, baseResponse);
}

function _getAssetResponse(asset, assetMap) {
switch (assetMap.kind) {
case 'title':
return asset.title.text
return asset.title.text;

case 'img':
return {
url: asset.img.url,
width: asset.img.w,
height: asset.img.h
}
};

case 'data':
return asset.data.value
return asset.data.value;
}
}

Expand All @@ -256,25 +259,25 @@ function _getSite(bidRequest, topLocation) {
publisher: {
id: utils.getBidIdParameter('publisherId', bidRequest.params)
}
}
};
}

function _getSeller(bidRequest) {
return {
id: utils.getBidIdParameter('sellerId', bidRequest.params)
}
};
}

function _getSizes(bidRequest) {
if (!utils.isArray(bidRequest.sizes)) {
return undefined
return undefined;
}
return bidRequest.sizes.filter(_isValidSize).map(size => {
return {
w: size[0],
h: size[1]
}
})
});
}

function _isValidSize(size) {
Expand All @@ -288,7 +291,18 @@ function _getDevice(_bidRequest) {
language: navigator['language'],
os: _getOs(navigator.userAgent.toLowerCase()),
osv: _getOsVersion(navigator.userAgent)
};
}

function _getRegs(bidderRequest) {
if (!bidderRequest || !bidderRequest.uspConsent) {
return undefined;
}
return {
ext: {
us_privacy: bidderRequest.uspConsent
}
};
}

function _getOs(userAgent) {
Expand All @@ -308,7 +322,7 @@ function _getOs(userAgent) {
}

function _getOsVersion(userAgent) {
let clientStrings = [
const clientStrings = [
{ s: 'Android', r: /Android/ },
{ s: 'iOS', r: /(iPhone|iPad|iPod)/ },
{ s: 'Mac OS X', r: /Mac OS X/ },
Expand All @@ -328,26 +342,4 @@ function _getOsVersion(userAgent) {
return cs ? cs.s : 'unknown';
}

export function hasCCPAConsent(bidderRequest) {
if (bidderRequest === undefined) return true;
if (typeof bidderRequest.uspConsent !== 'string') {
return true;
}
const usps = bidderRequest.uspConsent;
const version = usps[0];

// If we don't support the consent string, assume no-consent.
if (version !== '1' || usps.length < 3) {
return false;
}

const notice = usps[1];
const optOut = usps[2];

if (notice === 'N' || optOut === 'Y') {
return false;
}
return true;
}

registerBidder(spec);
46 changes: 7 additions & 39 deletions test/spec/modules/nextrollBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ describe('nextrollBidAdapter', function() {
expect(bannerObject.format[0].w).to.be.equal(300);
expect(bannerObject.format[0].h).to.be.equal(200);
});

it('sets the CCPA consent string', function () {
const us_privacy = "1YYY";
const request = spec.buildRequests([validBid], {"uspConsent": us_privacy})[0];

expect(request.data.regs.ext.us_privacy).to.be.equal(us_privacy);
});
});

describe('interpretResponse', function () {
Expand Down Expand Up @@ -258,43 +265,4 @@ describe('nextrollBidAdapter', function() {
expect(response[0].native).to.be.deep.equal(expectedResponse)
})
})

describe('hasCCPAConsent', function() {
function ccpaRequest(consentString) {
return {
bidderCode: 'bidderX',
auctionId: 'e3a336ad-2222-4a1c-bbbb-ecc7c5554a34',
uspConsent: consentString
};
}

const noNoticeCases = ['1NYY', '1NNN', '1N--'];
noNoticeCases.forEach((ccpaString, index) => {
it(`No notice should indicate no consent (case ${index})`, function () {
const req = ccpaRequest(ccpaString);
expect(hasCCPAConsent(req)).to.be.false;
});
});

const noConsentCases = ['1YYY', '1YYN', '1YY-'];
noConsentCases.forEach((ccpaString, index) => {
it(`Opt-Out should indicate no consent (case ${index})`, function () {
const req = ccpaRequest(ccpaString);
expect(hasCCPAConsent(req)).to.be.false;
});
});

const consentCases = [undefined, '1YNY', '1YN-', '1Y--', '1---'];
consentCases.forEach((ccpaString, index) => {
it(`should indicate consent (case ${index})`, function() {
const req = ccpaRequest(ccpaString);
expect(hasCCPAConsent(req)).to.be.true;
})
});

it('builds a request with no credentials', function () {
const noConsent = ccpaRequest('1YYY');
expect(spec.buildRequests([validBid], noConsent)[0].options.withCredentials).to.be.false;
});
});
});