From 997961fe1c10ac8e647b1ba019a1b189983daf9d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 1 Mar 2022 09:01:11 -0700 Subject: [PATCH 1/7] Prebid core: bidder-specific control over storage access via `bidderSettings.storageAllowed` (#7992) * Prebid core: bidder-specific control over storage access via `bidderSettings.allowStorage` https://github.com/prebid/Prebid.js/issues/7280 * Refactor signature of getStorageManager * Refactor calls to getStorageManager from bid adapters * Use bidderCode for StorageManager enforcement of bidderSettings.storageAllowed * Update adriver adapter to use new getStorageManager signature --- modules/adagioBidAdapter.js | 2 +- modules/adbookpspBidAdapter.js | 2 +- modules/adkernelAdnAnalyticsAdapter.js | 2 +- modules/adnuntiusBidAdapter.js | 2 +- modules/adplusBidAdapter.js | 2 +- modules/adqueryBidAdapter.js | 2 +- modules/adqueryIdSystem.js | 2 +- modules/adriverBidAdapter.js | 2 +- modules/adtrueBidAdapter.js | 2 +- modules/airgridRtdProvider.js | 2 +- modules/akamaiDapRtdProvider.js | 2 +- modules/amxBidAdapter.js | 2 +- modules/appnexusBidAdapter.js | 2 +- modules/apstreamBidAdapter.js | 2 +- modules/ccxBidAdapter.js | 2 +- modules/concertBidAdapter.js | 2 +- modules/conversantBidAdapter.js | 2 +- modules/craftBidAdapter.js | 2 +- modules/criteoBidAdapter.js | 2 +- modules/criteoIdSystem.js | 2 +- modules/cwireBidAdapter.js | 2 +- modules/datablocksBidAdapter.js | 2 +- modules/deepintentDpesIdSystem.js | 2 +- modules/eplanningBidAdapter.js | 3 +- modules/gjirafaBidAdapter.js | 2 +- modules/glimpseBidAdapter.js | 3 +- modules/gmosspBidAdapter.js | 2 +- modules/gridBidAdapter.js | 2 +- modules/gumgumBidAdapter.js | 3 +- modules/hadronIdSystem.js | 2 +- modules/hadronRtdProvider.js | 2 +- modules/haloIdSystem.js | 2 +- modules/haloRtdProvider.js | 2 +- modules/id5IdSystem.js | 2 +- modules/insticatorBidAdapter.js | 2 +- modules/integr8BidAdapter.js | 2 +- modules/intentIqIdSystem.js | 2 +- modules/invibesBidAdapter.js | 2 +- modules/ixBidAdapter.js | 2 +- modules/jixieBidAdapter.js | 2 +- modules/kargoBidAdapter.js | 2 +- modules/liveIntentIdSystem.js | 2 +- modules/livewrappedBidAdapter.js | 3 +- modules/lotamePanoramaIdSystem.js | 2 +- modules/malltvBidAdapter.js | 2 +- modules/mantisBidAdapter.js | 2 +- modules/mgidBidAdapter.js | 2 +- modules/nobidBidAdapter.js | 2 +- modules/novatiqIdSystem.js | 2 +- modules/onetagBidAdapter.js | 2 +- modules/orbidderBidAdapter.js | 2 +- modules/parrableIdSystem.js | 2 +- modules/permutiveRtdProvider.js | 2 +- modules/pixfutureBidAdapter.js | 2 +- modules/prebidmanagerAnalyticsAdapter.js | 2 +- modules/publinkIdSystem.js | 2 +- modules/quantcastBidAdapter.js | 2 +- modules/relaidoBidAdapter.js | 2 +- modules/rubiconAnalyticsAdapter.js | 2 +- modules/sharedIdSystem.js | 2 +- modules/teadsBidAdapter.js | 2 +- modules/trionBidAdapter.js | 3 +- modules/trustpidSystem.js | 2 +- modules/ucfunnelBidAdapter.js | 3 +- modules/uid2IdSystem.js | 2 +- modules/unicornBidAdapter.js | 2 +- modules/vidazooBidAdapter.js | 2 +- modules/weboramaRtdProvider.js | 2 +- modules/widespaceBidAdapter.js | 3 +- modules/winrBidAdapter.js | 4 +- modules/zeotapIdPlusIdSystem.js | 2 +- src/storageManager.js | 39 +++++++--- test/spec/modules/adnuntiusBidAdapter_spec.js | 2 +- test/spec/modules/publinkIdSystem_spec.js | 2 +- .../spec/modules/zeotapIdPlusIdSystem_spec.js | 2 +- test/spec/unit/core/storageManager_spec.js | 71 ++++++++++++++++++- 76 files changed, 175 insertions(+), 92 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 3381f00ff8f..e6c6b83f164 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -21,7 +21,7 @@ const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; const GVLID = 617; -export const storage = getStorageManager(GVLID, 'adagio'); +export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; const MAX_SESS_DURATION = 30 * 60 * 1000; const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp'; diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js index 1b93d4fe1c6..7e6b7f7bc88 100644 --- a/modules/adbookpspBidAdapter.js +++ b/modules/adbookpspBidAdapter.js @@ -577,7 +577,7 @@ function bannerHasSingleSize(bidRequest) { * USER SYNC */ -export const storage = getStorageManager(); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { return responses diff --git a/modules/adkernelAdnAnalyticsAdapter.js b/modules/adkernelAdnAnalyticsAdapter.js index 2b4e67736f3..de5d59ca6f8 100644 --- a/modules/adkernelAdnAnalyticsAdapter.js +++ b/modules/adkernelAdnAnalyticsAdapter.js @@ -10,7 +10,7 @@ const GVLID = 14; const ANALYTICS_VERSION = '1.0.2'; const DEFAULT_QUEUE_TIMEOUT = 4000; const DEFAULT_HOST = 'tag.adkernel.com'; -const storageObj = getStorageManager(GVLID); +const storageObj = getStorageManager({gvlid: GVLID}); const ADK_HB_EVENTS = { AUCTION_INIT: 'auctionInit', diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index f05cd9f9f32..9e05ea664d8 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -27,7 +27,7 @@ const getSegmentsFromOrtb = function (ortb2) { } const handleMeta = function () { - const storage = getStorageManager(GVLID, 'adnuntius') + const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}) let adnMeta = null if (storage.localStorageIsEnabled()) { adnMeta = JSON.parse(storage.getDataFromLocalStorage('adn.metaData')) diff --git a/modules/adplusBidAdapter.js b/modules/adplusBidAdapter.js index c001781a792..4707ca2ff5a 100644 --- a/modules/adplusBidAdapter.js +++ b/modules/adplusBidAdapter.js @@ -8,7 +8,7 @@ export const BIDDER_CODE = 'adplus'; export const ADPLUS_ENDPOINT = 'https://ssp.ad-plus.com.tr/server/headerBidding'; export const DGID_CODE = 'adplus_dg_id'; export const SESSION_CODE = 'adplus_s_id'; -export const storage = getStorageManager(undefined, BIDDER_CODE); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const COOKIE_EXP = 1000 * 60 * 60 * 24; // 1 day // #endregion diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index ce31f64d705..348bdc90808 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -11,7 +11,7 @@ const ADQUERY_USER_SYNC_DOMAIN = ADQUERY_BIDDER_DOMAIN_PROTOCOL + '://' + ADQUER const ADQUERY_DEFAULT_CURRENCY = 'PLN'; const ADQUERY_NET_REVENUE = true; const ADQUERY_TTL = 360; -const storage = getStorageManager(ADQUERY_GVLID); +const storage = getStorageManager({gvlid: ADQUERY_GVLID, bidderCode: ADQUERY_BIDDER_CODE}); /** @type {BidderSpec} */ export const spec = { diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js index 5357c1a1ffd..85421bf588d 100644 --- a/modules/adqueryIdSystem.js +++ b/modules/adqueryIdSystem.js @@ -13,7 +13,7 @@ import * as utils from '../src/utils.js'; const MODULE_NAME = 'qid'; const AU_GVLID = 902; -export const storage = getStorageManager(AU_GVLID, 'qid'); +export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'qid'}); /** * Param or default. diff --git a/modules/adriverBidAdapter.js b/modules/adriverBidAdapter.js index 83d3feef92e..5ab417520e9 100644 --- a/modules/adriverBidAdapter.js +++ b/modules/adriverBidAdapter.js @@ -7,7 +7,7 @@ const BIDDER_CODE = 'adriver'; const ADRIVER_BID_URL = 'https://pb.adriver.ru/cgi-bin/bid.cgi'; const TIME_TO_LIVE = 3000; -export const storage = getStorageManager(BIDDER_CODE); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index df848fba823..283e1273150 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -4,8 +4,8 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; -const storage = getStorageManager(); const BIDDER_CODE = 'adtrue'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); const ADTRUE_CURRENCY = 'USD'; const ENDPOINT_URL = 'https://hb.adtrue.com/prebid/auction'; const LOG_WARN_PREFIX = 'AdTrue: '; diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index f5403cca3eb..b2e78a7df78 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -16,7 +16,7 @@ const SUBMODULE_NAME = 'airgrid'; const AG_TCF_ID = 782; export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids' -export const storage = getStorageManager(AG_TCF_ID, SUBMODULE_NAME); +export const storage = getStorageManager({gvlid: AG_TCF_ID, moduleName: SUBMODULE_NAME}); /** * Attach script tag to DOM diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index d143a53fbf4..aca984d39c8 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -15,7 +15,7 @@ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments'; -export const storage = getStorageManager(null, SUBMODULE_NAME); +export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); /** * Lazy merge objects. diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index d48245e9604..d1754936d7f 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -5,7 +5,7 @@ import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'amx'; -const storage = getStorageManager(737, BIDDER_CODE); +const storage = getStorageManager({gvlid: 737, bidderCode: BIDDER_CODE}); const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; const VERSION = 'pba1.3.1'; diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 50ea716330b..90068992153 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -61,7 +61,7 @@ const SCRIPT_TAG_START = ' { ajaxBuilder(timeout)( diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 6b7c055b295..68c46cb6e78 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -5,9 +5,8 @@ import find from 'core-js-pure/features/array/find.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; -export const storage = getStorageManager(); - const BIDDER_CODE = 'livewrapped'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const URL = 'https://lwadm.com/ad'; const VERSION = '1.4'; diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 82503a57e9e..a03626d4a1f 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -29,7 +29,7 @@ const DAY_MS = 60 * 60 * 24 * 1000; const MISSING_CORE_CONSENT = 111; const GVLID = 95; -export const storage = getStorageManager(GVLID, MODULE_NAME); +export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); let cookieDomain; /** diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js index 218bd2f1a5e..53f745d4004 100644 --- a/modules/malltvBidAdapter.js +++ b/modules/malltvBidAdapter.js @@ -9,7 +9,7 @@ const SIZE_SEPARATOR = ';'; const BISKO_ID = 'biskoId'; const STORAGE_ID = 'bisko-sid'; const SEGMENTS = 'biskoSegments'; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/mantisBidAdapter.js b/modules/mantisBidAdapter.js index 61b7c31c8e4..8d62b0ffba7 100644 --- a/modules/mantisBidAdapter.js +++ b/modules/mantisBidAdapter.js @@ -1,7 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -export const storage = getStorageManager(); +export const storage = getStorageManager({bidderCode: 'mantis'}); function inIframe() { try { diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index c811a0b2981..51b713c8958 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -7,7 +7,7 @@ import { getStorageManager } from '../src/storageManager.js'; const GVLID = 358; const DEFAULT_CUR = 'USD'; const BIDDER_CODE = 'mgid'; -export const storage = getStorageManager(GVLID, BIDDER_CODE); +export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); const ENDPOINT_URL = 'https://prebid.mgid.com/prebid/'; const LOG_WARN_PREFIX = '[MGID warn]: '; const LOG_INFO_PREFIX = '[MGID info]: '; diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index d10c1d0e430..f788093f833 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -6,7 +6,7 @@ import { getStorageManager } from '../src/storageManager.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; -const storage = getStorageManager(GVLID, BIDDER_CODE); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); window.nobidVersion = '1.3.2'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index a2210090c0f..78c0604974f 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -94,7 +94,7 @@ export const novatiqIdSubmodule = { logInfo('NOVATIQ sharedID name redefined: ' + cookieOrStorageID); } - const storage = getStorageManager('', 'pubCommonId'); + const storage = getStorageManager({moduleName: 'pubCommonId'}); // first check local storage if (storage.hasLocalStorage()) { diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 5642dce9018..684a5a8c2c1 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -14,7 +14,7 @@ const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; const BIDDER_CODE = 'onetag'; const GVLID = 241; -const storage = getStorageManager(GVLID); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); /** * Determines whether or not the given bid request is valid. diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 111c1876e14..38af3a8d1d6 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; -const storageManager = getStorageManager(); +const storageManager = getStorageManager({bidderCode: 'orbidder'}); /** * Determines whether or not the given bid response is valid. diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index b1553bcb134..c62257d4f7f 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -23,7 +23,7 @@ const LEGACY_OPTOUT_COOKIE_NAME = '_parrable_optout'; const ONE_YEAR_MS = 364 * 24 * 60 * 60 * 1000; const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT'; -const storage = getStorageManager(PARRABLE_GVLID); +const storage = getStorageManager({gvlid: PARRABLE_GVLID}); function getExpirationDate() { const oneYearFromNow = new Date(timestamp() + ONE_YEAR_MS); diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 40282567506..19044e6015b 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -13,7 +13,7 @@ import { config } from '../src/config.js' import includes from 'core-js-pure/features/array/includes.js' const MODULE_NAME = 'permutive' -export const storage = getStorageManager(null, MODULE_NAME) +export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) function init (moduleConfig, userConsent) { return true diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index e9db875fc2f..99c2db66c29 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -8,7 +8,7 @@ import { auctionManager } from '../src/auctionManager.js'; import find from 'core-js-pure/features/array/find.js'; const SOURCE = 'pbjs'; -const storageManager = getStorageManager(); +const storageManager = getStorageManager({bidderCode: 'pixfuture'}); const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; export const spec = { code: 'pixfuture', diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index a1a0a636e3c..1ac7ba84916 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -7,7 +7,7 @@ import { getStorageManager } from '../src/storageManager.js'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager */ -export const storage = getStorageManager(undefined, 'prebidmanager'); +export const storage = getStorageManager({gvlid: undefined, moduleName: 'prebidmanager'}); const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint' const analyticsType = 'endpoint'; const analyticsName = 'Prebid Manager Analytics: '; diff --git a/modules/publinkIdSystem.js b/modules/publinkIdSystem.js index 990227e7cfe..9d5645a38cb 100644 --- a/modules/publinkIdSystem.js +++ b/modules/publinkIdSystem.js @@ -16,7 +16,7 @@ const GVLID = 24; const PUBLINK_COOKIE = '_publink'; const PUBLINK_S2S_COOKIE = '_publink_srv'; -export const storage = getStorageManager(GVLID); +export const storage = getStorageManager({gvlid: GVLID}); function isHex(s) { return /^[A-F0-9]+$/i.test(s); diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index e168339426d..1b392f077d1 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -21,7 +21,7 @@ export const QUANTCAST_PROTOCOL = 'https'; export const QUANTCAST_PORT = '8443'; export const QUANTCAST_FPA = '__qca'; -export const storage = getStorageManager(QUANTCAST_VENDOR_ID, BIDDER_CODE); +export const storage = getStorageManager({gvlid: QUANTCAST_VENDOR_ID, bidderCode: BIDDER_CODE}); function makeVideoImp(bid) { const videoInMediaType = deepAccess(bid, 'mediaTypes.video') || {}; diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index d3e3e10c507..db381555ef9 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -10,7 +10,7 @@ const ADAPTER_VERSION = '1.0.7'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); function isBidRequestValid(bid) { if (!deepAccess(bid, 'params.placementId')) { diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index ed24598e986..7bb84a39f73 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -8,7 +8,7 @@ import { getGlobal } from '../src/prebidGlobal.js'; import { getStorageManager } from '../src/storageManager.js'; const RUBICON_GVL_ID = 52; -export const storage = getStorageManager(RUBICON_GVL_ID, 'rubicon'); +export const storage = getStorageManager({gvlid: RUBICON_GVL_ID, moduleName: 'rubicon'}); const COOKIE_NAME = 'rpaSession'; const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 32a96100d43..656b62815c7 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -11,7 +11,7 @@ import { coppaDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; const GVLID = 887; -export const storage = getStorageManager(GVLID, 'pubCommonId'); +export const storage = getStorageManager({gvlid: GVLID, moduleName: 'pubCommonId'}); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const OPTOUT_NAME = '_pubcid_optout'; diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 4bea8858d98..a8902c896f6 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -12,7 +12,7 @@ const gdprStatus = { CMP_NOT_FOUND_OR_ERROR: 22 }; const FP_TEADS_ID_COOKIE_NAME = '_tfpvi'; -export const storage = getStorageManager(GVL_ID, BIDDER_CODE); +export const storage = getStorageManager({gvlid: GVL_ID, bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/trionBidAdapter.js b/modules/trionBidAdapter.js index dd1624f90d7..5750406116b 100644 --- a/modules/trionBidAdapter.js +++ b/modules/trionBidAdapter.js @@ -2,12 +2,11 @@ import { getBidIdParameter, parseSizesInput, tryAppendQueryString } from '../src import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); - const BID_REQUEST_BASE_URL = 'https://in-appadvertising.com/api/bidRequest'; const USER_SYNC_URL = 'https://in-appadvertising.com/api/userSync.html'; const BIDDER_CODE = 'trion'; const BASE_KEY = '_trion_'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/trustpidSystem.js b/modules/trustpidSystem.js index 74b522e8f1a..051775fa777 100644 --- a/modules/trustpidSystem.js +++ b/modules/trustpidSystem.js @@ -13,7 +13,7 @@ const LOG_PREFIX = 'Trustpid module' let mnoAcronym = ''; let mnoDomain = ''; -export const storage = getStorageManager(null, MODULE_NAME); +export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); /** * Handle an event for an iframe. diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 8b85f1ebad3..ec087d005d6 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { config } from '../src/config.js'; -const storage = getStorageManager(); + const COOKIE_NAME = 'ucf_uid'; const VER = 'ADGENT_PREBID-2018011501'; const BIDDER_CODE = 'ucfunnel'; @@ -13,6 +13,7 @@ const VIDEO_CONTEXT = { INSTREAM: 0, OUSTREAM: 2 } +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index c0cd9166784..23656639532 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -23,7 +23,7 @@ function readFromLocalStorage() { } function getStorage() { - return getStorageManager(GVLID, MODULE_NAME); + return getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); } const storage = getStorage(); diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js index 0209c808979..977e694acf7 100644 --- a/modules/unicornBidAdapter.js +++ b/modules/unicornBidAdapter.js @@ -3,12 +3,12 @@ import {BANNER} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; -const storage = getStorageManager(); const BIDDER_CODE = 'unicorn'; const UNICORN_ENDPOINT = 'https://ds.uncn.jp/pb/0/bid.json'; const UNICORN_DEFAULT_CURRENCY = 'JPY'; const UNICORN_PB_COOKIE_KEY = '__pb_unicorn_aud'; const UNICORN_PB_VERSION = '1.1'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * Placement ID and Account ID are required. diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index b6ab42499b9..cf252bda2dc 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -24,7 +24,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'pubcid': 1, 'tdid': 1, }; -const storage = getStorageManager(GVLID); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.cootlogix.com`; diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index ae9c22928e8..0885df02f05 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -82,7 +82,7 @@ const LOCAL_STORAGE_USER_TARGETING_SECTION = 'targeting'; /** @type {number} */ const GVLID = 284; /** @type {object} */ -export const storage = getStorageManager(GVLID, SUBMODULE_NAME); +export const storage = getStorageManager({gvlid: GVLID, moduleName: SUBMODULE_NAME}); /** @type {null|Object} */ let _weboContextualProfile = null; diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js index 7890628f94b..c7548180549 100644 --- a/modules/widespaceBidAdapter.js +++ b/modules/widespaceBidAdapter.js @@ -8,8 +8,6 @@ import includes from 'core-js-pure/features/array/includes.js'; import find from 'core-js-pure/features/array/find.js'; import { getStorageManager } from '../src/storageManager.js'; -export const storage = getStorageManager(); - const BIDDER_CODE = 'widespace'; const WS_ADAPTER_VERSION = '2.0.1'; const LS_KEYS = { @@ -17,6 +15,7 @@ const LS_KEYS = { LC_UID: 'wsLcuid', CUST_DATA: 'wsCustomData' }; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); let preReqTime = 0; diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js index 9213c113460..22b1f119d06 100644 --- a/modules/winrBidAdapter.js +++ b/modules/winrBidAdapter.js @@ -6,8 +6,6 @@ import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; import { getStorageManager } from '../src/storageManager.js'; -export const storage = getStorageManager(); - const BIDDER_CODE = 'winr'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; @@ -17,6 +15,8 @@ const SOURCE = 'pbjs'; const DEFAULT_CURRENCY = 'USD'; const GATE_COOKIE_NAME = 'wnr_gate'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + function buildBid(bidData) { const bid = bidData; const position = { diff --git a/modules/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js index 5cb3a836cd8..3437928df4b 100644 --- a/modules/zeotapIdPlusIdSystem.js +++ b/modules/zeotapIdPlusIdSystem.js @@ -21,7 +21,7 @@ function readFromLocalStorage() { } export function getStorage() { - return getStorageManager(ZEOTAP_VENDOR_ID, ZEOTAP_MODULE_NAME); + return getStorageManager({gvlid: ZEOTAP_VENDOR_ID, moduleName: ZEOTAP_MODULE_NAME}); } export const storage = getStorage(); diff --git a/src/storageManager.js b/src/storageManager.js index 888cdf24325..2da6d257c5e 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,6 +1,7 @@ import {hook} from './hook.js'; -import { hasDeviceAccess, checkCookieSupport, logError } from './utils.js'; +import {hasDeviceAccess, checkCookieSupport, logError, logInfo, isPlainObject} from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; +import {bidderSettings as defaultBidderSettings} from './bidderSettings.js'; const moduleTypeWhiteList = ['core', 'prebid-module']; @@ -10,7 +11,8 @@ export let storageCallbacks = []; * Storage options * @typedef {Object} storageOptions * @property {Number=} gvlid - Vendor id - * @property {string} moduleName - Module name + * @property {string} moduleName? - Module name + * @property {string=} bidderCode? - Bidder code * @property {string=} moduleType - Module type, value can be anyone of core or prebid-module */ @@ -18,23 +20,35 @@ export let storageCallbacks = []; * Returns list of storage related functions with gvlid, module name and module type in its scope. * All three argument are optional here. Below shows the usage of of these * - GVL Id: Pass GVL id if you are a vendor - * - Module name: All modules need to pass module name + * - Bidder code: All bid adapters need to pass bidderCode + * - Module name: All other modules need to pass module name * - Module type: Some modules may need these functions but are not vendor. e.g prebid core files in src and modules like currency. * @param {storageOptions} options */ -export function newStorageManager({gvlid, moduleName, moduleType} = {}) { +export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}, {bidderSettings = defaultBidderSettings} = {}) { + function isBidderDisallowed() { + if (bidderCode == null) { + return false; + } + const storageAllowed = bidderSettings.get(bidderCode, 'storageAllowed'); + return storageAllowed == null ? false : !storageAllowed; + } function isValid(cb) { if (includes(moduleTypeWhiteList, moduleType)) { let result = { valid: true } return cb(result); + } else if (isBidderDisallowed()) { + logInfo(`bidderSettings denied access to device storage for bidder '${bidderCode}'`); + const result = {valid: false}; + return cb(result); } else { let value; let hookDetails = { hasEnforcementHook: false } - validateStorageEnforcement(gvlid, moduleName, hookDetails, function(result) { + validateStorageEnforcement(gvlid, bidderCode || moduleName, hookDetails, function(result) { if (result && result.hasEnforcementHook) { value = cb(result); } else { @@ -303,12 +317,17 @@ export function getCoreStorageManager(moduleName) { /** * Note: Core modules or Prebid modules like Currency, SizeMapping should use getCoreStorageManager - * This function returns storage functions to access cookies and localstorage. Bidders and User id modules should import this and use it in their module if needed. GVL ID and Module name are optional param but gvl id is needed for when gdpr enforcement module is used. - * @param {Number=} gvlid Vendor id - * @param {string=} moduleName BidderCode or module name + * This function returns storage functions to access cookies and localstorage. Bidders and User id modules should import this and use it in their module if needed. + * Bid adapters should always provide `bidderCode`. GVL ID and Module name are optional param but gvl id is needed for when gdpr enforcement module is used. + * @param {Number=} gvlid? Vendor id - required for proper GDPR integration + * @param {string=} bidderCode? - required for bid adapters + * @param {string=} moduleName? module name */ -export function getStorageManager(gvlid, moduleName) { - return newStorageManager({gvlid: gvlid, moduleName: moduleName}); +export function getStorageManager({gvlid, moduleName, bidderCode} = {}) { + if (arguments.length > 1 || (arguments.length > 0 && !isPlainObject(arguments[0]))) { + throw new Error('Invalid invocation for getStorageManager') + } + return newStorageManager({gvlid, moduleName, bidderCode}); } export function resetData() { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 3b6ad8d0912..25b72216395 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -11,7 +11,7 @@ describe('adnuntiusBidAdapter', function () { const GVLID = 855; const usi = utils.generateUUID() const meta = [{ key: 'usi', value: usi }] - const storage = getStorageManager(GVLID, 'adnuntius') + const storage = getStorageManager({gvlid: GVLID, moduleName: 'adnuntius'}) storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) afterEach(function () { diff --git a/test/spec/modules/publinkIdSystem_spec.js b/test/spec/modules/publinkIdSystem_spec.js index cfb5f8ed135..4656afe1585 100644 --- a/test/spec/modules/publinkIdSystem_spec.js +++ b/test/spec/modules/publinkIdSystem_spec.js @@ -5,7 +5,7 @@ import sinon from 'sinon'; import {uspDataHandler} from '../../../src/adapterManager'; import {parseUrl} from '../../../src/utils'; -export const storage = getStorageManager(24); +export const storage = getStorageManager({gvlid: 24}); const TEST_COOKIE_VALUE = 'cookievalue'; describe('PublinkIdSystem', () => { describe('decode', () => { diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 9de6fa843bc..c3fe03391d6 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -54,7 +54,7 @@ describe('Zeotap ID System', function() { it('when a stored Zeotap ID exists it is added to bids', function() { let store = getStorage(); expect(getStorageManagerSpy.calledOnce).to.be.true; - sinon.assert.calledWith(getStorageManagerSpy, 301, 'zeotapIdPlus'); + sinon.assert.calledWith(getStorageManagerSpy, {gvlid: 301, moduleName: 'zeotapIdPlus'}); }); }); diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index 5bb766217f5..74a3b3b023f 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -1,8 +1,19 @@ -import { resetData, getCoreStorageManager, storageCallbacks, getStorageManager } from 'src/storageManager.js'; +import { + resetData, + getCoreStorageManager, + storageCallbacks, + getStorageManager, + newStorageManager +} from 'src/storageManager.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; +import {hook} from '../../../../src/hook.js'; describe('storage manager', function() { + before(() => { + hook.ready(); + }); + beforeEach(function() { resetData(); }); @@ -95,4 +106,62 @@ describe('storage manager', function() { expect(localStorage.getItem('unrelated')).to.be.eq('dummy'); }); }); + + describe('when bidderSettings.allowStorage is defined', () => { + const DENIED_BIDDER = 'denied-bidder'; + const DENY_KEY = 'storageAllowed'; + + const COOKIE = 'test-cookie'; + const LS_KEY = 'test-localstorage'; + + function mockBidderSettings() { + return { + get(bidder, key) { + if (bidder === DENIED_BIDDER && key === DENY_KEY) { + return false; + } else { + return undefined; + } + } + } + } + + Object.entries({ + disallowed: [DENIED_BIDDER, false], + allowed: ['allowed-bidder', true] + }).forEach(([test, [bidderCode, shouldWork]]) => { + describe(`for ${test} bidders`, () => { + let mgr; + + beforeEach(() => { + mgr = newStorageManager({bidderCode: bidderCode}, {bidderSettings: mockBidderSettings()}); + }) + + afterEach(() => { + mgr.setCookie(COOKIE, 'delete', new Date().toUTCString()); + mgr.removeDataFromLocalStorage(LS_KEY); + }) + + const testDesc = (desc) => `should ${shouldWork ? '' : 'not'} ${desc}`; + + it(testDesc('allow cookies'), () => { + mgr.setCookie(COOKIE, 'value'); + expect(mgr.getCookie(COOKIE)).to.equal(shouldWork ? 'value' : null); + }); + + it(testDesc('allow localStorage'), () => { + mgr.setDataInLocalStorage(LS_KEY, 'value'); + expect(mgr.getDataFromLocalStorage(LS_KEY)).to.equal(shouldWork ? 'value' : null); + }); + + it(testDesc('report localStorage as available'), () => { + expect(mgr.hasLocalStorage()).to.equal(shouldWork); + }); + + it(testDesc('report cookies as available'), () => { + expect(mgr.cookiesAreEnabled()).to.equal(shouldWork); + }); + }); + }); + }) }); From 5217befd55d9d92dcb866e71998a0f68ee88cc70 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Tue, 1 Mar 2022 11:58:06 -0500 Subject: [PATCH 2/7] appnexus bid adapter - convert keywords different for psp endpoint (#8115) --- modules/appnexusBidAdapter.js | 38 +++++++++++++++++++- test/spec/modules/appnexusBidAdapter_spec.js | 24 +++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 90068992153..a80c7d9def4 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -367,11 +367,20 @@ export const spec = { }, transformBidParams: function (params, isOpenRtb) { + let conversionFn = transformBidderParamKeywords; + if (isOpenRtb === true) { + let s2sConfig = config.getConfig('s2sConfig'); + let s2sEndpointUrl = deepAccess(s2sConfig, 'endpoint.p1Consent'); + if (s2sEndpointUrl && s2sEndpointUrl.match('/openrtb2/prebid')) { + conversionFn = convertKeywordsToString; + } + } + params = convertTypes({ 'member': 'string', 'invCode': 'string', 'placementId': 'number', - 'keywords': transformBidderParamKeywords, + 'keywords': conversionFn, 'publisherId': 'number' }, params); @@ -1164,4 +1173,31 @@ function getBidFloor(bid) { return null; } +// keywords: { 'genre': ['rock', 'pop'], 'pets': ['dog'] } goes to 'genre=rock,genre=pop,pets=dog' +function convertKeywordsToString(keywords) { + let result = ''; + Object.keys(keywords).forEach(key => { + // if 'text' or '' + if (isStr(keywords[key])) { + if (keywords[key] !== '') { + result += `${key}=${keywords[key]},` + } else { + result += `${key},`; + } + } else if (isArray(keywords[key])) { + if (keywords[key][0] === '') { + result += `${key},` + } else { + keywords[key].forEach(val => { + result += `${key}=${val},` + }); + } + } + }); + + // remove last trailing comma + result = result.substring(0, result.length - 1); + return result; +} + registerBidder(spec); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 75bfffea38b..11e92a1af2e 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1464,4 +1464,28 @@ describe('AppNexusAdapter', function () { expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); }); }); + + describe('transformBidParams', function () { + it('convert keywords param differently for psp endpoint', function () { + sinon.stub(config, 'getConfig') + .withArgs('s2sConfig') + .returns({ + endpoint: { + p1Consent: 'https://ib.adnxs.com/openrtb2/prebid' + } + }); + + const oldParams = { + keywords: { + genre: ['rock', 'pop'], + pets: 'dog' + } + }; + + const newParams = spec.transformBidParams(oldParams, true); + expect(newParams.keywords).to.equal('genre=rock,genre=pop,pets=dog'); + + config.getConfig.restore(); + }); + }); }); From 0da6138585ea1822bbabfaa63afd88666220c6d8 Mon Sep 17 00:00:00 2001 From: OronW <41260031+OronW@users.noreply.github.com> Date: Tue, 1 Mar 2022 18:59:29 +0200 Subject: [PATCH 3/7] Rise Bid Adapter: Added support for banner & gpid (#8083) * add Rise adapter * fixes * change param isOrg to org * Rise adapter * change email for rise * fix circle failed * bump * bump * bump * remove space * Upgrade Rise adapter to 5.0 * starting to combine adUnits to a single request. Added banner media type * improved logic * handle multiple response bids. Updated sent params from buildRequests * removed the old build request function * fixed testMode logic * setting different params by the mediaType, if they're available * updated media format for adUnits (vastXml / html) * updated response structure and some request params * updated request and response structure and logic * fixed netRevenue param * syntax and naming fix * adapter syntax fix and tests partial update according to adapter's new logic * more tests updated. Small adapter tweaks * updated tests to match new request and response structures * fixed config on one of the tests * improved logic * separated query for video and banner keys * added nurl tests * added display tests * updated requests structure and tests video/display together * removed TODO * changes bids object params to camelCase * moved tmax to general params instead of bids. Updated params names in spec file * sending playbackMethod as a single int * sending playbackMethod as a single int * support gpid * changed the playbackMethodValue signal to let * changed vars signal order * moved placement_id key to bids object * changed placementId to camelCase * fixed issue where placementId tried to access wrong object * fixed floorPrice bug * moved currency and netRevenue keys to each bid object * cr * updated placementId value. Added condition to currency and netRevenue * Delete package-lock.json * added package-lock.json back * removed line from package.json * updated some self tests logic * empty commit Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur --- modules/riseBidAdapter.js | 330 +++++++++++++---------- test/spec/modules/riseBidAdapter_spec.js | 288 +++++++++++--------- 2 files changed, 363 insertions(+), 255 deletions(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 9aca2491386..a8ea023d46a 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -1,17 +1,17 @@ -import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel } from '../src/utils.js'; +import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -const SUPPORTED_AD_TYPES = [VIDEO]; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'rise'; -const ADAPTER_VERSION = '5.0.0'; +const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const CURRENCY = 'USD'; const SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; const MODES = { - PRODUCTION: 'hb', - TEST: 'hb-test' + PRODUCTION: 'hb-multi', + TEST: 'hb-multi-test' } const SUPPORTED_SYNC_METHODS = { IFRAME: 'iframe', @@ -36,55 +36,70 @@ export const spec = { return true; }, - buildRequests: function (bidRequests, bidderRequest) { - if (bidRequests.length === 0) { - return []; - } + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; - const requests = []; + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; - bidRequests.forEach(bid => { - requests.push(buildVideoRequest(bid, bidderRequest)); - }); + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - return requests; + return { + method: 'POST', + url: getEndpoint(testMode), + data: combinedRequestsObject + } }, interpretResponse: function ({body}) { const bidResponses = []; - const bidResponse = { - requestId: body.requestId, - cpm: body.cpm, - width: body.width, - height: body.height, - creativeId: body.requestId, - currency: body.currency, - netRevenue: body.netRevenue, - ttl: body.ttl || TTL, - vastXml: body.vastXml, - nurl: body.nurl, - mediaType: VIDEO - }; - - if (body.adomain && body.adomain.length) { - bidResponse.meta = {}; - bidResponse.meta.advertiserDomains = body.adomain + if (body.bids) { + body.bids.forEach(adUnit => { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.requestId, + netRevenue: adUnit.netRevenue || true, + nurl: adUnit.nurl, + mediaType: adUnit.mediaType, + meta: { + mediaType: adUnit.mediaType + } + }; + + if (adUnit.mediaType === VIDEO) { + bidResponse.vastXml = adUnit.vastXml; + } else if (adUnit.mediaType === BANNER) { + bidResponse.ad = adUnit.ad; + } + + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } + + bidResponses.push(bidResponse); + }); } - bidResponses.push(bidResponse); return bidResponses; }, getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.userSyncURL) { + if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { syncs.push({ type: 'iframe', - url: response.body.userSyncURL + url: response.body.params.userSyncURL }); } - if (syncOptions.pixelEnabled && isArray(response.body.userSyncPixels)) { - const pixels = response.body.userSyncPixels.map(pixel => { + if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel @@ -104,8 +119,8 @@ export const spec = { if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { triggerPixel(bid.nurl); } - }, -} + } +}; registerBidder(spec); @@ -114,46 +129,33 @@ registerBidder(spec); * @param bid {bid} * @returns {Number} */ -function getFloor(bid) { +function getFloor(bid, mediaType) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ currency: CURRENCY, - mediaType: VIDEO, + mediaType: mediaType, size: '*' }); return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; } /** - * Build the video request - * @param bid {bid} - * @param bidderRequest {bidderRequest} - * @returns {Object} - */ -function buildVideoRequest(bid, bidderRequest) { - const sellerParams = generateParameters(bid, bidderRequest); - const {params} = bid; - return { - method: 'GET', - url: getEndpoint(params.testMode), - data: sellerParams - }; -} - -/** - * Get the the ad size from the bid + * Get the the ad sizes array from the bid * @param bid {bid} * @returns {Array} */ -function getSizes(bid) { - if (deepAccess(bid, 'mediaTypes.video.sizes')) { - return bid.mediaTypes.video.sizes[0]; +function getSizesArray(bid, mediaType) { + let sizesArray = [] + + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - return bid.sizes[0]; + sizesArray = bid.sizes; } - return []; + + return sizesArray; } /** @@ -250,122 +252,180 @@ function getDeviceType(ua) { return '1'; } +function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + + return bidsArray; +} + /** - * Generate query parameters for the request - * @param bid {bid} - * @param bidderRequest {bidderRequest} - * @returns {Object} + * Generate bid specific parameters + * @param {bid} bid + * @param {bidderRequest} bidderRequest + * @returns {Object} bid specific params object */ -function generateParameters(bid, bidderRequest) { +function generateBidParameters(bid, bidderRequest) { const {params} = bid; - const timeout = config.getConfig('bidderTimeout'); - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const [width, height] = getSizes(bid); - const {bidderCode} = bidderRequest; - const domain = window.location.hostname; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); // fix floor price in case of NAN if (isNaN(params.floorPrice)) { params.floorPrice = 0; } - const requestParams = { + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: getBidIdParameter('transactionId', bid), + }; + + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; + } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + if (mediaType === VIDEO) { + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + // verify playbackMethod is of type integer array, or integer only. + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + } + + return bidObject; +} + +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +/** + * Generate params that are common between all bids + * @param {single bid object} generalObject + * @param {bidderRequest} bidderRequest + * @returns {object} the common params object + */ +function generateGeneralParams(generalObject, bidderRequest) { + const domain = window.location.hostname; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const {bidderCode} = bidderRequest; + const generalBidParams = generalObject.params; + const timeout = config.getConfig('bidderTimeout'); + + // these params are snake_case instead of camelCase to allow backwards compatability on the server. + // in the future, these will be converted to camelCase to match our convention. + const generalParams = { wrapper_type: 'prebidjs', wrapper_vendor: '$$PREBID_GLOBAL$$', wrapper_version: '$prebid.version$', adapter_version: ADAPTER_VERSION, auction_start: timestamp(), - ad_unit_code: getBidIdParameter('adUnitCode', bid), - tmax: timeout, - width: width, - height: height, - publisher_id: params.org, - floor_price: Math.max(getFloor(bid), params.floorPrice), - ua: navigator.userAgent, - bid_id: getBidIdParameter('bidId', bid), - bidder_request_id: getBidIdParameter('bidderRequestId', bid), - transaction_id: getBidIdParameter('transactionId', bid), - session_id: getBidIdParameter('auctionId', bid), + publisher_id: generalBidParams.org, publisher_name: domain, site_domain: domain, dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent) - }; + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + session_id: getBidIdParameter('auctionId', generalObject), + tmax: timeout + } - const userIdsParam = getBidIdParameter('userId', bid); + const userIdsParam = getBidIdParameter('userId', generalObject); if (userIdsParam) { - requestParams.userIds = JSON.stringify(userIdsParam); + generalParams.userIds = JSON.stringify(userIdsParam); } const ortb2Metadata = config.getConfig('ortb2') || {}; if (ortb2Metadata.site) { - requestParams.site_metadata = JSON.stringify(ortb2Metadata.site); + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); } if (ortb2Metadata.user) { - requestParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - const playbackMethod = deepAccess(bid, 'mediaTypes.video.playbackmethod'); - if (playbackMethod) { - requestParams.playback_method = playbackMethod; - } - const placement = deepAccess(bid, 'mediaTypes.video.placement'); - if (placement) { - requestParams.placement = placement; - } - const pos = deepAccess(bid, 'mediaTypes.video.pos'); - if (pos) { - requestParams.pos = pos; - } - const minduration = deepAccess(bid, 'mediaTypes.video.minduration'); - if (minduration) { - requestParams.min_duration = minduration; - } - const maxduration = deepAccess(bid, 'mediaTypes.video.maxduration'); - if (maxduration) { - requestParams.max_duration = maxduration; - } - const skip = deepAccess(bid, 'mediaTypes.video.skip'); - if (skip) { - requestParams.skip = skip; - } - const linearity = deepAccess(bid, 'mediaTypes.video.linearity'); - if (linearity) { - requestParams.linearity = linearity; - } - - if (params.placementId) { - requestParams.placement_id = params.placementId; + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); } if (syncEnabled) { const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); if (allowedSyncMethod) { - requestParams.cs_method = allowedSyncMethod; + generalParams.cs_method = allowedSyncMethod; } } if (bidderRequest.uspConsent) { - requestParams.us_privacy = bidderRequest.uspConsent; + generalParams.us_privacy = bidderRequest.uspConsent; } if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; } - if (params.ifa) { - requestParams.ifa = params.ifa; + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; } - if (bid.schain) { - requestParams.schain = getSupplyChain(bid.schain); + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); } if (bidderRequest && bidderRequest.refererInfo) { - requestParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); - requestParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); + generalParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); } - return requestParams; + return generalParams } diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index d5504b1e57e..596c4d0f58c 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -2,12 +2,13 @@ import { expect } from 'chai'; import { spec } from 'modules/riseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { VIDEO } from '../../../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; -const ENDPOINT = 'https://hb.yellowblue.io/hb'; -const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-test'; +const ENDPOINT = 'https://hb.yellowblue.io/hb-multi'; +const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-multi-test'; const TTL = 360; +/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ describe('riseAdapter', function () { const adapter = newBidder(spec); @@ -54,6 +55,29 @@ describe('riseAdapter', function () { 'bidId': '299ffc8cca0b87', 'bidderRequestId': '1144f487e563f9', 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream' + } + }, + 'vastXml': '"..."' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + } + }, + 'ad': '""' } ]; @@ -77,44 +101,41 @@ describe('riseAdapter', function () { } const placementId = '12345678'; - it('sends the placementId as a query param', function () { + it('sends the placementId to ENDPOINT via POST', function () { bidRequests[0].params.placementId = placementId; - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data.placement_id).to.equal(placementId); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].placementId).to.equal(placementId); }); - it('sends bid request to ENDPOINT via GET', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('GET'); - } + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); }); - it('sends bid request to test ENDPOINT via GET', function () { - const requests = spec.buildRequests(testModeBidRequests, bidderRequest); - for (const request of requests) { - expect(request.url).to.equal(TEST_ENDPOINT); - expect(request.method).to.equal('GET'); - } + it('sends bid request to TEST ENDPOINT via POST', function () { + const request = spec.buildRequests(testModeBidRequests, bidderRequest); + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('POST'); }); it('should send the correct bid Id', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data.bid_id).to.equal('299ffc8cca0b87'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); }); - it('should send the correct width and height', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('width', 640); - expect(request.data).to.have.property('height', 480); - } + it('should send the correct sizes array', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sizes).to.be.an('array'); + expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) + expect(request.data.bids[1].sizes).to.be.an('array'); + expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + }); + + it('should send the correct media type', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].mediaType).to.equal(VIDEO) + expect(request.data.bids[1].mediaType).to.equal(BANNER) }); it('should respect syncEnabled option', function() { @@ -129,11 +150,9 @@ describe('riseAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('cs_method'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); }); it('should respect "iframe" filter settings', function () { @@ -148,11 +167,9 @@ describe('riseAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('cs_method', 'iframe'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); }); it('should respect "all" filter settings', function () { @@ -167,24 +184,21 @@ describe('riseAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('cs_method', 'iframe'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); }); it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.resetConfig(); config.setConfig({ userSync: { - syncEnabled: true + syncEnabled: true, } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('cs_method', 'pixel'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'pixel'); }); it('should respect total exclusion', function() { @@ -203,48 +217,38 @@ describe('riseAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('cs_method'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); }); it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('us_privacy', '1YNN'); - } + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('us_privacy', '1YNN'); }); it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('us_privacy'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('us_privacy'); }); it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('gdpr'); - expect(request.data).to.not.have.property('gdpr_consent'); - } + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gdpr'); + expect(request.data.params).to.not.have.property('gdpr_consent'); }); it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('gdpr', true); - expect(request.data).to.have.property('gdpr_consent', 'test-consent-string'); - } + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gdpr', true); + expect(request.data.params).to.have.property('gdpr_consent', 'test-consent-string'); }); it('should have schain param if it is available in the bidRequest', () => { @@ -254,14 +258,12 @@ describe('riseAdapter', function () { nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], }; bidRequests[0].schain = schain; - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); }); - it('should set floor_price to getFloor.floor value if it is greater than params.floorPrice', function() { + it('should set flooPrice to getFloor.floor value if it is greater than params.floorPrice', function() { const bid = utils.deepClone(bidRequests[0]); bid.getFloor = () => { return { @@ -270,12 +272,12 @@ describe('riseAdapter', function () { } } bid.params.floorPrice = 0.64; - const request = spec.buildRequests([bid], bidderRequest)[0]; - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('floor_price', 3.32); + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 3.32); }); - it('should set floor_price to params.floorPrice value if it is greater than getFloor.floor', function() { + it('should set floorPrice to params.floorPrice value if it is greater than getFloor.floor', function() { const bid = utils.deepClone(bidRequests[0]); bid.getFloor = () => { return { @@ -284,63 +286,109 @@ describe('riseAdapter', function () { } } bid.params.floorPrice = 1.5; - const request = spec.buildRequests([bid], bidderRequest)[0]; - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('floor_price', 1.5); + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); }); describe('interpretResponse', function () { const response = { + params: { + currency: 'USD', + netRevenue: true, + }, + bids: [{ + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: VIDEO + }, + { + cpm: 12.5, + ad: '""', + width: 300, + height: 250, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: BANNER + }] + }; + + const expectedVideoResponse = { + requestId: '21e12606d47ba7', cpm: 12.5, - vastXml: '', + currency: 'USD', width: 640, height: 480, - requestId: '21e12606d47ba7', + ttl: TTL, + creativeId: '21e12606d47ba7', netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: VIDEO, + meta: { + mediaType: VIDEO, + advertiserDomains: ['abc.com'] + }, + vastXml: '', + }; + + const expectedBannerResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, currency: 'USD', - adomain: ['abc.com'], + width: 640, + height: 480, + ttl: TTL, + creativeId: '21e12606d47ba7', + netRevenue: true, nurl: 'http://example.com/win/1234', + mediaType: BANNER, + meta: { + mediaType: BANNER, + advertiserDomains: ['abc.com'] + }, + ad: '""' }; it('should get correct bid response', function () { - let expectedResponse = [ - { - requestId: '21e12606d47ba7', - cpm: 12.5, - width: 640, - height: 480, - creativeId: '21e12606d47ba7', - currency: 'USD', - netRevenue: true, - ttl: TTL, - vastXml: '', - mediaType: VIDEO, - nurl: 'http://example.com/win/1234', - meta: { - advertiserDomains: ['abc.com'] - } - } - ]; const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + }); + + it('video type should have vastXml key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[0].vastXml).to.equal(expectedVideoResponse.vastXml) + }); + + it('banner type should have ad key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); }) describe('getUserSyncs', function() { const imageSyncResponse = { body: { - userSyncPixels: [ - 'https://image-sync-url.test/1', - 'https://image-sync-url.test/2', - 'https://image-sync-url.test/3' - ] + params: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } } }; const iframeSyncResponse = { body: { - userSyncURL: 'https://iframe-sync-url.test' + params: { + userSyncURL: 'https://iframe-sync-url.test' + } } }; From 72022b7ea6dbced9e229e6172b43f98a18d81a78 Mon Sep 17 00:00:00 2001 From: Bill Newman Date: Tue, 1 Mar 2022 19:25:14 +0200 Subject: [PATCH 4/7] Colossus Bidder: update user sync (#8050) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Mykhailo Yaremchuk --- modules/colossussspBidAdapter.js | 25 ++++++++++++++++--- .../modules/colossussspBidAdapter_spec.js | 6 ++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 72df7c7b465..94265617d8f 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -2,10 +2,11 @@ import { getWindowTop, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'colossusssp'; const G_URL = 'https://colossusssp.com/?c=o&m=multi'; -const G_URL_SYNC = 'https://colossusssp.com/?c=o&m=cookie'; +const G_URL_SYNC = 'https://sync.colossusssp.com'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { @@ -175,10 +176,26 @@ export const spec = { return response; }, - getUserSyncs: () => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'html' : 'hms.gif'; + let syncUrl = G_URL_SYNC + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + return [{ - type: 'image', - url: G_URL_SYNC + type: syncType, + url: syncUrl }]; }, diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index 91f900d6279..5a213589f4f 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -199,13 +199,13 @@ describe('ColossussspAdapter', function () { }) describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); + let userSync = spec.getUserSyncs({}, {}, {}, {}); it('Returns valid URL and type', function () { expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://colossusssp.com/?c=o&m=cookie'); + expect(userSync[0].type).to.be.equal('hms.gif'); + expect(userSync[0].url).to.be.equal('https://sync.colossusssp.com/hms.gif?pbjs=1&coppa=0'); }); }); }); From 69de981681bf27c983fefaf5154ddee1e0c536cc Mon Sep 17 00:00:00 2001 From: Rachel Joyce Date: Tue, 1 Mar 2022 12:32:29 -0700 Subject: [PATCH 5/7] Get floor from correct places on bid object & replace div ID with size (#8093) Co-authored-by: Rachel Joyce --- modules/googleAnalyticsAdapter.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/googleAnalyticsAdapter.js b/modules/googleAnalyticsAdapter.js index 98c73f455b1..b4c4c8c7009 100644 --- a/modules/googleAnalyticsAdapter.js +++ b/modules/googleAnalyticsAdapter.js @@ -208,8 +208,13 @@ function sendBidRequestToGa(bid) { _analyticsQueue.push(function () { _eventCount++; if (_sendFloors) { - var floor = (bid.floorMin) ? bid.floorMin : 'No Floor'; - window[_gaGlobal](_trackerSend, 'event', _category, 'Requests by Floor=' + floor, 'Ad Unit=' + bid.adUnitCode + ',' + bid.bidderCode, 1, _disableInteraction); + var floor = 'No Floor'; + if (bid.floorData) { + floor = bid.floorData.floorValue; + } else if (bid.bids.length) { + floor = bid.bids[0].getFloor().floor; + } + window[_gaGlobal](_trackerSend, 'event', _category, 'Requests by Floor=' + floor, bid.bidderCode, 1, _disableInteraction); } else { window[_gaGlobal](_trackerSend, 'event', _category, 'Requests', bid.bidderCode, 1, _disableInteraction); } @@ -239,8 +244,8 @@ function sendBidResponseToGa(bid) { window[_gaGlobal](_trackerSend, 'event', 'Prebid.js CPM Distribution', cpmDis, bidder, 1, _disableInteraction); } if (_sendFloors) { - var floor = (bid.floorMin) ? bid.floorMin : 'No Floor'; - window[_gaGlobal](_trackerSend, 'event', _category, 'Bids by Floor=' + floor, 'Ad Unit=' + bid.adUnitCode + ',' + bidder, cpmCents, _disableInteraction); + var floor = (bid.floorData) ? bid.floorData.floorValue : 'No Floor'; + window[_gaGlobal](_trackerSend, 'event', _category, 'Bids by Floor=' + floor, 'Size=' + bid.size + ',' + bidder, cpmCents, _disableInteraction); } else { window[_gaGlobal](_trackerSend, 'event', _category, 'Bids', bidder, cpmCents, _disableInteraction); } @@ -270,8 +275,8 @@ function sendBidWonToGa(bid) { _analyticsQueue.push(function () { _eventCount++; if (_sendFloors) { - var floor = (bid.floorMin) ? bid.floorMin : 'No Floor'; - window[_gaGlobal](_trackerSend, 'event', _category, 'Wins by Floor=' + floor, 'Ad Unit=' + bid.adUnitCode + ',' + bid.bidderCode, cpmCents, _disableInteraction); + var floor = (bid.floorData) ? bid.floorData.floorValue : 'No Floor'; + window[_gaGlobal](_trackerSend, 'event', _category, 'Wins by Floor=' + floor, 'Size=' + bid.size + ',' + bid.bidderCode, cpmCents, _disableInteraction); } else { window[_gaGlobal](_trackerSend, 'event', _category, 'Wins', bid.bidderCode, cpmCents, _disableInteraction); } From efbeaf0e5abdbcdc75b160c7e2dc8fabe67cd7ca Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Wed, 2 Mar 2022 17:02:02 +0100 Subject: [PATCH 6/7] Improve Digital adapter: custom creative renderer (#7975) * Feature Prebid First Party Data (#2) * HBT-166: Added PageCategory and Genre to request object from First-Party-Data * HBT-166: Added PageCategory and Genre to request object from First-Party-Data and Rewrite Test Cases * Update improvedigitalBidAdapter.js Version increased * Improve Digital COPPA support * Feature/razr integration (#3) Integrated RAZR rich creative renderer * Use renderer interface for Razr Co-authored-by: Faisal Islam <93644923+faisalvs@users.noreply.github.com> Co-authored-by: Samiul Amin Shanto <93644987+samiul-shanto@users.noreply.github.com> Co-authored-by: Faisal Islam --- modules/improvedigitalBidAdapter.js | 45 ++++++++++++++++++- .../modules/improvedigitalBidAdapter_spec.js | 37 +++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index a68e90754fb..9267a59edd1 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,4 +1,4 @@ -import { deepSetValue, logError, _each, getBidRequest, isNumber, isArray, deepAccess, isFn, isPlainObject, logWarn, getBidIdParameter, getUniqueIdentifierStr, isEmpty, isInteger, isStr } from '../src/utils.js'; +import { deepSetValue, logError, _each, getBidRequest, isNumber, isArray, deepAccess, isFn, isPlainObject, logWarn, getBidIdParameter, getUniqueIdentifierStr, isEmpty, isInteger, isStr, mergeDeep } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -10,8 +10,44 @@ const BIDDER_CODE = 'improvedigital'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; +const ID_RAZR = { + RENDERER_URL: 'https://razr.improvedigital.com/renderer.js', + addBidData({bid, bidRequest}) { + if (this.isValidBid(bid)) { + bid.renderer = Renderer.install({ + url: this.RENDERER_URL, + config: {bidRequest} + }); + bid.renderer.setRender(this.render); + } + }, + + isValidBid(bid) { + return bid && /razr:\\?\/\\?\//.test(bid.ad); + }, + + render(bid) { + const {bidRequest} = bid.renderer.getConfig(); + + const payload = { + type: 'prebid', + bidRequest, + bid, + config: mergeDeep( + {}, + config.getConfig('improvedigital.rendererConfig'), + deepAccess(bidRequest, 'params.rendererConfig') + ) + }; + + const razr = window.razr = window.razr || {}; + razr.queue = razr.queue || []; + razr.queue.push(payload); + } +}; + export const spec = { - version: '7.6.0', + version: '7.7.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -197,6 +233,11 @@ export const spec = { }; } + ID_RAZR.addBidData({ + bidRequest, + bid + }); + bids.push(bid); }); return bids; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 6fe50183cda..64c276ce806 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -605,6 +605,33 @@ describe('Improve Digital Adapter Tests', function () { } }; + const serverResponseRazr = { + 'body': { + 'id': '687a06c541d8d1', + 'site_id': 191642, + 'bid': [ + { + 'isNet': false, + 'id': '33e9500b21129f', + 'advid': '5279', + 'price': 1.45888594164456, + 'nurl': 'https://ice.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', + 'h': 290, + 'pid': 1053688, + 'sync': [ + 'https://link1', + 'https://link2' + ], + 'crid': '422031', + 'w': 600, + 'cid': '99006', + 'adm': 'document.writeln("