Skip to content

Commit

Permalink
Improve Digital adapter: publisher endpoint, addtl consent, syncs (#14)
Browse files Browse the repository at this point in the history
- add bidders to sync url when extend mode enabled
- set ConsentedProvidersSettings when extend mode enabled
- dynamically generated AD_SERVER_URL when publisherId available
  • Loading branch information
faisalvs authored and jbartek25 committed Oct 18, 2022
1 parent caf162e commit 7ab8dd6
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 26 deletions.
94 changes: 70 additions & 24 deletions modules/improvedigitalBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import {loadExternalScript} from '../src/adloader.js';
const BIDDER_CODE = 'improvedigital';
const CREATIVE_TTL = 300;

const AD_SERVER_URL = 'https://ad.360yield.com/pb';
const BASIC_ADS_URL = 'https://ad.360yield-basic.com/pb';
const AD_SERVER_BASE_URL = 'https://ad.360yield.com';
const BASIC_ADS_BASE_URL = 'https://ad.360yield-basic.com';
const PB_ENDPOINT = 'pb';
const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction';
const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html';

Expand Down Expand Up @@ -131,20 +132,6 @@ export const spec = {
deepSetValue(request, 'regs.ext.gdpr', Number(gdprConsent.gdprApplies));
}
deepSetValue(request, 'user.ext.consent', gdprConsent.consentString);

// Additional Consent String
const additionalConsent = deepAccess(gdprConsent, 'addtlConsent');
if (additionalConsent && additionalConsent.indexOf('~') !== -1) {
// Google Ad Tech Provider IDs
const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1);
if (atpIds) {
deepSetValue(
request,
'user.ext.consented_providers_settings.consented_providers',
atpIds.split('.').map(id => parseInt(id, 10))
);
}
}
}

// Timeout
Expand Down Expand Up @@ -238,6 +225,14 @@ export const spec = {
return [];
}

const bidders = new Set();
if (this.syncStore.extendMode && serverResponses) {
serverResponses.forEach(response => {
if (!response?.body?.ext?.responsetimemillis) return;
Object.keys(response.body.ext.responsetimemillis).forEach(b => bidders.add(b))
})
}

