Skip to content

Commit e08fea6

Browse files
jbartek25faisalvs
andauthored
Improve Digital adapter: publisher endpoint, addtl consent, syncs (prebid#9125)
* Improve Digital adapter: publisher endpoint, addtl consent, syncs (#14) - add bidders to sync url when extend mode enabled - set ConsentedProvidersSettings when extend mode enabled - dynamically generated AD_SERVER_URL when publisherId available * Code refactored * Minor changes Co-authored-by: Faisal Islam <[email protected]> Co-authored-by: Faisal Islam <[email protected]>
1 parent 11fd3d3 commit e08fea6

File tree

2 files changed

+131
-14
lines changed

2 files changed

+131
-14
lines changed

modules/improvedigitalBidAdapter.js

+47-12
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import {loadExternalScript} from '../src/adloader.js';
1010
const BIDDER_CODE = 'improvedigital';
1111
const CREATIVE_TTL = 300;
1212

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

@@ -79,14 +80,22 @@ export const spec = {
7980
const syncs = [];
8081
if ((this.syncStore.extendMode || !syncOptions.pixelEnabled) && syncOptions.iframeEnabled) {
8182
const { gdprApplies, consentString } = gdprConsent || {};
83+
const bidders = new Set();
84+
if (this.syncStore.extendMode && serverResponses) {
85+
serverResponses.forEach(response => {
86+
if (!response?.body?.ext?.responsetimemillis) return;
87+
Object.keys(response.body.ext.responsetimemillis).forEach(b => bidders.add(b))
88+
})
89+
}
8290
syncs.push({
8391
type: 'iframe',
8492
url: IFRAME_SYNC_URL +
8593
`?placement_id=${this.syncStore.placementId}` +
8694
(this.syncStore.extendMode ? '&pbs=1' : '') +
8795
(typeof gdprApplies === 'boolean' ? `&gdpr=${Number(gdprApplies)}` : '') +
8896
(consentString ? `&gdpr_consent=${consentString}` : '') +
89-
(uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : '')
97+
(uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : '') +
98+
(bidders.size ? `&bidders=${[...bidders].join(',')}` : '')
9099
});
91100
} else if (syncOptions.pixelEnabled) {
92101
serverResponses.forEach(response => {
@@ -220,8 +229,14 @@ export const CONVERTER = ortbConverter({
220229
},
221230
request: {
222231
gdprAddtlConsent(setAddtlConsent, ortbRequest, bidderRequest) {
223-
// override attlConsent processor to do some additional parsing, and use a different destination
224-
const additionalConsent = deepAccess(bidderRequest, 'gdprConsent.addtlConsent');
232+
const additionalConsent = bidderRequest?.gdprConsent?.addtlConsent;
233+
if (!additionalConsent) {
234+
return;
235+
}
236+
if (spec.syncStore.extendMode) {
237+
setAddtlConsent(ortbRequest, bidderRequest);
238+
return;
239+
}
225240
if (additionalConsent && additionalConsent.indexOf('~') !== -1) {
226241
// Google Ad Tech Provider IDs
227242
const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1);
@@ -247,23 +262,43 @@ const ID_REQUEST = {
247262
const extendBids = [];
248263
const adServerBids = [];
249264

250-
function formatRequest(bidRequests, extendMode) {
265+
function adServerUrl(extendMode, publisherId) {
266+
if (extendMode) {
267+
return EXTEND_URL;
268+
}
269+
const urlSegments = [];
270+
urlSegments.push(hasPurpose1Consent(bidderRequest?.gdprConsent) ? AD_SERVER_BASE_URL : BASIC_ADS_BASE_URL)
271+
if (publisherId) {
272+
urlSegments.push(publisherId)
273+
}
274+
urlSegments.push(PB_ENDPOINT)
275+
return urlSegments.join('/');
276+
}
277+
278+
function formatRequest(bidRequests, publisherId, extendMode) {
251279
const ortbRequest = CONVERTER.toORTB({bidRequests, bidderRequest, context: {extendMode}});
252-
const adServerUrl = hasPurpose1Consent(bidderRequest?.gdprConsent) ? AD_SERVER_URL : BASIC_ADS_URL;
253280
return {
254281
method: 'POST',
255-
url: extendMode ? EXTEND_URL : adServerUrl,
282+
url: adServerUrl(extendMode, publisherId),
256283
data: JSON.stringify(ortbRequest),
257-
ortbRequest
284+
ortbRequest,
285+
bidderRequest
258286
}
259287
}
260288

289+
let publisherId = null;
261290
bidRequests.map((bidRequest) => {
291+
const bidParamsPublisherId = bidRequest.params.publisherId;
262292
const extendModeEnabled = this.isExtendModeEnabled(globalExtendMode, bidRequest.params);
263293
if (singleRequestMode) {
294+
if (!publisherId) {
295+
publisherId = bidParamsPublisherId;
296+
} else if (bidParamsPublisherId && publisherId !== bidParamsPublisherId) {
297+
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.`);
298+
}
264299
extendModeEnabled ? extendBids.push(bidRequest) : adServerBids.push(bidRequest);
265300
} else {
266-
requests.push(formatRequest([bidRequest], extendModeEnabled));
301+
requests.push(formatRequest([bidRequest], bidParamsPublisherId, extendModeEnabled));
267302
}
268303
});
269304

@@ -272,10 +307,10 @@ const ID_REQUEST = {
272307
}
273308
// In the single request mode, split imps between those going to the ad server and those going to extend server
274309
if (extendBids.length) {
275-
requests.push(formatRequest(extendBids, true));
310+
requests.push(formatRequest(extendBids, publisherId, true));
276311
}
277312
if (adServerBids.length) {
278-
requests.push(formatRequest(adServerBids, false));
313+
requests.push(formatRequest(adServerBids, publisherId, false));
279314
}
280315

281316
return requests;

test/spec/modules/improvedigitalBidAdapter_spec.js

+84-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ import {createEidsArray} from '../../../modules/userId/eids.js';
1818

1919
describe('Improve Digital Adapter Tests', function () {
2020
const METHOD = 'POST';
21-
const AD_SERVER_URL = 'https://ad.360yield.com/pb';
22-
const BASIC_ADS_URL = 'https://ad.360yield-basic.com/pb';
21+
const AD_SERVER_BASE_URL = 'https://ad.360yield.com';
22+
const BASIC_ADS_BASE_URL = 'https://ad.360yield-basic.com';
23+
const PB_ENDPOINT = 'pb';
24+
const AD_SERVER_URL = `${AD_SERVER_BASE_URL}/${PB_ENDPOINT}`;
25+
const BASIC_ADS_URL = `${BASIC_ADS_BASE_URL}/${PB_ENDPOINT}`;
2326
const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction';
2427
const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html';
2528
const INSTREAM_TYPE = 1;
@@ -390,6 +393,7 @@ describe('Improve Digital Adapter Tests', function () {
390393
const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data);
391394
expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1);
392395
expect(payload.user.ext.consent).to.equal('CONSENT');
396+
expect(payload.user.ext.ConsentedProvidersSettings).to.not.exist;
393397
expect(payload.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]);
394398
});
395399

@@ -401,6 +405,15 @@ describe('Improve Digital Adapter Tests', function () {
401405
expect(payload.user.ext.consented_providers_settings).to.not.exist;
402406
});
403407

408+
it('should add ConsentedProvidersSettings when extend mode enabled', function () {
409+
const bidRequest = deepClone(extendBidRequest);
410+
const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data);
411+
expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1);
412+
expect(payload.user.ext.consent).to.equal('CONSENT');
413+
expect(payload.user.ext.ConsentedProvidersSettings.consented_providers).to.exist.and.to.equal('1~1.35.41.101');
414+
expect(payload.user.ext.consented_providers_settings).to.not.exist;
415+
});
416+
404417
it('should add CCPA consent string', function () {
405418
const bidRequest = Object.assign({}, simpleBidRequest);
406419
const request = spec.buildRequests([bidRequest], {...bidderRequest, ...{ uspConsent: '1YYY' }});
@@ -753,6 +766,64 @@ describe('Improve Digital Adapter Tests', function () {
753766
expect(requests[0].url).to.equal(AD_SERVER_URL);
754767
expect(requests[1].url).to.equal(EXTEND_URL);
755768
});
769+
770+
it('should add publisherId to request URL when available in request params', function() {
771+
function formatPublisherUrl(baseUrl, publisherId) {
772+
return `${baseUrl}/${publisherId}/${PB_ENDPOINT}`;
773+
}
774+
const bidRequest = deepClone(simpleBidRequest);
775+
bidRequest.params.publisherId = 1000;
776+
let request = spec.buildRequests([bidRequest], bidderRequest)[0];
777+
expect(request).to.be.an('object');
778+
sinon.assert.match(request, {
779+
method: METHOD,
780+
url: formatPublisherUrl(AD_SERVER_BASE_URL, 1000),
781+
bidderRequest
782+
});
783+
784+
const bidRequest2 = deepClone(simpleBidRequest)
785+
bidRequest2.params.publisherId = 1002;
786+
787+
const bidRequest3 = deepClone(extendBidRequest)
788+
bidRequest3.params.publisherId = 1002;
789+
790+
const request1 = spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[0];
791+
expect(request1.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000));
792+
const request2 = spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[1];
793+
expect(request2.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1002));
794+
const request3 = spec.buildRequests([bidRequest, bidRequest3], bidderRequest)[1];
795+
expect(request3.url).to.equal(EXTEND_URL);
796+
797+
// Enable single request mode
798+
getConfigStub = sinon.stub(config, 'getConfig');
799+
getConfigStub.withArgs('improvedigital.singleRequest').returns(true);
800+
try {
801+
spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[0];
802+
} catch (e) {
803+
expect(e.name).to.exist.equal('Error')
804+
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.`)
805+
}
806+
807+
bidRequest2.params.publisherId = null;
808+
request = spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[0];
809+
expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000));
810+
811+
const consent = deepClone(gdprConsent);
812+
deepSetValue(consent, 'vendorData.purpose.consents.1', false);
813+
const bidderRequestWithConsent = deepClone(bidderRequest);
814+
bidderRequestWithConsent.gdprConsent = consent;
815+
request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0];
816+
expect(request.url).to.equal(formatPublisherUrl(BASIC_ADS_BASE_URL, 1000));
817+
818+
deepSetValue(consent, 'vendorData.purpose.consents.1', true);
819+
bidderRequestWithConsent.gdprConsent = consent;
820+
request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0];
821+
expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000));
822+
823+
delete bidRequest.params.publisherId;
824+
request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0];
825+
expect(request.url).to.equal(AD_SERVER_URL);
826+
});
756827
});
757828

758829
const serverResponse = {
@@ -1285,5 +1356,16 @@ describe('Improve Digital Adapter Tests', function () {
12851356
const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses);
12861357
expect(syncs).to.deep.equal([{ type: 'iframe', url: basicIframeSyncUrl + '&pbs=1' }]);
12871358
});
1359+
1360+
it('should add bidders to iframe user sync url', function () {
1361+
getConfigStub = sinon.stub(config, 'getConfig');
1362+
getConfigStub.withArgs('improvedigital.extend').returns(true);
1363+
spec.buildRequests([simpleBidRequest], {});
1364+
const rawResponse = deepClone(serverResponse)
1365+
deepSetValue(rawResponse, 'body.ext.responsetimemillis', {a: 1, b: 1, c: 1, d: 1, e: 1})
1366+
let syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, [rawResponse]);
1367+
let url = basicIframeSyncUrl + '&pbs=1' + '&bidders=a,b,c,d,e'
1368+
expect(syncs).to.deep.equal([{ type: 'iframe', url }]);
1369+
});
12881370
});
12891371
});

0 commit comments

Comments
 (0)