From d9f3de5d78f780f040b725dd3c0e3aaba90cd1fd Mon Sep 17 00:00:00 2001 From: ccorbo Date: Tue, 5 Jul 2022 07:13:10 -0400 Subject: [PATCH] IX Bid Adapter: Add support for IMG based user syncs (#8606) Co-authored-by: Chris Corbo --- modules/ixBidAdapter.js | 76 +++++++++++++++++-- test/spec/modules/ixBidAdapter_spec.js | 101 +++++++++++++++++++++++-- 2 files changed, 164 insertions(+), 13 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 3d832b316..dc16a4c38 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -46,9 +46,9 @@ const OUTSTREAM_MINIMUM_PLAYER_SIZE = [144, 144]; const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; -const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; - +const IFRAME_USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; +const IMG_USER_SYNC_URL = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid' export const ERROR_CODES = { BID_SIZE_INVALID_FORMAT: 1, BID_SIZE_NOT_INCLUDED: 2, @@ -191,6 +191,9 @@ const NATIVE_EVENT_TRACKING_METHOD = { const LOCAL_STORAGE_KEY = 'ixdiag'; let hasRegisteredHandler = false; export const storage = getStorageManager({gvlid: GLOBAL_VENDOR_ID, bidderCode: BIDDER_CODE}); +let siteID = 0; +let gdprConsent = ''; +let usPrivacy = ''; // Possible values for bidResponse.seatBid[].bid[].mtype which indicates the type of the creative markup so that it can properly be associated with the right sub-object of the BidRequest.Imp. const MEDIA_TYPES = { @@ -882,6 +885,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (tmax) { r.ext.ixdiag.tmax = tmax } + + if (config.getConfig('userSync')) { + r.ext.ixdiag.syncsPerBidder = config.getConfig('userSync').syncsPerBidder; + } + // Get cached errors stored in LocalStorage const cachedErrors = getCachedErrors(); @@ -910,7 +918,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Apply GDPR information to the request if GDPR is enabled. if (bidderRequest) { if (bidderRequest.gdprConsent) { - const gdprConsent = bidderRequest.gdprConsent; + gdprConsent = bidderRequest.gdprConsent; if (gdprConsent.hasOwnProperty('gdprApplies')) { r.regs = { @@ -936,6 +944,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (bidderRequest.uspConsent) { deepSetValue(r, 'regs.ext.us_privacy', bidderRequest.uspConsent); + usPrivacy = bidderRequest.uspConsent; } if (pageUrl) { @@ -949,7 +958,9 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const payload = {}; // Use the siteId in the first bid request as the main siteId. - payload.s = validBidRequests[0].params.siteId; + siteID = validBidRequests[0].params.siteId; + payload.s = siteID; + payload.v = version; if (version) { payload.v = version; } @@ -1763,11 +1774,60 @@ export const spec = { * @returns {array} User sync pixels */ getUserSyncs: function (syncOptions, serverResponses) { - return (syncOptions.iframeEnabled) ? [{ - type: 'iframe', - url: USER_SYNC_URL - }] : []; + const syncs = []; + let publisherSyncsPerBidderOverride = null; + if (serverResponses.length > 0) { + publisherSyncsPerBidderOverride = deepAccess(serverResponses[0], 'body.ext.publishersyncsperbidderoverride'); + } + if (publisherSyncsPerBidderOverride !== undefined && publisherSyncsPerBidderOverride == 0) { + return []; + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: IFRAME_USER_SYNC_URL + }) + } else { + let publisherSyncsPerBidder = null; + if (config.getConfig('userSync')) { + publisherSyncsPerBidder = config.getConfig('userSync').syncsPerBidder + } + if (publisherSyncsPerBidder === 0) { + publisherSyncsPerBidder = publisherSyncsPerBidderOverride + } + if (publisherSyncsPerBidderOverride && (publisherSyncsPerBidder === 0 || publisherSyncsPerBidder)) { + publisherSyncsPerBidder = publisherSyncsPerBidderOverride > publisherSyncsPerBidder ? publisherSyncsPerBidder : publisherSyncsPerBidderOverride + } else { + publisherSyncsPerBidder = 1 + } + for (let i = 0; i < publisherSyncsPerBidder; i++) { + syncs.push({ + type: 'image', + url: buildImgSyncUrl(publisherSyncsPerBidder, i) + }) + } + } + return syncs; } }; +/** + * Build img user sync url + * @param {int} syncsPerBidder number of syncs Per Bidder + * @param {int} index index to pass + * @returns {string} img user sync url + */ +function buildImgSyncUrl(syncsPerBidder, index) { + let consentString = ''; + let gdprApplies = '0'; + if (gdprConsent && gdprConsent.hasOwnProperty('gdprApplies')) { + gdprApplies = gdprConsent.gdprApplies ? '1' : '0'; + } + if (gdprConsent && gdprConsent.hasOwnProperty('consentString')) { + consentString = gdprConsent.consentString || ''; + } + + return IMG_USER_SYNC_URL + '&site_id=' + siteID.toString() + '&p=' + syncsPerBidder.toString() + '&i=' + index.toString() + '&gdpr=' + gdprApplies + '&gdpr_consent=' + consentString + '&us_privacy=' + (usPrivacy || ''); +} + registerBidder(spec); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 74e670620..a79b73d34 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -677,18 +677,109 @@ describe('IndexexchangeAdapter', function () { const syncOptions = { 'iframeEnabled': true } - let userSync = spec.getUserSyncs(syncOptions); + let userSync = spec.getUserSyncs(syncOptions, []); expect(userSync[0].type).to.equal('iframe'); const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; expect(userSync[0].url).to.equal(USER_SYNC_URL); }); - it('When iframeEnabled is false, no userSync should be returned', function () { + it('When iframeEnabled = false, default to img', function () { const syncOptions = { - 'iframeEnabled': false + 'iframeEnabled': false, } - let userSync = spec.getUserSyncs(syncOptions); - expect(userSync).to.be.an('array').that.is.empty; + let userSync = spec.getUserSyncs(syncOptions, []); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=1&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL); + }); + + it('UserSync test : check type = pixel, check usermatch URL, no exchange data, only drop 1', function () { + const syncOptions = { + 'pixelEnabled': true + } + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 3 + } + }) + let userSync = spec.getUserSyncs(syncOptions, []); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=1&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL); + }); + + it('UserSync test : check type = pixel, check usermatch URL with override set to 0', function () { + const syncOptions = { + 'pixelEnabled': true + } + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 3 + } + }); + let userSync = spec.getUserSyncs(syncOptions, [{'body': {'ext': {'publishersyncsperbidderoverride': 0}}}]); + expect(userSync.length).to.equal(0); + }); + + it('UserSync test : check type = pixel, check usermatch URL with override set', function () { + const syncOptions = { + 'pixelEnabled': true + } + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 3 + } + }); + let userSync = spec.getUserSyncs(syncOptions, [{'body': {'ext': {'publishersyncsperbidderoverride': 2}}}]); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL_0 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=2&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + const USER_SYNC_URL_1 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=2&i=1&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL_0); + expect(userSync[1].url).to.equal(USER_SYNC_URL_1); + expect(userSync.length).to.equal(2); + }); + + it('UserSync test : check type = pixel, check usermatch URL with override greater than publisher syncs per bidder , use syncsperbidder', function () { + const syncOptions = { + 'pixelEnabled': true + } + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 3 + } + }); + let userSync = spec.getUserSyncs(syncOptions, [{'body': {'ext': {'publishersyncsperbidderoverride': 4}}}]); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL_0 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=3&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + const USER_SYNC_URL_1 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=3&i=1&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + const USER_SYNC_URL_2 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=3&i=2&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL_0); + expect(userSync[1].url).to.equal(USER_SYNC_URL_1); + expect(userSync[2].url).to.equal(USER_SYNC_URL_2); + expect(userSync.length).to.equal(3); + }); + + it('UserSync test : check type = pixel, syncsPerBidder = 0, still use override', function () { + const syncOptions = { + 'pixelEnabled': true + } + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 0 + } + }); + let userSync = spec.getUserSyncs(syncOptions, [{'body': {'ext': {'publishersyncsperbidderoverride': 2}}}]); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL_0 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=2&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + const USER_SYNC_URL_1 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=2&i=1&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL_0); + expect(userSync[1].url).to.equal(USER_SYNC_URL_1); + expect(userSync.length).to.equal(2); }); });