const syncs = [];
if ((this.syncStore.extendMode || !syncOptions.pixelEnabled) && syncOptions.iframeEnabled) {
const { gdprApplies, consentString } = gdprConsent || {};
Expand All @@ -248,7 +243,8 @@ export const spec = {
(this.syncStore.extendMode ? '&pbs=1' : '') +
(typeof gdprApplies === 'boolean' ? `&gdpr=${Number(gdprApplies)}` : '') +
(consentString ? `&gdpr_consent=${consentString}` : '') +
(uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : '')
(uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : '') +
(bidders.size ? `&bidders=${[...bidders].join(',')}` : '')
});
} else if (syncOptions.pixelEnabled) {
serverResponses.forEach(response => {
Expand Down Expand Up @@ -276,41 +272,91 @@ const ID_REQUEST = {
const extendImps = [];
const adServerImps = [];

function formatRequest(imps, transactionId, extendMode) {
function setAdditionalConsent(request, extendMode) {
const additionalConsent = bidderRequest?.gdprConsent?.addtlConsent;
if (!additionalConsent) {
return;
}
if (extendMode) {
deepSetValue(request, 'user.ext.ConsentedProvidersSettings.consented_providers', additionalConsent)
} else {
// Additional Consent String
if (additionalConsent && additionalConsent.indexOf('~') !== -1) {
// Google Ad Tech Provider IDs
const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1);
if (atpIds) {
deepSetValue(
request,
'user.ext.consented_providers_settings.consented_providers',
atpIds.split('.').map(id => parseInt(id, 10))
);
}
}
}
}

function adServerUrl(extendMode, publisherId) {
if (extendMode) {
return EXTEND_URL;
}

const urlSegments = [];
urlSegments.push(hasPurpose1Consent(bidderRequest?.gdprConsent) ? AD_SERVER_BASE_URL : BASIC_ADS_BASE_URL)
if (publisherId) {
urlSegments.push(publisherId)
}
urlSegments.push(PB_ENDPOINT)

return urlSegments.join('/');
}

function formatRequest(imps, {transactionId, publisherId}, extendMode) {
const request = deepClone(basicRequest);
request.imp = imps;
request.id = getUniqueIdentifierStr();
setAdditionalConsent(request, extendMode);
if (transactionId) {
deepSetValue(request, 'source.tid', transactionId);
}
const adServerUrl = hasPurpose1Consent(bidderRequest?.gdprConsent) ? AD_SERVER_URL : BASIC_ADS_URL;

return {
method: 'POST',
url: extendMode ? EXTEND_URL : adServerUrl,
url: adServerUrl(extendMode, publisherId),
data: JSON.stringify(request),
bidderRequest
}
};

let publisherId = null;
bidRequests.map((bidRequest) => {
const extendModeEnabled = this.isExtendModeEnabled(globalExtendMode, bidRequest.params);
const bidParams = bidRequest.params;
const extendModeEnabled = this.isExtendModeEnabled(globalExtendMode, bidParams);
const imp = this.buildImp(bidRequest, extendModeEnabled);
if (singleRequestMode) {
if (!publisherId) {
publisherId = bidParams?.publisherId;
} else if (publisherId && bidParams?.publisherId && publisherId !== bidParams?.publisherId) {
throw new Error(`All Improve Digital placements in a single call must have the same publisherId. Please check your 'params.publisherId' or turn off the single request mode.`);
}
extendModeEnabled ? extendImps.push(imp) : adServerImps.push(imp);
} else {
requests.push(formatRequest([imp], bidRequest.transactionId, extendModeEnabled));
requests.push(formatRequest([imp], {transactionId: bidRequest.transactionId, publisherId: bidParams?.publisherId}, extendModeEnabled));
}
});

if (!singleRequestMode) {
return requests;
}
const requestOptions = {
transactionId: bidderRequest.auctionId,
publisherId,
}
// In the single request mode, split imps between those going to the ad server and those going to extend server
if (extendImps.length) {
requests.push(formatRequest(extendImps, bidderRequest.auctionId, true));
requests.push(formatRequest(extendImps, requestOptions, true));
}
if (adServerImps.length) {
requests.push(formatRequest(adServerImps, bidderRequest.auctionId, false));
requests.push(formatRequest(adServerImps, requestOptions, false));
}

return requests;
Expand Down
86 changes: 84 additions & 2 deletions test/spec/modules/improvedigitalBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import { deepSetValue } from '../../../src/utils';

describe('Improve Digital Adapter Tests', function () {
const METHOD = 'POST';
const AD_SERVER_URL = 'https://ad.360yield.com/pb';
const BASIC_ADS_URL = 'https://ad.360yield-basic.com/pb';
const AD_SERVER_BASE_URL = 'https://ad.360yield.com';
const BASIC_ADS_BASE_URL = 'https://ad.360yield-basic.com';
const PB_ENDPOINT = 'pb';
const AD_SERVER_URL = `${AD_SERVER_BASE_URL}/${PB_ENDPOINT}`;
const BASIC_ADS_URL = `${BASIC_ADS_BASE_URL}/${PB_ENDPOINT}`;
const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction';
const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html';
const INSTREAM_TYPE = 1;
Expand Down Expand Up @@ -374,6 +377,7 @@ describe('Improve Digital Adapter Tests', function () {
const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data);
expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1);
expect(payload.user.ext.consent).to.equal('CONSENT');
expect(payload.user.ext.ConsentedProvidersSettings).to.not.exist;
expect(payload.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]);
});

Expand All @@ -385,6 +389,15 @@ describe('Improve Digital Adapter Tests', function () {
expect(payload.user.ext.consented_providers_settings).to.not.exist;
});

it('should add ConsentedProvidersSettings when extend mode enabled', function () {
const bidRequest = deepClone(extendBidRequest);
const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data);
expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1);
expect(payload.user.ext.consent).to.equal('CONSENT');
expect(payload.user.ext.ConsentedProvidersSettings.consented_providers).to.exist.and.to.equal('1~1.35.41.101');
expect(payload.user.ext.consented_providers_settings).to.not.exist;
});

it('should add CCPA consent string', function () {
const bidRequest = Object.assign({}, simpleBidRequest);
const request = spec.buildRequests([bidRequest], {...bidderRequest, ...{ uspConsent: '1YYY' }});
Expand Down Expand Up @@ -810,6 +823,64 @@ describe('Improve Digital Adapter Tests', function () {
expect(requests[0].url).to.equal(AD_SERVER_URL);
expect(requests[1].url).to.equal(EXTEND_URL);
});

it('should add publisherId to request URL when available in request params', function() {
function formatPublisherUrl(baseUrl, publisherId) {
return `${baseUrl}/${publisherId}/${PB_ENDPOINT}`;
}
const bidRequest = deepClone(simpleBidRequest);
bidRequest.params.publisherId = 1000;
let request = spec.buildRequests([bidRequest], bidderRequest)[0];
expect(request).to.be.an('object');
sinon.assert.match(request, {
method: METHOD,
url: formatPublisherUrl(AD_SERVER_BASE_URL, 1000),
bidderRequest
});

const bidRequest2 = deepClone(simpleBidRequest)
bidRequest2.params.publisherId = 1002;

const bidRequest3 = deepClone(extendBidRequest)
bidRequest3.params.publisherId = 1002;

const request1 = spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[0];
expect(request1.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000));
const request2 = spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[1];
expect(request2.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1002));
const request3 = spec.buildRequests([bidRequest, bidRequest3], bidderRequest)[1];
expect(request3.url).to.equal(EXTEND_URL);

// Enable single request mode
getConfigStub = sinon.stub(config, 'getConfig');
getConfigStub.withArgs('improvedigital.singleRequest').returns(true);
try {
spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[0];
} catch (e) {
expect(e.name).to.exist.equal('Error')
expect(e.message).to.exist.equal(`All Improve Digital placements in a single call must have the same publisherId. Please check your 'params.publisherId' or turn off the single request mode.`)
}

bidRequest2.params.publisherId = null;
request = spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[0];
expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000));

const consent = deepClone(gdprConsent);
deepSetValue(consent, 'vendorData.purpose.consents.1', false);
const bidderRequestWithConsent = deepClone(bidderRequest);
bidderRequestWithConsent.gdprConsent = consent;
request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0];
expect(request.url).to.equal(formatPublisherUrl(BASIC_ADS_BASE_URL, 1000));

deepSetValue(consent, 'vendorData.purpose.consents.1', true);
bidderRequestWithConsent.gdprConsent = consent;
request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0];
expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000));

delete bidRequest.params.publisherId;
request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0];
expect(request.url).to.equal(AD_SERVER_URL);
});
});

const serverResponse = {
Expand Down Expand Up @@ -1347,5 +1418,16 @@ describe('Improve Digital Adapter Tests', function () {
const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses);
expect(syncs).to.deep.equal([{ type: 'iframe', url: basicIframeSyncUrl + '&pbs=1' }]);
});

it('should add bidders to iframe user sync url', function () {
getConfigStub = sinon.stub(config, 'getConfig');
getConfigStub.withArgs('improvedigital.extend').returns(true);
spec.buildRequests([simpleBidRequest], {});
const rawResponse = deepClone(serverResponse)
deepSetValue(rawResponse, 'body.ext.responsetimemillis', {a: 1, b: 1, c: 1, d: 1, e: 1})
let syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, [rawResponse]);
let url = basicIframeSyncUrl + '&pbs=1' + '&bidders=a,b,c,d,e'
expect(syncs).to.deep.equal([{ type: 'iframe', url }]);
});
});
});

0 comments on commit 7ab8dd6

Please sign in to comment.