diff --git a/integrationExamples/gpt/fledge_example.html b/integrationExamples/gpt/fledge_example.html new file mode 100644 index 00000000000..5059e03daef --- /dev/null +++ b/integrationExamples/gpt/fledge_example.html @@ -0,0 +1,103 @@ + + + + + + + + + +

Prebid.js FLEDGE+GPT Example

+ +
Div-1
+
+ +
+ + diff --git a/integrationExamples/gpt/prebidServer_fledge_example.html b/integrationExamples/gpt/prebidServer_fledge_example.html new file mode 100644 index 00000000000..8523c0f2920 --- /dev/null +++ b/integrationExamples/gpt/prebidServer_fledge_example.html @@ -0,0 +1,111 @@ + + + + + + + + + +

Prebid.js FLEDGE+GPT Example

+ +
Div-1
+
+ +
+ + diff --git a/libraries/domainOverrideToRootDomain/index.js b/libraries/domainOverrideToRootDomain/index.js new file mode 100644 index 00000000000..95a334755d1 --- /dev/null +++ b/libraries/domainOverrideToRootDomain/index.js @@ -0,0 +1,39 @@ +/** + * Create a domainOverride callback for an ID module, closing over + * an instance of StorageManager. + * + * The domainOverride function, given document.domain, will return + * the topmost domain we are able to set a cookie on. For example, + * given subdomain.example.com, it would return example.com. + * + * @param {StorageManager} storage e.g. from getStorageManager() + * @param {string} moduleName the name of the module using this function + * @returns {function(): string} + */ +export function domainOverrideToRootDomain(storage, moduleName) { + return function() { + const domainElements = document.domain.split('.'); + const cookieName = `_gd${Date.now()}_${moduleName}`; + + for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { + const nextDomain = domainElements.slice(i).join('.'); + + // write test cookie + storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); + + // read test cookie to verify domain was valid + testCookie = storage.getCookie(cookieName); + + // delete test cookie + storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + + if (testCookie === '1') { + // cookie was written successfully using test domain so the topDomain is updated + topDomain = nextDomain; + } else { + // cookie failed to write using test domain so exit by returning the topDomain + return topDomain; + } + } + } +} diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js index 5fe411e6bcf..c38231d9002 100644 --- a/libraries/ortbConverter/processors/video.js +++ b/libraries/ortbConverter/processors/video.js @@ -6,6 +6,7 @@ import {sizesToFormat} from '../lib/sizes.js'; const ORTB_VIDEO_PARAMS = new Set([ 'pos', 'placement', + 'plcmt', 'api', 'mimes', 'protocols', diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js index ca489a10a90..805a2020fb4 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -1,8 +1,7 @@ -import { triggerPixel } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'ablida'; const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; @@ -77,7 +76,7 @@ export const spec = { const response = serverResponse.body; response.forEach(function(bid) { - bid.ttl = config.getConfig('_bidderTimeout'); + bid.ttl = 60 bidResponses.push(bid); }); return bidResponses; diff --git a/modules/acuityAdsBidAdapter.js b/modules/acuityAdsBidAdapter.js index f469fe48c60..b0bb132ddae 100644 --- a/modules/acuityAdsBidAdapter.js +++ b/modules/acuityAdsBidAdapter.js @@ -150,7 +150,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 3d3c1df0e45..86021d2a90c 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -43,7 +43,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: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({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/adkernelAdnAnalyticsAdapter.js b/modules/adkernelAdnAnalyticsAdapter.js index 901c0d2fd98..48897f8516b 100644 --- a/modules/adkernelAdnAnalyticsAdapter.js +++ b/modules/adkernelAdnAnalyticsAdapter.js @@ -5,12 +5,14 @@ import { logError, parseUrl, _each } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {config} from '../src/config.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +const MODULE_CODE = 'adkernelAdn'; const GVLID = 14; const ANALYTICS_VERSION = '1.0.2'; const DEFAULT_QUEUE_TIMEOUT = 4000; const DEFAULT_HOST = 'tag.adkernel.com'; -const storageObj = getStorageManager({gvlid: GVLID}); +const storageObj = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const ADK_HB_EVENTS = { AUCTION_INIT: 'auctionInit', @@ -104,7 +106,7 @@ analyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, - code: 'adkernelAdn', + code: MODULE_CODE, gvlid: GVLID }); diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 808c788fcb9..027f924ac5d 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,4 +1,4 @@ -import { getValue, logError, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; +import { getValue, logError, isEmpty, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -34,6 +34,7 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const bids = validBidRequests.map(buildRequestObject); + const blacklist = bidderRequest.ortb2; const networkId = getValue(validBidRequests[0].params, 'networkId'); const host = getValue(validBidRequests[0].params, 'host'); const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; @@ -59,6 +60,10 @@ export const spec = { } }; + if (!isEmpty(blacklist.badv)) { + payload.blacklist = blacklist.badv; + }; + if (payload) { switch (bidderName) { case 'pixad': @@ -69,7 +74,7 @@ export const spec = { break; } - return { method: 'POST', url: `https://${host}/pb?bidder=${bidderName}`, data: payload, options: { contentType: 'application/json' } }; + return { method: 'POST', url: `https://${host}/pb`, data: payload, options: { contentType: 'application/json' } }; } }, @@ -88,7 +93,7 @@ export const spec = { * @return {Bid[]} */ interpretResponse: (response, request) => { - const body = response.body || response; + const body = response.body; const bidResponses = []; if (body && body?.data && isArray(body.data)) { body.data.forEach(bid => { @@ -99,13 +104,23 @@ export const spec = { height: bid.height, currency: body.cur || 'TRY', netRevenue: true, - ad: bid.party_tag, creativeId: bid.creative_id, meta: { advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, - ttl: 360, - bidder: bid.bidder + bidder: bid.bidder, + mediaType: bid.type, + ttl: 60 + }; + + if (resbid.mediaType === 'video' && isUrl(bid.party_tag)) { + resbid.vastUrl = bid.party_tag; + resbid.vastImpUrl = bid.iurl; + } else if (resbid.mediaType === 'video') { + resbid.vastXml = bid.party_tag; + resbid.vastImpUrl = bid.iurl; + } else if (resbid.mediaType === 'banner') { + resbid.ad = bid.party_tag; }; bidResponses.push(resbid); @@ -115,6 +130,15 @@ export const spec = { } }; +function isUrl(str) { + try { + URL(str); + return true; + } catch (error) { + return false; + } +}; + function enrichSlotWithFloors(slot, bidRequest) { try { const slotFloors = {}; @@ -163,6 +187,14 @@ function parseSize(size) { function buildRequestObject(bid) { const reqObj = {}; reqObj.size = getSizes(bid); + if (bid.mediaTypes?.banner) { + reqObj.type = 'banner'; + reqObj.mediatype = {}; + } + if (bid.mediaTypes?.video) { + reqObj.type = 'video'; + reqObj.mediatype = bid.mediaTypes.video; + } reqObj.id = getBidIdParameter('bidId', bid); enrichSlotWithFloors(reqObj, bid); diff --git a/modules/admixerIdSystem.js b/modules/admixerIdSystem.js index 49ffe4f4680..7fbebecfc12 100644 --- a/modules/admixerIdSystem.js +++ b/modules/admixerIdSystem.js @@ -9,8 +9,10 @@ import { logError, logInfo } from '../src/utils.js' import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const NAME = 'admixerId'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: NAME}); /** @type {Submodule} */ export const admixerIdSubmodule = { @@ -18,7 +20,7 @@ export const admixerIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'admixerId', + name: NAME, /** * used to specify vendor id * @type {number} diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index ea3b723b316..e59d12eadad 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -6,6 +6,7 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'adnuntius'; const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; +const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i'; const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' // const DEFAULT_NATIVE = 'native' @@ -55,7 +56,7 @@ const getSegmentsFromOrtb = function (ortb2) { // } const handleMeta = function () { - const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }) + const storage = getStorageManager({ bidderCode: BIDDER_CODE }) let adnMeta = null if (storage.localStorageIsEnabled()) { adnMeta = JSON.parse(storage.getDataFromLocalStorage('adn.metaData')) @@ -130,10 +131,11 @@ export const spec = { const network = networkKeys[j]; const networkRequest = [...request] if (network.indexOf('_video') > -1) { networkRequest.push('tt=' + DEFAULT_VAST_VERSION) } + const requestURL = gdprApplies ? ENDPOINT_URL_EUROPE : ENDPOINT_URL // if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } requests.push({ method: 'POST', - url: ENDPOINT_URL + '?' + networkRequest.join('&'), + url: requestURL + '?' + networkRequest.join('&'), data: JSON.stringify(networks[network]), bid: bidRequests[network] }); diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index be5ca4b1057..63e0c7dbe22 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({gvlid: ADQUERY_GVLID, bidderCode: ADQUERY_BIDDER_CODE}); +const storage = getStorageManager({bidderCode: ADQUERY_BIDDER_CODE}); /** @type {BidderSpec} */ export const spec = { diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js index d9552a470d0..5171802caba 100644 --- a/modules/adqueryIdSystem.js +++ b/modules/adqueryIdSystem.js @@ -9,11 +9,12 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import { isFn, isStr, isPlainObject, logError } from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'qid'; const AU_GVLID = 902; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'qid'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'qid'}); /** * Param or default. diff --git a/modules/adrinoBidAdapter.js b/modules/adrinoBidAdapter.js index 2a5588eeb8c..9825c5701d7 100644 --- a/modules/adrinoBidAdapter.js +++ b/modules/adrinoBidAdapter.js @@ -1,6 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {triggerPixel} from '../src/utils.js'; -import {NATIVE} from '../src/mediaTypes.js'; +import {NATIVE, BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -12,7 +12,7 @@ const GVLID = 1072; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [NATIVE], + supportedMediaTypes: [NATIVE, BANNER], getBidderConfig: function (property) { return config.getConfig(`${BIDDER_CODE}.${property}`); @@ -24,27 +24,32 @@ export const spec = { !!(bid.params.hash) && (typeof bid.params.hash === 'string') && !!(bid.mediaTypes) && - Object.keys(bid.mediaTypes).includes(NATIVE) && + (Object.keys(bid.mediaTypes).includes(NATIVE) || Object.keys(bid.mediaTypes).includes(BANNER)) && (bid.bidder === BIDDER_CODE); }, buildRequests: function (validBidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const bidRequests = []; + let bids = []; for (let i = 0; i < validBidRequests.length; i++) { - let host = this.getBidderConfig('host') || BIDDER_HOST; - let requestData = { bidId: validBidRequests[i].bidId, - nativeParams: validBidRequests[i].nativeParams, placementHash: validBidRequests[i].params.hash, userId: validBidRequests[i].userId, referer: bidderRequest.refererInfo.page, userAgent: navigator.userAgent, } + if (validBidRequests[i].sizes != null && validBidRequests[i].sizes.length > 0) { + requestData.bannerParams = { sizes: validBidRequests[i].sizes }; + } + + if (validBidRequests[i].nativeParams != null) { + requestData.nativeParams = validBidRequests[i].nativeParams; + } + if (bidderRequest && bidderRequest.gdprConsent) { requestData.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, @@ -52,27 +57,37 @@ export const spec = { } } - bidRequests.push({ - method: REQUEST_METHOD, - url: host + '/bidder/bid/', - data: requestData, - options: { - contentType: 'application/json', - withCredentials: false, - } - }); + bids.push(requestData); } + let host = this.getBidderConfig('host') || BIDDER_HOST; + let bidRequests = []; + bidRequests.push({ + method: REQUEST_METHOD, + url: host + '/bidder/bids/', + data: bids, + options: { + contentType: 'application/json', + withCredentials: false, + } + }); + return bidRequests; }, interpretResponse: function (serverResponse, bidRequest) { const response = serverResponse.body; - const bidResponses = []; - if (!response.noAd) { - bidResponses.push(response); + const output = []; + + if (response.bidResponses) { + for (const bidResponse of response.bidResponses) { + if (!bidResponse.noAd) { + output.push(bidResponse); + } + } } - return bidResponses; + + return output; }, onBidWon: function (bid) { diff --git a/modules/adriverIdSystem.js b/modules/adriverIdSystem.js index fb8ce99ec16..b3ab00350ea 100644 --- a/modules/adriverIdSystem.js +++ b/modules/adriverIdSystem.js @@ -8,11 +8,12 @@ import { logError, isPlainObject } from '../src/utils.js' import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'adriverId'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const adriverIdSubmodule = { diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index e4d5c618b77..2f8732a8ec4 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -152,6 +152,7 @@ function getPageUrl(bidderRequest) { function buildWinNotice(bid) { const params = bid.params[0]; + const app = deepAccess(bid, 'meta.ext.app') return { publisherId: params.publisherId, siteId: params.siteId, @@ -167,6 +168,9 @@ function buildWinNotice(bid) { ttl: bid.ttl, requestTimestamp: bid.requestTimestamp, responseTimestamp: bid.responseTimestamp, + mediatype: bid.mediaType, + environment: app ? 'app' : 'web', + ...app }; } @@ -348,6 +352,7 @@ function getPrebidResponseBidObject(openRTBResponseBidObject) { function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) { logInfo('AIDEM Bid Adapter meta', openRTBResponseBidObject); deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', deepAccess(openRTBResponseBidObject, 'meta.advertiserDomains')); + deepSetValue(prebidResponseBidObject, 'meta.ext', deepAccess(openRTBResponseBidObject, 'meta.ext')); if (openRTBResponseBidObject.cat && Array.isArray(openRTBResponseBidObject.cat)) { const primaryCatId = openRTBResponseBidObject.cat.shift(); deepSetValue(prebidResponseBidObject, 'meta.primaryCatId', primaryCatId); diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 174502d1757..c94a71eecde 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -12,7 +12,8 @@ import { deepAccess, } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'airgrid'; @@ -20,7 +21,7 @@ const AG_TCF_ID = 782; export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids'; export const storage = getStorageManager({ - gvlid: AG_TCF_ID, + moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME, }); @@ -154,6 +155,7 @@ export const airgridSubmodule = { name: SUBMODULE_NAME, init: init, getBidRequestData: passAudiencesToBidders, + gvlid: AG_TCF_ID }; submodule(MODULE_NAME, airgridSubmodule); diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 67b448eb484..133a2fdbadf 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -2,19 +2,28 @@ import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const BIDDER_CODE = 'aja'; +const BidderCode = 'aja'; const URL = 'https://ad.as.amanad.adtdp.com/v2/prebid'; -const SDK_TYPE = 5; -const AD_TYPE = { - BANNER: 1, - NATIVE: 2, - VIDEO: 3, +const SDKType = 5; +const AdType = { + Banner: 1, + Native: 2, + Video: 3, }; +const BannerSizeMap = { + '970x250': 1, + '300x250': 2, + '320x50': 3, + '728x90': 4, + '320x100': 6, + '336x280': 31, + '300x600': 32, +} + export const spec = { - code: BIDDER_CODE, + code: BidderCode, supportedMediaTypes: [VIDEO, BANNER, NATIVE], /** @@ -36,9 +45,6 @@ export const spec = { * @returns {ServerRequest|ServerRequest[]} */ buildRequests: function(validBidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const bidRequests = []; const pageUrl = bidderRequest?.refererInfo?.page || undefined; @@ -48,7 +54,7 @@ export const spec = { const asi = getBidIdParameter('asi', bidRequest.params); queryString = tryAppendQueryString(queryString, 'asi', asi); - queryString = tryAppendQueryString(queryString, 'skt', SDK_TYPE); + queryString = tryAppendQueryString(queryString, 'skt', SDKType); queryString = tryAppendQueryString(queryString, 'tid', bidRequest.transactionId) queryString = tryAppendQueryString(queryString, 'prebid_id', bidRequest.bidId); queryString = tryAppendQueryString(queryString, 'prebid_ver', '$prebid.version$'); @@ -57,11 +63,27 @@ export const spec = { queryString = tryAppendQueryString(queryString, 'page_url', pageUrl); } + const banner = deepAccess(bidRequest, `mediaTypes.${BANNER}`) + if (banner) { + const adFormatIDs = []; + for (const size of banner.sizes || []) { + if (size.length !== 2) { + continue + } + + const adFormatID = BannerSizeMap[`${size[0]}x${size[1]}`]; + if (adFormatID) { + adFormatIDs.push(adFormatID); + } + } + queryString = tryAppendQueryString(queryString, 'ad_format_ids', adFormatIDs.join(',')); + } + const eids = bidRequest.userIdAsEids; if (eids && eids.length) { queryString = tryAppendQueryString(queryString, 'eids', JSON.stringify({ 'eids': eids, - })) + })); } const sua = deepAccess(bidRequest, 'ortb2.device.sua'); @@ -101,7 +123,7 @@ export const spec = { }, } - if (AD_TYPE.VIDEO === ad.ad_type) { + if (AdType.Video === ad.ad_type) { const videoAd = bidderResponseBody.ad.video; Object.assign(bid, { vastXml: videoAd.vtag, @@ -113,7 +135,7 @@ export const spec = { }); Array.prototype.push.apply(bid.meta.advertiserDomains, videoAd.adomain) - } else if (AD_TYPE.BANNER === ad.ad_type) { + } else if (AdType.Banner === ad.ad_type) { const bannerAd = bidderResponseBody.ad.banner; Object.assign(bid, { width: bannerAd.w, @@ -131,7 +153,7 @@ export const spec = { } Array.prototype.push.apply(bid.meta.advertiserDomains, bannerAd.adomain) - } else if (AD_TYPE.NATIVE === ad.ad_type) { + } else if (AdType.Native === ad.ad_type) { const nativeAds = ad.native.template_and_ads.ads; if (nativeAds.length === 0) { return []; diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index 1c2af70d737..f0bb7eb3a6c 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -10,6 +10,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; @@ -23,7 +24,7 @@ export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds export const DAP_MAX_RETRY_TOKENIZE = 1; export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' -export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); let dapRetryTokenize = 0; /** diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 65c935db5f5..68a3a370c01 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -1,21 +1,21 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import { - parseUrl, - deepAccess, _each, + deepAccess, formatQS, getUniqueIdentifierStr, - triggerPixel, + isArray, isFn, logError, - isArray, + parseUrl, + triggerPixel, } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {config} from '../src/config.js'; +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'amx'; -const storage = getStorageManager({ gvlid: 737, bidderCode: BIDDER_CODE }); +const storage = getStorageManager({ 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.2'; @@ -332,7 +332,7 @@ export const spec = { m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, fpd2: bidderRequest.ortb2, - tmax: config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, amp: refInfo(bidderRequest, 'isAmp', null), ri: buildReferrerInfo(bidderRequest), sync: getSyncSettings(), diff --git a/modules/amxIdSystem.js b/modules/amxIdSystem.js index 9dbab496f2c..5deffa00dfe 100644 --- a/modules/amxIdSystem.js +++ b/modules/amxIdSystem.js @@ -10,28 +10,25 @@ import {ajaxBuilder} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {deepAccess, logError} from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; const NAME = 'amxId'; const GVL_ID = 737; const ID_KEY = NAME; -const version = '1.0'; +const version = '2.0'; const SYNC_URL = 'https://id.a-mx.com/sync/'; const AJAX_TIMEOUT = 300; +const AJAX_OPTIONS = {method: 'GET', withCredentials: true, contentType: 'text/plain'}; -function validateConfig(config) { - if (config == null || config.storage == null) { - logError(`${NAME}: config.storage is required.`); - return false; - } - - if (config.storage.type !== 'html5') { - logError( - `${NAME} only supports storage.type "html5". ${config.storage.type} was provided` - ); - return false; - } +export const storage = getStorageManager({moduleName: NAME, moduleType: MODULE_TYPE_UID}); +const AMUID_KEY = '__amuidpb'; +const getBidAdapterID = () => storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(AMUID_KEY) : null; +function validateConfig(config) { if ( + config.storage != null && typeof config.storage.expires === 'number' && config.storage.expires > 30 ) { @@ -44,7 +41,7 @@ function validateConfig(config) { return true; } -function handleSyncResponse(client, response, callback) { +function handleSyncResponse(client, response, params, callback) { if (response.id != null && response.id.length > 0) { callback(response.id); return; @@ -72,7 +69,7 @@ function handleSyncResponse(client, response, callback) { logError(`${NAME} invalid value`, complete); callback(null); }, - }); + }, params, AJAX_OPTIONS); } export const amxIdSubmodule = { @@ -97,6 +94,8 @@ export const amxIdSubmodule = { ? { [ID_KEY]: value } : undefined, + domainOverride: domainOverrideToRootDomain(storage, NAME), + getId(config, consentData, _extant) { if (!validateConfig(config)) { return undefined; @@ -109,12 +108,18 @@ export const amxIdSubmodule = { const params = { tagId: deepAccess(config, 'params.tagId', ''), - // TODO: are these referer values correct? + ref: ref.ref, u: ref.location, + tl: ref.topmostLocation, + nf: ref.numIframes, + rt: ref.reachedTop, + v: '$prebid.version$', + av: version, vg: '$$PREBID_GLOBAL$$', us_privacy: usp, + am: getBidAdapterID(), gdpr: consent.gdprApplies ? 1 : 0, gdpr_consent: consent.consentString, }; @@ -131,7 +136,7 @@ export const amxIdSubmodule = { if (responseText != null && responseText.length > 0) { try { const parsed = JSON.parse(responseText); - handleSyncResponse(client, parsed, done); + handleSyncResponse(client, parsed, params, done); return; } catch (e) { logError(`${NAME} invalid response`, responseText); @@ -142,9 +147,7 @@ export const amxIdSubmodule = { }, }, params, - { - method: 'GET' - } + AJAX_OPTIONS ); return { callback }; diff --git a/modules/amxIdSystem.md b/modules/amxIdSystem.md index f67fefe261e..5d2b0c48478 100644 --- a/modules/amxIdSystem.md +++ b/modules/amxIdSystem.md @@ -29,15 +29,15 @@ pbjs.setConfig({ | Param under `userSync.userIds[]` | Scope | Type | Description | Example | | -------------------------------- | -------- | ------ | --------------------------- | ----------------------------------------- | | name | Required | string | ID for the amxId module | `"amxId"` | -| storage | Required | Object | Settings for amxId storage | See [storage settings](#storage-settings) | +| storage | Optional | Object | Settings for amxId storage | See [storage settings](#storage-settings) | | params | Optional | Object | Parameters for amxId module | See [params](#params) | ### Storage Settings -The following settings are available for the `storage` property in the `userSync.userIds[]` object: +The following settings are suggested for the `storage` property in the `userSync.userIds[]` object: -| Param under `storage` | Scope | Type | Description | Example | -| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | -| name | Required | String | Where the ID will be stored | `"amxId"` | -| type | Required | String | This must be `"html5"` | `"html5"` | -| expires | Required | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` | +| Param under `storage` | Type | Description | Example | +| --------------------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| name | String | Where the ID will be stored | `"amxId"` | +| type | String | For best performance, this should be `"html5"` | `"html5"` | +| expires | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` | diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index f354eb053b1..cf3763be9c8 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -94,7 +94,7 @@ const SCRIPT_TAG_START = ' { - const adPodTags = createAdPodRequest(tags, adPodBid); - // don't need the original adpod placement because it's in adPodTags - const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); - payload.tags = [...nonPodTags, ...adPodTags]; - }); + if (FEATURES.VIDEO) { + const hasAdPodBid = find(bidRequests, hasAdPod); + if (hasAdPodBid) { + bidRequests.filter(hasAdPod).forEach(adPodBid => { + const adPodTags = createAdPodRequest(tags, adPodBid); + // don't need the original adpod placement because it's in adPodTags + const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); + payload.tags = [...nonPodTags, ...adPodTags]; + }); + } } if (bidRequests[0].userId) { @@ -653,7 +655,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); } - if (rtbBid.rtb.video) { + if (FEATURES.VIDEO && rtbBid.rtb.video) { // shared video properties used for all 3 contexts Object.assign(bid, { width: rtbBid.rtb.video.player_width, @@ -865,107 +867,111 @@ function bidToTag(bid) { } } - const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = deepAccess(bid, 'mediaTypes.video.context'); - - if (videoMediaType && context === 'adpod') { - tag.hb_source = 7; - } else { - tag.hb_source = 1; - } - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } - - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } + if (FEATURES.VIDEO) { + const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = deepAccess(bid, 'mediaTypes.video.context'); - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'context': - case 'playback_method': - let type = bid.params.video[param]; - type = (isArray(type)) ? type[0] : type; - tag.video[param] = VIDEO_MAPPING[param][type]; - break; - // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks - case 'frameworks': - break; - default: - tag.video[param] = bid.params.video[param]; - } - }); + if (videoMediaType && context === 'adpod') { + tag.hb_source = 7; + } else { + tag.hb_source = 1; + } + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } - if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { - tag['video_frameworks'] = bid.params.video.frameworks; + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; } - } - // use IAB ORTB values if the corresponding values weren't already set by bid.params.video - if (videoMediaType) { - tag.video = tag.video || {}; - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_RTB_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'minduration': - case 'maxduration': - if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; - break; - case 'skip': - if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); - break; - case 'skipafter': - if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; - break; - case 'playbackmethod': - if (typeof tag.video['playback_method'] !== 'number') { - let type = videoMediaType[param]; + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'context': + case 'playback_method': + let type = bid.params.video[param]; type = (isArray(type)) ? type[0] : type; + tag.video[param] = VIDEO_MAPPING[param][type]; + break; + // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks + case 'frameworks': + break; + default: + tag.video[param] = bid.params.video[param]; + } + }); - // we only support iab's options 1-4 at this time. - if (type >= 1 && type <= 4) { - tag.video['playback_method'] = type; - } - } - break; - case 'api': - if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { - // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) - let apiTmp = videoMediaType[param].map(val => { - let v = (val === 4) ? 5 : (val === 5) ? 4 : val; - - if (v >= 1 && v <= 5) { - return v; + if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { + tag['video_frameworks'] = bid.params.video.frameworks; + } + } + + // use IAB ORTB values if the corresponding values weren't already set by bid.params.video + if (videoMediaType) { + tag.video = tag.video || {}; + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_RTB_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'minduration': + case 'maxduration': + if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; + break; + case 'skip': + if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); + break; + case 'skipafter': + if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; + break; + case 'playbackmethod': + if (typeof tag.video['playback_method'] !== 'number') { + let type = videoMediaType[param]; + type = (isArray(type)) ? type[0] : type; + + // we only support iab's options 1-4 at this time. + if (type >= 1 && type <= 4) { + tag.video['playback_method'] = type; } - }).filter(v => v); - tag['video_frameworks'] = apiTmp; - } - break; - - case 'startdelay': - case 'placement': - const contextKey = 'context'; - if (typeof tag.video[contextKey] !== 'number') { - const placement = videoMediaType['placement']; - const startdelay = videoMediaType['startdelay']; - const context = getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); - tag.video[contextKey] = VIDEO_MAPPING[contextKey][context]; - } - break; - } - }); - } + } + break; + case 'api': + if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { + // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) + let apiTmp = videoMediaType[param].map(val => { + let v = (val === 4) ? 5 : (val === 5) ? 4 : val; + + if (v >= 1 && v <= 5) { + return v; + } + }).filter(v => v); + tag['video_frameworks'] = apiTmp; + } + break; + + case 'startdelay': + case 'placement': + const contextKey = 'context'; + if (typeof tag.video[contextKey] !== 'number') { + const placement = videoMediaType['placement']; + const startdelay = videoMediaType['startdelay']; + const context = getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); + tag.video[contextKey] = VIDEO_MAPPING[contextKey][context]; + } + break; + } + }); + } - if (bid.renderer) { - tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + } + } else { + tag.hb_source = 1; } if (bid.params.frameworks && isArray(bid.params.frameworks)) { diff --git a/modules/appushBidAdapter.js b/modules/appushBidAdapter.js index 1ad8fe27e42..97772b65e45 100644 --- a/modules/appushBidAdapter.js +++ b/modules/appushBidAdapter.js @@ -154,7 +154,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index 30abb17bfde..871b04442da 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -9,7 +9,7 @@ const CONSTANTS = { BIDDER_CODE: 'apstream', GVLID: 394 }; -const storage = getStorageManager({gvlid: CONSTANTS.GVLID, bidderCode: CONSTANTS.BIDDER_CODE}); +const storage = getStorageManager({bidderCode: CONSTANTS.BIDDER_CODE}); var dsuModule = (function() { 'use strict'; diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index 448969c6f29..8e92146694f 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -6,7 +6,9 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {getGlobal} from '../src/prebidGlobal.js'; -export const storage = getStorageManager(); +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +const MODULE_CODE = 'atsAnalytics'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); /** * Analytics adapter for - https://liveramp.com @@ -399,7 +401,7 @@ atsAnalyticsAdapter.callHandler = function (evtype, args) { adaptermanager.registerAnalyticsAdapter({ adapter: atsAnalyticsAdapter, - code: 'atsAnalytics', + code: MODULE_CODE, gvlid: 97 }); diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index 5435bf09059..87c3aff444a 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -1,8 +1,8 @@ -import { isArray, logError, deepAccess, isEmpty, triggerPixel, replaceAuctionPrice } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { ajax } from '../src/ajax.js'; +import {deepAccess, isArray, isEmpty, logError, replaceAuctionPrice, triggerPixel} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'axonix'; const BIDDER_VERSION = '1.0.2'; @@ -150,7 +150,7 @@ export const spec = { for (const resp of response) { if (resp.requestId) { responses.push(Object.assign(resp, { - ttl: config.getConfig('_bidderTimeout') + ttl: 60 })); } } diff --git a/modules/beyondmediaBidAdapter.js b/modules/beyondmediaBidAdapter.js index 57c141dc2aa..bbcd972470c 100644 --- a/modules/beyondmediaBidAdapter.js +++ b/modules/beyondmediaBidAdapter.js @@ -145,7 +145,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js index 0908b02de2e..b6cfa54170c 100644 --- a/modules/bidwatchAnalyticsAdapter.js +++ b/modules/bidwatchAnalyticsAdapter.js @@ -2,6 +2,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; +import { getRefererInfo } from '../src/refererDetection.js'; const analyticsType = 'endpoint'; const url = 'URL_TO_SERVER_ENDPOINT'; @@ -145,6 +146,7 @@ function handleBidWon(args) { }); } args['cpmIncrement'] = increment; + args['referer'] = encodeURIComponent(getRefererInfo().page || getRefererInfo().topmostLocation); if (typeof saveEvents.bidRequested == 'object' && saveEvents.bidRequested.length > 0 && saveEvents.bidRequested[0].gdprConsent) { args.gdpr = saveEvents.bidRequested[0].gdprConsent; } ajax(endpoint + '.bidwatch.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true}); } diff --git a/modules/blueconicRtdProvider.js b/modules/blueconicRtdProvider.js index 9a7f5984637..b6eb9374671 100644 --- a/modules/blueconicRtdProvider.js +++ b/modules/blueconicRtdProvider.js @@ -9,13 +9,14 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {mergeDeep, isPlainObject, logMessage, logError} from '../src/utils.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'blueconic'; export const RTD_LOCAL_NAME = 'bcPrebidData'; -export const storage = getStorageManager({moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** * Try parsing stringified array of data. diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js index 2e087b41868..ea8b4af690c 100644 --- a/modules/braveBidAdapter.js +++ b/modules/braveBidAdapter.js @@ -83,7 +83,7 @@ export const spec = { domain: parseUrl(page).hostname, page: page, }, - tmax: bidderRequest.timeout || config.getConfig('bidderTimeout') || 500, + tmax: bidderRequest.timeout, imp }; diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index 15b4175b59a..c4cc5394a03 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -68,7 +68,7 @@ function buildRequests(bidReqs, bidderRequest) { w: screen.width, h: screen.height }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest?.timeout }; if (bidderRequest && bidderRequest.gdprConsent) { @@ -100,7 +100,6 @@ function buildRequests(bidReqs, bidderRequest) { method: 'POST', url: URL, data: JSON.stringify(brightcomBidReq), - options: {contentType: 'text/plain', withCredentials: false} }; } catch (e) { logError(e, {bidReqs, bidderRequest}); diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js index b7a9aa3fdc9..b85a01c8fc7 100644 --- a/modules/brightcomSSPBidAdapter.js +++ b/modules/brightcomSSPBidAdapter.js @@ -84,7 +84,7 @@ function buildRequests(bidReqs, bidderRequest) { w: screen.width, h: screen.height }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest?.timeout }; if (bidderRequest?.gdprConsent) { @@ -116,7 +116,6 @@ function buildRequests(bidReqs, bidderRequest) { method: 'POST', url: URL, data: JSON.stringify(payload), - options: {contentType: 'text/plain', withCredentials: false} }; } catch (e) { logError(e, {bidReqs, bidderRequest}); diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 31b2d709f35..4a61f40600d 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -24,8 +24,10 @@ import {find, includes} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +const MODULE_NAME = 'browsi'; -const storage = getStorageManager(); +const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME}); /** @type {ModuleParams} */ let _moduleParams = {}; @@ -336,7 +338,7 @@ export const browsiSubmodule = { * used to link submodule with realTimeData * @type {string} */ - name: 'browsi', + name: MODULE_NAME, /** * get data and send back to realTimeData module * @function diff --git a/modules/byDataAnalyticsAdapter.js b/modules/byDataAnalyticsAdapter.js index 84479ee618b..81fd4388c7d 100644 --- a/modules/byDataAnalyticsAdapter.js +++ b/modules/byDataAnalyticsAdapter.js @@ -5,9 +5,10 @@ import enc from 'crypto-js/enc-utf8'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import { auctionManager } from '../src/auctionManager.js'; import { ajax } from '../src/ajax.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const versionCode = '4.4.1' const secretKey = 'bydata@123456' @@ -16,7 +17,9 @@ const DEFAULT_EVENT_URL = 'https://pbjs-stream.bydata.com/topics/prebid' const analyticsType = 'endpoint' const isBydata = isKeyInUrl('bydata_debug') const adunitsMap = {} -const storage = getStorageManager(); +const MODULE_CODE = 'bydata'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); + let initOptions = {} var payload = {} var winPayload = {} @@ -391,7 +394,7 @@ function sendDataOnKf(dataObj) { adapterManager.registerAnalyticsAdapter({ adapter: ascAdapter, - code: 'bydata' + code: MODULE_CODE, }); function _logInfo(message, meta) { diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js index 7e9fb964a87..15731423c49 100644 --- a/modules/coinzillaBidAdapter.js +++ b/modules/coinzillaBidAdapter.js @@ -1,5 +1,4 @@ import { parseSizesInput } from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'coinzilla'; @@ -78,7 +77,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: bidRequest.timeout, referrer: referrer, ad: response.ad, mediaType: response.mediaType, diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js index a89c4c617b6..addcdfebb27 100644 --- a/modules/compassBidAdapter.js +++ b/modules/compassBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 176729dd607..a25d9086446 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -42,9 +42,9 @@ export const spec = { pageUrl: bidderRequest.refererInfo.page, screen: [window.screen.width, window.screen.height].join('x'), debug: debugTurnedOn(), - uid: getUid(bidderRequest), + uid: getUid(bidderRequest, validBidRequests), optedOut: hasOptedOutOfPersonalization(), - adapterVersion: '1.1.1', + adapterVersion: '1.2.0', uspConsent: bidderRequest.uspConsent, gdprConsent: bidderRequest.gdprConsent, gppConsent: bidderRequest.gppConsent, @@ -158,16 +158,23 @@ export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * Check or generate a UID for the current user. */ -function getUid(bidderRequest) { +function getUid(bidderRequest, validBidRequests) { if (hasOptedOutOfPersonalization() || !consentAllowsPpid(bidderRequest)) { return false; } - const sharedId = deepAccess(bidderRequest, 'userId._sharedid.id'); + /** + * check for shareId or pubCommonId before generating a new one + * sharedId: @see https://docs.prebid.org/dev-docs/modules/userId.html + * pubCid (no longer supported): @see https://docs.prebid.org/dev-docs/modules/pubCommonId.html#adapter-integration + */ + const sharedId = + deepAccess(validBidRequests[0], 'userId.sharedid.id') || + deepAccess(validBidRequests[0], 'userId.pubcid') + const pubCid = deepAccess(validBidRequests[0], 'crumbs.pubcid'); - if (sharedId) { - return sharedId; - } + if (sharedId) return sharedId; + if (pubCid) return pubCid; const LEGACY_CONCERT_UID_KEY = 'c_uid'; const CONCERT_UID_KEY = 'vmconcert_uid'; diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 38c9c00b0ed..82669f12623 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -8,14 +8,86 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {includes} from '../src/polyfill.js'; -import {formatQS, logError} from '../src/utils.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {formatQS, isPlainObject, logError, parseUrl} from '../src/utils.js'; +import {uspDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'connectId'; +const STORAGE_EXPIRY_DAYS = 14; const VENDOR_ID = 25; const PLACEHOLDER = '__PIXEL_ID__'; const UPS_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; const OVERRIDE_OPT_OUT_KEY = 'connectIdOptOut'; const INPUT_PARAM_KEYS = ['pixelId', 'he', 'puid']; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); + +/** + * @function + * @param {Object} obj + */ +function storeObject(obj) { + const expires = Date.now() + (60 * 60 * 24 * 1000 * STORAGE_EXPIRY_DAYS); + if (storage.cookiesAreEnabled()) { + setEtldPlusOneCookie(MODULE_NAME, JSON.stringify(obj), new Date(expires), getSiteHostname()); + } else if (storage.localStorageIsEnabled()) { + obj.__expires = expires; + storage.setDataInLocalStorage(MODULE_NAME, obj); + } +} + +/** + * Attempts to store a cookie on eTLD + 1 + * + * @function + * @param {String} key + * @param {String} value + * @param {Date} expirationDate + * @param {String} hostname + */ +function setEtldPlusOneCookie(key, value, expirationDate, hostname) { + const subDomains = hostname.split('.'); + for (let i = 0; i < subDomains.length; ++i) { + const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.'); + try { + storage.setCookie(key, value, expirationDate.toUTCString(), null, '.' + domain); + const storedCookie = storage.getCookie(key); + if (storedCookie && storedCookie === value) { + break; + } + } catch (error) {} + } +} + +function getIdFromCookie() { + if (storage.cookiesAreEnabled()) { + try { + return JSON.parse(storage.getCookie(MODULE_NAME)); + } catch {} + } + return null; +} + +function getIdFromLocalStorage() { + if (storage.localStorageIsEnabled()) { + const storedIdData = storage.getDataFromLocalStorage(MODULE_NAME); + if (storedIdData) { + if (isPlainObject(storedIdData) && storedIdData.__expires && + storedIdData.__expires <= Date.now()) { + storage.removeDataFromLocalStorage(MODULE_NAME); + return null; + } + return storedIdData; + } + } + return null; +} + +function getSiteHostname() { + const pageInfo = parseUrl(getRefererInfo().page); + return pageInfo.hostname; +} /** @type {Submodule} */ export const connectIdSubmodule = { @@ -37,8 +109,8 @@ export const connectIdSubmodule = { if (connectIdSubmodule.userHasOptedOut()) { return undefined; } - return (typeof value === 'object' && value.connectid) - ? {connectId: value.connectid} : undefined; + return (isPlainObject(value) && (value.connectId || value.connectid)) + ? {connectId: value.connectId || value.connectid} : undefined; }, /** * Gets the Yahoo ConnectID @@ -54,21 +126,28 @@ export const connectIdSubmodule = { const params = config.params || {}; if (!params || (typeof params.he !== 'string' && typeof params.puid !== 'string') || (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) { - logError('The connectId submodule requires the \'pixelId\' and at least one of the \'he\' ' + - 'or \'puid\' parameters to be defined.'); + logError(`${MODULE_NAME} module: configurataion requires the 'pixelId' and at ` + + `least one of the 'he' or 'puid' parameters to be defined.`); return; } + const storedId = getIdFromCookie() || getIdFromLocalStorage(); + if (storedId) { + return {id: storedId}; + } + + const uspString = uspDataHandler.getConsentData() || ''; const data = { + v: '1', '1p': includes([1, '1', true], params['1p']) ? '1' : '0', gdpr: connectIdSubmodule.isEUConsentRequired(consentData) ? '1' : '0', - gdpr_consent: connectIdSubmodule.isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', - us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' + gdpr_consent: connectIdSubmodule.isEUConsentRequired(consentData) ? consentData.consentString : '', + us_privacy: uspString }; - if (connectIdSubmodule.isUnderGPPJurisdiction(consentData)) { - data.gpp = consentData.gppConsent.gppString; - data.gpp_sid = encodeURIComponent(consentData.gppConsent.applicableSections.join(',')); + let topmostLocation = getRefererInfo().topmostLocation; + if (typeof topmostLocation === 'string') { + data.url = topmostLocation.split('?')[0]; } INPUT_PARAM_KEYS.forEach(key => { @@ -84,6 +163,11 @@ export const connectIdSubmodule = { if (response) { try { responseObj = JSON.parse(response); + if (isPlainObject(responseObj) && Object.keys(responseObj).length > 0) { + storeObject(responseObj); + } else { + logError(`${MODULE_NAME} module: UPS response returned an invalid payload ${response}`); + } } catch (error) { logError(error); } @@ -91,7 +175,7 @@ export const connectIdSubmodule = { callback(responseObj); }, error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + logError(`${MODULE_NAME} module: ID fetch encountered an error`, error); callback(); } }; @@ -108,16 +192,7 @@ export const connectIdSubmodule = { * @returns {Boolean} */ isEUConsentRequired(consentData) { - return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); - }, - - /** - * Utility function that returns a boolean flag indicating if the opportunity - * is subject to GPP jurisdiction. - * @returns {Boolean} - */ - isUnderGPPJurisdiction(consentData) { - return !!(consentData && consentData.gppConsent && consentData.gppConsent.gppString); + return !!(consentData?.gdprApplies); }, /** diff --git a/modules/connectIdSystem.md b/modules/connectIdSystem.md index c671c036b77..a3b69a0082c 100644 --- a/modules/connectIdSystem.md +++ b/modules/connectIdSystem.md @@ -27,8 +27,9 @@ The below parameters apply only to the Yahoo ConnectID user ID Module. | Param under usersync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | -| name | Required | String | ID value for the Yahoo ConnectID module - `"connectId"` | `"connectId"` | -| params | Required | Object | Data for Yahoo ConnectID initialization. | | -| params.pixelId | Required | Number | The Yahoo supplied publisher specific pixel Id. | `8976` | -| params.he | Optional | String | The SHA-256 hashed user email address. One of either the `he` parameter or the `puid` parameter must be supplied. | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | -| params.puid | Optional | String | The publisher-supplied user identifier. One of either the `he` parameter or the `puid` parameter must be supplied. | `"P-975484817"` | +| name | Required | String | The name of this module. | `"connectId"` | +| params | Required | Object | Container of all module params. || +| params.pixelId | Required | Number | +The Yahoo-supplied publisher-specific pixel ID. | `"0000"` | +| params.he | Optional | String | The SHA-256 hashed user email address which has been lowercased prior to hashing. Pass both `he` and `puid` params if present, otherwise pass either of the two that is available. |`"ed8ddbf5a171981db8ef938596ca297d5e3f84bcc280041c5880dba3baf9c1d4"`| +| params.puid | Optional | String | The publisher supplied user identifier such as a first-party cookie. Pass both `he` and `puid` params if present, otherwise pass either of the two that is available. | `"ab9iibf5a231ii1db8ef911596ca297d5e3f84biii00041c5880dba3baf9c1da"` | diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js index becc21555e3..be5900407ea 100644 --- a/modules/contentexchangeBidAdapter.js +++ b/modules/contentexchangeBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index c2ffba8bb48..e17a9fe4021 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -124,6 +124,9 @@ export const spec = { const payload = { id: requestId, imp: conversantImps, + source: { + tid: requestId + }, site: { id: siteId, mobile: document.querySelector('meta[name="viewport"][content*="width=device-width"]') !== null ? 1 : 0, diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index e6564ca19d6..8f24dd6c1aa 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -6,17 +6,15 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { find } from '../src/polyfill.js'; import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 import { getStorageManager } from '../src/storageManager.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { hasPurpose1Consent } from '../src/utils/gpdr.js'; - const GVLID = 91; export const ADAPTER_VERSION = 35; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; export const PROFILE_ID_PUBLISHERTAG = 185; -export const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const LOG_PREFIX = 'Criteo: '; /* @@ -146,9 +144,6 @@ export const spec = { * @return {ServerRequest} */ buildRequests: (bidRequests, bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); - let url; let data; let fpd = bidderRequest.ortb2 || {}; @@ -223,7 +218,7 @@ export const spec = { creativeId: slot.creativecode, width: slot.width, height: slot.height, - dealId: slot.dealCode, + dealId: slot.deal, }; if (body.ext?.paf?.transmission && slot.ext?.paf?.content_id) { const pafResponseMeta = { @@ -418,7 +413,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { ext: bidderRequest.publisherExt, }, regs: { - coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined) + coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined), + gpp: bidderRequest.ortb2?.regs?.gpp, + gpp_sid: bidderRequest.ortb2?.regs?.gpp_sid }, slots: bidRequests.map(bidRequest => { networkId = bidRequest.params.networkId || networkId; @@ -437,6 +434,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.ext) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); } + if (bidRequest.nativeOrtbRequest?.assets) { + slot.ext = Object.assign({}, slot.ext, {assets: bidRequest.nativeOrtbRequest.assets}); + } if (bidRequest.params.publisherSubId) { slot.publishersubid = bidRequest.params.publisherSubId; } @@ -512,6 +512,10 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidderRequest && bidderRequest.uspConsent) { request.user.uspIab = bidderRequest.uspConsent; } + if (bidderRequest && bidderRequest.ortb2?.device?.sua) { + request.user.ext = request.user.ext || {}; + request.user.ext.sua = bidderRequest.ortb2?.device?.sua || {}; + } return request; } diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index 867e4315945..b48612203f8 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -10,10 +10,12 @@ import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; const gvlid = 91; const bidderCode = 'criteo'; -export const storage = getStorageManager({ gvlid: gvlid, moduleName: bidderCode }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: bidderCode }); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; @@ -76,17 +78,33 @@ function getCriteoDataFromAllStorages() { } } -function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) { - const url = 'https://gum.criteo.com/sid/json?origin=prebid' + +function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent) { + let url = 'https://gum.criteo.com/sid/json?origin=prebid' + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + `${dnaBundle ? '&info=' + encodeURIComponent(dnaBundle) : ''}` + - `${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` + `${areCookiesWriteable ? '&cw=1' : ''}` + `${isPublishertagPresent ? '&pbt=1' : ''}` + `${isLocalStorageWritable ? '&lsw=1' : ''}`; + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString) { + url = url + `&us_privacy=${encodeURIComponent(usPrivacyString)}`; + } + + const gdprConsent = gdprDataHandler.getConsentData() + if (gdprConsent) { + url = url + `${gdprConsent.consentString ? '&gdprString=' + encodeURIComponent(gdprConsent.consentString) : ''}`; + url = url + `&gdpr=${gdprConsent.gdprApplies === true ? 1 : 0}`; + } + + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + url = url + `${gppConsent.gppString ? '&gpp=' + encodeURIComponent(gppConsent.gppString) : ''}`; + url = url + `${gppConsent.applicableSections ? '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections) : ''}`; + } + return url; } @@ -102,6 +120,9 @@ function callSyncPixel(domain, pixel) { saveOnAllStorages(pixel.storageKeyName, jsonResponse[pixel.bundlePropertyName], domain); } } + }, + error: error => { + logError(`criteoIdSystem: unable to sync user id`, error); } }, undefined, @@ -112,7 +133,7 @@ function callSyncPixel(domain, pixel) { } } -function callCriteoUserSync(parsedCriteoData, gdprString, callback) { +function callCriteoUserSync(parsedCriteoData, callback) { const cw = storage.cookiesAreEnabled(); const lsw = storage.localStorageIsEnabled(); const topUrl = extractProtocolHost(getRefererInfo().page); @@ -127,8 +148,7 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) { parsedCriteoData.dnaBundle, cw, lsw, - isPublishertagPresent, - gdprString + isPublishertagPresent ); const callbacks = { @@ -187,13 +207,10 @@ export const criteoIdSubmodule = { * @param {ConsentData} [consentData] * @returns {{id: {criteoId: string} | undefined}}} */ - getId(config, consentData) { - const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; - const gdprConsentString = hasGdprData ? consentData.consentString : undefined; - + getId() { let localData = getCriteoDataFromAllStorages(); - const result = (callback) => callCriteoUserSync(localData, gdprConsentString, callback); + const result = (callback) => callCriteoUserSync(localData, callback); return { id: localData.bidId ? { criteoId: localData.bidId } : undefined, diff --git a/modules/czechAdIdSystem.js b/modules/czechAdIdSystem.js index 7354ed2e483..957b3ed30bd 100644 --- a/modules/czechAdIdSystem.js +++ b/modules/czechAdIdSystem.js @@ -6,10 +6,11 @@ */ import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js' +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; // Returns StorageManager -export const storage = getStorageManager({ gvlid: 570, moduleName: 'czechAdId' }) +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: 'czechAdId' }) // Returns the id string from either cookie or localstorage const readId = () => { return storage.getCookie('czaid') || storage.getDataFromLocalStorage('czaid') } diff --git a/modules/dacIdSystem.js b/modules/dacIdSystem.js index 856e1976bb1..5adca074c87 100644 --- a/modules/dacIdSystem.js +++ b/modules/dacIdSystem.js @@ -19,8 +19,9 @@ import { import { getStorageManager } from '../src/storageManager.js'; - -export const storage = getStorageManager(); +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +const MODULE_NAME = 'dacId'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export const FUUID_COOKIE_NAME = '_a1_f'; export const AONEID_COOKIE_NAME = '_a1_d'; @@ -116,7 +117,7 @@ export const dacIdSystemSubmodule = { * used to link submodule with config * @type {string} */ - name: 'dacId', + name: MODULE_NAME, /** * decode the stored id value for passing to bid requests diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js index 43c7af1b3cc..f2b50d535eb 100644 --- a/modules/deepintentDpesIdSystem.js +++ b/modules/deepintentDpesIdSystem.js @@ -6,10 +6,11 @@ */ import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'deepintentId'; -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const deepintentDpesSubmodule = { diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 7930efc4fa8..a0f864d529f 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -6,7 +6,7 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js'; const BIDDER_CODE = 'discovery'; const ENDPOINT_URL = 'https://rtb-jp.mediago.io/api/bid?tn='; const TIME_TO_LIVE = 500; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); let globals = {}; let itemMaps = {}; const MEDIATYPE = [BANNER, NATIVE]; diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index 3bfb660cd7e..74a93ce086e 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -1,5 +1,4 @@ -import {deepAccess, getBidIdParameter, logError, logMessage, logWarn, isFn} from '../src/utils.js'; -import {config} from '../src/config.js'; +import {deepAccess, getBidIdParameter, isFn, logError, logMessage, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; @@ -183,7 +182,7 @@ export const spec = { currency: currency, netRevenue: netRevenue, type: response.type, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, meta: { advertiserDomains: response.adomain || [] } diff --git a/modules/emtvBidAdapter.js b/modules/emtvBidAdapter.js new file mode 100644 index 00000000000..7a2fdae8adf --- /dev/null +++ b/modules/emtvBidAdapter.js @@ -0,0 +1,211 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'emtv'; +const AD_URL = 'https://us-east-ep.engagemedia.tv/pbjs'; +const SYNC_URL = 'https://cs.engagemedia.tv'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${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: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/emtvBidAdapter.md b/modules/emtvBidAdapter.md new file mode 100644 index 00000000000..ed58ca43294 --- /dev/null +++ b/modules/emtvBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: EMTV Bidder Adapter +Module Type: EMTV Bidder Adapter +Maintainer: support@engagemedia.tv +``` + +# Description + +Connects to EMTV exchange for bids. +EMTV bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'emtv', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'emtv', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'emtv', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js new file mode 100644 index 00000000000..4a00b97614b --- /dev/null +++ b/modules/eskimiBidAdapter.js @@ -0,0 +1,72 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' + +const BIDDER_CODE = 'eskimi'; +// const ENDPOINT = 'https://hb.eskimi.com/bids' +const ENDPOINT = 'https://sspback.eskimi.com/bid-request' + +const DEFAULT_BID_TTL = 30; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const GVLID = 814; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!bid.params.placementId; + }, + + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}) + + let bid = bidRequests.find((b) => b.params.placementId) + if (!data.site) data.site = {} + data.site.ext = {placementId: bid.params.placementId} + + if (bidderRequest.gdprConsent) { + if (!data.user) data.user = {}; + if (!data.user.ext) data.user.ext = {}; + if (!data.regs) data.regs = {}; + if (!data.regs.ext) data.regs.ext = {}; + data.user.ext.consent = bidderRequest.gdprConsent.consentString; + data.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + + return [{ + method: 'POST', + url: ENDPOINT, + data, + options: {contentType: 'application/json;charset=UTF-8', withCredentials: false} + }] + }, + + interpretResponse(response, request) { + return converter.fromORTB({response: response.body, request: request.data}).bids; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} bid The bid that won the auction + */ + onBidWon: function (bid) { + if (bid.burl) { + utils.triggerPixel(bid.burl); + } + } +} + +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, + currency: DEFAULT_CURRENCY, + mediaType: BANNER // TODO: support more types, we should set mtype on the winning bid + } +}); + +registerBidder(spec); diff --git a/modules/eskimiBidAdapter.md b/modules/eskimiBidAdapter.md new file mode 100644 index 00000000000..83ae87fd01b --- /dev/null +++ b/modules/eskimiBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +Module Name: ESKIMI Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech@eskimi.com + +# Description + +An adapter to get a bid from Eskimi DSP. + +# Test Parameters +```javascript + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + + bids: [{ + bidder: 'eskimi', + params: { + placementId: 612 + } + }] + + }]; +``` + +Where: + +* placementId - Placement ID of the ad unit (required) + diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js index 88c5f85f15d..be661c96061 100644 --- a/modules/fintezaAnalyticsAdapter.js +++ b/modules/fintezaAnalyticsAdapter.js @@ -2,10 +2,12 @@ import { parseUrl, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import CONSTANTS from '../src/constants.json'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'finteza'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const ANALYTICS_TYPE = 'endpoint'; const FINTEZA_HOST = 'https://content.mql5.com/tr'; @@ -439,7 +441,7 @@ fntzAnalyticsAdapter.enableAnalytics = function (config) { adapterManager.registerAnalyticsAdapter({ adapter: fntzAnalyticsAdapter, - code: 'finteza' + code: MODULE_CODE, }); export default fntzAnalyticsAdapter; diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index c5fb813540e..8f5af48d16b 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -424,6 +424,14 @@ export const spec = { requestParams.playerSize = playerSize[0] + 'x' + playerSize[1]; } + // Add video context and placement in requestParams + if (currentBidRequest.mediaTypes.video) { + var videoContext = currentBidRequest.mediaTypes.video.context ? currentBidRequest.mediaTypes.video.context : 'instream'; + var videoPlacement = currentBidRequest.mediaTypes.video.placement ? currentBidRequest.mediaTypes.video.placement : 1; + requestParams.video_context = videoContext; + requestParams.video_placement = videoPlacement; + } + return { method: 'GET', url: FREEWHEEL_ADSSETUP, diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 244807a3164..5f09a315b34 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -6,19 +6,19 @@ */ import * as utils from '../src/utils.js'; -import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { uspDataHandler } from '../src/adapterManager.js'; -import { loadExternalScript } from '../src/adloader.js'; +import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {uspDataHandler} from '../src/adapterManager.js'; +import {loadExternalScript} from '../src/adloader.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'ftrackId'; const LOG_PREFIX = 'FTRACK - '; const LOCAL_STORAGE_EXP_DAYS = 30; -const VENDOR_ID = null; const LOCAL_STORAGE = 'html5'; const FTRACK_STORAGE_NAME = 'ftrackId'; const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`; -const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME}); +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); let consentInfo = { gdpr: { diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 9553ad0586a..798dfc848da 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -11,7 +11,13 @@ import {getHook} from '../src/hook.js'; import {validateStorageEnforcement} from '../src/storageManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; -import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js'; +import { + MODULE_TYPE_ANALYTICS, + MODULE_TYPE_BIDDER, + MODULE_TYPE_CORE, MODULE_TYPE_RTD, + MODULE_TYPE_UID +} from '../src/activities/modules.js'; export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; @@ -49,77 +55,56 @@ const analyticsBlocked = []; let hooksAdded = false; let strictStorageEnforcement = false; -// Helps in stubbing these functions in unit tests. -export const internal = { - getGvlidForBidAdapter, - getGvlidForUserIdModule, - getGvlidForAnalyticsAdapter -}; +const GVLID_LOOKUP_PRIORITY = [ + MODULE_TYPE_BIDDER, + MODULE_TYPE_UID, + MODULE_TYPE_ANALYTICS, + MODULE_TYPE_RTD +]; /** - * Returns GVL ID for a Bid adapter / an USERID submodule / an Analytics adapter. - * If modules of different types have the same moduleCode: For example, 'appnexus' is the code for both Bid adapter and Analytics adapter, - * then, we assume that their GVL IDs are same. This function first checks if GVL ID is defined for a Bid adapter, if not found, tries to find User ID - * submodule's GVL ID, if not found, tries to find Analytics adapter's GVL ID. In this process, as soon as it finds a GVL ID, it returns it - * without going to the next check. - * @param {{string|Object}} - module - * @return {number} - GVL ID + * Retrieve a module's GVL ID. */ -export function getGvlid(module, ...args) { - let gvlid = null; - if (module) { +export function getGvlid(moduleType, moduleName, fallbackFn) { + if (moduleName) { // Check user defined GVL Mapping in pbjs.setConfig() const gvlMapping = config.getConfig('gvlMapping'); - // For USER ID Module, we pass the submodule object itself as the "module" parameter, this check is required to grab the module code - const moduleCode = typeof module === 'string' ? module : module.name; - // Return GVL ID from user defined gvlMapping - if (gvlMapping && gvlMapping[moduleCode]) { - gvlid = gvlMapping[moduleCode]; - return gvlid; - } - - gvlid = internal.getGvlidForBidAdapter(moduleCode) || internal.getGvlidForUserIdModule(module) || internal.getGvlidForAnalyticsAdapter(moduleCode, ...args); - } - return gvlid; -} - -/** - * Returns GVL ID for a bid adapter. If the adapter does not have an associated GVL ID, it returns 'null'. - * @param {string=} bidderCode - The 'code' property of the Bidder spec. - * @return {number} GVL ID - */ -function getGvlidForBidAdapter(bidderCode) { - let gvlid = null; - bidderCode = bidderCode || config.getCurrentBidder(); - if (bidderCode) { - const bidder = adapterManager.getBidAdapter(bidderCode); - if (bidder && bidder.getSpec) { - gvlid = bidder.getSpec().gvlid; + if (gvlMapping && gvlMapping[moduleName]) { + return gvlMapping[moduleName]; + } else if (moduleType === MODULE_TYPE_CORE) { + return VENDORLESS_GVLID; + } else { + let {gvlid, modules} = GDPR_GVLIDS.get(moduleName); + if (gvlid == null && Object.keys(modules).length > 0) { + // this behavior is for backwards compatibility; if multiple modules with the same + // name declare different GVL IDs, pick the bidder's first, then userId, then analytics + for (const type of GVLID_LOOKUP_PRIORITY) { + if (modules.hasOwnProperty(type)) { + gvlid = modules[type]; + if (type !== moduleType && !fallbackFn) { + logWarn(`Multiple GVL IDs found for module '${moduleName}'; using the ${type} module's ID (${gvlid}) instead of the ${moduleType}'s ID (${modules[moduleType]})`) + } + break; + } + } + } + if (gvlid == null && fallbackFn) { + gvlid = fallbackFn(); + } + return gvlid || null; } } - return gvlid; -} - -/** - * Returns GVL ID for an userId submodule. If an userId submodules does not have an associated GVL ID, it returns 'null'. - * @param {Object} userIdModule - * @return {number} GVL ID - */ -function getGvlidForUserIdModule(userIdModule) { - return (typeof userIdModule === 'object' ? userIdModule.gvlid : null); + return null; } /** - * Returns GVL ID for an analytics adapter. If an analytics adapter does not have an associated GVL ID, it returns 'null'. - * @param {string} code - 'provider' property on the analytics adapter config - * @param {{}} config - analytics configuration object - * @return {number} GVL ID + * Retrieve GVL IDs that are dynamically set on analytics adapters. */ -function getGvlidForAnalyticsAdapter(code, config) { +export function getGvlidFromAnalyticsAdapter(code, config) { const adapter = adapterManager.getAnalyticsAdapter(code); - return adapter?.gvlid || ((gvlid) => { + return ((gvlid) => { if (typeof gvlid !== 'function') return gvlid; try { return gvlid.call(adapter.adapter, config); @@ -185,30 +170,33 @@ export function validateRules(rule, consentData, currentModule, gvlId) { /** * This hook checks whether module has permission to access device or not. Device access include cookie and local storage + * * @param {Function} fn reference to original function (used by hook logic) - * @param {Number=} gvlid gvlid of the module + * @param {string} moduleType type of the module * @param {string=} moduleName name of the module * @param result + * @param validate */ -export function deviceAccessHook(fn, gvlid, moduleName, result, {validate = validateRules} = {}) { +export function deviceAccessHook(fn, moduleType, moduleName, result, {validate = validateRules} = {}) { result = Object.assign({}, { hasEnforcementHook: true }); if (!hasDeviceAccess()) { logWarn('Device access is disabled by Publisher'); result.valid = false; - } else if (gvlid === VENDORLESS_GVLID && !strictStorageEnforcement) { + } else if (moduleType === MODULE_TYPE_CORE && !strictStorageEnforcement) { // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set result.valid = true; } else { const consentData = gdprDataHandler.getConsentData(); + let gvlid; if (shouldEnforce(consentData, 1, moduleName)) { const curBidder = config.getCurrentBidder(); // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder if (curBidder && (curBidder !== moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { - gvlid = getGvlid(curBidder); + gvlid = getGvlid(moduleType, curBidder); } else { - gvlid = getGvlid(moduleName) || gvlid; + gvlid = getGvlid(moduleType, moduleName) } const curModule = moduleName || curBidder; let isAllowed = validate(purpose1Rule, consentData, curModule, gvlid,); @@ -223,7 +211,7 @@ export function deviceAccessHook(fn, gvlid, moduleName, result, {validate = vali result.valid = true; } } - fn.call(this, gvlid, moduleName, result); + fn.call(this, moduleType, moduleName, result); } /** @@ -235,7 +223,7 @@ export function userSyncHook(fn, ...args) { const consentData = gdprDataHandler.getConsentData(); const curBidder = config.getCurrentBidder(); if (shouldEnforce(consentData, 1, curBidder)) { - const gvlid = getGvlid(curBidder); + const gvlid = getGvlid(MODULE_TYPE_BIDDER, curBidder); let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); if (isAllowed) { fn.call(this, ...args); @@ -257,8 +245,8 @@ export function userSyncHook(fn, ...args) { export function userIdHook(fn, submodules, consentData) { if (shouldEnforce(consentData, 1, 'User ID')) { let userIdModules = submodules.map((submodule) => { - const gvlid = getGvlid(submodule.submodule); const moduleName = submodule.submodule.name; + const gvlid = getGvlid(MODULE_TYPE_UID, moduleName); let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); if (isAllowed) { return submodule; @@ -286,7 +274,7 @@ export function makeBidRequestsHook(fn, adUnits, ...args) { adUnits.forEach(adUnit => { adUnit.bids = adUnit.bids.filter(bid => { const currBidder = bid.bidder; - const gvlId = getGvlid(currBidder); + const gvlId = getGvlid(MODULE_TYPE_BIDDER, currBidder); if (includes(biddersBlocked, currBidder)) return false; const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId); if (!isAllowed) { @@ -316,7 +304,7 @@ export function enableAnalyticsHook(fn, config) { } config = config.filter(conf => { const analyticsAdapterCode = conf.provider; - const gvlid = getGvlid(analyticsAdapterCode, conf); + const gvlid = getGvlid(MODULE_TYPE_ANALYTICS, analyticsAdapterCode, () => getGvlidFromAnalyticsAdapter(analyticsAdapterCode, conf)); const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); if (!isAllowed) { analyticsBlocked.push(analyticsAdapterCode); diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js index bbb4dbb30cd..53dc0bd3e1a 100644 --- a/modules/glimpseBidAdapter.js +++ b/modules/glimpseBidAdapter.js @@ -1,5 +1,4 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { @@ -12,10 +11,7 @@ import { const GVLID = 1012; const BIDDER_CODE = 'glimpse'; -const storageManager = getStorageManager({ - gvlid: GVLID, - bidderCode: BIDDER_CODE, -}); +const storageManager = getStorageManager({bidderCode: BIDDER_CODE}); const ENDPOINT = 'https://market.glimpsevault.io/public/v1/prebid'; const LOCAL_STORAGE_KEY = { vault: { @@ -102,7 +98,7 @@ function getReferer(bidderRequest) { function buildQuery(bidderRequest) { let url = appendQueryParam(ENDPOINT, 'ver', '$prebid.version$'); - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; url = appendQueryParam(url, 'tmax', timeout); if (gdprApplies(bidderRequest)) { diff --git a/modules/globalsunBidAdapter.js b/modules/globalsunBidAdapter.js index ae3407bbbdd..5b5d97c2cac 100644 --- a/modules/globalsunBidAdapter.js +++ b/modules/globalsunBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/gnetBidAdapter.js b/modules/gnetBidAdapter.js index 8bab043d0db..0b02a29c0d4 100644 --- a/modules/gnetBidAdapter.js +++ b/modules/gnetBidAdapter.js @@ -4,10 +4,9 @@ import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; -const storage = getStorageManager(); - const BIDDER_CODE = 'gnet'; const ENDPOINT = 'https://service.gnetrtb.com/api'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/gravitoIdSystem.js b/modules/gravitoIdSystem.js index 809263a1c68..aa25ea7db2c 100644 --- a/modules/gravitoIdSystem.js +++ b/modules/gravitoIdSystem.js @@ -6,9 +6,11 @@ */ import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const MODULE_NAME = 'gravitompId'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export const cookieKey = 'gravitompId'; @@ -17,7 +19,7 @@ export const gravitoIdSystemSubmodule = { * used to link submodule with config * @type {string} */ - name: 'gravitompId', + name: MODULE_NAME, /** * performs action to obtain id diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 7da2c778c43..a043483d9b0 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -6,7 +6,10 @@ import { generateUUID, mergeDeep, logWarn, - parseUrl, isArray, isNumber + parseUrl, + isArray, + isNumber, + isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; @@ -31,7 +34,7 @@ const TIME_TO_LIVE = 360; const USER_ID_KEY = 'tmguid'; const GVLID = 686; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', @@ -52,7 +55,12 @@ const ALIAS_CONFIG = { bidResponseExternal: { netRevenue: false } - } + }, + 'gridNM': { + defaultParams: { + multiRequest: true + } + }, }; let hasSynced = false; @@ -60,7 +68,7 @@ let hasSynced = false; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['playwire', 'adlivetech', { code: 'trustx', skipPbsAliasing: true }], + aliases: ['playwire', 'adlivetech', 'gridNM', { code: 'trustx', skipPbsAliasing: true }], supportedMediaTypes: [ BANNER, VIDEO ], /** * Determines whether or not the given bid request is valid. @@ -93,7 +101,7 @@ export const spec = { let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo, gppConsent} = bidderRequest || {}; const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; - const tmax = timeout || config.getConfig('bidderTimeout'); + const tmax = timeout; const imp = []; const bidsMap = {}; const requests = []; @@ -126,8 +134,9 @@ export const spec = { if (!endpoint) { endpoint = ALIAS_CONFIG[bid.bidder] && ALIAS_CONFIG[bid.bidder].endpoint; } - const { params: { uid, keywords, forceBidder, multiRequest }, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; - const { secid, pubid, source, content: bidParamsContent } = bid.params; + const { params, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; + const { defaultParams } = ALIAS_CONFIG[bid.bidder] || {}; + const { secid, pubid, source, uid, keywords, forceBidder, multiRequest, content: bidParamsContent, video: videoParams } = { ...defaultParams, ...params }; const bidFloor = _getFloor(mediaTypes || {}, bid); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; if (jwTargeting && !content && jwTargeting.content) { @@ -178,7 +187,7 @@ export const spec = { } } if (mediaTypes && mediaTypes[VIDEO]) { - const video = createVideoRequest(bid, mediaTypes[VIDEO]); + const video = createVideoRequest(videoParams, mediaTypes[VIDEO], bid.sizes); if (video) { impObj.video = video; } @@ -588,27 +597,41 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bid } } -function createVideoRequest(bid, mediaType) { - const { playerSize, mimes, durationRangeSec, protocols } = mediaType; - const size = (playerSize || bid.sizes || [])[0]; - if (!size) return; +function createVideoRequest(videoParams, mediaType, bidSizes) { + const { mind, maxd, size, playerSize, protocols, durationRangeSec = [], ...videoData } = { ...mediaType, ...videoParams }; + if (size && isStr(size)) { + const sizeArray = size.split('x'); + if (sizeArray.length === 2 && parseInt(sizeArray[0]) && parseInt(sizeArray[1])) { + videoData.w = parseInt(sizeArray[0]); + videoData.h = parseInt(sizeArray[1]); + } + } + if (!videoData.w || !videoData.h) { + const pSizesString = (playerSize || bidSizes || []).toString(); + const pSizeString = (pSizesString.match(/^\d+,\d+/) || [])[0]; + const pSize = pSizeString && pSizeString.split(',').map((d) => parseInt(d)); + if (pSize && pSize.length === 2) { + Object.assign(videoData, parseGPTSingleSizeArrayToRtbSize(pSize)); + } + } - let result = parseGPTSingleSizeArrayToRtbSize(size); + if (!videoData.w || !videoData.h) return; - if (mimes) { - result.mimes = mimes; - } + const minDur = mind || durationRangeSec[0] || videoData.minduration; + const maxDur = maxd || durationRangeSec[1] || videoData.maxduration; - if (durationRangeSec && durationRangeSec.length === 2) { - result.minduration = durationRangeSec[0]; - result.maxduration = durationRangeSec[1]; + if (minDur) { + videoData.minduration = minDur; + } + if (maxDur) { + videoData.maxduration = maxDur; } if (protocols && protocols.length) { - result.protocols = protocols; + videoData.protocols = protocols; } - return result; + return videoData; } function createBannerRequest(bid, mediaType) { diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js deleted file mode 100644 index 41689aeeb55..00000000000 --- a/modules/gridNMBidAdapter.js +++ /dev/null @@ -1,422 +0,0 @@ -import { - isStr, - deepAccess, - isArray, - isNumber, - logError, - logWarn, - parseGPTSingleSizeArrayToRtbSize, - mergeDeep -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'gridNM'; -const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; -const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; -const TIME_TO_LIVE = 360; -const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -let hasSynced = false; - -const LOG_ERROR_MESS = { - noAdm: 'Bid from response has no adm parameter - ', - noPrice: 'Bid from response has no price parameter - ', - wrongContentType: 'Bid from response has wrong content_type parameter - ', - noBid: 'Array of bid objects is empty', - noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', - emptyUids: 'Uids should be not empty', - emptySeatbid: 'Seatbid array from response has empty item', - emptyResponse: 'Response is empty', - hasEmptySeatbidArray: 'Response has empty seatbid array', - hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' -}; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [ VIDEO ], - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - let invalid = - !bid.params.source || !isStr(bid.params.source) || - !bid.params.secid || !isStr(bid.params.secid) || - !bid.params.pubid || !isStr(bid.params.pubid); - - const video = deepAccess(bid, 'mediaTypes.video') || {}; - const { protocols = video.protocols, mimes = video.mimes } = deepAccess(bid, 'params.video') || {}; - if (!invalid) { - invalid = !protocols || !mimes; - } - if (!invalid) { - invalid = !isArray(mimes) || !mimes.length || mimes.filter((it) => !(it && isStr(it))).length; - if (!invalid) { - invalid = !isArray(protocols) || !protocols.length || protocols.filter((it) => !(isNumber(it) && it > 0 && !(it % 1))).length; - } - } - return !invalid; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests - an array of bids - * @param {bidderRequest} bidderRequest bidder request object - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - const bids = validBidRequests || []; - const requests = []; - let { bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo } = bidderRequest || {}; - - const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; - - bids.forEach(bid => { - let user; - let userExt; - - const schain = bid.schain; - const userIdAsEids = bid.userIdAsEids; - - if (!bidderRequestId) { - bidderRequestId = bid.bidderRequestId; - } - if (!auctionId) { - auctionId = bid.auctionId; - } - const { - params: { floorcpm, source, secid, pubid, content, video }, - mediaTypes, bidId, adUnitCode, rtd, ortb2Imp, sizes - } = bid; - - const bidFloor = _getFloor(mediaTypes || {}, bid, isNumber(floorcpm) && floorcpm); - const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; - - const siteContent = content || (jwTargeting && jwTargeting.content); - - const impObj = { - id: bidId.toString(), - tagid: secid.toString(), - video: createVideoForImp(mergeDeep({}, video, mediaTypes && mediaTypes.video), sizes), - ext: { - divid: adUnitCode.toString() - } - }; - - if (ortb2Imp && ortb2Imp.ext && ortb2Imp.ext.data) { - impObj.ext.data = ortb2Imp.ext.data; - if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { - impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); - } else { - impObj.ext.gpid = ortb2Imp.ext.data.pbadslot && ortb2Imp.ext.data.pbadslot.toString(); - } - } - - if (bidFloor) { - impObj.bidfloor = bidFloor; - } - - const imp = [impObj]; - - const reqSource = { - tid: auctionId && auctionId.toString(), - ext: { - wrapper: 'Prebid_js', - wrapper_version: '$prebid.version$' - } - }; - - if (schain) { - reqSource.ext.schain = schain; - } - - const bidderTimeout = config.getConfig('bidderTimeout') || timeout; - const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; - - const request = { - id: bidderRequestId && bidderRequestId.toString(), - site: { - page: referer, - publisher: { - id: pubid, - }, - }, - source: reqSource, - tmax, - imp, - }; - - if (siteContent) { - request.site.content = siteContent; - } - - if (gdprConsent && gdprConsent.consentString) { - userExt = { consent: gdprConsent.consentString }; - } - - if (userIdAsEids && userIdAsEids.length) { - userExt = userExt || {}; - userExt.eids = [...userIdAsEids]; - } - - if (userExt && Object.keys(userExt).length) { - user = user || {}; - user.ext = userExt; - } - - const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); - if (ortb2UserData && ortb2UserData.length) { - if (!user) { - user = { data: [] }; - } - user = mergeDeep(user, { - data: [...ortb2UserData] - }); - } - - if (user) { - request.user = user; - } - const site = deepAccess(bidderRequest, 'ortb2.site'); - if (site) { - const data = deepAccess(site, 'content.data'); - if (data && data.length) { - const siteContent = request.site.content || {}; - request.site.content = mergeDeep(siteContent, { data }); - } - } - - if (gdprConsent && gdprConsent.gdprApplies) { - request.regs = { - ext: { - gdpr: gdprConsent.gdprApplies ? 1 : 0 - } - }; - } - - if (uspConsent) { - if (!request.regs) { - request.regs = { ext: {} }; - } - request.regs.ext.us_privacy = uspConsent; - } - - if (config.getConfig('coppa') === true) { - if (!request.regs) { - request.regs = {}; - } - request.regs.coppa = 1; - } - - requests.push({ - method: 'POST', - url: ENDPOINT_URL + '?no_mapping=1&sp=' + source, - bid: bid, - data: request - }); - }); - - return requests; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @param {*} bidRequest - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidRequest) { - serverResponse = serverResponse && serverResponse.body; - const bidResponses = []; - - let errorMessage; - - if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (serverResponse.seatbid && !serverResponse.seatbid.length) { - errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - } - - if (!errorMessage && serverResponse.seatbid) { - const serverBid = _getBidFromResponse(serverResponse.seatbid[0]); - if (serverBid) { - if (!serverBid.adm && !serverBid.nurl) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); - else if (!serverBid.price) errorMessage = LOG_ERROR_MESS.noPrice + JSON.stringify(serverBid); - else if (serverBid.content_type !== 'video') errorMessage = LOG_ERROR_MESS.wrongContentType + serverBid.content_type; - if (!errorMessage) { - const bid = bidRequest.bid; - const bidResponse = { - requestId: bid.bidId, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid || bid.bidderRequestId, - currency: 'USD', - netRevenue: true, - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid, - mediaType: VIDEO, - meta: { - advertiserDomains: serverBid.adomain ? serverBid.adomain : [] - } - }; - - if (serverBid.adm) { - bidResponse.vastXml = serverBid.adm; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - } else if (serverBid.nurl) { - bidResponse.vastUrl = serverBid.nurl; - } - - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }); - } - bidResponses.push(bidResponse); - } - } - } - if (errorMessage) logError(errorMessage); - return bidResponses; - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - if (!hasSynced && syncOptions.pixelEnabled) { - let params = ''; - - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent) { - params += `&us_privacy=${uspConsent}`; - } - - hasSynced = true; - return { - type: 'image', - url: SYNC_URL + params - }; - } - } -}; - -/** - * Gets bidfloor - * @param {Object} mediaTypes - * @param {Object} bid - * @param {Number} floor - * @returns {Number} floor - */ -function _getFloor (mediaTypes, bid, floor) { - const curMediaType = mediaTypes.video ? 'video' : 'banner'; - - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: curMediaType, - size: bid.sizes.map(([w, h]) => ({w, h})) - }); - - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); - } - } - - return floor; -} - -function _getBidFromResponse(respItem) { - if (!respItem) { - logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - -function outstreamRender (bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - targetId: bid.adUnitCode, - adResponse: bid.adResponse - }); - }); -} - -function createRenderer (bid, rendererParams) { - const renderer = Renderer.install({ - id: rendererParams.id, - url: rendererParams.url, - loaded: false - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } - - return renderer; -} - -function createVideoForImp({ mind, maxd, size, ...paramsVideo }, bidSizes) { - if (size && isStr(size)) { - const sizeArray = size.split('x'); - if (sizeArray.length === 2 && parseInt(sizeArray[0]) && parseInt(sizeArray[1])) { - paramsVideo.w = parseInt(sizeArray[0]); - paramsVideo.h = parseInt(sizeArray[1]); - } - } - - if (!paramsVideo.w || !paramsVideo.h) { - const playerSizes = paramsVideo.playerSize && paramsVideo.playerSize.length === 2 ? paramsVideo.playerSize : bidSizes; - if (playerSizes) { - const playerSize = playerSizes[0]; - if (playerSize) { - Object.assign(paramsVideo, parseGPTSingleSizeArrayToRtbSize(playerSize)); - } - } - } - - if (paramsVideo.playerSize) { - delete paramsVideo.playerSize; - } - - const durationRangeSec = paramsVideo.durationRangeSec || []; - const minDur = mind || durationRangeSec[0] || paramsVideo.minduration; - const maxDur = maxd || durationRangeSec[1] || paramsVideo.maxduration; - - if (minDur) { - paramsVideo.minduration = minDur; - } - if (maxDur) { - paramsVideo.maxduration = maxDur; - } - - return paramsVideo; -} - -export function resetUserSync() { - hasSynced = false; -} - -export function getSyncUrl() { - return SYNC_URL; -} - -registerBidder(spec); diff --git a/modules/gridNMBidAdapter.md b/modules/gridNMBidAdapter.md deleted file mode 100644 index 6decdde7f4c..00000000000 --- a/modules/gridNMBidAdapter.md +++ /dev/null @@ -1,39 +0,0 @@ -# Overview - -Module Name: The Grid Media Bidder Adapter -Module Type: Bidder Adapter -Maintainer: grid-tech@themediagrid.com - -# Description - -Module that connects to Grid demand source to fetch bids. -Grid bid adapter supports Banner and Video (instream and outstream). - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - video: { - playerSize: [728, 90], - context: 'outstream' - } - }, - bids: [ - { - bidder: "gridNM", - params: { - source: 'jwp', - secid: '11', - pubid: '22', - video: { - mimes: ['video/mp4', 'video/x-ms-wmv'], - protocols: [1,2,3,4,5,6] - } - } - } - ] - } - ]; -``` diff --git a/modules/growthCodeAnalyticsAdapter.js b/modules/growthCodeAnalyticsAdapter.js index 1f11b891139..a2ab4ddbfac 100644 --- a/modules/growthCodeAnalyticsAdapter.js +++ b/modules/growthCodeAnalyticsAdapter.js @@ -6,15 +6,16 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {logError, logInfo} from '../src/utils.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const MODULE_NAME = 'growthCodeAnalytics'; const DEFAULT_PID = 'INVALID_PID' const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb/analytics' -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}); let sessionId = utils.generateUUID(); diff --git a/modules/growthCodeIdSystem.js b/modules/growthCodeIdSystem.js index edd7cd33012..fae022f1a56 100644 --- a/modules/growthCodeIdSystem.js +++ b/modules/growthCodeIdSystem.js @@ -8,14 +8,15 @@ import {logError, logInfo, tryAppendQueryString} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -const GCID_EXPIRY = 7; const MODULE_NAME = 'growthCodeId'; const GC_DATA_KEY = '_gc_data'; +const GCID_KEY = 'gcid'; const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb?' -export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); /** * Read GrowthCode data from cookie or local storage @@ -25,14 +26,16 @@ export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_ export function readData(key) { try { let payload - if (storage.cookiesAreEnabled()) { - payload = tryParse(storage.getCookie(key)) + if (storage.cookiesAreEnabled(null)) { + payload = tryParse(storage.getCookie(key, null)) } if (storage.hasLocalStorage()) { - payload = tryParse(storage.getDataFromLocalStorage(key)) + payload = tryParse(storage.getDataFromLocalStorage(key, null)) } - if ((payload.expire_at !== undefined) && (payload.expire_at > (Date.now() / 1000))) { - return payload + if (payload !== undefined) { + if (payload.expire_at > (Date.now() / 1000)) { + return payload + } } } catch (error) { logError(error); @@ -50,12 +53,8 @@ function storeData(key, value) { logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); if (value) { - if (storage.hasLocalStorage()) { - storage.setDataInLocalStorage(key, value); - } - const expiresStr = (new Date(Date.now() + (GCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); - if (storage.cookiesAreEnabled()) { - storage.setCookie(key, value, expiresStr, 'LAX'); + if (storage.hasLocalStorage(null)) { + storage.setDataInLocalStorage(key, value, null); } } } catch (error) { @@ -69,11 +68,15 @@ function storeData(key, value) { * @param {object|null} */ function tryParse(data) { + let payload; try { - return JSON.parse(data); + payload = JSON.parse(data); + if (payload == null) { + return undefined + } + return payload } catch (err) { - logError(err); - return null; + return undefined; } } @@ -149,6 +152,7 @@ export const growthCodeIdSubmodule = { // If response is a valid json and should save is true if (respJson) { storeData(GC_DATA_KEY, JSON.stringify(respJson)) + storeData(GCID_KEY, respJson.gc_id); callback(respJson); } else { callback(); diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index e9bdc955c1e..8a6c4efa7fc 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -6,13 +6,13 @@ import {getStorageManager} from '../src/storageManager.js'; import {includes} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'gumgum' +const BIDDER_CODE = 'gumgum'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); -const ALIAS_BIDDER_CODE = ['gg'] -const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp` +const ALIAS_BIDDER_CODE = ['gg']; +const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp`; const JCSI = { t: 0, rq: 8, pbv: '$prebid.version$' } -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO] -const TIME_TO_LIVE = 60 +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +const TIME_TO_LIVE = 60; const DELAY_REQUEST_TIME = 1800000; // setting to 30 mins let invalidRequestIds = {}; @@ -99,17 +99,6 @@ function getWrapperCode(wrapper, data) { return wrapper.replace('AD_JSON', window.btoa(JSON.stringify(data))) } -function _getDigiTrustQueryParams(userId) { - let digiTrustId = userId.digitrustid && userId.digitrustid.data; - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return {}; - } - return { - dt: digiTrustId.id - }; -} - /** * Serializes the supply chain object according to IAB standards * @see https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md @@ -295,7 +284,7 @@ function buildRequests(validBidRequests, bidderRequest) { const gdprConsent = bidderRequest && bidderRequest.gdprConsent; const uspConsent = bidderRequest && bidderRequest.uspConsent; const gppConsent = bidderRequest && bidderRequest.gppConsent; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest && bidderRequest.timeout const coppa = config.getConfig('coppa') === true ? 1 : 0; const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page; _each(validBidRequests, bidRequest => { @@ -416,7 +405,7 @@ function buildRequests(validBidRequests, bidderRequest) { sizes, url: BID_ENDPOINT, method: 'GET', - data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId)) + data: Object.assign(data, _getBrowserParams(topWindowUrl)) }); }); return bids; @@ -596,6 +585,7 @@ function getUserSyncs(syncOptions, serverResponses) { export const spec = { code: BIDDER_CODE, + gvlid: 61, aliases: ALIAS_BIDDER_CODE, isBidRequestValid, buildRequests, diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index 52829cf754d..e4c09c5b6c9 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -3,8 +3,9 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; /** * hadronAnalyticsAdapter.js - Audigent Hadron Analytics Adapter @@ -14,8 +15,9 @@ const HADRON_ANALYTICS_URL = 'https://analytics.hadron.ad.gt/api/v1/analytics'; const HADRONID_ANALYTICS_VER = 'pbadgt0'; const DEFAULT_PARTNER_ID = 0; const AU_GVLID = 561; +const MODULE_CODE = 'hadronAnalytics'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); var viewId = utils.generateUUID(); @@ -191,7 +193,7 @@ function sendEvent(event) { adapterManager.registerAnalyticsAdapter({ adapter: hadronAnalyticsAdapter, - code: 'hadronAnalytics', + code: MODULE_CODE, gvlid: AU_GVLID }); diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index a75c03ee1c4..596bf9611e6 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -9,13 +9,14 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isFn, isStr, isPlainObject, logError, logInfo} from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const HADRONID_LOCAL_NAME = 'auHadronId'; const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'hadron'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'hadron'}); /** * Param or default. @@ -48,6 +49,7 @@ export const hadronIdSubmodule = { * @type {string} */ name: MODULE_NAME, + gvlid: AU_GVLID, /** * decode the stored id value for passing to bid requests * @function diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 7a0299fc427..6fb982815c1 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -12,6 +12,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isFn, isStr, isArray, deepEqual, isPlainObject, logError, logInfo} from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const LOG_PREFIX = 'User ID - HadronRtdProvider submodule: '; const MODULE_NAME = 'realTimeData'; @@ -21,7 +22,7 @@ const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebi const HADRON_SEGMENT_URL = 'https://id.hadron.ad.gt/api/v1/rtd'; export const HALOID_LOCAL_NAME = 'auHadronId'; export const RTD_LOCAL_NAME = 'auHadronRtd'; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** * @param {string} url @@ -251,7 +252,8 @@ function init(provider, userConsent) { export const hadronSubmodule = { name: SUBMODULE_NAME, getBidRequestData: getRealTimeData, - init: init + init: init, + gvlid: AU_GVLID, }; submodule(MODULE_NAME, hadronSubmodule); diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index c26ba3662e1..b7ff836a7e6 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -20,6 +20,7 @@ import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; import {uspDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'id5Id'; const GVLID = 131; @@ -34,7 +35,7 @@ const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid'; // cookie in the array is the most preferred to use const LEGACY_COOKIE_NAMES = ['pbjs-id5id', 'id5id.1st', 'id5id']; -export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const id5IdSubmodule = { diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js index 9678739672d..29dda216fdc 100644 --- a/modules/idWardRtdProvider.js +++ b/modules/idWardRtdProvider.js @@ -8,11 +8,12 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'idWard'; -export const storage = getStorageManager({moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** * Add real-time data & merge segments. * @param ortb2 object to merge into diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index df7b03b4e6e..ab10288f38f 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -9,8 +9,11 @@ import * as utils from '../src/utils.js' import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const MODULE_NAME = 'identityLink'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const identityLinkSubmodule = { @@ -18,7 +21,7 @@ export const identityLinkSubmodule = { * used to link submodule with config * @type {string} */ - name: 'identityLink', + name: MODULE_NAME, /** * used to specify vendor id * @type {number} diff --git a/modules/idxIdSystem.js b/modules/idxIdSystem.js index 908edad4c04..3c00bbde34c 100644 --- a/modules/idxIdSystem.js +++ b/modules/idxIdSystem.js @@ -6,11 +6,12 @@ */ import { isStr, isPlainObject, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const IDX_MODULE_NAME = 'idx'; const IDX_COOKIE_NAME = '_idx'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: IDX_MODULE_NAME}); function readIDxFromCookie() { return storage.cookiesAreEnabled ? storage.getCookie(IDX_COOKIE_NAME) : null; diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index bc01896d062..26d49c49f8c 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -18,16 +18,18 @@ import { isFn } from '../src/utils.js' import {submodule} from '../src/hook.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; export const imUidLocalName = '__im_uid'; export const imVidCookieName = '_im_vid'; export const imRtdLocalName = '__im_sids'; -export const storage = getStorageManager(); const submoduleName = 'im'; const segmentsMaxAge = 3600000; // 1 hour (30 * 60 * 1000) const uidMaxAge = 1800000; // 30 minites (30 * 60 * 1000) const vidMaxAge = 97200000000; // 37 months ((365 * 3 + 30) * 24 * 60 * 60 * 1000) +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: submoduleName}); + function setImDataInCookie(value) { storage.setCookie( imVidCookieName, diff --git a/modules/imuIdSystem.js b/modules/imuIdSystem.js index 41ff95b6702..898f32b27b0 100644 --- a/modules/imuIdSystem.js +++ b/modules/imuIdSystem.js @@ -8,9 +8,12 @@ import { timestamp, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js' import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const MODULE_NAME = 'imuid'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export const storageKey = '__im_uid'; export const storagePpKey = '__im_ppid'; @@ -112,7 +115,7 @@ export const imuIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'imuid', + name: MODULE_NAME, /** * decode the stored id value for passing to bid requests * @function diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 150e9d3c5c2..46ff17d2a5a 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -12,7 +12,7 @@ const USER_ID_COOKIE_EXP = 2592000000; // 30 days const BID_TTL = 300; // 5 minutes const GVLID = 910; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); config.setDefaults({ insticator: { diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 563435dee65..b16624ac368 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -8,7 +8,8 @@ import { logError, logInfo } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const PCID_EXPIRY = 365; @@ -16,7 +17,7 @@ const MODULE_NAME = 'intentIqId'; export const FIRST_PARTY_KEY = '_iiq_fdata'; export var FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); const INVALID_ID = 'INVALID_ID'; diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index c3e5cf6cca8..b2444043c22 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -17,7 +17,7 @@ const CONSTANTS = { DISABLE_USER_SYNC: true }; -const storage = getStorageManager({gvlid: CONSTANTS.INVIBES_VENDOR_ID, bidderCode: CONSTANTS.BIDDER_CODE}); +const storage = getStorageManager({bidderCode: CONSTANTS.BIDDER_CODE}); export const spec = { code: CONSTANTS.BIDDER_CODE, diff --git a/modules/iqmBidAdapter.js b/modules/iqmBidAdapter.js index 1c36bafd3ce..51cd8b3bdca 100644 --- a/modules/iqmBidAdapter.js +++ b/modules/iqmBidAdapter.js @@ -1,6 +1,5 @@ import {_each, deepAccess, getBidIdParameter, isArray} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {INSTREAM} from '../src/video.js'; @@ -155,7 +154,7 @@ export const spec = { auctionId: bidRequest.data.auctionId, mediaType: bidRequest.data.imp.mediatype, - ttl: bid.ttl || config.getConfig('_bidderTimeout') + ttl: bid.ttl || 60 }; if (bidRequest.data.imp.mediatype === VIDEO) { diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index f2de447d6a7..52f3be7e4b4 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -154,7 +154,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/ivsBidAdapter.js b/modules/ivsBidAdapter.js new file mode 100644 index 00000000000..47685fbbe46 --- /dev/null +++ b/modules/ivsBidAdapter.js @@ -0,0 +1,85 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { deepAccess, deepSetValue, getBidIdParameter, logError } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { INSTREAM } from '../src/video.js'; + +const BIDDER_CODE = 'ivs'; +const ENDPOINT_URL = 'https://a.ivstracker.net/prod/openrtb/2.5'; + +export const converter = ortbConverter({ + context: { + mediaType: VIDEO, + ttl: 360, + netRevenue: true + } +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if (bid && typeof bid.params !== 'object') { + logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); + return false; + } + + if (!deepAccess(bid, 'mediaTypes.video')) { + logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); + return false; + } + + if (deepAccess(bid, 'mediaTypes.video.context') !== INSTREAM) { + logError(BIDDER_CODE + ': only instream video context is allowed.'); + return false; + } + + if (!getBidIdParameter('publisherId', bid.params)) { + logError(BIDDER_CODE + ': publisherId is not present in bidder params.'); + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const data = converter.toORTB({ bidderRequest, validBidRequests, context: {mediaType: 'video'} }); + deepSetValue(data.site, 'publisher.id', validBidRequests[0].params.publisherId); + + return { + method: 'POST', + url: ENDPOINT_URL, + data: data, + options: { + contentType: 'application/json' + }, + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + if (!serverResponse.body) return; + return converter.fromORTB({ response: serverResponse.body, request: bidRequest.data }).bids; + }, +} + +registerBidder(spec); diff --git a/modules/ivsBidAdapter.md b/modules/ivsBidAdapter.md new file mode 100644 index 00000000000..d50061b640d --- /dev/null +++ b/modules/ivsBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: IVS Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@ivs.tv +``` + +# Description + +Module that connects to IVS's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'ivs', + params: { + publisherId: '3001234' // required + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index a00fd90506a..06f96edca30 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -13,7 +13,6 @@ import { logError, logWarn, mergeDeep, - parseQueryStringParameters, safeJSONParse } from '../src/utils.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; @@ -102,11 +101,14 @@ const VIDEO_PARAMS_ALLOW_LIST = [ const LOCAL_STORAGE_KEY = 'ixdiag'; export const LOCAL_STORAGE_FEATURE_TOGGLES_KEY = `${BIDDER_CODE}_features`; let hasRegisteredHandler = false; -export const storage = getStorageManager({ gvlid: GLOBAL_VENDOR_ID, bidderCode: BIDDER_CODE }); +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const FEATURE_TOGGLES = { + // Update with list of CFTs to be requested from Exchange + REQUESTED_FEATURE_TOGGLES: [], + featureToggles: {}, isFeatureEnabled: function (ft) { - return deepAccess(this.featureToggles, `features.${ft}.activated`) + return deepAccess(this.featureToggles, `features.${ft}.activated`, false) }, getFeatureToggles: function () { if (storage.localStorageIsEnabled()) { @@ -152,11 +154,6 @@ const MEDIA_TYPES = { Native: 4 }; -let baseRequestSize = 0; -let currentRequestSize = 0; -let wasAdUnitImpressionsTrimmed = false; -let currentImpressionSize = 0; - /** * Transform valid bid request config object to banner impression object that will be sent to ad server. * @@ -198,6 +195,9 @@ function bidToVideoImp(bid) { // populate imp level transactionId imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + // AdUnit-Specific First Party Data + addAdUnitFPD(imp, bid) + // copy all video properties to imp object for (const adUnitProperty in videoAdUnitRef) { if (VIDEO_PARAMS_ALLOW_LIST.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { @@ -271,6 +271,9 @@ function bidToNativeImp(bid) { // populate imp level transactionId imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + // AdUnit-Specific First Party Data + addAdUnitFPD(imp, bid) + _applyFloor(bid, imp, NATIVE); return imp; @@ -598,19 +601,12 @@ function getEidInfo(allEids) { * */ function buildRequest(validBidRequests, bidderRequest, impressions, version) { - baseRequestSize = 0; - currentRequestSize = 0; - wasAdUnitImpressionsTrimmed = false; - currentImpressionSize = 0; - // Always use secure HTTPS protocol. let baseUrl = SECURE_BID_URL; // Get ids from Prebid User ID Modules let eidInfo = getEidInfo(deepAccess(validBidRequests, '0.userIdAsEids')); let userEids = eidInfo.toSend; - let MAX_REQUEST_SIZE = 8000; - // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist if (window.headertag && typeof window.headertag.getIdentityInfo === 'function') { @@ -625,6 +621,9 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const requests = []; let r = createRequest(validBidRequests); + // Add FTs to be requested from Exchange + r = addRequestedFeatureToggles(r, FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES) + // getting ixdiags for adunits of the video, outstream & multi format (MF) style let ixdiag = buildIXDiag(validBidRequests); for (var key in ixdiag) { @@ -636,25 +635,17 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r = applyRegulations(r, bidderRequest); let payload = {}; - createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload, MAX_REQUEST_SIZE); + createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload); - let requestSequenceNumber = 0; const transactionIds = Object.keys(impressions); let isFpdAdded = false; for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { - // buildRequestV2 does not have request spliting logic. - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { - if (currentRequestSize >= MAX_REQUEST_SIZE) { - break; - } - } if (requests.length >= MAX_REQUEST_LIMIT) { break; } - r = addImpressions(impressions, transactionIds, r, adUnitIndex, MAX_REQUEST_SIZE); - currentRequestSize += currentImpressionSize; + r = addImpressions(impressions, transactionIds, r, adUnitIndex); const fpd = deepAccess(bidderRequest, 'ortb2') || {}; const site = { ...(fpd.site || fpd.context) }; @@ -667,31 +658,17 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { clonedRObject.site = mergeDeep({}, clonedRObject.site, site); clonedRObject.user = mergeDeep({}, clonedRObject.user, user); - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; - - if (requestSize < MAX_REQUEST_SIZE) { - r.site = mergeDeep({}, r.site, site); - r.user = mergeDeep({}, r.user, user); - isFpdAdded = true; - const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; - currentRequestSize += fpdRequestSize; - } else { - logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE }); - } + r.site = mergeDeep({}, r.site, site); + r.user = mergeDeep({}, r.user, user); + isFpdAdded = true; } // add identifiers info to ixDiag - r = addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl, MAX_REQUEST_SIZE); + r = addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl); const isLastAdUnit = adUnitIndex === transactionIds.length - 1; - if (wasAdUnitImpressionsTrimmed || isLastAdUnit) { - if (!isLastAdUnit || requestSequenceNumber) { - r.ext.ixdiag.sn = requestSequenceNumber; - } - - requestSequenceNumber++; - + if (isLastAdUnit) { requests.push({ method: 'POST', url: baseUrl + '?s=' + siteID, @@ -702,7 +679,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { validBidRequests }); - currentRequestSize = baseRequestSize; r.imp = []; isFpdAdded = false; } @@ -751,6 +727,24 @@ function createRequest(validBidRequests) { return r } +/** + * Adds requested feature toggles to the provided request object to be sent to Exchange. + * @param {object} r - The request object to add feature toggles to. + * @param {Array} requestedFeatureToggles - The list of feature toggles to add. + * @returns {object} The updated request object with the added feature toggles. + */ +function addRequestedFeatureToggles(r, requestedFeatureToggles) { + if (requestedFeatureToggles.length > 0) { + r.ext.features = {}; + // Loop through each feature toggle and add it to the features object. + // Add current activation status as well. + requestedFeatureToggles.forEach(toggle => { + r.ext.features[toggle] = { activated: FEATURE_TOGGLES.isFeatureEnabled(toggle) }; + }); + } + return r; +} + /** * enrichRequest adds userSync configs, source, and referer info to request and ixDiag objects. * @@ -871,9 +865,8 @@ function applyRegulations(r, bidderRequest) { * @param {string} baseUrl Base exchagne URL. * @param {array} requests List of request obejcts. * @param {object} payload Request payload object. - * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). */ -function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload, MAX_REQUEST_SIZE) { +function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload) { // Use the siteId in the first bid request as the main siteId. siteID = validBidRequests[0].params.siteId; payload.s = siteID; @@ -882,16 +875,6 @@ function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, pa const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix'; const otherIxConfig = config.getConfig(bidderCode); - baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; - - if (baseRequestSize > MAX_REQUEST_SIZE) { - logError('IX Bid Adapter: Base request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.EXCEEDS_MAX_SIZE }); - return requests; - } - - currentRequestSize = baseRequestSize; - let fpdRequestSize = 0; - if (otherIxConfig) { // Append firstPartyData to r.site.page if firstPartyData exists. if (typeof otherIxConfig.firstPartyData === 'object') { @@ -904,25 +887,10 @@ function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, pa } firstPartyString = firstPartyString.slice(0, -1); - fpdRequestSize = encodeURIComponent(firstPartyString).length; - - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { - if (fpdRequestSize < MAX_REQUEST_SIZE) { - if ('page' in r.site) { - r.site.page += firstPartyString; - } else { - r.site.page = firstPartyString; - } - currentRequestSize += fpdRequestSize; - } else { - logError('IX Bid Adapter: IX config FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE }); - } + if ('page' in r.site) { + r.site.page += firstPartyString; } else { - if ('page' in r.site) { - r.site.page += firstPartyString; - } else { - r.site.page = firstPartyString; - } + r.site.page = firstPartyString; } } } @@ -935,30 +903,17 @@ function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, pa * @param {array} transactionIds List of transaction Ids. * @param {object} r Reuqest object. * @param {int} adUnitIndex Index of the current add unit - * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). * @return {object} Reqyest object with added impressions describing the request to the server. */ -function addImpressions(impressions, transactionIds, r, adUnitIndex, MAX_REQUEST_SIZE) { +function addImpressions(impressions, transactionIds, r, adUnitIndex) { const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; const { missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; - - let remainingRequestSize = MAX_REQUEST_SIZE - currentRequestSize; const sourceImpressions = { ixImps, missingBannerImpressions }; const impressionObjects = Object.keys(sourceImpressions) .map((key) => sourceImpressions[key]) .filter(item => Array.isArray(item)) .reduce((acc, curr) => acc.concat(...curr), []); - currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; - - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { - while (impressionObjects.length && currentImpressionSize > remainingRequestSize) { - wasAdUnitImpressionsTrimmed = true; - impressionObjects.pop(); - currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; - } - } - const gpid = impressions[transactionIds[adUnitIndex]].gpid; const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; const tid = impressions[transactionIds[adUnitIndex]].tid; @@ -1007,6 +962,11 @@ function addImpressions(impressions, transactionIds, r, adUnitIndex, MAX_REQUEST _bannerImpression.bidfloorcur = impressionObjects[0].bidfloorcur; } + const adUnitFPD = impressions[transactionIds[adUnitIndex]].adUnitFPD + if (adUnitFPD) { + _bannerImpression.ext.data = adUnitFPD; + } + r.imp.push(_bannerImpression); } else { // set imp.ext.gpid to resolved gpid for each imp @@ -1062,6 +1022,19 @@ function addFPD(bidderRequest, r, fpd, site, user) { return r; } +/** + * Adds First-Party Data (FPD) from the bid object to the imp object. + * + * @param {Object} imp - The imp object, representing an impression in the OpenRTB format. + * @param {Object} bid - The bid object, containing information about the bid request. + */ +function addAdUnitFPD(imp, bid) { + const adUnitFPD = deepAccess(bid, 'ortb2Imp.ext.data'); + if (adUnitFPD) { + deepSetValue(imp, 'ext.data', adUnitFPD) + } +} + /** * addIdentifiersInfo adds indentifier info to ixDaig. * @@ -1071,30 +1044,18 @@ function addFPD(bidderRequest, r, fpd, site, user) { * @param {int} adUnitIndex Index of the current add unit * @param {object} payload Request payload object. * @param {string} baseUrl Base exchagne URL. - * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). * @return {object} Reqyest object with added indentigfier info to ixDiag. */ -function addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl, MAX_REQUEST_SIZE) { +function addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl) { const pbaAdSlot = impressions[transactionIds[adUnitIndex]].pbadslot; const tagId = impressions[transactionIds[adUnitIndex]].tagId; const adUnitCode = impressions[transactionIds[adUnitIndex]].adUnitCode; const divId = impressions[transactionIds[adUnitIndex]].divId; if (pbaAdSlot || tagId || adUnitCode || divId) { - const clonedRObject = deepClone(r); - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { - if (requestSize < MAX_REQUEST_SIZE) { - r.ext.ixdiag.pbadslot = pbaAdSlot; - r.ext.ixdiag.tagid = tagId; - r.ext.ixdiag.adunitcode = adUnitCode; - r.ext.ixdiag.divId = divId; - } - } else { - r.ext.ixdiag.pbadslot = pbaAdSlot; - r.ext.ixdiag.tagid = tagId; - r.ext.ixdiag.adunitcode = adUnitCode; - r.ext.ixdiag.divId = divId; - } + r.ext.ixdiag.pbadslot = pbaAdSlot; + r.ext.ixdiag.tagid = tagId; + r.ext.ixdiag.adunitcode = adUnitCode; + r.ext.ixdiag.divId = divId; } return r; @@ -1259,6 +1220,12 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { bannerImps[validBidRequest.transactionId].tagId = deepAccess(validBidRequest, 'params.tagId'); bannerImps[validBidRequest.transactionId].pos = deepAccess(validBidRequest, 'mediaTypes.banner.pos'); + // AdUnit-Specific First Party Data + const adUnitFPD = deepAccess(validBidRequest, 'ortb2Imp.ext.data'); + if (adUnitFPD) { + bannerImps[validBidRequest.transactionId].adUnitFPD = adUnitFPD; + } + const sid = deepAccess(validBidRequest, 'params.id'); if (sid && (typeof sid === 'string' || typeof sid === 'number')) { bannerImps[validBidRequest.transactionId].sid = String(sid); diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js index 6264522ad83..ed6b69d756a 100644 --- a/modules/jwplayerVideoProvider.js +++ b/modules/jwplayerVideoProvider.js @@ -47,31 +47,29 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba function init() { if (!jwplayer) { - triggerSetupFailure(-1); // TODO: come up with error code schema- player is absent + triggerSetupFailure({ code: -1 }); // TODO: come up with error code schema- player is absent return; } playerVersion = jwplayer.version; if (playerVersion < minimumSupportedPlayerVersion) { - triggerSetupFailure(-2); // TODO: come up with error code schema - version not supported + triggerSetupFailure({ code: -2 }); // TODO: come up with error code schema - version not supported return; } if (!document.getElementById(divId)) { - triggerSetupFailure(-3); // TODO: come up with error code schema - missing div id + triggerSetupFailure({ code: -3 }); // TODO: come up with error code schema - missing div id return; } player = jwplayer(divId); if (!player || !player.getState) { - triggerSetupFailure(-4); // TODO: come up with error code schema - factory function failure + triggerSetupFailure({ code: -4 }); // TODO: come up with error code schema - factory function failure } else if (player.getState() === undefined) { setupPlayer(playerConfig); } else { - const payload = getSetupCompletePayload(); - setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); - setupCompleteCallbacks = []; + triggerSetupComplete(); } } @@ -192,8 +190,12 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba function onEvent(externalEventName, callback, basePayload) { if (externalEventName === SETUP_COMPLETE) { setupCompleteCallbacks.push(callback); - } else if (externalEventName === SETUP_FAILED) { + return; + } + + if (externalEventName === SETUP_FAILED) { setupFailedCallbacks.push(callback); + return; } if (!player) { @@ -203,25 +205,6 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba let getEventPayload; switch (externalEventName) { - case SETUP_COMPLETE: - getEventPayload = () => { - setupCompleteCallbacks = []; - return getSetupCompletePayload(); - }; - break; - - case SETUP_FAILED: - getEventPayload = e => { - setupFailedCallbacks = []; - return { - playerVersion, - errorCode: e.code, - errorMessage: e.message, - sourceError: e.sourceError - }; - }; - break; - case AD_REQUEST: case AD_PLAY: case AD_PAUSE: @@ -496,7 +479,17 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba if (!config) { return; } - player.setup(utils.getJwConfig(config)); + player.setup(utils.getJwConfig(config)).on('ready', triggerSetupComplete).on('setupError', triggerSetupFailure); + } + + function triggerSetupComplete() { + if (!setupCompleteCallbacks.length) { + return; + } + + const payload = getSetupCompletePayload(); + setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); + setupCompleteCallbacks = []; } function getSetupCompletePayload() { @@ -511,7 +504,7 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba }; } - function triggerSetupFailure(errorCode) { + function triggerSetupFailure(e) { if (!setupFailedCallbacks.length) { return; } @@ -520,9 +513,9 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba divId, playerVersion, type: SETUP_FAILED, - errorCode, - errorMessage: '', - sourceError: null + errorCode: e.code, + errorMessage: e.message, + sourceError: e.sourceError }; setupFailedCallbacks.forEach(callback => callback(SETUP_FAILED, payload)); diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index b3d5bc2af64..b612c88bb12 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -1,295 +1,522 @@ -import { _each, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; +import { _each, isEmpty, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -const BIDDER_CODE = 'kargo'; -const HOST = 'https://krk.kargo.com'; -const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}'; -const PREBID_VERSION = '$prebid.version$'; -const SYNC_COUNT = 5; -const GVLID = 972; -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const PREBID_VERSION = '$prebid.version$' + +const BIDDER = Object.freeze({ + CODE: 'kargo', + HOST: 'krk2.kargo.com', + REQUEST_METHOD: 'POST', + REQUEST_ENDPOINT: '/api/v1/prebid', + TIMEOUT_ENDPOINT: '/api/v1/event/timeout', + GVLID: 972, + SUPPORTED_MEDIA_TYPES: [BANNER, VIDEO], +}); + +const STORAGE = getStorageManager({bidderCode: BIDDER.CODE}); + +const CURRENCY = Object.freeze({ + KEY: 'currency', + US_DOLLAR: 'USD', +}); + +const REQUEST_KEYS = Object.freeze({ + SOCIAL_CANVAS: 'params.socialCanvas', + SUA: 'ortb2.device.sua', + TDID_ADAPTER: 'userId.tdid', +}); + +const SUA = Object.freeze({ + BROWSERS: 'browsers', + MOBILE: 'mobile', + MODEL: 'model', + PLATFORM: 'platform', + SOURCE: 'source', +}); + +const SUA_ATTRIBUTES = [ + SUA.BROWSERS, + SUA.MOBILE, + SUA.MODEL, + SUA.SOURCE, + SUA.PLATFORM, +]; + +const CERBERUS = Object.freeze({ + KEY: 'krg_crb', + SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}&gpp={GPP_STRING}&gpp_sid={GPP_SID}', + SYNC_COUNT: 5, + PAGE_VIEW_ID: 'pageViewId', + PAGE_VIEW_TIMESTAMP: 'pageViewTimestamp', + PAGE_VIEW_URL: 'pageViewUrl' +}); let sessionId, lastPageUrl, requestCounter; -export const spec = { - gvlid: GVLID, - code: BIDDER_CODE, - isBidRequestValid: function(bid) { - if (!bid || !bid.params) { - return false; - } +function isBidRequestValid(bid) { + if (!bid || !bid.params) { + return false; + } - return !!bid.params.placementId; - }, - buildRequests: function(validBidRequests, bidderRequest) { - const currencyObj = config.getConfig('currency'); - const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; - const bidIDs = {}; - const bidSizes = {}; - - _each(validBidRequests, bid => { - bidIDs[bid.bidId] = bid.params.placementId; - bidSizes[bid.bidId] = bid.sizes; - }); + return !!bid.params.placementId; +} + +function buildRequests(validBidRequests, bidderRequest) { + const currencyObj = config.getConfig(CURRENCY.KEY); + const currency = (currencyObj && currencyObj.adServerCurrency) ? currencyObj.adServerCurrency : null; + const impressions = []; + + _each(validBidRequests, bid => { + impressions.push(getImpression(bid)) + }); + + const firstBidRequest = validBidRequests[0]; + const tdidAdapter = deepAccess(firstBidRequest, REQUEST_KEYS.TDID_ADAPTER); + + const metadata = getAllMetadata(bidderRequest); + + const krakenParams = Object.assign({}, { + pbv: PREBID_VERSION, + aid: firstBidRequest.auctionId, + sid: _getSessionId(), + url: metadata.pageURL, + timeout: bidderRequest.timeout, + ts: new Date().getTime(), + device: { + size: [ + window.screen.width, + window.screen.height + ] + }, + imp: impressions, + user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent), + }); + + const reqCount = getRequestCount() + if (reqCount != null) { + krakenParams.requestCount = reqCount; + } - const firstBidRequest = validBidRequests[0]; - - const tdid = deepAccess(firstBidRequest, 'userId.tdid') - - const transformedParams = Object.assign({}, { - sessionId: spec._getSessionId(), - requestCount: spec._getRequestCount(), - timeout: bidderRequest.timeout, - currency, - cpmGranularity: 1, - timestamp: (new Date()).getTime(), - cpmRange: { - floor: 0, - ceil: 20 - }, - bidIDs, - bidSizes, - device: { - width: window.screen.width, - height: window.screen.height - }, - prebidRawBidRequests: validBidRequests, - prebidVersion: PREBID_VERSION - }, spec._getAllMetadata(bidderRequest, tdid)); - - // User Agent Client Hints / SUA - const uaClientHints = deepAccess(firstBidRequest, 'ortb2.device.sua'); - if (uaClientHints) { - transformedParams.device.sua = pick(uaClientHints, ['browsers', 'platform', 'mobile', 'model']); - } + if (currency != null && currency != CURRENCY.US_DOLLAR) { + krakenParams.cur = currency; + } - // Pull Social Canvas segments and embed URL - const socialCanvas = deepAccess(firstBidRequest, 'params.socialCanvas'); - if (socialCanvas) { - transformedParams.socialCanvasSegments = socialCanvas.segments; - transformedParams.socialEmbedURL = socialCanvas.embedURL; - } + if (metadata.rawCRB != null) { + krakenParams.rawCRB = metadata.rawCRB + } - const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); - return Object.assign({}, bidderRequest, { - method: 'GET', - url: `${HOST}/api/v2/bid`, - data: `json=${encodedParams}`, - currency: currency - }); - }, - interpretResponse: function(response, bidRequest) { - let bids = response.body; - const bidResponses = []; - for (let bidId in bids) { - let adUnit = bids[bidId]; - let meta = { - mediaType: BANNER - }; - - if (adUnit.metadata && adUnit.metadata.landingPageDomain) { - meta.clickUrl = adUnit.metadata.landingPageDomain[0]; - meta.advertiserDomains = adUnit.metadata.landingPageDomain; + if (metadata.rawCRBLocalStorage != null) { + krakenParams.rawCRBLocalStorage = metadata.rawCRBLocalStorage + } + + // Pull Social Canvas segments and embed URL + const socialCanvas = deepAccess(firstBidRequest, REQUEST_KEYS.SOCIAL_CANVAS); + + if (socialCanvas != null) { + krakenParams.socan = socialCanvas; + } + + // User Agent Client Hints / SUA + const uaClientHints = deepAccess(firstBidRequest, REQUEST_KEYS.SUA); + if (uaClientHints) { + const suaValidAttributes = [] + + SUA_ATTRIBUTES.forEach(suaKey => { + const suaValue = uaClientHints[suaKey]; + if (!suaValue) { + return; } - if (adUnit.mediaType && SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType)) { - meta.mediaType = adUnit.mediaType; + // Do not pass any empty strings + if (typeof suaValue == 'string' && suaValue.trim() === '') { + return; } - const bidResponse = { - ad: adUnit.adm, - requestId: bidId, - cpm: Number(adUnit.cpm), - width: adUnit.width, - height: adUnit.height, - ttl: 300, - creativeId: adUnit.id, - dealId: adUnit.targetingCustom, - netRevenue: true, - currency: adUnit.currency || bidRequest.currency, - mediaType: meta.mediaType, - meta: meta - }; - - if (meta.mediaType == VIDEO) { - if (adUnit.admUrl) { - bidResponse.vastUrl = adUnit.admUrl; - } else { - bidResponse.vastXml = adUnit.adm; - } + switch (suaKey) { + case SUA.MOBILE && suaValue < 1: // Do not pass 0 value for mobile + case SUA.SOURCE && suaValue < 1: // Do not pass 0 value for source + break; + default: + suaValidAttributes.push(suaKey); } + }); - bidResponses.push(bidResponse); - } + krakenParams.device.sua = pick(uaClientHints, suaValidAttributes); + } + + const validPageId = getLocalStorageSafely(CERBERUS.PAGE_VIEW_ID) != null + const validPageTimestamp = getLocalStorageSafely(CERBERUS.PAGE_VIEW_TIMESTAMP) != null + const validPageUrl = getLocalStorageSafely(CERBERUS.PAGE_VIEW_URL) != null + + const page = {} + if (validPageId) { + page.id = getLocalStorageSafely(CERBERUS.PAGE_VIEW_ID); + } + if (validPageTimestamp) { + page.timestamp = Number(getLocalStorageSafely(CERBERUS.PAGE_VIEW_TIMESTAMP)); + } + if (validPageUrl) { + page.url = getLocalStorageSafely(CERBERUS.PAGE_VIEW_URL); + } + if (!isEmpty(page)) { + krakenParams.page = page; + } + return Object.assign({}, bidderRequest, { + method: BIDDER.REQUEST_METHOD, + url: `https://${BIDDER.HOST}${BIDDER.REQUEST_ENDPOINT}`, + data: krakenParams, + currency: currency + }); +} + +function interpretResponse(response, bidRequest) { + let bids = response.body; + const bidResponses = []; + + if (isEmpty(bids)) { return bidResponses; - }, - getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { - const syncs = []; - const seed = spec._generateRandomUuid(); - const clientId = spec._getClientId(); - var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; - var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; - // don't sync if opted out via usPrivacy - if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { - return syncs; + } + + if (typeof bids !== 'object') { + return bidResponses; + } + + Object.entries(bids).forEach((entry) => { + const [bidID, adUnit] = entry; + + let meta = { + mediaType: adUnit.mediaType && BIDDER.SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType) ? adUnit.mediaType : BANNER + }; + + if (adUnit.metadata && adUnit.metadata.landingPageDomain) { + meta.clickUrl = adUnit.metadata.landingPageDomain[0]; + meta.advertiserDomains = adUnit.metadata.landingPageDomain; } - if (syncOptions.iframeEnabled && seed && clientId) { - for (let i = 0; i < SYNC_COUNT; i++) { - syncs.push({ - type: 'iframe', - url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed) - .replace('{INDEX}', i) - .replace('{GDPR}', gdpr) - .replace('{GDPR_CONSENT}', gdprConsentString) - .replace('{US_PRIVACY}', usPrivacy || '') - }); + + const bidResponse = { + requestId: bidID, + cpm: Number(adUnit.cpm), + width: adUnit.width, + height: adUnit.height, + ttl: 300, + creativeId: adUnit.id, + dealId: adUnit.targetingCustom, + netRevenue: true, + currency: adUnit.currency || bidRequest.currency, + mediaType: meta.mediaType, + meta: meta + }; + + if (meta.mediaType == VIDEO) { + if (adUnit.admUrl) { + bidResponse.vastUrl = adUnit.admUrl; + } else { + bidResponse.vastXml = adUnit.adm; } + } else { + bidResponse.ad = adUnit.adm; } + + bidResponses.push(bidResponse); + }) + + return bidResponses; +} + +function getUserSyncs(syncOptions, _, gdprConsent, usPrivacy, gppConsent) { + const syncs = []; + const seed = _generateRandomUUID(); + const clientId = getClientId(); + + var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; + var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; + + var gppString = (gppConsent && gppConsent.consentString) ? gppConsent.consentString : ''; + var gppApplicableSections = (gppConsent && gppConsent.applicableSections && Array.isArray(gppConsent.applicableSections)) ? gppConsent.applicableSections.join(',') : ''; + + // don't sync if opted out via usPrivacy + if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { return syncs; - }, - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - onTimeout: function(timeoutData) { - if (timeoutData == null) { - return; + } + if (syncOptions.iframeEnabled && seed && clientId) { + for (let i = 0; i < CERBERUS.SYNC_COUNT; i++) { + syncs.push({ + type: 'iframe', + url: CERBERUS.SYNC_URL.replace('{UUID}', clientId) + .replace('{SEED}', seed) + .replace('{INDEX}', i) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') + .replace('{GPP_STRING}', gppString) + .replace('{GPP_SID}', gppApplicableSections) + }); } + } + return syncs; +} - timeoutData.forEach((bid) => { - this._sendTimeoutData(bid.auctionId, bid.timeout); - }); - }, - - _getCrbFromCookie() { - try { - const crb = JSON.parse(storage.getCookie('krg_crb')); - if (crb && crb.v) { - let vParsed = JSON.parse(atob(crb.v)); - if (vParsed) { - return vParsed; - } +function onTimeout(timeoutData) { + if (timeoutData == null) { + return; + } + + timeoutData.forEach((bid) => { + sendTimeoutData(bid.auctionId, bid.timeout); + }); +} + +function _generateRandomUUID() { + try { + // crypto.getRandomValues is supported everywhere but Opera Mini for years + var buffer = new Uint8Array(16); + crypto.getRandomValues(buffer); + buffer[6] = (buffer[6] & ~176) | 64; + buffer[8] = (buffer[8] & ~64) | 128; + var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { + return ('00' + x.toString(16)).slice(-2); + }).join(''); + return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20); + } catch (e) { + return ''; + } +} + +function _getCrb() { + let localStorageCrb = getCrbFromLocalStorage(); + if (Object.keys(localStorageCrb).length) { + return localStorageCrb; + } + return getCrbFromCookie(); +} + +function _getSessionId() { + if (!sessionId) { + sessionId = _generateRandomUUID(); + } + return sessionId; +} + +function getCrbFromCookie() { + try { + const crb = JSON.parse(STORAGE.getCookie(CERBERUS.KEY)); + if (crb && crb.v) { + let vParsed = JSON.parse(atob(crb.v)); + if (vParsed) { + return vParsed; } - return {}; - } catch (e) { - return {}; } - }, + return {}; + } catch (e) { + return {}; + } +} + +function getCrbFromLocalStorage() { + try { + return JSON.parse(atob(getLocalStorageSafely(CERBERUS.KEY))); + } catch (e) { + return {}; + } +} + +function getLocalStorageSafely(key) { + try { + return STORAGE.getDataFromLocalStorage(key); + } catch (e) { + return null; + } +} + +function getUserIds(tdidAdapter, usp, gdpr, eids, gpp) { + const crb = spec._getCrb(); + const userIds = { + crbIDs: crb.syncIds || {} + }; + + // Pull Trade Desk ID from adapter + if (tdidAdapter) { + userIds.tdID = tdidAdapter; + } + + // Pull Trade Desk ID from our storage + if (!tdidAdapter && crb.tdID) { + userIds.tdID = crb.tdID; + } - _getCrbFromLocalStorage() { - try { - return JSON.parse(atob(spec._getLocalStorageSafely('krg_crb'))); - } catch (e) { - return {}; + if (usp) { + userIds.usp = usp; + } + + try { + if (gdpr) { + userIds['gdpr'] = { + consent: gdpr.consentString || '', + applies: !!gdpr.gdprApplies, + } } - }, + } catch (e) { + } - _getCrb() { - let localStorageCrb = spec._getCrbFromLocalStorage(); - if (Object.keys(localStorageCrb).length) { - return localStorageCrb; + if (crb.lexId != null) { + userIds.kargoID = crb.lexId; + } + + if (crb.clientId != null) { + userIds.clientID = crb.clientId; + } + + if (crb.optOut != null) { + userIds.optOut = crb.optOut; + } + + if (eids != null) { + userIds.sharedIDEids = eids; + } + + if (gpp) { + const parsedGPP = {} + if (gpp && gpp.consentString) { + parsedGPP.gppString = gpp.consentString } - return spec._getCrbFromCookie(); - }, - - _getLocalStorageSafely(key) { - try { - return storage.getDataFromLocalStorage(key); - } catch (e) { - return null; + if (gpp && gpp.applicableSections) { + parsedGPP.applicableSections = gpp.applicableSections } - }, - - _getUserIds(tdid, usp, gdpr) { - const crb = spec._getCrb(); - const userIds = { - kargoID: crb.lexId, - clientID: crb.clientId, - crbIDs: crb.syncIds || {}, - optOut: crb.optOut, - usp: usp - }; + if (!isEmpty(parsedGPP)) { + userIds.gpp = parsedGPP + } + } - try { - if (gdpr) { - userIds['gdpr'] = { - consent: gdpr.consentString || '', - applies: !!gdpr.gdprApplies, - } - } - } catch (e) { + return userIds; +} + +function getClientId() { + const crb = spec._getCrb(); + return crb.clientId; +} + +function getAllMetadata(bidderRequest) { + return { + pageURL: bidderRequest?.refererInfo?.page, + rawCRB: STORAGE.getCookie(CERBERUS.KEY), + rawCRBLocalStorage: getLocalStorageSafely(CERBERUS.KEY) + }; +} + +function getRequestCount() { + if (lastPageUrl === window.location.pathname) { + return ++requestCounter; + } + lastPageUrl = window.location.pathname; + return requestCounter = 0; +} + +function sendTimeoutData(auctionId, auctionTimeout) { + let params = { + aid: auctionId, + ato: auctionTimeout + }; + + try { + let timeoutRequestUrl = buildUrl({ + protocol: 'https', + hostname: BIDDER.HOST, + pathname: BIDDER.TIMEOUT_ENDPOINT, + search: params + }); + + triggerPixel(timeoutRequestUrl); + } catch (e) {} +} + +function getImpression(bid) { + const imp = { + id: bid.bidId, + tid: bid.transactionId, + pid: bid.params.placementId, + code: bid.adUnitCode + }; + + if (bid.floorData != null && bid.floorData.floorMin > 0) { + imp.floor = bid.floorData.floorMin; + } + + if (bid.bidRequestsCount > 0) { + imp.bidRequestCount = bid.bidRequestsCount; + } + + if (bid.bidderRequestsCount > 0) { + imp.bidderRequestCount = bid.bidderRequestsCount; + } + + if (bid.bidderWinsCount > 0) { + imp.bidderWinCount = bid.bidderWinsCount; + } + + const gpid = getGPID(bid) + if (gpid != null && gpid != '') { + imp.fpd = { + gpid: gpid } - if (tdid) { - userIds.tdID = tdid; + } + + if (bid.mediaTypes != null) { + if (bid.mediaTypes.banner != null) { + imp.banner = bid.mediaTypes.banner; } - return userIds; - }, - - _getClientId() { - const crb = spec._getCrb(); - return crb.clientId; - }, - - _getAllMetadata(bidderRequest, tdid) { - return { - userIDs: spec._getUserIds(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent), - pageURL: bidderRequest?.refererInfo?.page, - rawCRB: storage.getCookie('krg_crb'), - rawCRBLocalStorage: spec._getLocalStorageSafely('krg_crb') - }; - }, - _getSessionId() { - if (!sessionId) { - sessionId = spec._generateRandomUuid(); + if (bid.mediaTypes.video != null) { + imp.video = bid.mediaTypes.video; } - return sessionId; - }, - _getRequestCount() { - if (lastPageUrl === window.location.pathname) { - return ++requestCounter; + if (bid.mediaTypes.native != null) { + imp.native = bid.mediaTypes.native; } - lastPageUrl = window.location.pathname; - return requestCounter = 0; - }, - - _generateRandomUuid() { - try { - // crypto.getRandomValues is supported everywhere but Opera Mini for years - var buffer = new Uint8Array(16); - crypto.getRandomValues(buffer); - buffer[6] = (buffer[6] & ~176) | 64; - buffer[8] = (buffer[8] & ~64) | 128; - var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { - return ('00' + x.toString(16)).slice(-2); - }).join(''); - return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20); - } catch (e) { - return ''; + } + + return imp +} + +function getGPID(bid) { + if (bid.ortb2Imp != null) { + if (bid.ortb2Imp.gpid != null && bid.ortb2Imp.gpid != '') { + return bid.ortb2Imp.gpid; } - }, - _sendTimeoutData(auctionId, auctionTimeout) { - let params = { - aid: auctionId, - ato: auctionTimeout, - }; + if (bid.ortb2Imp.ext != null && bid.ortb2Imp.ext.data != null) { + if (bid.ortb2Imp.ext.data.pbAdSlot != null && bid.ortb2Imp.ext.data.pbAdSlot != '') { + return bid.ortb2Imp.ext.data.pbAdSlot; + } - try { - let timeoutRequestUrl = buildUrl({ - protocol: 'https', - hostname: 'krk.kargo.com', - pathname: '/api/v1/event/timeout', - search: params - }); + if (bid.ortb2Imp.ext.data.adServer != null && bid.ortb2Imp.ext.data.adServer.adSlot != null && bid.ortb2Imp.ext.data.adServer.adSlot != '') { + return bid.ortb2Imp.ext.data.adServer.adSlot; + } + } + } - triggerPixel(timeoutRequestUrl); - } catch (e) {} + if (bid.adUnitCode != null && bid.adUnitCode != '') { + return bid.adUnitCode; } + return ''; +} + +export const spec = { + gvlid: BIDDER.GVLID, + code: BIDDER.CODE, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + supportedMediaTypes: BIDDER.SUPPORTED_MEDIA_TYPES, + onTimeout, + _getCrb, + _getSessionId }; + registerBidder(spec); diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 1dc22d0099a..4eef99024f9 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -17,7 +17,6 @@ const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; const DEV_BIDDER_ENDPOINT = 'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'; const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout'; const SUPPORTED_CURRENCY = 'USD'; -const DEFAULT_TIMEOUT = 1000; const TIME_TO_LIVE_IN_SECONDS = 10 * 60; export const isBidRequestValid = function (bid) { @@ -124,7 +123,7 @@ function getPageUrlFromRefererInfo() { function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { const imps = validBidRequests.map(buildOpenRtbImpObject); - const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; + const timeout = bidderRequest.timeout; const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) const request = { id: bidderRequest.auctionId, diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js index 24d339d1938..6efb67ddec8 100644 --- a/modules/kueezBidAdapter.js +++ b/modules/kueezBidAdapter.js @@ -293,7 +293,7 @@ function generateSharedParams(sharedParams, bidderRequest) { const generalBidParams = getBidIdParameter('params', sharedParams); const userIds = getBidIdParameter('userId', sharedParams); const ortb2Metadata = bidderRequest.ortb2 || {}; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; const params = { adapter_version: VERSION, diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 26c0d871a12..ef53ed9baf4 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -23,7 +23,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { @@ -77,6 +77,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { const pId = extractPID(params); const subDomain = extractSubDomain(params); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ currency: 'USD', @@ -105,6 +107,7 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { res: `${screen.width}x${screen.height}`, schain: schain, mediaTypes: mediaTypes, + gpid: gpid, auctionId: auctionId, transactionId: transactionId, bidderRequestId: bidderRequestId, @@ -204,7 +207,7 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; + const {creativeId, ad, price, exp, width, height, currency, metaData, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -218,11 +221,20 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - meta: { - advertiserDomains: advertiserDomains || [] - } }; + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + if (mediaType === BANNER) { Object.assign(response, { ad: ad, diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 4fd2a80afd0..3ecd061085c 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -9,11 +9,12 @@ import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const EVENTS_TOPIC = 'pre_lips' const MODULE_NAME = 'liveIntentId'; -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); const defaultRequestedAttributes = {'nonId': true} const calls = { ajaxGet: (url, onSuccess, onError, timeout) => { @@ -187,6 +188,14 @@ export const liveIntentIdSubmodule = { result.uid2 = { 'id': value.uid2 } } + if (value.bidswitch) { + result.bidswitch = { 'id': value.bidswitch } + } + + if (value.medianet) { + result.medianet = { 'id': value.medianet } + } + return result } diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 883c931824b..02b01b8bd9d 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -16,8 +16,9 @@ import { } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import { uspDataHandler } from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const KEY_ID = 'panoramaId'; const KEY_EXPIRY = `${KEY_ID}_expiry`; @@ -31,7 +32,7 @@ const GVLID = 95; const ID_HOST = 'id.crwdcntrl.net'; const ID_HOST_COOKIELESS = 'c.ltmsphrcl.net'; -export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); let cookieDomain; /** diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 835c04ba074..059a07b9999 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -292,7 +292,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { source: { tid: bidRequest.transactionId }, - tmax: config.getConfig('timeout') || 1500, + tmax: bidderRequest.timeout, imp: currentImps.concat([{ id: bidRequest.bidId, secure: 1, diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 9d437c0b246..7cede6af38d 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -1,14 +1,33 @@ -import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, deepSetValue, deepClone, logInfo, isGptPubadsDefined } from '../src/utils.js'; +import { + debugTurnedOn, + deepAccess, + deepClone, + deepSetValue, + generateUUID, + getWindowLocation, + isAdUnitCodeMatchingSlot, + isEmpty, + isGptPubadsDefined, + isNumber, + logError, + logInfo, + logWarn, + mergeDeep, + parseQS, + parseUrl, + pick +} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const RUBICON_GVL_ID = 52; -export const storage = getStorageManager({ gvlid: RUBICON_GVL_ID, moduleName: 'magnite' }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'magnite' }); const COOKIE_NAME = 'mgniSession'; const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours @@ -37,7 +56,8 @@ const { BIDDER_DONE, BID_TIMEOUT, BID_WON, - BILLABLE_EVENT + BILLABLE_EVENT, + SEAT_NON_BID }, STATUS: { GOOD, @@ -464,7 +484,7 @@ const findMatchingAdUnitFromAuctions = (matchesFunction, returnFirstMatch) => { } } return matches; -} +}; const getRenderingIds = bidWonData => { // if bid caching off -> return the bidWon auction id @@ -821,7 +841,16 @@ magniteAdapter.track = ({ eventType, args }) => { auctionEntry.floors.dealsEnforced = args.floorData.enforcements.floorDeals; } - // Log error if no matching bid! + // no-bid from server. report it! + if (!bid && args.seatBidId) { + bid = adUnit.bids[args.seatBidId] = { + bidder: args.bidderCode, + source: 'server', + bidId: args.seatBidId, + unknownBid: true + }; + } + if (!bid) { logError(`${MODULE_NAME}: Could not find associated bid request for bid response with requestId: `, args.requestId); break; @@ -852,6 +881,9 @@ magniteAdapter.track = ({ eventType, args }) => { bid.pbsBidId = pbsBidId; } break; + case SEAT_NON_BID: + handleNonBidEvent(args); + break; case BIDDER_DONE: const serverError = deepAccess(args, 'serverErrors.0'); const serverResponseTimeMs = args.serverResponseTimeMs; @@ -939,6 +971,66 @@ magniteAdapter.track = ({ eventType, args }) => { } }; +const handleNonBidEvent = function(args) { + const {seatnonbid, auctionId} = args; + const auction = deepAccess(cache, `auctions.${auctionId}.auction`); + // if no auction just bail + if (!auction) { + logWarn(`Unable to match nonbid to auction`); + return; + } + const adUnits = auction.adUnits; + seatnonbid.forEach(seatnonbid => { + let {seat} = seatnonbid; + seatnonbid.nonbid.forEach(nonbid => { + try { + const {status, impid} = nonbid; + const matchingTid = Object.keys(adUnits).find(tid => adUnits[tid].adUnitCode === impid); + const adUnit = adUnits[matchingTid]; + const statusInfo = statusMap[status] || { status: 'no-bid' }; + adUnit.bids[generateUUID()] = { + bidder: seat, + source: 'server', + isSeatNonBid: true, + clientLatencyMillis: Date.now() - auction.auctionStart, + ...statusInfo + }; + } catch (error) { + logWarn(`Unable to match nonbid to adUnit`); + } + }); + }); +}; + +const statusMap = { + 0: { + status: 'no-bid' + }, + 100: { + status: 'error', + error: { + code: 'request-error', + description: 'general error' + } + }, + 101: { + status: 'error', + error: { + code: 'timeout-error', + description: 'prebid server timeout' + } + }, + 200: { + status: 'rejected' + }, + 202: { + status: 'rejected' + }, + 301: { + status: 'rejected-ipf' + } +}; + adapterManager.registerAnalyticsAdapter({ adapter: magniteAdapter, code: 'magnite', diff --git a/modules/mathildeadsBidAdapter.js b/modules/mathildeadsBidAdapter.js index 6b886b72955..929cee8f3c0 100644 --- a/modules/mathildeadsBidAdapter.js +++ b/modules/mathildeadsBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index e61c2e65c39..87347ca8d27 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -62,7 +62,7 @@ const SCRIPT_TAG_START = ' slotParams(request)), ortb2: bidderRequests.ortb2, - tmax: bidderRequests.timeout || config.getConfig('bidderTimeout') + tmax: bidderRequests.timeout } } diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 819ff280e35..4076cd6927a 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {Renderer} from '../src/Renderer.js'; +import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' @@ -170,7 +171,7 @@ export const spec = { */ onBidWon: function(bid) { // fires a pixel to confirm a winning bid - let params = {'pbjs': '$prebid.version$'}; + let params = { pbjs: '$prebid.version$', referer: encodeURIComponent(getRefererInfo().page || getRefererInfo().topmostLocation) }; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'requestId', 'auctionId', 'originalCpm', 'originalCurrency']; if (bid.hasOwnProperty('mediasquare')) { diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index 6a10c2a94eb..c522d588970 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -9,13 +9,14 @@ import { logInfo, logError, logWarn } from '../src/utils.js'; import * as ajaxLib from '../src/ajax.js'; import {submodule} from '../src/hook.js' import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'merkleId'; const ID_URL = 'https://prebid.sv.rkdms.com/identity/'; const DEFAULT_REFRESH = 7 * 3600; const SESSION_COOKIE_NAME = '_svsid'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); function getSession(configParams) { let session = null; diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 036effecd88..0079936d803 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -1,14 +1,31 @@ -import { _each, deepAccess, isPlainObject, isArray, isStr, logInfo, parseUrl, isEmpty, triggerPixel, logWarn, getBidIdParameter, isFn, isNumber } from '../src/utils.js'; +import { + _each, + deepAccess, + isPlainObject, + isArray, + isStr, + logInfo, + parseUrl, + isEmpty, + triggerPixel, + logWarn, + getBidIdParameter, + isFn, + isNumber, + isBoolean, + isInteger, deepSetValue, +} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {USERSYNC_DEFAULT_CONFIG} from '../src/userSync.js'; const GVLID = 358; const DEFAULT_CUR = 'USD'; const BIDDER_CODE = 'mgid'; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const ENDPOINT_URL = 'https://prebid.mgid.com/prebid/'; const LOG_WARN_PREFIX = '[MGID warn]: '; const LOG_INFO_PREFIX = '[MGID info]: '; @@ -64,7 +81,7 @@ _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAs _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); export const spec = { - VERSION: '1.5', + VERSION: '1.6', code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, NATIVE], @@ -115,22 +132,19 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {BidRequest[]} validBidRequests A non-empty list of bid requests which should be sent to the Server. + * @param bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - + const [bidRequest] = validBidRequests; logInfo(LOG_INFO_PREFIX + `buildRequests`); if (validBidRequests.length === 0) { return; } const info = pageInfo(); - // TODO: the fallback seems to never be used here, and probably in the wrong order - const page = info.location || deepAccess(bidderRequest, 'refererInfo.page') - const hostname = parseUrl(page).hostname; - let domain = extractDomainFromHost(hostname) || hostname; const accountId = setOnAny(validBidRequests, 'params.accountId'); const muid = getLocalStorageSafely('mgMuidn'); let url = (setOnAny(validBidRequests, 'params.bidUrl') || ENDPOINT_URL) + accountId; @@ -182,31 +196,108 @@ export const spec = { let request = { id: deepAccess(bidderRequest, 'bidderRequestId'), - site: {domain, page}, + site: ortb2Data?.site || {}, cur: [cur], geo: {utcoffset: info.timeOffset}, - device: { - ua: navigator.userAgent, - js: 1, - dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, - h: screen.height, - w: screen.width, - language: getLanguage() - }, + device: ortb2Data?.device || {}, ext: { mgid_ver: spec.VERSION, prebid_ver: '$prebid.version$', - ...ortb2Data }, - imp + imp, + tmax: bidderRequest?.timeout || config.getConfig('bidderTimeout') || 500, }; - if (bidderRequest && bidderRequest.gdprConsent) { - request.user = {ext: {consent: bidderRequest.gdprConsent.consentString}}; - request.regs = {ext: {gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)}} + // request level + const bcat = ortb2Data?.bcat || bidRequest?.params?.bcat || []; + const badv = ortb2Data?.badv || bidRequest?.params?.badv || []; + const wlang = ortb2Data?.wlang || bidRequest?.params?.wlang || []; + if (bcat.length > 0) { + request.bcat = bcat; + } + if (badv.length > 0) { + request.badv = badv; + } + if (wlang.length > 0) { + request.wlang = wlang; + } + // site level + const page = deepAccess(bidderRequest, 'refererInfo.page') || info.location + if (!isStr(deepAccess(request.site, 'domain'))) { + const hostname = parseUrl(page).hostname; + request.site.domain = extractDomainFromHost(hostname) || hostname + } + if (!isStr(deepAccess(request.site, 'page'))) { + request.site.page = page + } + if (!isStr(deepAccess(request.site, 'ref'))) { + const ref = deepAccess(bidderRequest, 'refererInfo.ref') || info.referrer; + if (ref) { + request.site.ref = ref + } + } + // device level + if (!isStr(deepAccess(request.device, 'ua'))) { + request.device.ua = navigator.userAgent; + } + request.device.js = 1; + if (!isInteger(deepAccess(request.device, 'dnt'))) { + request.device.dnt = (navigator?.doNotTrack === 'yes' || navigator?.doNotTrack === '1' || navigator?.msDoNotTrack === '1') ? 1 : 0; + } + if (!isInteger(deepAccess(request.device, 'h'))) { + request.device.h = screen.height; } - if (info.referrer) { - request.site.ref = info.referrer + if (!isInteger(deepAccess(request.device, 'w'))) { + request.device.w = screen.width; + } + if (!isStr(deepAccess(request.device, 'language'))) { + request.device.language = getLanguage(); + } + // user & regs & privacy + if (isPlainObject(ortb2Data?.user)) { + request.user = ortb2Data.user; + } + if (isPlainObject(ortb2Data?.regs)) { + request.regs = ortb2Data.regs; + } + if (bidderRequest && isPlainObject(bidderRequest.gdprConsent)) { + if (!isStr(deepAccess(request.user, 'ext.consent'))) { + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent?.consentString); + } + if (!isBoolean(deepAccess(request.regs, 'ext.gdpr'))) { + deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent?.gdprApplies ? 1 : 0); + } + } + const userId = deepAccess(bidderRequest, 'userId') + if (isStr(userId)) { + deepSetValue(request, 'user.id', userId); + } + const eids = setOnAny(validBidRequests, 'userIdAsEids') + if (eids && eids.length > 0) { + deepSetValue(request, 'user.ext.eids', eids); + } + if (bidderRequest && isStr(bidderRequest.uspConsent)) { + if (!isBoolean(deepAccess(request.regs, 'ext.us_privacy'))) { + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } } + if (bidderRequest && isPlainObject(bidderRequest.gppConsent)) { + if (!isStr(deepAccess(request.regs, 'gpp'))) { + deepSetValue(request, 'regs.gpp', bidderRequest.gppConsent?.gppString); + } + if (!isArray(deepAccess(request.regs, 'gpp_sid'))) { + deepSetValue(request, 'regs.gpp_sid', bidderRequest.gppConsent?.applicableSections); + } + } + if (config.getConfig('coppa')) { + if (!isInteger(deepAccess(request.regs, 'coppa'))) { + deepSetValue(request, 'regs.coppa', 1); + } + } + const schain = setOnAny(validBidRequests, 'schain'); + if (schain) { + deepSetValue(request, 'source.ext.schain', schain); + } + logInfo(LOG_INFO_PREFIX + `buildRequest:`, request); return { method: 'POST', @@ -218,6 +309,7 @@ export const spec = { * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequests * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: (serverResponse, bidRequests) => { @@ -268,8 +360,66 @@ export const spec = { } logInfo(LOG_INFO_PREFIX + `onBidWon`); }, - getUserSyncs: (syncOptions, serverResponses) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { logInfo(LOG_INFO_PREFIX + `getUserSyncs`); + const spb = isPlainObject(config.getConfig('userSync')) && + isNumber(config.getConfig('userSync').syncsPerBidder) + ? config.getConfig('userSync').syncsPerBidder : USERSYNC_DEFAULT_CONFIG.syncsPerBidder; + + if (spb > 0 && isPlainObject(syncOptions) && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + let pixels = []; + if (serverResponses && + isArray(serverResponses) && + serverResponses.length > 0 && + isPlainObject(serverResponses[0].body) && + isPlainObject(serverResponses[0].body.ext) && + isArray(serverResponses[0].body.ext.cm) && + serverResponses[0].body.ext.cm.length > 0) { + pixels = serverResponses[0].body.ext.cm; + } + + const syncs = []; + const query = []; + query.push('cbuster=' + Math.round(new Date().getTime())); + query.push('consentData=' + encodeURIComponent(isPlainObject(gdprConsent) && isStr(gdprConsent?.consentString) ? gdprConsent.consentString : '')); + if (isPlainObject(gdprConsent) && typeof gdprConsent?.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { + query.push('gdprApplies=1'); + } else { + query.push('gdprApplies=0'); + } + if (isPlainObject(uspConsent) && uspConsent?.consentString) { + query.push(`uspString=${encodeURIComponent(uspConsent?.consentString)}`); + } + if (isPlainObject(gppConsent) && gppConsent?.gppString) { + query.push(`gppString=${encodeURIComponent(gppConsent?.gppString)}`); + } + if (config.getConfig('coppa')) { + query.push('coppa=1') + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://cm.mgid.com/i.html?' + query.join('&') + }); + } else if (syncOptions.pixelEnabled) { + if (pixels.length === 0) { + for (let i = 0; i < spb; i++) { + syncs.push({ + type: 'image', + url: 'https://cm.mgid.com/i.gif?' + query.join('&') // randomly selects partner if sync required + }); + } + } else { + for (let i = 0; i < spb && i < pixels.length; i++) { + syncs.push({ + type: 'image', + url: pixels[i] + (pixels[i].indexOf('?') > 0 ? '&' : '?') + query.join('&') + }); + } + } + } + return syncs; + } } }; @@ -287,6 +437,7 @@ function setOnAny(collection, key) { /** * Unpack the Server's Bid into a Prebid-compatible one. * @param serverBid + * @param cur * @return Bid */ function prebidBid(serverBid, cur) { @@ -330,7 +481,7 @@ function setMediaType(bid, newBid) { } function extractDomainFromHost(pageHost) { - if (pageHost == 'localhost') { + if (pageHost === 'localhost') { return 'localhost' } let domain = null; @@ -600,6 +751,7 @@ function pageInfo() { * Get the floor price from bid.params for backward compatibility. * If not found, then check floor module. * @param bid A valid bid object + * @param cur * @returns {*|number} floor price */ function getBidFloor(bid, cur) { diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js index f30f14ea528..fd2c0bbe6fd 100644 --- a/modules/mgidRtdProvider.js +++ b/modules/mgidRtdProvider.js @@ -3,6 +3,7 @@ import {ajax} from '../src/ajax.js'; import {deepAccess, logError, logInfo, mergeDeep} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'mgid'; @@ -13,7 +14,7 @@ const ORTB2_NAME = 'www.mgid.com' const GVLID = 358; /** @type {?Object} */ export const storage = getStorageManager({ - gvlid: GVLID, + moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); @@ -185,6 +186,7 @@ export const mgidSubmodule = { name: SUBMODULE_NAME, init: init, getBidRequestData: getBidRequestData, + gvlid: GVLID }; submodule(MODULE_NAME, mgidSubmodule); diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index d953558bf31..a80a37f5ead 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -366,7 +366,7 @@ function generateGeneralParams(generalObject, bidderRequest) { const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; const {bidderCode} = bidderRequest; const generalBidParams = generalObject.params; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; // 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. diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js index 69b2387d053..bd7874c405b 100644 --- a/modules/minutemediaplusBidAdapter.js +++ b/modules/minutemediaplusBidAdapter.js @@ -23,7 +23,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { @@ -77,6 +77,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { const pId = extractPID(params); const subDomain = extractSubDomain(params); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ currency: 'USD', @@ -105,6 +107,7 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { res: `${screen.width}x${screen.height}`, schain: schain, mediaTypes: mediaTypes, + gpid: gpid, auctionId: auctionId, transactionId: transactionId, bidderRequestId: bidderRequestId, @@ -204,7 +207,7 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; + const {creativeId, ad, price, exp, width, height, currency, metaData, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -218,11 +221,20 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - meta: { - advertiserDomains: advertiserDomains || [] - } }; + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + if (mediaType === BANNER) { Object.assign(response, { ad: ad, diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index f461440c5a1..33fa6857e85 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -8,7 +8,7 @@ const EVENTS_DOMAIN = 'events.missena.io'; const EVENTS_DOMAIN_DEV = 'events.staging.missena.xyz'; export const spec = { - aliases: [BIDDER_CODE], + aliases: ['msna'], code: BIDDER_CODE, gvlid: 687, supportedMediaTypes: [BANNER], diff --git a/modules/mwOpenLinkIdSystem.js b/modules/mwOpenLinkIdSystem.js index 552223fa73c..9b1035cbf18 100644 --- a/modules/mwOpenLinkIdSystem.js +++ b/modules/mwOpenLinkIdSystem.js @@ -8,14 +8,15 @@ import { timestamp, logError, deepClone, generateUUID, isPlainObject } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const openLinkID = { name: 'mwol', cookie_expiration: (86400 * 1000 * 365 * 1) // 1 year } -const storage = getStorageManager(); +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: openLinkID.name}); function getExpirationDate() { return (new Date(timestamp() + openLinkID.cookie_expiration)).toGMTString(); diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index a92168492d0..c62a74e6d6c 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -1,6 +1,7 @@ import { deepAccess, isEmpty } from '../src/utils.js' import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER } from '../src/mediaTypes.js' +import { getGlobal } from '../src/prebidGlobal.js' // import { config } from 'src/config' const BIDDER_CODE = 'nativo' @@ -14,6 +15,8 @@ const SUPPORTED_AD_TYPES = [BANNER] const FLOOR_PRICE_CURRENCY = 'USD' const PRICE_FLOOR_WILDCARD = '*' +const localPbjsRef = getGlobal() + /** * Keep track of bid data by keys * @returns {Object} - Map of bid data that can be referenced by multiple keys @@ -133,6 +136,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + const requestData = new RequestData() + requestData.addBidRequestDataSource(new UserEIDs()) + // Parse values from bid requests const placementIds = new Set() const bidDataMap = BidDataMap() @@ -166,6 +172,8 @@ export const spec = { if (bidRequestFloorPriceData) { floorPriceData[bidRequest.adUnitCode] = bidRequestFloorPriceData } + + requestData.processBidRequestData(bidRequest, bidderRequest) }) bidRequestMap[bidderRequest.bidderRequestId] = bidDataMap @@ -174,6 +182,10 @@ export const spec = { // Build basic required QS Params let params = [ + // Prebid version + { + key: 'ntv_pbv', value: localPbjsRef.version + }, // Prebid request id { key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId }, // Ad unit data @@ -255,9 +267,12 @@ export const spec = { params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) } + const qsParamStrings = [requestData.getRequestDataQueryString(), arrayToQS(params)] + const requestUrl = buildRequestUrl(BIDDER_ENDPOINT, qsParamStrings) + let serverRequest = { method: 'GET', - url: BIDDER_ENDPOINT + arrayToQS(params), + url: requestUrl } return serverRequest @@ -404,13 +419,6 @@ export const spec = { return syncs }, - /** - * Will be called when an adpater timed out for an auction. - * Adapter can fire a ajax or pixel call to register a timeout at thier end. - * @param {Object} timeoutData - Timeout specific data - */ - onTimeout: function (timeoutData) {}, - /** * Will be called when a bid from the adapter won the auction. * @param {Object} bid - The bid that won the auction @@ -425,12 +433,6 @@ export const spec = { appendFilterData(campaignsToFilter, ext.campaignsToFilter) }, - /** - * Will be called when the adserver targeting has been set for a bid from the adapter. - * @param {Object} bidder - The bid of which the targeting has been set - */ - onSetTargeting: function (bid) {}, - /** * Maps Prebid's bidId to Nativo's placementId values per unique bidderRequestId * @param {String} bidderRequestId - The unique ID value associated with the bidderRequest @@ -451,6 +453,78 @@ export const spec = { registerBidder(spec) // Utils +export class RequestData { + constructor() { + this.bidRequestDataSources = [] + } + + addBidRequestDataSource(bidRequestDataSource) { + if (!(bidRequestDataSource instanceof BidRequestDataSource)) return + + this.bidRequestDataSources.push(bidRequestDataSource) + } + + processBidRequestData(bidRequest, bidderRequest) { + for (let bidRequestDataSource of this.bidRequestDataSources) { + bidRequestDataSource.processBidRequestData(bidRequest, bidderRequest) + } + } + + getRequestDataQueryString() { + if (this.bidRequestDataSources.length == 0) return + + const queryParams = this.bidRequestDataSources.map(dataSource => dataSource.getRequestQueryString()).filter(queryString => queryString !== '') + return queryParams.join('&') + } +} + +export class BidRequestDataSource { + constructor() { + this.type = 'BidRequestDataSource' + } + processBidRequestData(bidRequest, bidderRequest) { } + getRequestQueryString() { return '' } +} + +export class UserEIDs extends BidRequestDataSource { + constructor() { + super() + this.type = 'UserEIDs' + this.qsParam = new QueryStringParam('ntv_pb_eid') + this.eids = [] + } + + processBidRequestData(bidRequest, bidderRequest) { + if (bidRequest.userIdAsEids === undefined || this.eids.length > 0) return + this.eids = bidRequest.userIdAsEids + } + + getRequestQueryString() { + if (this.eids.length === 0) return '' + + const encodedValueArray = encodeToBase64(this.eids) + this.qsParam.value = encodedValueArray + return this.qsParam.toString() + } +} + +export class QueryStringParam { + constructor(key, value) { + this.key = key + this.value = value + } +} + +QueryStringParam.prototype.toString = function () { + return `${this.key}=${this.value}` +} + +export function encodeToBase64(value) { + try { + return btoa(JSON.stringify(value)) + } catch (err) { } +} + export function parseFloorPriceData(bidRequest) { if (typeof bidRequest.getFloor !== 'function') return @@ -589,12 +663,9 @@ function appendQSParamString(str, key, value) { * @returns */ function arrayToQS(arr) { - return ( - '?' + - arr.reduce((value, obj) => { - return appendQSParamString(value, obj.key, obj.value) - }, '') - ) + return arr.reduce((value, obj) => { + return appendQSParamString(value, obj.key, obj.value) + }, '') } /** @@ -615,6 +686,24 @@ function getLargestSize(sizes, method = area) { }) } +/** + * Build the final request url + */ +export function buildRequestUrl(baseUrl, qsParamStringArray = []) { + if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) return baseUrl + + const nonEmptyQSParamStrings = qsParamStringArray.filter(qsParamString => qsParamString.trim() !== '') + + if (nonEmptyQSParamStrings.length === 0) return baseUrl + + let requestUrl = `${baseUrl}?${nonEmptyQSParamStrings[0]}` + for (let i = 1; i < nonEmptyQSParamStrings.length; i++) { + requestUrl += `&${nonEmptyQSParamStrings[i]}` + } + + return requestUrl +} + /** * Calculate the area * @param {Array} size - [width, height] @@ -645,7 +734,7 @@ export function getPageUrlFromBidRequest(bidRequest) { try { const url = new URL(paramPageUrl) return url.href - } catch (err) {} + } catch (err) { } } export function hasProtocol(url) { diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index 25f0afe4733..878ae7fadb2 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -7,7 +7,8 @@ import { isStr, isPlainObject, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { ajax } from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'naveggId'; const OLD_NAVEGG_ID = 'nid'; @@ -16,7 +17,7 @@ const BASE_URL = 'https://id.navegg.com/uid/'; const DEFAULT_EXPIRE = 8 * 24 * 3600 * 1000; const INVALID_EXPIRE = 3600 * 1000; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); function getNaveggIdFromApi() { const callbacks = { diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index 2ee4601e200..00a3c59b4a6 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -7,7 +7,6 @@ import CONSTANTS from '../src/constants.json'; export const DATA_PROVIDER = 'neuwo.ai'; const SEGTAX_IAB = 6 // IAB - Content Taxonomy version 2 -const CATTAX_IAB = 6 // IAB Tech Lab Content Taxonomy 2.2 const RESPONSE_IAB_TIER_1 = 'marketing_categories.iab_tier_1' const RESPONSE_IAB_TIER_2 = 'marketing_categories.iab_tier_2' @@ -106,7 +105,6 @@ export function injectTopics(topics, bidsConfig) { // upgrade category taxonomy to IAB 2.2, inject result to page categories if (segment.length > 0) { - addFragment(bidsConfig.ortb2Fragments.global, 'site.cattax', CATTAX_IAB) addFragment(bidsConfig.ortb2Fragments.global, 'site.pagecat', segment.map(s => s.id)) } diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 6f9385094e7..89c6e677007 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -31,6 +31,7 @@ const VIDEO_PARAMS = [ 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'protocols', 'startdelay' ]; +const GVLID = 1060; const sendingDataStatistic = initSendingDataStatistic(); events.on(CONSTANTS.EVENTS.AUCTION_INIT, auctionInitHandler); @@ -44,6 +45,7 @@ events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], + gvlid: GVLID, isBidRequestValid: function(bid) { return !!( diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index a1971f4f9a5..9a7541fdd96 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -25,7 +25,6 @@ const ALIASES = [ ]; export const storage = getStorageManager({ - gvlid: GVLID, bidderCode: BIDDER_CODE, }); diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 6f6cbec3400..7bf1c4b80db 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -7,7 +7,7 @@ import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); window.nobidVersion = '1.3.3'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index 1d1ad6f054d..7a801a945ae 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -8,7 +8,10 @@ import { logInfo, getWindowLocation } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; + +const MODULE_NAME = 'novatiq'; /** @type {Submodule} */ export const novatiqIdSubmodule = { @@ -17,7 +20,7 @@ export const novatiqIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'novatiq', + name: MODULE_NAME, /** * used to specify vendor id * @type {number} @@ -217,7 +220,7 @@ export const novatiqIdSubmodule = { let sharedId = null; if (this.useSharedId(configParams)) { let cookieOrStorageID = this.getCookieOrStorageID(configParams); - const storage = getStorageManager({gvlid: this.gvlid, moduleName: 'pubCommonId'}); + const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); // first check local storage if (storage.hasLocalStorage()) { diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index da62ce5c0a1..d09320c00fe 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -11,7 +11,7 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.4.0'; +const ADAPTER_VERSION = '1.4.1'; function getClientWidth() { const documentElementClientWidth = window.top.document.documentElement.clientWidth @@ -90,7 +90,8 @@ function buildRequests(validBidRequests, bidderRequest) { }, device: { w: getClientWidth(), - h: getClientHeight() + h: getClientHeight(), + pxratio: window.devicePixelRatio } }; diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 70238294c38..2ae879cdcbc 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -13,7 +13,7 @@ const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; const BIDDER_CODE = 'onetag'; const GVLID = 241; -const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +const storage = getStorageManager({ bidderCode: BIDDER_CODE }); /** * Determines whether or not the given bid request is valid. diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index d99455f3f73..edab625e541 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -13,7 +13,6 @@ import { } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; const BIDDER_CODE = 'onomagic'; const URL = 'https://bidder.onomagic.com/hb'; @@ -80,7 +79,7 @@ function buildRequests(bidReqs, bidderRequest) { w: screen.width, h: screen.height }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest?.timeout }; return { diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index b6f4523bb50..03423a028b4 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,609 +1,238 @@ -import { - _each, - _map, - convertTypes, - deepAccess, - deepSetValue, - inIframe, - isArray, - parseSizesInput, - parseUrl -} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {mergeDeep} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {includes} from '../src/polyfill.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', - 'startdelay', 'skippable', 'playbackmethod', 'api', 'protocols', 'boxingallowed', - 'linearity', 'delivery', 'protocol', 'placement', 'minbitrate', 'maxbitrate']; -const BIDDER_CODE = 'openx'; -const BIDDER_CONFIG = 'hb_pb'; -const BIDDER_VERSION = '3.0.3'; - -const DEFAULT_CURRENCY = 'USD'; - -export const USER_ID_CODE_TO_QUERY_ARG = { - britepoolid: 'britepoolid', // BritePool ID - criteoId: 'criteoid', // CriteoID - fabrickId: 'nuestarid', // Fabrick ID by Nuestar - hadronId: 'audigentid', // Hadron ID from Audigent - id5id: 'id5id', // ID5 ID - idl_env: 'lre', // LiveRamp IdentityLink - IDP: 'zeotapid', // zeotapIdPlus ID+ - idxId: 'idxid', // idIDx, - intentIqId: 'intentiqid', // IntentIQ ID - lipb: 'lipbid', // LiveIntent ID - lotamePanoramaId: 'lotameid', // Lotame Panorama ID - merkleId: 'merkleid', // Merkle ID - netId: 'netid', // netID - parrableId: 'parrableid', // Parrable ID - pubcid: 'pubcid', // PubCommon ID - quantcastId: 'quantcastid', // Quantcast ID - tapadId: 'tapadid', // Tapad Id - tdid: 'ttduuid', // The Trade Desk Unified ID - uid2: 'uid2', // Unified ID 2.0 - admixerId: 'admixerid', // AdMixer ID - deepintentId: 'deepintentid', // DeepIntent ID - dmdId: 'dmdid', // DMD Marketing Corp ID - nextrollId: 'nextrollid', // NextRoll ID - novatiq: 'novatiqid', // Novatiq ID - mwOpenLinkId: 'mwopenlinkid', // MediaWallah OpenLink ID - dapId: 'dapid', // Akamai DAP ID - amxId: 'amxid', // AMX RTB ID - kpuid: 'kpuid', // Kinesso ID - publinkId: 'publinkid', // Publisher Link - naveggId: 'naveggid', // Navegg ID - imuid: 'imuid', // IM-UID by Intimate Merger - adtelligentId: 'adtelligentid' // Adtelligent ID +const bidderConfig = 'hb_pb_ortb'; +const bidderVersion = '2.0'; +export const REQUEST_URL = 'https://rtb.openx.net/openrtbb/prebidjs'; +export const SYNC_URL = 'https://u.openx.net/w/1.0/pd'; +export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; +export const spec = { + code: 'openx', + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + transformBidParams }; -export const spec = { - code: BIDDER_CODE, - gvlid: 69, - supportedMediaTypes: SUPPORTED_AD_TYPES, - isBidRequestValid: function (bidRequest) { - const hasDelDomainOrPlatform = bidRequest.params.delDomain || bidRequest.params.platform; - if (deepAccess(bidRequest, 'mediaTypes.banner') && hasDelDomainOrPlatform) { - return !!bidRequest.params.unit || deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; - } +registerBidder(spec); - return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300 }, - buildRequests: function (bidRequests, bidderRequest) { - if (bidRequests.length === 0) { - return []; - } - - let requests = []; - let [videoBids, bannerBids] = partitionByVideoBids(bidRequests); - - // build banner requests - if (bannerBids.length > 0) { - requests.push(buildOXBannerRequest(bannerBids, bidderRequest)); - } - // build video requests - if (videoBids.length > 0) { - videoBids.forEach(videoBid => { - requests.push(buildOXVideoRequest(videoBid, bidderRequest)) - }); + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + mergeDeep(imp, { + tagid: bidRequest.params.unit, + ext: { + divid: bidRequest.adUnitCode + } + }); + if (bidRequest.params.customParams) { + utils.deepSetValue(imp, 'ext.customParams', bidRequest.params.customParams); } - - return requests; - }, - interpretResponse: function ({body: oxResponseObj}, serverRequest) { - let mediaType = getMediaTypeFromRequest(serverRequest); - - return mediaType === VIDEO ? createVideoBidResponses(oxResponseObj, serverRequest.payload) - : createBannerBidResponses(oxResponseObj, serverRequest.payload); - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { - let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let url = deepAccess(responses, '0.body.ads.pixels') || - deepAccess(responses, '0.body.pixels') || - generateDefaultSyncUrl(gdprConsent, uspConsent); - - return [{ - type: pixelType, - url: url - }]; + if (bidRequest.params.customFloor && !imp.bidfloor) { + imp.bidfloor = bidRequest.params.customFloor; } + return imp; }, - transformBidParams: function(params, isOpenRtb) { - return convertTypes({ - 'unit': 'string', - 'customFloor': 'number' - }, params); - } -}; - -function generateDefaultSyncUrl(gdprConsent, uspConsent) { - let url = 'https://u.openx.net/w/1.0/pd'; - let queryParamStrings = []; - - if (gdprConsent) { - queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); - queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); - } - - // CCPA - if (uspConsent) { - queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); - } - - return `${url}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}`; -} - -function isVideoRequest(bidRequest) { - return (deepAccess(bidRequest, 'mediaTypes.video') && !deepAccess(bidRequest, 'mediaTypes.banner')) || bidRequest.mediaType === VIDEO; -} - -function createBannerBidResponses(oxResponseObj, {bids, startTime}) { - let adUnits = oxResponseObj.ads.ad; - let bidResponses = []; - for (let i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - let adUnitIdx = parseInt(adUnit.idx, 10); - let bidResponse = {}; - - bidResponse.requestId = bids[adUnitIdx].bidId; - - if (adUnit.pub_rev) { - bidResponse.cpm = Number(adUnit.pub_rev) / 1000; - } else { - // No fill, do not add the bidresponse - continue; + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + at: 1, + ext: { + bc: `${bidderConfig}_${bidderVersion}` + } + }) + const bid = context.bidRequests[0]; + if (bid.params.coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); } - let creative = adUnit.creative[0]; - if (creative) { - bidResponse.width = creative.width; - bidResponse.height = creative.height; + if (bid.params.doNotTrack) { + utils.deepSetValue(req, 'device.dnt', 1); } - bidResponse.creativeId = creative.id; - bidResponse.ad = adUnit.html; - if (adUnit.deal_id) { - bidResponse.dealId = adUnit.deal_id; + if (bid.params.platform) { + utils.deepSetValue(req, 'ext.platform', bid.params.platform); } - // default 5 mins - bidResponse.ttl = 300; - // true is net, false is gross - bidResponse.netRevenue = true; - bidResponse.currency = adUnit.currency; - - // additional fields to add - if (adUnit.tbd) { - bidResponse.tbd = adUnit.tbd; + if (bid.params.delDomain) { + utils.deepSetValue(req, 'ext.delDomain', bid.params.delDomain); } - bidResponse.ts = adUnit.ts; - - bidResponse.meta = {}; - if (adUnit.brand_id) { - bidResponse.meta.brandId = adUnit.brand_id; + if (bid.params.response_template_name) { + utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); } - - if (adUnit.adomain && length(adUnit.adomain) > 0) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } else { - bidResponse.meta.advertiserDomains = []; + if (bid.params.test) { + req.test = 1 } - - if (adUnit.adv_id) { - bidResponse.meta.dspid = adUnit.adv_id; - } - - bidResponses.push(bidResponse); - } - return bidResponses; -} - -function getViewportDimensions(isIfr) { - let width; - let height; - let tWin = window; - let tDoc = document; - let docEl = tDoc.documentElement; - let body; - - if (isIfr) { - try { - tWin = window.top; - tDoc = window.top.document; - } catch (e) { - return; + return req; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + if (bid.ext) { + bidResponse.meta.networkId = bid.ext.dsp_id; + bidResponse.meta.advertiserId = bid.ext.buyer_id; + bidResponse.meta.brandId = bid.ext.brand_id; + } + const {ortbResponse} = context; + if (ortbResponse.ext && ortbResponse.ext.paf) { + bidResponse.meta.paf = Object.assign({}, ortbResponse.ext.paf); + bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); + } + return bidResponse; + }, + response(buildResponse, bidResponses, ortbResponse, context) { + // pass these from request to the responses for use in userSync + const {ortbRequest} = context; + if (ortbRequest.ext) { + if (ortbRequest.ext.delDomain) { + utils.deepSetValue(ortbResponse, 'ext.delDomain', ortbRequest.ext.delDomain); + } + if (ortbRequest.ext.platform) { + utils.deepSetValue(ortbResponse, 'ext.platform', ortbRequest.ext.platform); + } } - body = tDoc.body; - - width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; - height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; - } else { - width = tWin.innerWidth || docEl.clientWidth; - height = tWin.innerHeight || docEl.clientHeight; - } - - return `${width}x${height}`; -} - -function formatCustomParms(customKey, customParams) { - let value = customParams[customKey]; - if (isArray(value)) { - // if value is an array, join them with commas first - value = value.join(','); - } - // return customKey=customValue format, escaping + to . and / to _ - return (customKey.toLowerCase() + '=' + value.toLowerCase()).replace('+', '.').replace('/', '_') -} - -function partitionByVideoBids(bidRequests) { - return bidRequests.reduce(function (acc, bid) { - // Fallback to banner ads if nothing specified - if (isVideoRequest(bid)) { - acc[0].push(bid); + const response = buildResponse(bidResponses, ortbResponse, context); + // TODO: we may want to standardize this and move fledge logic to ortbConverter + let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return { + bidId, + config: Object.assign({ + auctionSignals: {}, + }, cfg) + } + }); + return { + bids: response.bids, + fledgeAuctionConfigs, + } } else { - acc[1].push(bid); - } - return acc; - }, [[], []]); -} - -function getMediaTypeFromRequest(serverRequest) { - return /avjp$/.test(serverRequest.url) ? VIDEO : BANNER; -} - -function buildCommonQueryParamsFromBids(bids, bidderRequest) { - const isInIframe = inIframe(); - let defaultParams; - - defaultParams = { - ju: bidderRequest.refererInfo.page, - ch: document.charSet || document.characterSet, - res: `${screen.width}x${screen.height}x${screen.colorDepth}`, - ifr: isInIframe, - tz: new Date().getTimezoneOffset(), - tws: getViewportDimensions(isInIframe), - be: 1, - bc: bids[0].params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, - dddid: _map(bids, bid => bid.transactionId).join(','), - nocache: new Date().getTime() - }; - - const userAgentClientHints = deepAccess(bidderRequest, 'ortb2.device.sua'); - if (userAgentClientHints) { - defaultParams.sua = JSON.stringify(userAgentClientHints); - } - - const userDataSegments = buildFpdQueryParams('user.data', bidderRequest.ortb2); - if (userDataSegments.length > 0) { - defaultParams.sm = userDataSegments; - } - - const siteContentDataSegments = buildFpdQueryParams('site.content.data', bidderRequest.ortb2); - if (siteContentDataSegments.length > 0) { - defaultParams.scsm = siteContentDataSegments; - } - - if (bids[0].params.platform) { - defaultParams.ph = bids[0].params.platform; - } - - if (bidderRequest.gdprConsent) { - let gdprConsentConfig = bidderRequest.gdprConsent; - - if (gdprConsentConfig.consentString !== undefined) { - defaultParams.gdpr_consent = gdprConsentConfig.consentString; - } - - if (gdprConsentConfig.gdprApplies !== undefined) { - defaultParams.gdpr = gdprConsentConfig.gdprApplies ? 1 : 0; + return response.bids } - - if (config.getConfig('consentManagement.cmpApi') === 'iab') { - defaultParams.x_gdpr_f = 1; - } - } - - if (bidderRequest && bidderRequest.uspConsent) { - defaultParams.us_privacy = bidderRequest.uspConsent; - } - - // normalize publisher common id - if (deepAccess(bids[0], 'crumbs.pubcid')) { - deepSetValue(bids[0], 'userId.pubcid', deepAccess(bids[0], 'crumbs.pubcid')); - } - defaultParams = appendUserIdsToQueryParams(defaultParams, bids[0].userId); - - // supply chain support - if (bids[0].schain) { - defaultParams.schain = serializeSupplyChain(bids[0].schain); - } - - return defaultParams; -} - -function buildFpdQueryParams(fpdPath, ortb2) { - const firstPartyData = deepAccess(ortb2, fpdPath); - if (!Array.isArray(firstPartyData) || !firstPartyData.length) { - return ''; - } - const fpd = firstPartyData - .filter( - data => (Array.isArray(data.segment) && - data.segment.length > 0 && - data.name !== undefined && - data.name.length > 0) - ) - .reduce((acc, data) => { - const name = typeof data.ext === 'object' && data.ext.segtax ? `${data.name}/${data.ext.segtax}` : data.name; - acc[name] = (acc[name] || []).concat(data.segment.map(seg => seg.id)); - return acc; - }, {}) - return Object.keys(fpd) - .map((name, _) => name + ':' + fpd[name].join('|')) - .join(',') -} - -function appendUserIdsToQueryParams(queryParams, userIds) { - _each(userIds, (userIdObjectOrValue, userIdProviderKey) => { - const key = USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]; - - if (USER_ID_CODE_TO_QUERY_ARG.hasOwnProperty(userIdProviderKey)) { - switch (userIdProviderKey) { - case 'merkleId': - queryParams[key] = userIdObjectOrValue.id; - break; - case 'uid2': - queryParams[key] = userIdObjectOrValue.id; - break; - case 'lipb': - queryParams[key] = userIdObjectOrValue.lipbid; - if (Array.isArray(userIdObjectOrValue.segments) && userIdObjectOrValue.segments.length > 0) { - const liveIntentSegments = 'liveintent:' + userIdObjectOrValue.segments.join('|'); - queryParams.sm = `${queryParams.sm ? queryParams.sm + ',' : ''}${liveIntentSegments}`; + }, + overrides: { + imp: { + bidfloor(setBidFloor, imp, bidRequest, context) { + // enforce floors should always be in USD + // TODO: does it make sense that request.cur can be any currency, but request.imp[].bidfloorcur must be USD? + const floor = {}; + setBidFloor(floor, bidRequest, {...context, currency: 'USD'}); + if (floor.bidfloorcur === 'USD') { + Object.assign(imp, floor); + } + }, + video(orig, imp, bidRequest, context) { + if (FEATURES.VIDEO) { + // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] + // to populate imp.video + // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + if (imp.video && videoParams?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; } - break; - case 'parrableId': - queryParams[key] = userIdObjectOrValue.eid; - break; - case 'id5id': - queryParams[key] = userIdObjectOrValue.uid; - break; - case 'novatiq': - queryParams[key] = userIdObjectOrValue.snowflake; - break; - default: - queryParams[key] = userIdObjectOrValue; + } } } - }); - - return queryParams; -} - -function serializeSupplyChain(supplyChain) { - return `${supplyChain.ver},${supplyChain.complete}!${serializeSupplyChainNodes(supplyChain.nodes)}`; -} - -function serializeSupplyChainNodes(supplyChainNodes) { - const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + } +}); - return supplyChainNodes.map(supplyChainNode => { - return supplyChainNodePropertyOrder.map(property => supplyChainNode[property] || '') - .join(','); - }).join('!'); +function transformBidParams(params, isOpenRtb) { + return utils.convertTypes({ + 'unit': 'string', + 'customFloor': 'number' + }, params); } -function buildOXBannerRequest(bids, bidderRequest) { - let customParamsForAllBids = []; - let hasCustomParam = false; - let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); - let auids = _map(bids, bid => bid.params.unit); +function isBidRequestValid(bidRequest) { + const hasDelDomainOrPlatform = bidRequest.params.delDomain || + bidRequest.params.platform; - queryParams.aus = _map(bids, bid => parseSizesInput(bid.mediaTypes.banner.sizes).join(',')).join('|'); - queryParams.divids = _map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); - // gpid - queryParams.aucs = _map(bids, function (bid) { - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - return encodeURIComponent(gpid || '') - }).join(','); - - if (auids.some(auid => auid)) { - queryParams.auid = auids.join(','); - } - - if (bids.some(bid => bid.params.doNotTrack)) { - queryParams.ns = 1; + if (utils.deepAccess(bidRequest, 'mediaTypes.banner') && + hasDelDomainOrPlatform) { + return !!bidRequest.params.unit || + utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; } - if (config.getConfig('coppa') === true || bids.some(bid => bid.params.coppa)) { - queryParams.tfcd = 1; - } + return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +} - bids.forEach(function (bid) { - if (bid.params.customParams) { - let customParamsForBid = _map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); - let formattedCustomParams = window.btoa(customParamsForBid.join('&')); - hasCustomParam = true; - customParamsForAllBids.push(formattedCustomParams); - } else { - customParamsForAllBids.push(''); - } +function buildRequests(bids, bidderRequest) { + let videoBids = bids.filter(bid => isVideoBid(bid)); + let bannerBids = bids.filter(bid => isBannerBid(bid)); + let requests = bannerBids.length ? [createRequest(bannerBids, bidderRequest, BANNER)] : []; + videoBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); }); - if (hasCustomParam) { - queryParams.tps = customParamsForAllBids.join(','); - } - - enrichQueryWithFloors(queryParams, BANNER, bids); - - let url = queryParams.ph - ? `https://u.openx.net/w/1.0/arj` - : `https://${bids[0].params.delDomain}/w/1.0/arj`; - - return { - method: 'GET', - url: url, - data: queryParams, - payload: {'bids': bids, 'startTime': new Date()} - }; + return requests; } -function buildOXVideoRequest(bid, bidderRequest) { - let oxVideoParams = generateVideoParameters(bid, bidderRequest); - let url = oxVideoParams.ph - ? `https://u.openx.net/v/1.0/avjp` - : `https://${bid.params.delDomain}/v/1.0/avjp`; +function createRequest(bidRequests, bidderRequest, mediaType) { return { - method: 'GET', - url: url, - data: oxVideoParams, - payload: {'bid': bid, 'startTime': new Date()} - }; -} - -function generateVideoParameters(bid, bidderRequest) { - const videoMediaType = deepAccess(bid, `mediaTypes.video`); - let queryParams = buildCommonQueryParamsFromBids([bid], bidderRequest); - let oxVideoConfig = deepAccess(bid, 'params.video') || {}; - let context = deepAccess(bid, 'mediaTypes.video.context'); - let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - let width; - let height; - - // normalize config for video size - if (isArray(bid.sizes) && bid.sizes.length === 2 && !isArray(bid.sizes[0])) { - width = parseInt(bid.sizes[0], 10); - height = parseInt(bid.sizes[1], 10); - } else if (isArray(bid.sizes) && isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { - width = parseInt(bid.sizes[0][0], 10); - height = parseInt(bid.sizes[0][1], 10); - } else if (isArray(playerSize) && playerSize.length === 2) { - width = parseInt(playerSize[0], 10); - height = parseInt(playerSize[1], 10); - } - - let openRtbParams = {w: width, h: height}; - - // legacy openrtb params could be in video, openrtb, or video.openrtb - let legacyParams = bid.params.video || bid.params.openrtb || {}; - if (legacyParams.openrtb) { - legacyParams = legacyParams.openrtb; - } - // support for video object or full openrtb object - if (isArray(legacyParams.imp)) { - legacyParams = legacyParams.imp[0].video; + method: 'POST', + url: config.getConfig('openxOrtbUrl') || REQUEST_URL, + data: converter.toORTB({bidRequests, bidderRequest, context: {mediaType}}) } - Object.keys(legacyParams) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => openRtbParams[param] = legacyParams[param]); - - // 5.0 openrtb video params - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => openRtbParams[param] = videoMediaType[param]); - - let openRtbReq = { - imp: [ - { - video: openRtbParams - } - ] - }; - - queryParams['openrtb'] = JSON.stringify(openRtbReq); - - queryParams.auid = bid.params.unit; - // override prebid config with openx config if available - queryParams.vwd = width || oxVideoConfig.vwd; - queryParams.vht = height || oxVideoConfig.vht; - - if (context === 'outstream') { - queryParams.vos = '101'; - } - - if (oxVideoConfig.mimes) { - queryParams.vmimes = oxVideoConfig.mimes; - } - - if (bid.params.test) { - queryParams.vtest = 1; - } - - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - if (gpid) { - queryParams.aucs = encodeURIComponent(gpid); - } - - // each video bid makes a separate request - enrichQueryWithFloors(queryParams, VIDEO, [bid]); - - return queryParams; } -function createVideoBidResponses(response, {bid, startTime}) { - let bidResponses = []; - - if (response !== undefined && response.vastUrl !== '' && response.pub_rev > 0) { - let vastQueryParams = parseUrl(response.vastUrl).search || {}; - let bidResponse = {}; - bidResponse.requestId = bid.bidId; - if (response.deal_id) { - bidResponse.dealId = response.deal_id; - } - // default 5 mins - bidResponse.ttl = 300; - // true is net, false is gross - bidResponse.netRevenue = true; - bidResponse.currency = response.currency; - bidResponse.cpm = parseInt(response.pub_rev, 10) / 1000; - bidResponse.width = parseInt(response.width, 10); - bidResponse.height = parseInt(response.height, 10); - bidResponse.creativeId = response.adid; - bidResponse.vastUrl = response.vastUrl; - bidResponse.mediaType = VIDEO; +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} - // enrich adunit with vast parameters - response.ph = vastQueryParams.ph; - response.colo = vastQueryParams.colo; - response.ts = vastQueryParams.ts; +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} - bidResponses.push(bidResponse); +function interpretResponse(resp, req) { + if (!resp.body) { + resp.body = {nbr: 0}; } - - return bidResponses; + return converter.fromORTB({request: req.data, response: resp.body}); } -function enrichQueryWithFloors(queryParams, mediaType, bids) { - let customFloorsForAllBids = []; - let hasCustomFloor = false; - bids.forEach(function (bid) { - let floor = getBidFloor(bid, mediaType); - - if (floor) { - customFloorsForAllBids.push(floor); - hasCustomFloor = true; +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let queryParamStrings = []; + let syncUrl = SYNC_URL; + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + if (responses.length > 0 && responses[0].body && responses[0].body.ext) { + const ext = responses[0].body.ext; + if (ext.delDomain) { + syncUrl = `https://${ext.delDomain}/w/1.0/pd` + } else if (ext.platform) { + queryParamStrings.push('ph=' + ext.platform) + } } else { - customFloorsForAllBids.push(0); + queryParamStrings.push('ph=' + DEFAULT_PH) } - }); - if (hasCustomFloor) { - queryParams.aumfs = customFloorsForAllBids.join(','); + return [{ + type: pixelType, + url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` + }]; } } - -function getBidFloor(bidRequest, mediaType) { - let floorInfo = {}; - const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; - - if (typeof bidRequest.getFloor === 'function') { - floorInfo = bidRequest.getFloor({ - currency: currency, - mediaType: mediaType, - size: '*' - }); - } - let floor = floorInfo.floor || bidRequest.params.customFloor || 0; - - return Math.round(floor * 1000); // normalize to micro currency -} - -registerBidder(spec); diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 0690bf6b4fc..a39aa1580cd 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -9,30 +9,31 @@ Maintainer: team-openx@openx.com # Description Module that connects to OpenX's demand sources. -Note there is an updated version of the OpenX bid adapter called openxOrtbBidAdapter. -Publishers are welcome to test the other adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. +Note that this adapter mirrors openxOrtbBidAdapter and any updates must be +completed in both adapters. +openxOrtbBidAdapter will be removed in a future release and should not be used. +Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. # Bid Parameters ## Banner -| Name | Scope | Type | Description | Example | -|---------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------| -| `delDomain` ~~or `platform`~~** | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | -| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` | -| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue

Note: OpenX suggests using the [Price Floor Module](https://docs.prebid.org/dev-docs/modules/floors.html) instead of customFloor. The Price Floor Module is prioritized over customFloor if both are present. | 1.50 | -| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true | -| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true | - -** platform is deprecated. Please use delDomain instead. If you have any questions please contact your representative. +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `delDomain` or `platform` | required | String | OpenX delivery domain or platform id provided by your OpenX representative. | "PUBLISHER-d.openx.net" or "555not5a-real-plat-form-id0123456789" +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` +| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 +| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. Use of `pbjs.setConfig({coppa: true});` is now preferred. | true ## Video -| Name | Scope | Type | Description | Example | -|-------------|----------|--------------------|--------------------------------------------------------------|----------------------------------------------------------------| -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | -| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | -| `openrtb` | optional | OpenRTB Impression | An OpenRtb Impression with Video subtype properties | `{ imp: [{ video: {mimes: ['video/x-ms-wmv, video/mp4']} }] }` | +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" +| `video` | optional | OpenRTB video subtypes | Use of adUnit.mediaTypes.video is now preferred. | `{ video: {mimes: ['video/mp4']}` + # Example ```javascript @@ -70,7 +71,8 @@ var adUnits = [ mediaTypes: { video: { playerSize: [640, 480], - context: 'instream' + context: 'instream', + mimes: ['video/x-ms-wmv, video/mp4'] } }, bids: [{ @@ -79,10 +81,10 @@ var adUnits = [ unit: '1611023124', delDomain: 'PUBLISHER-d.openx.net', video: { - mimes: ['video/x-ms-wmv, video/mp4'] + mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }] + }]p } ]; ``` diff --git a/modules/openxOrtbBidAdapter.js b/modules/openxOrtbBidAdapter.js index e550423094f..5afee034d5f 100644 --- a/modules/openxOrtbBidAdapter.js +++ b/modules/openxOrtbBidAdapter.js @@ -29,9 +29,6 @@ const converter = ortbConverter({ }, imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); - if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') { - imp.video.placement = imp.video.placement || 4; - } mergeDeep(imp, { tagid: bidRequest.params.unit, ext: { @@ -55,6 +52,9 @@ const converter = ortbConverter({ } }) const bid = context.bidRequests[0]; + if (bid.params.coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); + } if (bid.params.doNotTrack) { utils.deepSetValue(req, 'device.dnt', 1); } @@ -127,6 +127,22 @@ const converter = ortbConverter({ if (floor.bidfloorcur === 'USD') { Object.assign(imp, floor); } + }, + video(orig, imp, bidRequest, context) { + if (FEATURES.VIDEO) { + // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] + // to populate imp.video + // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + if (imp.video && videoParams?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; + } + } } } } diff --git a/modules/openxOrtbBidAdapter.md b/modules/openxOrtbBidAdapter.md index fd926b27b9f..b5e1820021a 100644 --- a/modules/openxOrtbBidAdapter.md +++ b/modules/openxOrtbBidAdapter.md @@ -1,15 +1,17 @@ # Overview ``` -Module Name: OpenX OpenRTB Bidder Adapter +Module Name: OpenX Bidder Adapter Module Type: Bidder Adapter Maintainer: team-openx@openx.com ``` # Description +DEPRECATED. Use openxBidAdapter. -This is an updated version of the OpenX bid adapter which calls our new serving architecture. -Publishers are welcome to test this adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. +This adapter was originally an adapter used to test OpenX serving architecture changes. +This adapter now mirrors openxBidAdapter and this adapter will be removed in a future release. Please use openxBidAdapter. +Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. # Bid Parameters ## Banner @@ -21,7 +23,7 @@ Publishers are welcome to test this adapter and give feedback. Please note you s | `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` | `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 | `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true -| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. Use of `pbjs.setConfig({coppa: true});` is now preferred. | true ## Video @@ -29,7 +31,7 @@ Publishers are welcome to test this adapter and give feedback. Please note you s | ---- | ----- | ---- | ----------- | ------- | `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" -| `video` | optional | OpenRTB video subtypes | Alternatively can be added under adUnit.mediaTypes.video | `{ video: {mimes: ['video/mp4']}` +| `video` | optional | OpenRTB video subtypes | Use of adUnit.mediaTypes.video is now preferred. | `{ video: {mimes: ['video/mp4']}` # Example @@ -68,7 +70,8 @@ var adUnits = [ mediaTypes: { video: { playerSize: [640, 480], - context: 'instream' + context: 'instream', + mimes: ['video/x-ms-wmv, video/mp4'] } }, bids: [{ @@ -77,10 +80,10 @@ var adUnits = [ unit: '1611023124', delDomain: 'PUBLISHER-d.openx.net', video: { - mimes: ['video/x-ms-wmv, video/mp4'] + mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }] + }]p } ]; ``` diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index aa548debf32..48c6246ce6b 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -228,7 +228,7 @@ function buildOpenRtbBidRequest(bidRequest, bidderRequest) { // build OpenRTB request body const payload = { id: bidderRequest.auctionId, - tmax: bidderRequest.timeout || config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, test: config.getConfig('debug') ? 1 : 0, imp: createImp(bidRequest), device: getDevice(), diff --git a/modules/orbitsoftBidAdapter.js b/modules/orbitsoftBidAdapter.js index d72a8719bd8..4c3f2e38c58 100644 --- a/modules/orbitsoftBidAdapter.js +++ b/modules/orbitsoftBidAdapter.js @@ -1,6 +1,5 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; const BIDDER_CODE = 'orbitsoft'; let styleParamsMap = { @@ -122,7 +121,7 @@ export const spec = { const HEIGHT = serverBody.height; const CREATIVE = serverBody.content_url; const CALLBACK_UID = serverBody.callback_uid; - const TIME_TO_LIVE = config.getConfig('_bidderTimeout'); + const TIME_TO_LIVE = 60; const REFERER = utils.getWindowTop(); let bidRequest = request.bidRequest; if (CPM > 0 && WIDTH > 0 && HEIGHT > 0) { diff --git a/modules/pairIdSystem.js b/modules/pairIdSystem.js new file mode 100644 index 00000000000..4d73d4efb7e --- /dev/null +++ b/modules/pairIdSystem.js @@ -0,0 +1,79 @@ +/** + * This module adds PAIR Id to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/pairIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js' +import { logError } from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; + +const MODULE_NAME = 'pairId'; +const PAIR_ID_KEY = 'pairId'; +const DEFAULT_LIVERAMP_PAIR_ID_KEY = '_lr_pairId'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); + +function pairIdFromLocalStorage(key) { + return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(key) : null; +} + +function pairIdFromCookie(key) { + return storage.cookiesAreEnabled ? storage.getCookie(key) : null; +} + +/** @type {Submodule} */ +export const pairIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param { string | undefined } value + * @returns {{pairId:string} | undefined } + */ + decode(value) { + return value && Array.isArray(value) ? {'pairId': value} : undefined + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @returns {id: string | undefined } + */ + getId(config) { + const pairIdsString = pairIdFromLocalStorage(PAIR_ID_KEY) || pairIdFromCookie(PAIR_ID_KEY) + let ids = [] + if (pairIdsString && typeof pairIdsString == 'string') { + try { + ids = ids.concat(JSON.parse(atob(pairIdsString))) + } catch (error) { + logError(error) + } + } + + const configParams = (config && config.params) || {}; + if (configParams && configParams.liveramp) { + let LRStorageLocation = configParams.liveramp.storageKey || DEFAULT_LIVERAMP_PAIR_ID_KEY + const liverampValue = pairIdFromLocalStorage(LRStorageLocation) || pairIdFromCookie(LRStorageLocation) + try { + const obj = JSON.parse(atob(liverampValue)); + ids = ids.concat(obj.envelope); + } catch (error) { + logError(error) + } + } + + if (ids.length == 0) { + logError('PairId not found.') + return undefined; + } + return {'id': ids}; + } +}; + +submodule('userId', pairIdSubmodule); diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 4a777097914..01ffec3c249 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -14,6 +14,7 @@ import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {uspDataHandler} from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const PARRABLE_URL = 'https://h.parrable.com/prebid'; const PARRABLE_COOKIE_NAME = '_parrable_id'; @@ -22,8 +23,9 @@ const LEGACY_ID_COOKIE_NAME = '_parrable_eid'; 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 MODULE_NAME = 'parrableId'; -const storage = getStorageManager({gvlid: PARRABLE_GVLID}); +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); function getExpirationDate() { const oneYearFromNow = new Date(timestamp() + ONE_YEAR_MS); @@ -336,7 +338,7 @@ export const parrableIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'parrableId', + name: MODULE_NAME, /** * Global Vendor List ID * @type {number} diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 7e53a0f5271..ab827f2b6a5 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -10,6 +10,7 @@ import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse, prefixLog} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'permutive' @@ -20,7 +21,7 @@ export const PERMUTIVE_STANDARD_KEYWORD = 'p_standard' export const PERMUTIVE_CUSTOM_COHORTS_KEYWORD = 'permutive' export const PERMUTIVE_STANDARD_AUD_KEYWORD = 'p_standard_aud' -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME}) function init(moduleConfig, userConsent) { readPermutiveModuleConfigFromCache() diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index 908be5400e4..b877918d16d 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -2,13 +2,14 @@ import { generateUUID, getParameterByName, logError, parseUrl, logInfo } from '. import {ajaxBuilder} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import CONSTANTS from '../src/constants.json'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager */ -export const storage = getStorageManager({gvlid: undefined, moduleName: 'prebidmanager'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'prebidmanager'}); const DEFAULT_EVENT_URL = 'https://endpt.prebidmanager.com/endpoint'; const analyticsType = 'endpoint'; const analyticsName = 'Prebid Manager Analytics'; diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index a32e26ef6c2..ba24190322e 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -7,12 +7,15 @@ import { logMessage, parseUrl, buildUrl, triggerPixel, generateUUID, isArray } f import { config } from '../src/config.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; -import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import {getGlobal} from '../src/prebidGlobal.js'; -const storage = getStorageManager({moduleName: 'pubCommonId', gvlid: VENDORLESS_GVLID}); +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'pubCommonId'}); +// register our GVL ID directly, since this is not a "real" user ID module we don't have a spec where to declare it +GDPR_GVLIDS.register(MODULE_TYPE_UID, 'pubCommonId', VENDORLESS_GVLID); const ID_NAME = '_pubcid'; const OPTOUT_NAME = '_pubcid_optout'; diff --git a/modules/publinkIdSystem.js b/modules/publinkIdSystem.js index 9d5645a38cb..6a5504b5ba0 100644 --- a/modules/publinkIdSystem.js +++ b/modules/publinkIdSystem.js @@ -10,13 +10,14 @@ import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import { parseUrl, buildUrl, logError } from '../src/utils.js'; import {uspDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'publinkId'; const GVLID = 24; const PUBLINK_COOKIE = '_publink'; const PUBLINK_S2S_COOKIE = '_publink_srv'; -export const storage = getStorageManager({gvlid: GVLID}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); function isHex(s) { return /^[A-F0-9]+$/i.test(s); diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index a4a13c56a68..9e2a5b1cfeb 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -291,6 +291,17 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { }, []) } +function getSizesForAdUnit(adUnit) { + var bid = Object.values(adUnit.bids).filter((bid) => !!bid.bidResponse && bid.bidResponse.mediaType === 'native')[0]; + if (!!bid || (bid === undefined && adUnit.dimensions.length === 0)) { + return ['1x1']; + } else { + return adUnit.dimensions.map(function (e) { + return e[0] + 'x' + e[1]; + }) + } +} + function getAdUnitAdFormats(adUnit) { var af = adUnit ? Object.keys(adUnit.mediaTypes || {}).map(format => MEDIATYPE[format.toUpperCase()]) : []; return af; @@ -346,7 +357,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'sn': adUnitId, 'au': origAdUnit.adUnitId || adUnitId, 'mt': getAdUnitAdFormats(origAdUnit), - 'sz': adUnit.dimensions.map(e => e[0] + 'x' + e[1]), + 'sz': getSizesForAdUnit(adUnit, adUnitId), 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)), 'fskp': floorData ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, }; diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 5ca8a2c04b3..edfee7a0b6d 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,10 +1,10 @@ -import { getBidRequest, logWarn, _each, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques } from '../src/utils.js'; +import { getBidRequest, logWarn, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques, isPlainObject, isInteger } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE, ADPOD } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { bidderSettings } from '../src/bidderSettings.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import CONSTANTS from '../src/constants.json'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; @@ -20,6 +20,7 @@ const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; + const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url 'gender': '', // User gender @@ -55,56 +56,11 @@ const VIDEO_CUSTOM_PARAMS = { 'skip': DATA_TYPES.NUMBER } -const NATIVE_ASSETS = { - 'TITLE': { ID: 1, KEY: 'title', TYPE: 0 }, - 'IMAGE': { ID: 2, KEY: 'image', TYPE: 0 }, - 'ICON': { ID: 3, KEY: 'icon', TYPE: 0 }, - 'SPONSOREDBY': { ID: 4, KEY: 'sponsoredBy', TYPE: 1 }, // please note that type of SPONSORED is also 1 - 'BODY': { ID: 5, KEY: 'body', TYPE: 2 }, // please note that type of DESC is also set to 2 - 'CLICKURL': { ID: 6, KEY: 'clickUrl', TYPE: 0 }, - 'VIDEO': { ID: 7, KEY: 'video', TYPE: 0 }, - 'EXT': { ID: 8, KEY: 'ext', TYPE: 0 }, - 'DATA': { ID: 9, KEY: 'data', TYPE: 0 }, - 'LOGO': { ID: 10, KEY: 'logo', TYPE: 0 }, - 'SPONSORED': { ID: 11, KEY: 'sponsored', TYPE: 1 }, // please note that type of SPONSOREDBY is also set to 1 - 'DESC': { ID: 12, KEY: 'data', TYPE: 2 }, // please note that type of BODY is also set to 2 - 'RATING': { ID: 13, KEY: 'rating', TYPE: 3 }, - 'LIKES': { ID: 14, KEY: 'likes', TYPE: 4 }, - 'DOWNLOADS': { ID: 15, KEY: 'downloads', TYPE: 5 }, - 'PRICE': { ID: 16, KEY: 'price', TYPE: 6 }, - 'SALEPRICE': { ID: 17, KEY: 'saleprice', TYPE: 7 }, - 'PHONE': { ID: 18, KEY: 'phone', TYPE: 8 }, - 'ADDRESS': { ID: 19, KEY: 'address', TYPE: 9 }, - 'DESC2': { ID: 20, KEY: 'desc2', TYPE: 10 }, - 'DISPLAYURL': { ID: 21, KEY: 'displayurl', TYPE: 11 }, - 'CTA': { ID: 22, KEY: 'cta', TYPE: 12 } -}; - const NATIVE_ASSET_IMAGE_TYPE = { 'ICON': 1, - 'LOGO': 2, 'IMAGE': 3 } -// check if title, image can be added with mandatory field default values -const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ - { - id: NATIVE_ASSETS.SPONSOREDBY.ID, - required: true, - data: { - type: 1 - } - }, - { - id: NATIVE_ASSETS.TITLE.ID, - required: true, - }, - { - id: NATIVE_ASSETS.IMAGE.ID, - required: true, - } -] - const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', @@ -175,16 +131,9 @@ const MEDIATYPE = [ let publisherId = 0; let isInvalidNativeRequest = false; -let NATIVE_ASSET_ID_TO_KEY_MAP = {}; -let NATIVE_ASSET_KEY_TO_ASSET_MAP = {}; let biddersList = ['pubmatic']; const allBiddersList = ['all']; -// loading NATIVE_ASSET_ID_TO_KEY_MAP -_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); -// loading NATIVE_ASSET_KEY_TO_ASSET_MAP -_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); - export function _getDomainFromURL(url) { let anchor = document.createElement('a'); anchor.href = url; @@ -355,133 +304,189 @@ function _checkParamDataType(key, value, datatype) { return UNDEFINED; } -function _commonNativeRequestObject(nativeAsset, params) { - var key = nativeAsset.KEY; - return { - id: nativeAsset.ID, - required: params[key].required ? 1 : 0, - data: { - type: nativeAsset.TYPE, - len: params[key].len, - ext: params[key].ext - } - }; -} +// TODO delete this code when removing native 1.1 support +const PREBID_NATIVE_DATA_KEYS_TO_ORTB = { + 'desc': 'desc', + 'desc2': 'desc2', + 'body': 'desc', + 'body2': 'desc2', + 'sponsoredBy': 'sponsored', + 'cta': 'ctatext', + 'rating': 'rating', + 'address': 'address', + 'downloads': 'downloads', + 'likes': 'likes', + 'phone': 'phone', + 'price': 'price', + 'salePrice': 'saleprice', + 'displayUrl': 'displayurl', + 'saleprice': 'saleprice', + 'displayurl': 'displayurl' +}; -function _createNativeRequest(params) { - var nativeRequestObject = { +const { NATIVE_IMAGE_TYPES, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS, NATIVE_ASSET_TYPES } = CONSTANTS; +const PREBID_NATIVE_DATA_KEY_VALUES = Object.values(PREBID_NATIVE_DATA_KEYS_TO_ORTB); + +// TODO remove this function when the support for 1.1 is removed +/** + * Copy of the function toOrtbNativeRequest from core native.js to handle the title len/length + * and ext and mimes parameters from legacy assets. + * @param {object} legacyNativeAssets + * @returns an OpenRTB format of the same bid request + */ +export function toOrtbNativeRequest(legacyNativeAssets) { + if (!legacyNativeAssets && !isPlainObject(legacyNativeAssets)) { + logWarn(`${LOG_WARN_PREFIX}: Native assets object is empty or not an object: ${legacyNativeAssets}`); + isInvalidNativeRequest = true; + return; + } + const ortb = { + ver: '1.2', assets: [] }; - for (var key in params) { - if (params.hasOwnProperty(key)) { - var assetObj = {}; - if (!(nativeRequestObject.assets && nativeRequestObject.assets.length > 0 && nativeRequestObject.assets.hasOwnProperty(key))) { - switch (key) { - case NATIVE_ASSETS.TITLE.KEY: - assetObj = { - id: NATIVE_ASSETS.TITLE.ID, - required: params[key].required ? 1 : 0, - title: { - len: params[key].len || params[key].length, - ext: params[key].ext - } - }; - break; - case NATIVE_ASSETS.IMAGE.KEY: - assetObj = { - id: NATIVE_ASSETS.IMAGE.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.IMAGE, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), - wmin: params[key].wmin || params[key].minimumWidth || (params[key].minsizes ? params[key].minsizes[0] : UNDEFINED), - hmin: params[key].hmin || params[key].minimumHeight || (params[key].minsizes ? params[key].minsizes[1] : UNDEFINED), - mimes: params[key].mimes, - ext: params[key].ext, - } - }; - break; - case NATIVE_ASSETS.ICON.KEY: - assetObj = { - id: NATIVE_ASSETS.ICON.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.ICON, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), - } - }; - break; - case NATIVE_ASSETS.VIDEO.KEY: - assetObj = { - id: NATIVE_ASSETS.VIDEO.ID, - required: params[key].required ? 1 : 0, - video: { - minduration: params[key].minduration, - maxduration: params[key].maxduration, - protocols: params[key].protocols, - mimes: params[key].mimes, - ext: params[key].ext - } - }; - break; - case NATIVE_ASSETS.EXT.KEY: - assetObj = { - id: NATIVE_ASSETS.EXT.ID, - required: params[key].required ? 1 : 0, - }; - break; - case NATIVE_ASSETS.LOGO.KEY: - assetObj = { - id: NATIVE_ASSETS.LOGO.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.LOGO, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED) - } - }; - break; - case NATIVE_ASSETS.SPONSOREDBY.KEY: - case NATIVE_ASSETS.BODY.KEY: - case NATIVE_ASSETS.RATING.KEY: - case NATIVE_ASSETS.LIKES.KEY: - case NATIVE_ASSETS.DOWNLOADS.KEY: - case NATIVE_ASSETS.PRICE.KEY: - case NATIVE_ASSETS.SALEPRICE.KEY: - case NATIVE_ASSETS.PHONE.KEY: - case NATIVE_ASSETS.ADDRESS.KEY: - case NATIVE_ASSETS.DESC2.KEY: - case NATIVE_ASSETS.DISPLAYURL.KEY: - case NATIVE_ASSETS.CTA.KEY: - assetObj = _commonNativeRequestObject(NATIVE_ASSET_KEY_TO_ASSET_MAP[key], params); - break; + for (let key in legacyNativeAssets) { + // skip conversion for non-asset keys + if (NATIVE_KEYS_THAT_ARE_NOT_ASSETS.includes(key)) continue; + if (!NATIVE_KEYS.hasOwnProperty(key) && !PREBID_NATIVE_DATA_KEY_VALUES.includes(key)) { + logWarn(`${LOG_WARN_PREFIX}: Unrecognized native asset code: ${key}. Asset will be ignored.`); + continue; + } + + const asset = legacyNativeAssets[key]; + let required = 0; + if (asset.required && isBoolean(asset.required)) { + required = Number(asset.required); + } + const ortbAsset = { + id: ortb.assets.length, + required + }; + // data cases + if (key in PREBID_NATIVE_DATA_KEYS_TO_ORTB) { + ortbAsset.data = { + type: NATIVE_ASSET_TYPES[PREBID_NATIVE_DATA_KEYS_TO_ORTB[key]] + } + if (asset.len || asset.length) { + ortbAsset.data.len = asset.len || asset.length; + } + if (asset.ext) { + ortbAsset.data.ext = asset.ext; + } + // icon or image case + } else if (key === 'icon' || key === 'image') { + ortbAsset.img = { + type: key === 'icon' ? NATIVE_IMAGE_TYPES.ICON : NATIVE_IMAGE_TYPES.MAIN, + } + // if min_width and min_height are defined in aspect_ratio, they are preferred + if (asset.aspect_ratios) { + if (!isArray(asset.aspect_ratios)) { + logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios was passed, but it's not a an array: ${asset.aspect_ratios}`); + } else if (!asset.aspect_ratios.length) { + logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios was passed, but it's empty: ${asset.aspect_ratios}`); + } else { + const { min_width: minWidth, min_height: minHeight } = asset.aspect_ratios[0]; + if (!isInteger(minWidth) || !isInteger(minHeight)) { + logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios min_width or min_height are invalid: ${minWidth}, ${minHeight}`); + } else { + ortbAsset.img.wmin = minWidth; + ortbAsset.img.hmin = minHeight; + } + const aspectRatios = asset.aspect_ratios + .filter((ar) => ar.ratio_width && ar.ratio_height) + .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`); + if (aspectRatios.length > 0) { + ortbAsset.img.ext = { + aspectratios: aspectRatios + } + } + } + } + + ortbAsset.img.w = asset.w || asset.width; + ortbAsset.img.h = asset.h || asset.height; + ortbAsset.img.wmin = asset.wmin || asset.minimumWidth || (asset.minsizes ? asset.minsizes[0] : UNDEFINED); + ortbAsset.img.hmin = asset.hmin || asset.minimumHeight || (asset.minsizes ? asset.minsizes[1] : UNDEFINED); + + // if asset.sizes exist, by OpenRTB spec we should remove wmin and hmin + if (asset.sizes) { + if (asset.sizes.length !== 2 || !isInteger(asset.sizes[0]) || !isInteger(asset.sizes[1])) { + logWarn(`${LOG_WARN_PREFIX}: image.sizes was passed, but its value is not an array of integers: ${asset.sizes}`); + } else { + logInfo(`${LOG_WARN_PREFIX}: if asset.sizes exist, by OpenRTB spec we should remove wmin and hmin`); + ortbAsset.img.w = asset.sizes[0]; + ortbAsset.img.h = asset.sizes[1]; + delete ortbAsset.img.hmin; + delete ortbAsset.img.wmin; } } + asset.ext && (ortbAsset.img.ext = asset.ext); + asset.mimes && (ortbAsset.img.mimes = asset.mimes); + // title case + } else if (key === 'title') { + ortbAsset.title = { + // in openRTB, len is required for titles, while in legacy prebid was not. + // for this reason, if len is missing in legacy prebid, we're adding a default value of 140. + len: asset.len || asset.length || 140 + } + asset.ext && (ortbAsset.title.ext = asset.ext); + // all extensions to the native bid request are passed as is + } else if (key === 'ext') { + ortbAsset.ext = asset; + // in `ext` case, required field is not needed + delete ortbAsset.required; } - if (assetObj && assetObj.id) { - nativeRequestObject.assets[nativeRequestObject.assets.length] = assetObj; + ortb.assets.push(ortbAsset); + } + + if (ortb.assets.length < 1) { + logWarn(`${LOG_WARN_PREFIX}: Could not find any valid asset`); + isInvalidNativeRequest = true; + return; + } + + return ortb; +} +// TODO delete this code when removing native 1.1 support + +function _createNativeRequest(params) { + var nativeRequestObject; + + // TODO delete this code when removing native 1.1 support + if (!params.ortb) { // legacy assets definition found + nativeRequestObject = toOrtbNativeRequest(params); + } else { // ortb assets definition found + params = params.ortb; + // TODO delete this code when removing native 1.1 support + nativeRequestObject = { ver: '1.2', ...params, assets: [] }; + const { assets } = params; + + const isValidAsset = (asset) => asset.title || asset.img || asset.data || asset.video; + + if (assets.length < 1 || !assets.some(asset => isValidAsset(asset))) { + logWarn(`${LOG_WARN_PREFIX}: Native assets object is empty or contains some invalid object`); + isInvalidNativeRequest = true; + return nativeRequestObject; } - }; - // for native image adtype prebid has to have few required assests i.e. title,sponsoredBy, image - // if any of these are missing from the request then request will not be sent - var requiredAssetCount = NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.length; - var presentrequiredAssetCount = 0; - NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.forEach(ele => { - var lengthOfExistingAssets = nativeRequestObject.assets.length; - for (var i = 0; i < lengthOfExistingAssets; i++) { - if (ele.id == nativeRequestObject.assets[i].id) { - presentrequiredAssetCount++; - break; + assets.forEach(asset => { + var assetObj = asset; + if (assetObj.img) { + if (assetObj.img.type == NATIVE_ASSET_IMAGE_TYPE.IMAGE) { + assetObj.w = assetObj.w || assetObj.width || (assetObj.sizes ? assetObj.sizes[0] : UNDEFINED); + assetObj.h = assetObj.h || assetObj.height || (assetObj.sizes ? assetObj.sizes[1] : UNDEFINED); + assetObj.wmin = assetObj.wmin || assetObj.minimumWidth || (assetObj.minsizes ? assetObj.minsizes[0] : UNDEFINED); + assetObj.hmin = assetObj.hmin || assetObj.minimumHeight || (assetObj.minsizes ? assetObj.minsizes[1] : UNDEFINED); + } else if (assetObj.img.type == NATIVE_ASSET_IMAGE_TYPE.ICON) { + assetObj.w = assetObj.w || assetObj.width || (assetObj.sizes ? assetObj.sizes[0] : UNDEFINED); + assetObj.h = assetObj.h || assetObj.height || (assetObj.sizes ? assetObj.sizes[1] : UNDEFINED); + } + } + + if (assetObj && assetObj.id !== undefined && isValidAsset(assetObj)) { + nativeRequestObject.assets.push(assetObj); } } - }); - if (requiredAssetCount == presentrequiredAssetCount) { - isInvalidNativeRequest = false; - } else { - isInvalidNativeRequest = true; + ); } return nativeRequestObject; } @@ -529,7 +534,7 @@ function _createBannerRequest(bid) { export function checkVideoPlacement(videoData, adUnitCode) { // Check for video.placement property. If property is missing display log message. - if (!deepAccess(videoData, 'placement')) { + if (FEATURES.VIDEO && !deepAccess(videoData, 'placement')) { logWarn(MSG_VIDEO_PLACEMENT_MISSING + ' for ' + adUnitCode); }; } @@ -538,7 +543,7 @@ function _createVideoRequest(bid) { var videoData = mergeDeep(deepAccess(bid.mediaTypes, 'video'), bid.params.video); var videoObj; - if (videoData !== UNDEFINED) { + if (FEATURES.VIDEO && videoData !== UNDEFINED) { videoObj = {}; checkVideoPlacement(videoData, bid.adUnitCode); for (var key in VIDEO_CUSTOM_PARAMS) { @@ -624,7 +629,7 @@ function _addJWPlayerSegmentData(imp, bid) { ext && ext.key_val === undefined ? ext.key_val = jwPlayerData : ext.key_val += '|' + jwPlayerData; } -function _createImpressionObject(bid, conf) { +function _createImpressionObject(bid) { var impObj = {}; var bannerObj; var videoObj; @@ -657,14 +662,18 @@ function _createImpressionObject(bid, conf) { } break; case NATIVE: + // TODO uncomment below line when removing native 1.1 support + // nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeOrtbRequest)); + // TODO delete below line when removing native 1.1 support nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeParams)); if (!isInvalidNativeRequest) { impObj.native = nativeObj; } else { logWarn(LOG_WARN_PREFIX + 'Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); + isInvalidNativeRequest = false; } break; - case VIDEO: + case FEATURES.VIDEO && VIDEO: videoObj = _createVideoRequest(bid); if (videoObj !== UNDEFINED) { impObj.video = videoObj; @@ -700,7 +709,7 @@ function _createImpressionObject(bid, conf) { return impObj.hasOwnProperty(BANNER) || impObj.hasOwnProperty(NATIVE) || - impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; + (FEATURES.VIDEO && impObj.hasOwnProperty(VIDEO)) ? impObj : UNDEFINED; } function _addImpressionFPD(imp, bid) { @@ -801,7 +810,7 @@ function _checkMediaType(bid, newBid) { var videoRegex = new RegExp(/VAST\s+version/); if (adm.indexOf('span class="PubAPIAd"') >= 0) { newBid.mediaType = BANNER; - } else if (videoRegex.test(adm)) { + } else if (FEATURES.VIDEO && videoRegex.test(adm)) { newBid.mediaType = VIDEO; } else { try { @@ -817,7 +826,6 @@ function _checkMediaType(bid, newBid) { } function _parseNativeResponse(bid, newBid) { - newBid.native = {}; if (bid.hasOwnProperty('adm')) { var adm = ''; try { @@ -826,53 +834,15 @@ function _parseNativeResponse(bid, newBid) { logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); return; } - if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { - newBid.mediaType = NATIVE; - for (let i = 0, len = adm.native.assets.length; i < len; i++) { - switch (adm.native.assets[i].id) { - case NATIVE_ASSETS.TITLE.ID: - newBid.native.title = adm.native.assets[i].title && adm.native.assets[i].title.text; - break; - case NATIVE_ASSETS.IMAGE.ID: - newBid.native.image = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; - break; - case NATIVE_ASSETS.ICON.ID: - newBid.native.icon = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; - break; - case NATIVE_ASSETS.SPONSOREDBY.ID: - case NATIVE_ASSETS.BODY.ID: - case NATIVE_ASSETS.LIKES.ID: - case NATIVE_ASSETS.DOWNLOADS.ID: - case NATIVE_ASSETS.PRICE: - case NATIVE_ASSETS.SALEPRICE.ID: - case NATIVE_ASSETS.PHONE.ID: - case NATIVE_ASSETS.ADDRESS.ID: - case NATIVE_ASSETS.DESC2.ID: - case NATIVE_ASSETS.CTA.ID: - case NATIVE_ASSETS.RATING.ID: - case NATIVE_ASSETS.DISPLAYURL.ID: - newBid.native[NATIVE_ASSET_ID_TO_KEY_MAP[adm.native.assets[i].id]] = adm.native.assets[i].data && adm.native.assets[i].data.value; - break; - } - } - newBid.native.clickUrl = adm.native.link && adm.native.link.url; - newBid.native.clickTrackers = (adm.native.link && adm.native.link.clicktrackers) || []; - newBid.native.impressionTrackers = adm.native.imptrackers || []; - newBid.native.jstracker = adm.native.jstracker || []; - if (!newBid.width) { - newBid.width = DEFAULT_WIDTH; - } - if (!newBid.height) { - newBid.height = DEFAULT_HEIGHT; - } + newBid.native = { + ortb: { ...adm.native } + }; + newBid.mediaType = NATIVE; + if (!newBid.width) { + newBid.width = DEFAULT_WIDTH; + } + if (!newBid.height) { + newBid.height = DEFAULT_HEIGHT; } } } @@ -926,7 +896,10 @@ function _assignRenderer(newBid, request) { for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === newBid.requestId) { bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; - context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; + + if (FEATURES.VIDEO) { + context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; + } adUnitCode = request.bidderRequest.bids[bidderRequestBidsIndex].adUnitCode; } } @@ -946,7 +919,7 @@ function _assignRenderer(newBid, request) { * @returns */ export function assignDealTier(newBid, bid, request) { - if (!bid?.ext?.prebiddealpriority) return; + if (!bid?.ext?.prebiddealpriority || !FEATURES.VIDEO) return; const bidRequest = getBidRequest(newBid.requestId, [request.bidderRequest]); const videoObj = deepAccess(bidRequest, 'mediaTypes.video'); if (videoObj?.context != ADPOD) return; @@ -1029,7 +1002,7 @@ export const spec = { return false; } // video ad validation - if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (FEATURES.VIDEO && bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { // bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array let mediaTypesVideoMimes = deepAccess(bid.mediaTypes, 'video.mimes'); let paramsVideoMimes = deepAccess(bid, 'params.video.mimes'); @@ -1075,7 +1048,7 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + // validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -1121,7 +1094,7 @@ export const spec = { if (bid.params.hasOwnProperty('acat') && isArray(bid.params.acat)) { allowedIabCategories = allowedIabCategories.concat(bid.params.acat); } - var impObj = _createImpressionObject(bid, conf); + var impObj = _createImpressionObject(bid); if (impObj) { payload.imp.push(impObj); } @@ -1241,7 +1214,7 @@ export const spec = { // bidderRequest has timeout property if publisher sets during calling requestBids function from page // if not bidderRequest contains global value set by Prebid if (bidderRequest?.timeout) { - payload.tmax = bidderRequest.timeout || config.getConfig('bidderTimeout'); + payload.tmax = bidderRequest.timeout; } else { payload.tmax = window?.PWT?.versionDetails?.timeout; } @@ -1314,7 +1287,7 @@ export const spec = { switch (newBid.mediaType) { case BANNER: break; - case VIDEO: + case FEATURES.VIDEO && VIDEO: newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; newBid.vastXml = bid.adm; diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index 0a775b07b7f..6aed462f2d5 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -3,8 +3,10 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +const MODULE_CODE = 'pubwise'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); /**** * PubWise.io Analytics @@ -334,7 +336,7 @@ pubwiseAnalytics.enableAnalytics = function (config) { adapterManager.registerAnalyticsAdapter({ adapter: pubwiseAnalytics, - code: 'pubwise', + code: MODULE_CODE, gvlid: 842 }); diff --git a/modules/pubxBidAdapter.js b/modules/pubxBidAdapter.js index 17c4ba3848e..ee28d549475 100644 --- a/modules/pubxBidAdapter.js +++ b/modules/pubxBidAdapter.js @@ -1,4 +1,4 @@ -import { deepSetValue } from '../src/utils.js'; +import { deepSetValue, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'pubx'; @@ -16,8 +16,11 @@ export const spec = { const bidId = bidRequest.bidId; const params = bidRequest.params; const sid = params.sid; + const pageUrl = deepAccess(bidRequest, 'ortb2.site.page').replace(/\?.*$/, ''); + const pageEnc = encodeURIComponent(pageUrl); const payload = { - sid: sid + sid: sid, + pu: pageEnc, }; return { id: bidId, diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 19a559edfda..2c721a61616 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -22,7 +22,7 @@ export const QUANTCAST_PROTOCOL = 'https'; export const QUANTCAST_PORT = '8443'; export const QUANTCAST_FPA = '__qca'; -export const storage = getStorageManager({gvlid: QUANTCAST_VENDOR_ID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); function makeVideoImp(bid) { const videoInMediaType = deepAccess(bid, 'mediaTypes.video') || {}; diff --git a/modules/quantcastIdSystem.js b/modules/quantcastIdSystem.js index 97cd01f98da..6a07082b61c 100644 --- a/modules/quantcastIdSystem.js +++ b/modules/quantcastIdSystem.js @@ -6,9 +6,10 @@ */ import {submodule} from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import { triggerPixel, logInfo } from '../src/utils.js'; import { uspDataHandler, coppaDataHandler, gdprDataHandler } from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const QUANTCAST_FPA = '__qca'; const DEFAULT_COOKIE_EXP_DAYS = 392; // (13 months - 2 days) @@ -23,8 +24,9 @@ const QC_TCF_CONSENT_FIRST_PURPOSES = [PURPOSE_DATA_COLLECT]; const QC_TCF_CONSENT_ONLY_PUPROSES = [PURPOSE_DATA_COLLECT]; const GDPR_PRIVACY_STRING = gdprDataHandler.getConsentData(); const US_PRIVACY_STRING = uspDataHandler.getConsentData(); +const MODULE_NAME = 'quantcastId'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export function firePixel(clientId, cookieExpDays = DEFAULT_COOKIE_EXP_DAYS) { // check for presence of Quantcast Measure tag _qevent obj and publisher provided clientID @@ -160,7 +162,7 @@ export const quantcastIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'quantcastId', + name: MODULE_NAME, /** * Vendor id of Quantcast diff --git a/modules/radsBidAdapter.js b/modules/radsBidAdapter.js index fcb8c3a8c2a..ae16bcf9d83 100644 --- a/modules/radsBidAdapter.js +++ b/modules/radsBidAdapter.js @@ -1,7 +1,6 @@ -import { deepAccess } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {deepAccess} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'rads'; const ENDPOINT_URL = 'https://rads.recognified.net/md.request.php'; @@ -86,7 +85,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, meta: { advertiserDomains: response.adomain || [] } diff --git a/modules/realvuAnalyticsAdapter.js b/modules/realvuAnalyticsAdapter.js index 0811c70a2f7..e4bcf1b474a 100644 --- a/modules/realvuAnalyticsAdapter.js +++ b/modules/realvuAnalyticsAdapter.js @@ -2,9 +2,12 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import { logMessage, logError } from '../src/utils.js'; -const storage = getStorageManager(); +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +const MODULE_CODE = 'realvuAnalytics'; + +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); let realvuAnalyticsAdapter = adapter({ global: 'realvuAnalytics', @@ -967,7 +970,7 @@ realvuAnalyticsAdapter.disableAnalytics = function () { adapterManager.registerAnalyticsAdapter({ adapter: realvuAnalyticsAdapter, - code: 'realvuAnalytics' + code: MODULE_CODE, }); export default realvuAnalyticsAdapter; diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 0c4d6148f2b..89e4e85c627 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -384,7 +384,7 @@ function generateGeneralParams(generalObject, bidderRequest) { const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; const {bidderCode} = bidderRequest; const generalBidParams = generalObject.params; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; // 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. diff --git a/modules/roxotAnalyticsAdapter.js b/modules/roxotAnalyticsAdapter.js index 1ded17f3a5b..2c3be3e1757 100644 --- a/modules/roxotAnalyticsAdapter.js +++ b/modules/roxotAnalyticsAdapter.js @@ -5,8 +5,11 @@ import adapterManager from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; import {ajaxBuilder} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'roxot'; + +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); let ajax = ajaxBuilder(0); @@ -504,7 +507,7 @@ function buildLogMessage(message) { adapterManager.registerAnalyticsAdapter({ adapter: roxotAdapter, - code: 'roxot' + code: MODULE_CODE, }); export default roxotAdapter; diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 5c3d430aadf..5ebf51b6e0d 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -85,15 +85,12 @@ export const spec = { } const ortb2Params = bidderRequest?.ortb2 || {}; - if (ortb2Params.site) { - mergeDeep(request, { site: ortb2Params.site }); - } - if (ortb2Params.user) { - mergeDeep(request, { user: ortb2Params.user }); - } - if (ortb2Params.device) { - mergeDeep(request, { device: ortb2Params.device }); - } + ['site', 'user', 'device', 'bcat', 'badv'].forEach(entry => { + const ortb2Param = ortb2Params[entry]; + if (ortb2Param) { + mergeDeep(request, { [entry]: ortb2Param }); + } + }); let computedEndpointUrl = ENDPOINT_URL; @@ -479,7 +476,7 @@ function interpretNativeBid(serverBid) { function interpretNativeAd(adm) { const native = JSON.parse(adm).native; const result = { - clickUrl: encodeURIComponent(native.link.url), + clickUrl: encodeURI(native.link.url), impressionTrackers: native.imptrackers }; native.assets.forEach(asset => { @@ -489,14 +486,14 @@ function interpretNativeAd(adm) { break; case OPENRTB.NATIVE.ASSET_ID.IMAGE: result.image = { - url: encodeURIComponent(asset.img.url), + url: encodeURI(asset.img.url), width: asset.img.w, height: asset.img.h }; break; case OPENRTB.NATIVE.ASSET_ID.ICON: result.icon = { - url: encodeURIComponent(asset.img.url), + url: encodeURI(asset.img.url), width: asset.img.w, height: asset.img.h }; diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 05594c63132..29e2ce3de43 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -166,6 +166,8 @@ import CONSTANTS from '../../src/constants.json'; import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../../src/adapterManager.js'; import {find} from '../../src/polyfill.js'; import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; +import {GDPR_GVLIDS} from '../../src/consentHandler.js'; +import {MODULE_TYPE_RTD} from '../../src/activities/modules.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; @@ -188,6 +190,7 @@ let _userConsent; */ export function attachRealTimeDataProvider(submodule) { registeredSubModules.push(submodule); + GDPR_GVLIDS.register(MODULE_TYPE_RTD, submodule.name, submodule.gvlid) return function detach() { const idx = registeredSubModules.indexOf(submodule) if (idx >= 0) { diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 76043b71c64..7bbc435cb27 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -5,10 +5,11 @@ import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const RUBICON_GVL_ID = 52; -export const storage = getStorageManager({gvlid: RUBICON_GVL_ID, moduleName: 'rubicon'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, 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 860c9d7483d..ef56e10870b 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -5,13 +5,15 @@ * @requires module:modules/userId */ -import { parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID } from '../src/utils.js'; +import {parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID} from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import { coppaDataHandler } from '../src/adapterManager.js'; +import {coppaDataHandler} from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; -export const storage = getStorageManager({moduleName: 'pubCommonId', gvlid: VENDORLESS_GVLID}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'pubCommonId'}); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const OPTOUT_NAME = '_pubcid_optout'; @@ -171,31 +173,7 @@ export const sharedIdSystemSubmodule = { } }, - domainOverride: function () { - const domainElements = document.domain.split('.'); - const cookieName = `_gd${Date.now()}`; - for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { - const nextDomain = domainElements.slice(i).join('.'); - - // write test cookie - storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); - - // read test cookie to verify domain was valid - testCookie = storage.getCookie(cookieName); - - // delete test cookie - storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); - - if (testCookie === '1') { - // cookie was written successfully using test domain so the topDomain is updated - topDomain = nextDomain; - } else { - // cookie failed to write using test domain so exit by returning the topDomain - return topDomain; - } - } - } - + domainOverride: domainOverrideToRootDomain(storage, 'sharedId'), }; submodule('userId', sharedIdSystemSubmodule); diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 07dc065a419..9c91af8b130 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -21,7 +21,7 @@ export const sharethroughAdapterSpec = { isBidRequestValid: bid => !!bid.params.pkey && bid.bidder === BIDDER_CODE, buildRequests: (bidRequests, bidderRequest) => { - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; const firstPartyData = bidderRequest.ortb2 || {}; const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 0ce2eed6479..f93736894f5 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -364,7 +364,7 @@ function generateGeneralParams(generalObject, bidderRequest) { const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; const {bidderCode} = bidderRequest; const generalBidParams = generalObject.params; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; // 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. diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js index dc386813978..18e1e20e3e3 100644 --- a/modules/sigmoidAnalyticsAdapter.js +++ b/modules/sigmoidAnalyticsAdapter.js @@ -6,8 +6,10 @@ import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {generateUUID, logError, logInfo} from '../src/utils.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'sigmoid'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const url = 'https://kinesis.us-east-1.amazonaws.com/'; const analyticsType = 'endpoint'; @@ -285,7 +287,7 @@ function pushEvent(eventType, args) { adapterManager.registerAnalyticsAdapter({ adapter: sigmoidAdapter, - code: 'sigmoid' + code: MODULE_CODE, }); export default sigmoidAdapter; diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 6aab6a8b57e..2889bd5358b 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -125,7 +125,7 @@ function buildRequestParams(bidderRequest = {}, placements = []) { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; } diff --git a/modules/staqAnalyticsAdapter.js b/modules/staqAnalyticsAdapter.js index 13996adfb7e..c1aaa727af5 100644 --- a/modules/staqAnalyticsAdapter.js +++ b/modules/staqAnalyticsAdapter.js @@ -4,9 +4,11 @@ import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { ajax } from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storageObj = getStorageManager(); +const MODULE_CODE = 'staq'; +const storageObj = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const ANALYTICS_VERSION = '1.0.0'; const DEFAULT_QUEUE_TIMEOUT = 4000; @@ -117,7 +119,7 @@ analyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, - code: 'staq' + code: MODULE_CODE, }); export default analyticsAdapter; diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js index 32a3d289c61..ec88ee9c620 100644 --- a/modules/stvBidAdapter.js +++ b/modules/stvBidAdapter.js @@ -1,7 +1,6 @@ -import { deepAccess } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {deepAccess} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; const BIDDER_CODE = 'stv'; @@ -128,7 +127,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, meta: { advertiserDomains: response.adomain || [] } diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 4ebed12cb05..7165f778e5f 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -49,9 +49,7 @@ export const spec = { imp: [] }; - const callbackTimeout = bidderRequest.timeout; - const globalTimeout = config.getConfig('bidderTimeout'); - const tmax = globalTimeout ? Math.min(globalTimeout, callbackTimeout) : callbackTimeout; + const tmax = bidderRequest.timeout; if (tmax) { openRtbBidRequest.tmax = tmax; } diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index a833d8ec552..49ca8da71c8 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -24,7 +24,7 @@ const COOKIE_KEY = 'trc_cookie_storage'; * 4. new user set it to 0 */ export const userData = { - storageManager: getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}), + storageManager: getStorageManager({bidderCode: BIDDER_CODE}), getUserId: () => { const {getFromLocalStorage, getFromCookie, getFromTRC} = userData; diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 56b21d0d4cf..f750af0f64d 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({gvlid: GVL_ID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/teadsIdSystem.js b/modules/teadsIdSystem.js index c18f8fb76ac..b4067bf75c3 100644 --- a/modules/teadsIdSystem.js +++ b/modules/teadsIdSystem.js @@ -10,6 +10,7 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {uspDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'teadsId'; const GVL_ID = 132; @@ -28,7 +29,7 @@ export const gdprReason = { GDPR_APPLIES_PUBLISHER_CLASSIC: 120, }; -export const storage = getStorageManager({gvlid: GVL_ID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const teadsIdSubmodule = { diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 1f654d34c6a..45931bce27e 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -11,7 +11,7 @@ const BANNER_TIME_TO_LIVE = 300; const VIDEO_TIME_TO_LIVE = 3600; let gdprApplies = true; let consentString = null; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const tripleliftAdapterSpec = { gvlid: GVLID, @@ -130,8 +130,15 @@ function _buildPostBody(bidRequests, bidderRequest) { } if (!isEmpty(bidRequest.ortb2Imp)) { + // legacy method for extracting ortb2Imp.ext imp.fpd = _getAdUnitFpd(bidRequest.ortb2Imp); + + // preferred method for extracting ortb2Imp.ext + if (!isEmpty(bidRequest.ortb2Imp.ext)) { + imp.ext = { ...bidRequest.ortb2Imp.ext }; + } } + return imp; }); diff --git a/modules/truereachBidAdapter.js b/modules/truereachBidAdapter.js index e343842543d..13bb558fbf6 100755 --- a/modules/truereachBidAdapter.js +++ b/modules/truereachBidAdapter.js @@ -1,6 +1,5 @@ import { deepAccess, getUniqueIdentifierStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; const SUPPORTED_AD_TYPES = [BANNER]; @@ -140,7 +139,7 @@ function buildCommonQueryParamsFromBids(validBidRequests, bidderRequest) { device: { ua: window.navigator.userAgent }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; return defaultParams; diff --git a/modules/trustpidSystem.js b/modules/trustpidSystem.js index cb61ffcc8b5..1d971b3c813 100644 --- a/modules/trustpidSystem.js +++ b/modules/trustpidSystem.js @@ -6,13 +6,14 @@ */ import { logInfo } from '../src/utils.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'trustpid'; const LOG_PREFIX = 'Trustpid module' let mnoDomain = ''; -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** * Handle an event for an iframe. diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index db166577d82..b9e209f7cd7 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -164,15 +164,18 @@ function getImpression(bidRequest) { const gpid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); const tid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.tid'); + const rwdd = utils.deepAccess(bidRequest, 'ortb2Imp.rwdd'); if (gpid || tid) { impression.ext = {} if (gpid) { impression.ext.gpid = gpid } if (tid) { impression.ext.tid = tid } } - + if (rwdd) { + impression.rwdd = rwdd; + } const tagid = gpid || bidRequest.params.placementId; if (tagid) { - impression.tagid = tagid + impression.tagid = tagid; } const mediaTypesVideo = utils.deepAccess(bidRequest, 'mediaTypes.video'); @@ -251,6 +254,7 @@ function video(bid) { const api = utils.deepAccess(bid, 'mediaTypes.video.api'); const mimes = utils.deepAccess(bid, 'mediaTypes.video.mimes'); const placement = utils.deepAccess(bid, 'mediaTypes.video.placement'); + const plcmt = utils.deepAccess(bid, 'mediaTypes.video.plcmt'); const protocols = utils.deepAccess(bid, 'mediaTypes.video.protocols'); const playbackmethod = utils.deepAccess(bid, 'mediaTypes.video.playbackmethod'); const pos = utils.deepAccess(bid, 'mediaTypes.video.pos'); @@ -286,6 +290,9 @@ function video(bid) { if (playbackmethod) { video.playbackmethod = playbackmethod; } + if (plcmt) { + video.plcmt = plcmt; + } if (pos) { video.pos = pos; } diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 9fd75f12591..2ffab8cc68f 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -8,7 +8,8 @@ import { logInfo } from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'uid2'; const MODULE_REVISION = `1.0`; @@ -24,7 +25,7 @@ const UID2_PROD_URL = 'https://prod.uidapi.com'; const UID2_BASE_URL = UID2_PROD_URL; function getStorage() { - return getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); + return getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); } function createLogInfo(prefix) { diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 233d5f080a9..bdc5c04279f 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -1,21 +1,18 @@ import { - logMessage, + deepAccess, flatten, - parseSizesInput, + getWindowSelf, + getWindowTop, isGptPubadsDefined, isSlotMatchingAdUnitCode, logInfo, + logMessage, logWarn, - getWindowSelf, - getWindowTop, - deepAccess + parseSizesInput } from '../src/utils.js'; -import { - config -} from '../src/config.js'; -import { - registerBidder -} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; + const BIDDER_CODE = 'underdogmedia'; const UDM_ADAPTER_VERSION = '7.30V'; const UDM_VENDOR_ID = '159'; @@ -377,7 +374,7 @@ function makeNotification(bid, mid, bidParam) { url += `;version=${UDM_ADAPTER_VERSION}`; url += ';cb=' + Math.random(); url += ';qqq=' + (1 / bid.cpm); - url += ';hbt=' + config.getConfig('_bidderTimeout'); + url += ';hbt=' + config.getConfig('bidderTimeout'); url += ';style=adapter'; url += ';vis=' + encodeURIComponent(document.visibilityState); diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index eda4b6f579c..28893d04b3f 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -76,6 +76,7 @@ function getBannerCoords(id) { export const spec = { code: BIDDER_CODE, + gvlid: 677, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { if (bid && bid.params && bid.params.publisherId) { diff --git a/modules/userId/eids.js b/modules/userId/eids.js index a96dbaee4af..fa8e6f7647c 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -45,6 +45,12 @@ export const USER_IDS_CONFIG = { atype: 1 }, + // pairId + 'pairId': { + source: 'google.com', + atype: 571187 + }, + // justId 'justId': { source: 'justtag.com', @@ -146,6 +152,24 @@ export const USER_IDS_CONFIG = { } }, + // bidswitchId + 'bidswitch': { + source: 'bidswitch.net', + atype: 3, + getValue: function(data) { + return data.id; + } + }, + + // medianetId + 'medianet': { + source: 'media.net', + atype: 3, + getValue: function(data) { + return data.id; + } + }, + // britepoolId 'britepoolid': { source: 'britepool.com', diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 83414cbe295..f3d487126a9 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -105,6 +105,22 @@ userIdAsEids = [ segments: ['s1', 's2'] } }, + + { + source: 'bidswitch.net', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, + + { + source: 'media.net', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, { source: 'merkleinc.com', diff --git a/modules/userId/index.js b/modules/userId/index.js index d4a8679f0ca..d16d341fd2d 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -157,6 +157,8 @@ import {hasPurpose1Consent} from '../../src/utils/gpdr.js'; import {registerOrtbProcessor, REQUEST} from '../../src/pbjsORTB.js'; import {newMetrics, timedAuctionHook, useMetrics} from '../../src/utils/perfMetrics.js'; import {findRootDomain} from '../../src/fpd/rootDomain.js'; +import {GDPR_GVLIDS} from '../../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../../src/activities/modules.js'; const MODULE_NAME = 'User ID'; const COOKIE = STORAGE_TYPE_COOKIES; @@ -1019,6 +1021,7 @@ export function requestDataDeletion(next, ...args) { export function attachIdSystem(submodule) { if (!find(submoduleRegistry, i => i.name === submodule.name)) { submoduleRegistry.push(submodule); + GDPR_GVLIDS.register(MODULE_TYPE_UID, submodule.name, submodule.gvlid) updateSubmodules(); // TODO: a test case wants this to work even if called after init (the setConfig({userId})) // so we trigger a refresh. But is that even possible outside of tests? diff --git a/modules/validationFpdModule/index.js b/modules/validationFpdModule/index.js index 8771e50b156..70af9d30ec3 100644 --- a/modules/validationFpdModule/index.js +++ b/modules/validationFpdModule/index.js @@ -5,9 +5,10 @@ import {deepAccess, isEmpty, isNumber, logWarn} from '../../src/utils.js'; import {ORTB_MAP} from './config.js'; import {submodule} from '../../src/hook.js'; -import {getStorageManager} from '../../src/storageManager.js'; +import {getCoreStorageManager} from '../../src/storageManager.js'; -const STORAGE = getStorageManager(); +// TODO: do FPD modules need their own namespace? +const STORAGE = getCoreStorageManager('FPDValidation'); let optout; /** diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index a57eae5f328..167c378ac6d 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -1,5 +1,4 @@ -import { getAdUnitSizes } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {getAdUnitSizes} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -87,7 +86,7 @@ export const spec = { // dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, // referrer: referrer, // ad: response.adm // ad: adCreative, diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 44538e30921..0d7aaa1a12d 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -28,7 +28,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'pubProvidedId': 1 }; export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { @@ -227,7 +227,7 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; + const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, metaData, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -241,11 +241,20 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - meta: { - advertiserDomains: advertiserDomains || [] - } }; + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + if (mediaType === BANNER) { Object.assign(response, { ad: ad, diff --git a/modules/videoheroesBidAdapter.js b/modules/videoheroesBidAdapter.js index 4818d9e1c58..e992fae1d06 100644 --- a/modules/videoheroesBidAdapter.js +++ b/modules/videoheroesBidAdapter.js @@ -80,7 +80,7 @@ export const spec = { domain: parseUrl(page).hostname, page: page, }, - tmax: bidderRequest.timeout || config.getConfig('bidderTimeout') || 500, + tmax: bidderRequest.timeout, imp }; diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index 08c7d1b31ac..c9ac9fae0f4 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -1,20 +1,21 @@ -import { logError, deepAccess, parseSizesInput } from '../src/utils.js'; +import {deepAccess, logError, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import { Renderer } from '../src/Renderer.js'; -import { INSTREAM, OUTSTREAM } from '../src/video.js'; +import {Renderer} from '../src/Renderer.js'; +import {INSTREAM, OUTSTREAM} from '../src/video.js'; const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; const BIDDER_CODE = 'vidoomy'; const GVLID = 380; const COOKIE_SYNC_FALLBACK_URLS = [ - 'https://x.bidswitch.net/sync?ssp=vidoomy', - 'https://ib.adnxs.com/getuid?https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', + 'https://x.bidswitch.net/sync?ssp=vidoomy&gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=', 'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D', 'https://cm.adform.net/cookie?redirect_url=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', - 'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}' + 'https://pixel.rubiconproject.com/exchange/sync.php?p=pbs-vidoomy&gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=', + 'https://rtb.openx.net/sync/prebid?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&r=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dopenx%26uid%3D$%7BUID%7D', + 'https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=&predirect=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D' ]; const isBidRequestValid = bid => { @@ -74,6 +75,26 @@ function serializeSupplyChainObj(schainObj) { return serializedSchain; } +/** + * Gets highest floor between getFloor.floor and params.bidfloor + * @param {Object} bid + * @param {Object} mediaType + * @param {Array} sizes + * @param {Number} bidfloor + * @returns {Number} floor + */ +function getBidFloor(bid, mediaType, sizes, bidfloor) { + let floor = bidfloor; + var size = sizes && sizes.length > 0 ? sizes[0] : '*'; + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({currency: 'USD', mediaType, size}); + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(bidfloor, parseFloat(floorInfo.floor)); + } + } + return floor; +} + const isBidResponseValid = bid => { if (!bid || !bid.requestId || !bid.cpm || !bid.ttl || !bid.currency) { return false; @@ -106,6 +127,21 @@ const buildRequests = (validBidRequests, bidderRequest) => { const videoContext = deepAccess(bid, 'mediaTypes.video.context'); const bidfloor = deepAccess(bid, `params.bidfloor`, 0); + const floor = getBidFloor(bid, adType, sizes, bidfloor); + + const ortb2 = bidderRequest.ortb2 || { + bcat: [], + badv: [], + bapp: [], + btype: [], + battr: [] + }; + + let eids; + const userEids = deepAccess(bid, 'userIdAsEids'); + if (Array.isArray(userEids) && userEids.length > 0) { + eids = JSON.stringify(userEids) || ''; + } const queryParams = { id: bid.params.id, @@ -120,13 +156,19 @@ const buildRequests = (validBidRequests, bidderRequest) => { pid: bid.params.pid, requestId: bid.bidId, schain: serializeSupplyChainObj(bid.schain) || '', - bidfloor, + eids: eids || '', + bidfloor: floor, d: getDomainWithoutSubdomain(hostname), // 'vidoomy.com', // TODO: does the fallback make sense here? sp: encodeURIComponent(bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation), usp: bidderRequest.uspConsent || '', coppa: !!config.getConfig('coppa'), - videoContext: videoContext || '' + videoContext: videoContext || '', + bcat: ortb2.bcat || bid.params.bcat || [], + badv: ortb2.badv || bid.params.badv || [], + bapp: ortb2.bapp || bid.params.bapp || [], + btype: ortb2.btype || bid.params.btype || [], + battr: ortb2.battr || bid.params.battr || [] }; if (bidderRequest.gdprConsent) { @@ -232,7 +274,7 @@ const interpretResponse = (serverResponse, bidRequest) => { } }; -function getUserSyncs (syncOptions, responses, gdprConsent, uspConsent) { +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { const pixelType = syncOptions.pixelEnabled ? 'image' : 'iframe'; const urls = deepAccess(responses, '0.body.pixels') || COOKIE_SYNC_FALLBACK_URLS; @@ -259,7 +301,7 @@ export const spec = { registerBidder(spec); -function getDomainWithoutSubdomain (hostname) { +function getDomainWithoutSubdomain(hostname) { const parts = hostname.split('.'); const newParts = []; for (let i = parts.length - 1; i >= 0; i--) { diff --git a/modules/vidoomyBidAdapter.md b/modules/vidoomyBidAdapter.md index d91ace5a7b9..b4095606d9b 100644 --- a/modules/vidoomyBidAdapter.md +++ b/modules/vidoomyBidAdapter.md @@ -27,7 +27,12 @@ var adUnits = [ params: { id: '123123', pid: '123123', - bidfloor: 0.5 // This is optional + bidfloor: 0.5, // This is optional + bcat: ['IAB1-1'], // Optional - default is [] + badv: ['example.com'], // Optional - default is [] + bapp: ['app.com'], // Optional - default is [] + btype: [1, 2, 3], // Optional - default is [] + battr: [1, 2, 3] // Optional - default is [] } } ] @@ -52,7 +57,12 @@ var adUnits = [ params: { id: '123123', pid: '123123', - bidfloor: 0.5 // This is optional + bidfloor: 0.5, // This is optional + bcat: ['IAB1-1'], // Optional - default is [] + badv: ['example.com'], // Optional - default is [] + bapp: ['app.com'], // Optional - default is [] + btype: [1, 2, 3], // Optional - default is [] + battr: [1, 2, 3] // Optional - default is [] } } ] diff --git a/modules/visiblemeasuresBidAdapter.js b/modules/visiblemeasuresBidAdapter.js index 89dcf36917f..e77477c812b 100644 --- a/modules/visiblemeasuresBidAdapter.js +++ b/modules/visiblemeasuresBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/vrtcalBidAdapter.js b/modules/vrtcalBidAdapter.js index 301d278493b..e41a5da50cd 100644 --- a/modules/vrtcalBidAdapter.js +++ b/modules/vrtcalBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { coppa = 1; } - tmax = config.getConfig('bidderTimeout'); + tmax = bid.timeout; const params = { prebidJS: 1, diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 12e8d4b23c8..7e5b21de5a6 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -122,6 +122,7 @@ import { getStorageManager } from '../src/storageManager.js'; import adapterManager from '../src/adapterManager.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; @@ -153,7 +154,7 @@ const SFBX_LITE_DATA_SOURCE_LABEL = 'lite'; const GVLID = 284; export const storage = getStorageManager({ - gvlid: GVLID, + moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); @@ -180,6 +181,7 @@ export const storage = getStorageManager({ class WeboramaRtdProvider { #components; name = SUBMODULE_NAME; + gvlid = GVLID; /** * @param {Components} components */ diff --git a/modules/wipesBidAdapter.js b/modules/wipesBidAdapter.js index 3d040fee8d3..56a4aeecd71 100644 --- a/modules/wipesBidAdapter.js +++ b/modules/wipesBidAdapter.js @@ -1,5 +1,4 @@ -import { logWarn } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -50,7 +49,7 @@ function interpretResponse(serverResponse, bidRequest) { dealId: response.deal_id, currency: 'JPY', netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, referrer: bidRequest.data.r || '', mediaType: BANNER, ad: response.ad_tag, diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 526e1911a06..bef453cb4ad 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -68,6 +68,7 @@ export const spec = { const videoBidRequests = bidRequests.filter(request => hasVideoMediaType(request)); let serverRequests = []; const eids = getEids(bidRequests[0]) || []; + if (bannerBidRequests.length > 0) { let serverRequest = { pbav: '$prebid.version$', @@ -80,7 +81,9 @@ export const spec = { userConsent: JSON.stringify({ // case of undefined, stringify will remove param gdprApplies: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', - cmp: deepAccess(bidderRequest, 'gdprConsent.consentString') || '' + cmp: deepAccess(bidderRequest, 'gdprConsent.consentString') || '', + gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || '', + gpp_sid: deepAccess(bidderRequest, 'gppConsent.applicableSections') || [] }), us_privacy: deepAccess(bidderRequest, 'uspConsent') || '' }; @@ -514,12 +517,19 @@ function openRtbSite(bidRequest, bidderRequest) { */ function populateOpenRtbGdpr(openRtbRequest, bidderRequest) { const gdpr = bidderRequest.gdprConsent; - if (gdpr && 'gdprApplies' in gdpr) { - deepSetValue(openRtbRequest, 'regs.ext.gdpr', gdpr.gdprApplies ? 1 : 0); - deepSetValue(openRtbRequest, 'user.ext.consent', gdpr.consentString); + const gpp = deepAccess(bidderRequest, 'gppConsent.gppString'); + const gppsid = deepAccess(bidderRequest, 'gppConsent.applicableSections'); + if (gpp) { + deepSetValue(openRtbRequest, 'regs.ext.gpp', gpp); + } else { + deepSetValue(openRtbRequest, 'regs.ext.gdpr', gdpr && gdpr.gdprApplies ? 1 : 0); + deepSetValue(openRtbRequest, 'user.ext.consent', gdpr && gdpr.consentString ? gdpr.consentString : ''); + } + if (gppsid && gppsid.length > 0) { + deepSetValue(openRtbRequest, 'regs.ext.gpp_sid', gppsid); } const uspConsent = deepAccess(bidderRequest, 'uspConsent'); - if (uspConsent) { + if (!gpp && uspConsent) { deepSetValue(openRtbRequest, 'regs.ext.us_privacy', uspConsent); } } diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index d408a0595f9..2ff747a238a 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -1,8 +1,7 @@ import {deepAccess, isEmpty, isStr, logWarn, parseSizesInput} from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; /** * @typedef {import('../src/adapters/bidderFactory').Bid} Bid @@ -53,7 +52,7 @@ export const spec = { const bidId = bidRequest.bidId; const transactionId = bidRequest.transactionId; const unitCode = bidRequest.adUnitCode; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; const language = window.navigator.language; const screenSize = window.screen.width + 'x' + window.screen.height; const payload = { @@ -147,7 +146,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, referrer: referrer, meta: { advertiserDomains: response.adomain ? response.adomain : [] diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index a16e4ec8d36..820e6365a9f 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -7,8 +7,10 @@ import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {includes as strIncludes} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'yuktamedia'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const yuktamediaAnalyticsVersion = 'v3.1.0'; let initOptions; @@ -261,7 +263,7 @@ yuktamediaAnalyticsAdapter.enableAnalytics = function (config) { adapterManager.registerAnalyticsAdapter({ adapter: yuktamediaAnalyticsAdapter, - code: 'yuktamedia' + code: MODULE_CODE, }); export default yuktamediaAnalyticsAdapter; diff --git a/modules/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js index 3437928df4b..4cb827cbdff 100644 --- a/modules/zeotapIdPlusIdSystem.js +++ b/modules/zeotapIdPlusIdSystem.js @@ -6,7 +6,8 @@ */ import { isStr, isPlainObject } from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; const ZEOTAP_VENDOR_ID = 301; @@ -21,7 +22,7 @@ function readFromLocalStorage() { } export function getStorage() { - return getStorageManager({gvlid: ZEOTAP_VENDOR_ID, moduleName: ZEOTAP_MODULE_NAME}); + return getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: ZEOTAP_MODULE_NAME}); } export const storage = getStorage(); diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 2f392ccdbd3..531384b9f27 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -77,6 +77,9 @@ export const spec = { id: request.bidId, secure: secure }; + if (params.tagid) { + impData.tagid = params.tagid; + } if (request.mediaTypes) { for (const mediaType in request.mediaTypes) { switch (mediaType) { diff --git a/package-lock.json b/package-lock.json index 276fa15775a..614efa22f2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.44.0-pre", + "version": "7.48.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c4efb745c42..9b870eceef3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.44.0-pre", + "version": "7.48.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/Renderer.js b/src/Renderer.js index cdee9c79e63..2f9b2e025cb 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -22,6 +22,7 @@ export function Renderer(options) { this.config = config; this.handlers = {}; this.id = id; + this.renderNow = renderNow; // a renderer may push to the command queue to delay rendering until the // render function is loaded by loadExternalScript, at which point the the command @@ -110,7 +111,7 @@ Renderer.prototype.process = function() { * @returns {Boolean} */ export function isRendererRequired(renderer) { - return !!(renderer && renderer.url); + return !!(renderer && (renderer.url || renderer.renderNow)); } /** diff --git a/src/activities/modules.js b/src/activities/modules.js new file mode 100644 index 00000000000..d140b10387f --- /dev/null +++ b/src/activities/modules.js @@ -0,0 +1,5 @@ +export const MODULE_TYPE_CORE = 'core'; +export const MODULE_TYPE_BIDDER = 'bidder'; +export const MODULE_TYPE_UID = 'userId'; +export const MODULE_TYPE_RTD = 'rtd'; +export const MODULE_TYPE_ANALYTICS = 'analytics'; diff --git a/src/adapterManager.js b/src/adapterManager.js index 850488af8ae..45438f59b55 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -31,11 +31,12 @@ import {hook} from './hook.js'; import {find, includes} from './polyfill.js'; import {adunitCounter} from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; -import {GdprConsentHandler, UspConsentHandler, GppConsentHandler} from './consentHandler.js'; +import {GdprConsentHandler, UspConsentHandler, GppConsentHandler, GDPR_GVLIDS} from './consentHandler.js'; import * as events from './events.js'; import CONSTANTS from './constants.json'; import {useMetrics} from './utils/perfMetrics.js'; import {auctionManager} from './auctionManager.js'; +import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER} from './activities/modules.js'; export const PARTITIONS = { CLIENT: 'client', @@ -463,6 +464,7 @@ adapterManager.registerBidAdapter = function (bidAdapter, bidderCode, {supported if (bidAdapter && bidderCode) { if (typeof bidAdapter.callBids === 'function') { _bidderRegistry[bidderCode] = bidAdapter; + GDPR_GVLIDS.register(MODULE_TYPE_BIDDER, bidderCode, bidAdapter.getSpec?.().gvlid); if (FEATURES.VIDEO && includes(supportedMediaTypes, 'video')) { adapterManager.videoAdapters.push(bidderCode); @@ -532,6 +534,7 @@ adapterManager.registerAnalyticsAdapter = function ({adapter, code, gvlid}) { if (typeof adapter.enableAnalytics === 'function') { adapter.code = code; _analyticsRegistry[code] = { adapter, gvlid }; + GDPR_GVLIDS.register(MODULE_TYPE_ANALYTICS, code, gvlid); } else { logError(`Prebid Error: Analytics adaptor error for analytics "${code}" analytics adapter must implement an enableAnalytics() function`); diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 9357041c505..53d2a4d3ca6 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -199,7 +199,7 @@ export function registerBidder(spec) { export function newBidder(spec) { return Object.assign(new Adapter(spec.code), { getSpec: function() { - return Object.freeze(spec); + return Object.freeze(Object.assign({}, spec)); }, registerSyncs, callBids: function(bidderRequest, addBidResponse, done, ajax, onTimelyResponse, configEnabledCallback) { diff --git a/src/config.js b/src/config.js index 0922a2a2bea..48909d677e9 100644 --- a/src/config.js +++ b/src/config.js @@ -66,158 +66,109 @@ export function newConfig() { function resetConfig() { defaults = {}; - let newConfig = { - // `debug` is equivalent to legacy `pbjs.logging` property - _debug: DEFAULT_DEBUG, - get debug() { - return this._debug; - }, - set debug(val) { - this._debug = val; - }, - // default timeout for all bids - _bidderTimeout: DEFAULT_BIDDER_TIMEOUT, - get bidderTimeout() { - return this._bidderTimeout; - }, - set bidderTimeout(val) { - this._bidderTimeout = val; - }, + function getProp(name) { + return props[name].val; + } - _publisherDomain: null, - get publisherDomain() { - return this._publisherDomain; - }, - set publisherDomain(val) { - logWarn('publisherDomain is deprecated and has no effect since v7 - use pageUrl instead') - this._publisherDomain = val; - }, + function setProp(name, val) { + props[name].val = val; + } - // calls existing function which may be moved after deprecation - _priceGranularity: GRANULARITY_OPTIONS.MEDIUM, - set priceGranularity(val) { - if (validatePriceGranularity(val)) { - if (typeof val === 'string') { - this._priceGranularity = (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM; - } else if (isPlainObject(val)) { - this._customPriceBucket = val; - this._priceGranularity = GRANULARITY_OPTIONS.CUSTOM; - logMessage('Using custom price granularity'); + const props = { + publisherDomain: { + set(val) { + if (val != null) { + logWarn('publisherDomain is deprecated and has no effect since v7 - use pageUrl instead') } + setProp('publisherDomain', val); } }, - get priceGranularity() { - return this._priceGranularity; - }, - - _customPriceBucket: {}, - get customPriceBucket() { - return this._customPriceBucket; - }, - - /** - * mediaTypePriceGranularity - * @type {MediaTypePriceGranularity} - */ - _mediaTypePriceGranularity: {}, - - get mediaTypePriceGranularity() { - return this._mediaTypePriceGranularity; - }, - set mediaTypePriceGranularity(val) { - this._mediaTypePriceGranularity = Object.keys(val).reduce((aggregate, item) => { - if (validatePriceGranularity(val[item])) { + priceGranularity: { + val: GRANULARITY_OPTIONS.MEDIUM, + set(val) { + if (validatePriceGranularity(val)) { if (typeof val === 'string') { - aggregate[item] = (hasGranularity(val[item])) ? val[item] : this._priceGranularity; + setProp('priceGranularity', (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM); } else if (isPlainObject(val)) { - aggregate[item] = val[item]; - logMessage(`Using custom price granularity for ${item}`); + setProp('customPriceBucket', val); + setProp('priceGranularity', GRANULARITY_OPTIONS.CUSTOM) + logMessage('Using custom price granularity'); } - } else { - logWarn(`Invalid price granularity for media type: ${item}`); } - return aggregate; - }, {}); - }, - - _sendAllBids: DEFAULT_ENABLE_SEND_ALL_BIDS, - get enableSendAllBids() { - return this._sendAllBids; + } }, - set enableSendAllBids(val) { - this._sendAllBids = val; + customPriceBucket: { + val: {}, + set() {} }, - - _useBidCache: DEFAULT_BID_CACHE, - get useBidCache() { - return this._useBidCache; + mediaTypePriceGranularity: { + val: {}, + set(val) { + val != null && setProp('mediaTypePriceGranularity', Object.keys(val).reduce((aggregate, item) => { + if (validatePriceGranularity(val[item])) { + if (typeof val === 'string') { + aggregate[item] = (hasGranularity(val[item])) ? val[item] : getProp('priceGranularity'); + } else if (isPlainObject(val)) { + aggregate[item] = val[item]; + logMessage(`Using custom price granularity for ${item}`); + } + } else { + logWarn(`Invalid price granularity for media type: ${item}`); + } + return aggregate; + }, {})); + } }, - set useBidCache(val) { - this._useBidCache = val; + bidderSequence: { + val: DEFAULT_BIDDER_SEQUENCE, + set(val) { + if (VALID_ORDERS[val]) { + setProp('bidderSequence', val); + } else { + logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); + } + } }, + auctionOptions: { + val: {}, + set(val) { + if (validateauctionOptions(val)) { + setProp('auctionOptions', val); + } + } + } + } + let newConfig = { + // `debug` is equivalent to legacy `pbjs.logging` property + debug: DEFAULT_DEBUG, + bidderTimeout: DEFAULT_BIDDER_TIMEOUT, + enableSendAllBids: DEFAULT_ENABLE_SEND_ALL_BIDS, + useBidCache: DEFAULT_BID_CACHE, /** * deviceAccess set to false will disable setCookie, getCookie, hasLocalStorage * @type {boolean} */ - _deviceAccess: DEFAULT_DEVICE_ACCESS, - get deviceAccess() { - return this._deviceAccess; - }, - set deviceAccess(val) { - this._deviceAccess = val; - }, - - _bidderSequence: DEFAULT_BIDDER_SEQUENCE, - get bidderSequence() { - return this._bidderSequence; - }, - set bidderSequence(val) { - if (VALID_ORDERS[val]) { - this._bidderSequence = val; - } else { - logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); - } - }, + deviceAccess: DEFAULT_DEVICE_ACCESS, // timeout buffer to adjust for bidder CDN latency - _timeoutBuffer: DEFAULT_TIMEOUTBUFFER, - get timeoutBuffer() { - return this._timeoutBuffer; - }, - set timeoutBuffer(val) { - this._timeoutBuffer = val; - }, - - _disableAjaxTimeout: DEFAULT_DISABLE_AJAX_TIMEOUT, - get disableAjaxTimeout() { - return this._disableAjaxTimeout; - }, - set disableAjaxTimeout(val) { - this._disableAjaxTimeout = val; - }, + timeoutBuffer: DEFAULT_TIMEOUTBUFFER, + disableAjaxTimeout: DEFAULT_DISABLE_AJAX_TIMEOUT, // default max nested iframes for referer detection - _maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, - get maxNestedIframes() { - return this._maxNestedIframes; - }, - set maxNestedIframes(val) { - this._maxNestedIframes = val; - }, - - _auctionOptions: {}, - get auctionOptions() { - return this._auctionOptions; - }, - set auctionOptions(val) { - if (validateauctionOptions(val)) { - this._auctionOptions = val; - } - }, + maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, }; + Object.defineProperties(newConfig, + Object.fromEntries(Object.entries(props) + .map(([k, def]) => [k, Object.assign({ + get: getProp.bind(null, k), + set: setProp.bind(null, k), + enumerable: true, + }, def)])) + ); + if (config) { callSubscribers( Object.keys(config).reduce((memo, topic) => { diff --git a/src/consentHandler.js b/src/consentHandler.js index b1b2a04c043..4776a8ece02 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -118,3 +118,45 @@ export class GppConsentHandler extends ConsentHandler { } } } + +export function gvlidRegistry() { + const registry = {}; + const flat = {}; + const none = {}; + return { + /** + * Register a module's GVL ID. + * @param {string} moduleType defined in `activities/modules.js` + * @param {string} moduleName + * @param {number} gvlid + */ + register(moduleType, moduleName, gvlid) { + if (gvlid) { + (registry[moduleName] = registry[moduleName] || {})[moduleType] = gvlid; + if (flat.hasOwnProperty(moduleName)) { + if (flat[moduleName] !== gvlid) flat[moduleName] = none; + } else { + flat[moduleName] = gvlid; + } + } + }, + /** + * Get a module's GVL ID(s). + * + * @param {string} moduleName + * @return {{modules: {[moduleType]: number}, gvlid?: number}} an object where: + * `modules` is a map from module type to that module's GVL ID; + * `gvlid` is the single GVL ID for this family of modules (only defined + * if all modules with this name declared the same ID). + */ + get(moduleName) { + const result = {modules: registry[moduleName] || {}}; + if (flat.hasOwnProperty(moduleName) && flat[moduleName] !== none) { + result.gvlid = flat[moduleName]; + } + return result; + } + } +} + +export const GDPR_GVLIDS = gvlidRegistry(); diff --git a/src/native.js b/src/native.js index c4413a1a6de..79a972371da 100644 --- a/src/native.js +++ b/src/native.js @@ -750,7 +750,11 @@ export function toLegacyResponse(ortbResponse, ortbRequest) { if (asset.title) { legacyResponse.title = asset.title.text; } else if (asset.img) { - legacyResponse[requestAsset.img.type === NATIVE_IMAGE_TYPES.MAIN ? 'image' : 'icon'] = asset.img.url; + legacyResponse[requestAsset.img.type === NATIVE_IMAGE_TYPES.MAIN ? 'image' : 'icon'] = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; } else if (asset.data) { legacyResponse[PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE[NATIVE_ASSET_TYPES_INVERSE[requestAsset.data.type]]] = asset.data.value; } diff --git a/src/storageManager.js b/src/storageManager.js index b8906b131e4..0248237fbc4 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,51 +1,30 @@ import {hook} from './hook.js'; -import {hasDeviceAccess, checkCookieSupport, logError, logInfo, isPlainObject} from './utils.js'; +import {checkCookieSupport, hasDeviceAccess, logError, logInfo} from './utils.js'; import {bidderSettings as defaultBidderSettings} from './bidderSettings.js'; -import {VENDORLESS_GVLID} from './consentHandler.js'; - -const moduleTypeWhiteList = ['core', 'prebid-module']; - -export let storageCallbacks = []; +import {MODULE_TYPE_BIDDER, MODULE_TYPE_CORE} from './activities/modules.js'; export const STORAGE_TYPE_LOCALSTORAGE = 'html5'; export const STORAGE_TYPE_COOKIES = 'cookie'; -/** - * Storage options - * @typedef {Object} storageOptions - * @property {Number=} gvlid - Vendor id - * @property {string} moduleName? - Module name - * @property {string=} bidderCode? - Bidder code - * @property {string=} moduleType - Module type, value can be anyone of core or prebid-module - */ +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 - * - 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 +/* + * Storage manager constructor. Consumers should prefer one of `getStorageManager` or `getCoreStorageManager`. */ -export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}, {bidderSettings = defaultBidderSettings} = {}) { +export function newStorageManager({moduleName, moduleType} = {}, {bidderSettings = defaultBidderSettings} = {}) { function isBidderAllowed(storageType) { - if (bidderCode == null) { + if (moduleType !== MODULE_TYPE_BIDDER) { return true; } - const storageAllowed = bidderSettings.get(bidderCode, 'storageAllowed'); + const storageAllowed = bidderSettings.get(moduleName, 'storageAllowed'); if (!storageAllowed || storageAllowed === true) return !!storageAllowed; if (Array.isArray(storageAllowed)) return storageAllowed.some((e) => e === storageType); return storageAllowed === storageType; } - if (moduleTypeWhiteList.includes(moduleType)) { - gvlid = gvlid || VENDORLESS_GVLID; - } - function isValid(cb, storageType) { if (!isBidderAllowed(storageType)) { - logInfo(`bidderSettings denied access to device storage for bidder '${bidderCode}'`); + logInfo(`bidderSettings denied access to device storage for bidder '${moduleName}'`); const result = {valid: false}; return cb(result); } else { @@ -53,7 +32,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = let hookDetails = { hasEnforcementHook: false } - validateStorageEnforcement(gvlid, bidderCode || moduleName, hookDetails, function(result) { + validateStorageEnforcement(moduleType, moduleName, hookDetails, function(result) { if (result && result.hasEnforcementHook) { value = cb(result); } else { @@ -252,31 +231,38 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = /** * This hook validates the storage enforcement if gdprEnforcement module is included */ -export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, hookDetails, callback) { +export const validateStorageEnforcement = hook('async', function(moduleType, moduleName, hookDetails, callback) { callback(hookDetails); }, 'validateStorageEnforcement'); /** - * This function returns storage functions to access cookies and localstorage. This function will bypass the gdpr enforcement requirement. Prebid as a software needs to use storage in some scenarios and is not a vendor so GDPR enforcement rules does not apply on Prebid. - * @param {string} moduleName Module name + * Get a storage manager for a particular module. + * + * Either bidderCode or a combination of moduleType + moduleName must be provided. The former is a shorthand + * for `{moduleType: 'bidder', moduleName: bidderCode}`. + * */ -export function getCoreStorageManager(moduleName) { - return newStorageManager({moduleName: moduleName, moduleType: 'core'}); +export function getStorageManager({moduleType, moduleName, bidderCode} = {}) { + function err() { + throw new Error(`Invalid invocation for getStorageManager: must set either bidderCode, or moduleType + moduleName`) + } + if (bidderCode) { + if ((moduleType && moduleType !== MODULE_TYPE_BIDDER) || moduleName) err() + moduleType = MODULE_TYPE_BIDDER; + moduleName = bidderCode; + } else if (!moduleName || !moduleType) { + err() + } + return newStorageManager({moduleType, 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. - * 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 + * Get a storage manager for "core" (vendorless, or first-party) modules. Shorthand for `getStorageManager({moduleName, moduleType: 'core'})`. + * + * @param {string} moduleName Module name */ -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 getCoreStorageManager(moduleName) { + return newStorageManager({moduleName: moduleName, moduleType: MODULE_TYPE_CORE}); } export function resetData() { diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 03d0650effe..3e5a39e5ee5 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -1403,8 +1403,8 @@ describe('auctionmanager.js', function () { assert.equal(addedBid.native.title, 'Sample title') assert.equal(addedBid.native.sponsoredBy, 'Sample sponsoredBy') assert.equal(addedBid.native.clickUrl, 'http://www.click.com') - assert.equal(addedBid.native.image, 'https://www.example.com/image.png') - assert.equal(addedBid.native.icon, 'https://www.example.com/icon.png') + assert.equal(addedBid.native.image.url, 'https://www.example.com/image.png') + assert.equal(addedBid.native.icon.url, 'https://www.example.com/icon.png') assert.equal(addedBid.native.impressionTrackers[0], 'http://www.imptracker.com') assert.equal(addedBid.native.javascriptTrackers, '') }); diff --git a/test/spec/libraries/domainOverrideToRootDomain/index_spec.js b/test/spec/libraries/domainOverrideToRootDomain/index_spec.js new file mode 100644 index 00000000000..b490d80fd40 --- /dev/null +++ b/test/spec/libraries/domainOverrideToRootDomain/index_spec.js @@ -0,0 +1,77 @@ +import {domainOverrideToRootDomain} from 'libraries/domainOverrideToRootDomain/index.js'; +import {getStorageManager} from 'src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../../../src/activities/modules'; + +const storage = getStorageManager({ moduleName: 'test', moduleType: MODULE_TYPE_UID }); +const domainOverride = domainOverrideToRootDomain(storage, 'test'); + +describe('domainOverride', () => { + let sandbox, domain, cookies, rejectCookiesFor; + let setCookieStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(document, 'domain').get(() => domain); + cookies = {}; + sandbox.stub(storage, 'getCookie').callsFake((key) => cookies[key]); + rejectCookiesFor = null; + setCookieStub = sandbox.stub(storage, 'setCookie').callsFake((key, value, expires, sameSite, domain) => { + if (domain !== rejectCookiesFor) { + if (expires != null) { + expires = new Date(expires); + } + if (expires == null || expires > Date.now()) { + cookies[key] = value; + } else { + delete cookies[key]; + } + } + }); + }); + + afterEach(() => sandbox.restore()) + + it('test cookies include the module name', () => { + domain = 'greatpublisher.com' + rejectCookiesFor = 'greatpublisher.com' + + // stub Date.now() to return a constant value + sandbox.stub(Date, 'now').returns(1234567890) + + const randomName = `adapterV${(Math.random() * 1e8).toString(16)}` + const localDomainOverride = domainOverrideToRootDomain(storage, randomName) + + const time = Date.now(); + localDomainOverride(); + + sandbox.assert.callCount(setCookieStub, 2) + sandbox.assert.calledWith(setCookieStub, `_gd${time}_${randomName}`, '1', undefined, undefined, 'greatpublisher.com') + }); + + it('will return the root domain when given a subdomain', () => { + const test_domains = [ + 'deeply.nested.subdomain.for.greatpublisher.com', + 'greatpublisher.com', + 'subdomain.greatpublisher.com', + 'a-subdomain.greatpublisher.com', + ]; + + test_domains.forEach((testDomain) => { + domain = testDomain + rejectCookiesFor = 'com' + expect(domainOverride()).to.equal('greatpublisher.com'); + }); + }); + + it(`If we can't set cookies on the root domain, we'll return the subdomain`, () => { + domain = 'subdomain.greatpublisher.com' + rejectCookiesFor = 'greatpublisher.com' + expect(domainOverride()).to.equal('subdomain.greatpublisher.com'); + }); + + it('Will return undefined if we can\'t set cookies on the root domain or the subdomain', () => { + domain = 'subdomain.greatpublisher.com' + rejectCookiesFor = 'subdomain.greatpublisher.com' + expect(domainOverride()).to.equal(undefined); + }); +}); diff --git a/test/spec/modules/1plusXRtdProvider_spec.js b/test/spec/modules/1plusXRtdProvider_spec.js index 496c005f470..8657c37d7d8 100644 --- a/test/spec/modules/1plusXRtdProvider_spec.js +++ b/test/spec/modules/1plusXRtdProvider_spec.js @@ -332,15 +332,16 @@ describe('1plusXRtdProvider', () => { } it('correctly builds URLs if gdpr parameters are present', () => { - const url1 = getPapiUrl(customer) - const url2 = getPapiUrl(customer, extractConsent(consent)) - expect(['&consent_string=myConsent&gdpr_applies=1', '&gdpr_applies=1&consent_string=myConsent']).to.contain(url2.replace(url1, '')) + const url1 = getPapiUrl(customer); + const url2 = getPapiUrl(customer, extractConsent(consent)); + expect(['&consent_string=myConsent&gdpr_applies=1', '&gdpr_applies=1&consent_string=myConsent']).to.contain(url2.replace(url1, '')); }) - it('correctly builds URLs if fpid parameters are present') - const url1 = getPapiUrl(customer) - const url2 = getPapiUrl(customer, {}, 'my_first_party_id') - expect(url2.replace(url1, '')).to.equal('&fpid=my_first_party_id') + it('correctly builds URLs if fpid parameters are present', () => { + const url1 = getPapiUrl(customer); + const url2 = getPapiUrl(customer, {}, 'my_first_party_id'); + expect(url2.replace(url1, '')).to.equal('&fpid=my_first_party_id'); + }) }) describe('updateBidderConfig', () => { diff --git a/test/spec/modules/acuityAdsBidAdapter_spec.js b/test/spec/modules/acuityAdsBidAdapter_spec.js index 18ea574c1ce..05c59036ff3 100644 --- a/test/spec/modules/acuityAdsBidAdapter_spec.js +++ b/test/spec/modules/acuityAdsBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('AcuityAdsBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 759b16a81bb..adba79ddc96 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1481,31 +1481,6 @@ describe('Adagio bid adapter', () => { }); }); - describe.skip('optional params auto detection', function() { - it('should auto detect adUnitElementId when GPT is used', function() { - sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').withArgs('banner').returns({divId: 'gpt-banner'}); - expect(adagio.autoDetectAdUnitElementId('banner')).to.eq('gpt-banner'); - }); - }); - - describe.skip('print number handling', function() { - it('should return 1 if no adunit-code found. This means it is the first auction', function() { - sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); - expect(adagio.computePrintNumber('adunit-code')).to.eql(1); - }); - - it('should increment the adunit print number when the adunit-code has already been used for an other auction', function() { - sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); - - window.top.ADAGIO.adUnits['adunit-code'] = { - pageviewId: 'abc-def', - printNumber: 1, - }; - - expect(adagio.computePrintNumber('adunit-code')).to.eql(2); - }); - }); - describe('site information using refererDetection or window.top', function() { it('should returns domain, page and window.referrer in a window.top context', function() { const bidderRequest = new BidderRequestBuilder({ diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index 65a7b4111b7..1d2fb1e79cb 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -3,7 +3,7 @@ import {spec, storage} from 'modules/admaticBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {getStorageManager} from 'src/storageManager'; -const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb?bidder=admatic'; +const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb'; describe('admaticBidAdapter', () => { const adapter = newBidder(spec); @@ -22,11 +22,13 @@ describe('admaticBidAdapter', () => { 'host': 'layer.serve.admatic.com.tr' }, 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', - 'creativeId': 'er2ee' + 'creativeId': 'er2ee', + 'ortb2': { 'badv': ['admatic.com.tr'] } }; it('should return true when required params found', function() { @@ -34,15 +36,11 @@ describe('admaticBidAdapter', () => { }); it('should return false when required params are not passed', function() { - let bid = Object.assign({}, bid); - delete bid.params; - - bid.params = { - 'networkId': 0, - 'host': 'layer.serve.admatic.com.tr' + let bid2 = {}; + bid2.params = { + 'someIncorrectParam': 0 }; - - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); }); }); @@ -54,6 +52,7 @@ describe('admaticBidAdapter', () => { 'networkId': 10433394, 'host': 'layer.serve.admatic.com.tr' }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, 'mediaTypes': { 'banner': { 'sizes': [[300, 250], [728, 90]] @@ -98,6 +97,8 @@ describe('admaticBidAdapter', () => { 'h': 90 } ], + 'mediatype': {}, + 'type': 'banner', 'id': '2205da7a81846b', 'floors': { 'banner': { @@ -105,6 +106,49 @@ describe('admaticBidAdapter', () => { '728x90': { 'currency': 'USD', 'floor': 2 } } } + }, + { + 'size': [ + { + 'w': 338, + 'h': 280 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 338, + 280 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 1, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2 + }, + 'id': '45e86fc7ce7fc93' } ], 'ext': { @@ -118,6 +162,7 @@ describe('admaticBidAdapter', () => { 'networkId': 10433394, 'host': 'layer.serve.admatic.com.tr' }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, 'mediaTypes': { 'banner': { 'sizes': [[300, 250], [728, 90]] @@ -163,12 +208,57 @@ describe('admaticBidAdapter', () => { } ], 'id': '2205da7a81846b', + 'mediatype': {}, + 'type': 'banner', 'floors': { 'banner': { '300x250': { 'currency': 'USD', 'floor': 1 }, '728x90': { 'currency': 'USD', 'floor': 2 } } } + }, + { + 'size': [ + { + 'w': 338, + 'h': 280 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 338, + 280 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 1, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2 + }, + 'id': '45e86fc7ce7fc93' } ], 'ext': { @@ -194,6 +284,7 @@ describe('admaticBidAdapter', () => { 'sizes': [[300, 250], [728, 90]] } }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, getFloor: inputParams => { if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { return { @@ -217,6 +308,7 @@ describe('admaticBidAdapter', () => { 'networkId': 10433394, 'host': 'layer.serve.admatic.com.tr' }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [728, 90]], 'bidId': '30b31c1838de1e', @@ -249,9 +341,35 @@ describe('admaticBidAdapter', () => { 'width': 300, 'height': 250, 'price': 0.01, + 'type': 'banner', 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], - 'party_tag': '
' + 'party_tag': '
', + 'iurl': 'https://www.admatic.com.tr' + }, + { + 'id': 2, + 'creative_id': '3741', + 'width': 300, + 'height': 250, + 'price': 0.01, + 'type': 'video', + 'bidder': 'admatic', + 'adomain': ['admatic.com.tr'], + 'party_tag': '', + 'iurl': 'https://www.admatic.com.tr' + }, + { + 'id': 3, + 'creative_id': '3741', + 'width': 300, + 'height': 250, + 'price': 0.01, + 'type': 'video', + 'bidder': 'admatic', + 'adomain': ['admatic.com.tr'], + 'party_tag': 'https://www.admatic.com.tr', + 'iurl': 'https://www.admatic.com.tr' } ], 'queryId': 'cdnbh24rlv0hhkpfpln0', @@ -265,13 +383,48 @@ describe('admaticBidAdapter', () => { width: 300, height: 250, currency: 'TRY', + mediaType: 'banner', netRevenue: true, ad: '
', creativeId: '374', meta: { advertiserDomains: ['admatic.com.tr'] }, - ttl: 360, + ttl: 60, + bidder: 'admatic' + }, + { + requestId: 2, + cpm: 0.01, + width: 300, + height: 250, + currency: 'TRY', + mediaType: 'video', + netRevenue: true, + vastImpUrl: 'https://www.admatic.com.tr', + vastXml: '', + creativeId: '3741', + meta: { + advertiserDomains: ['admatic.com.tr'] + }, + ttl: 60, + bidder: 'admatic' + }, + { + requestId: 3, + cpm: 0.01, + width: 300, + height: 250, + currency: 'TRY', + mediaType: 'video', + netRevenue: true, + vastImpUrl: 'https://www.admatic.com.tr', + vastXml: 'https://www.admatic.com.tr', + creativeId: '3741', + meta: { + advertiserDomains: ['admatic.com.tr'] + }, + ttl: 60, bidder: 'admatic' } ]; diff --git a/test/spec/modules/admixerIdSystem_spec.js b/test/spec/modules/admixerIdSystem_spec.js index 18107b780db..753b1e3c2d5 100644 --- a/test/spec/modules/admixerIdSystem_spec.js +++ b/test/spec/modules/admixerIdSystem_spec.js @@ -1,9 +1,6 @@ import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; -import {getStorageManager} from '../../../src/storageManager.js'; - -export const storage = getStorageManager(); const pid = '4D393FAC-B6BB-4E19-8396-0A4813607316'; const getIdParams = {params: {pid: pid}}; diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index b787a52d6f2..153565eb3ca 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -8,26 +8,27 @@ import { getStorageManager } from 'src/storageManager.js'; describe('adnuntiusBidAdapter', function () { const URL = 'https://ads.adnuntius.delivery/i?tzo='; + const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo='; const GVLID = 855; const usi = utils.generateUUID() const meta = [{ key: 'usi', value: usi }] before(() => { - const storage = getStorageManager({ gvlid: GVLID, moduleName: 'adnuntius' }) - storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) - }); - - beforeEach(function () { $$PREBID_GLOBAL$$.bidderSettings = { adnuntius: { storageAllowed: true } }; + const storage = getStorageManager({ bidderCode: 'adnuntius' }) + storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) + }); + + after(() => { + $$PREBID_GLOBAL$$.bidderSettings = {}; }); afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.bidderSettings = {}; }); const tzo = new Date().getTimezoneOffset(); @@ -35,7 +36,7 @@ describe('adnuntiusBidAdapter', function () { const ENDPOINT_URL_VIDEO = `${URL}${tzo}&format=json&userId=${usi}&tt=vast4`; const ENDPOINT_URL_NOCOOKIE = `${URL}${tzo}&format=json&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${URL}${tzo}&format=json&segments=segment1,segment2,segment3&userId=${usi}`; - const ENDPOINT_URL_CONSENT = `${URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; + const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; const adapter = newBidder(spec); const bidderRequests = [ diff --git a/test/spec/modules/adrinoBidAdapter_spec.js b/test/spec/modules/adrinoBidAdapter_spec.js index 78cea8da9ac..72f006d4e4a 100644 --- a/test/spec/modules/adrinoBidAdapter_spec.js +++ b/test/spec/modules/adrinoBidAdapter_spec.js @@ -43,7 +43,7 @@ describe('adrinoBidAdapter', function () { it('should return false when unsupported media type is requested', function () { const bid = { ...validBid }; - bid.mediaTypes = { banner: { sizes: [[300, 250]] } }; + bid.mediaTypes = { video: {} }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -54,7 +54,46 @@ describe('adrinoBidAdapter', function () { }); }); - describe('buildRequests', function () { + describe('buildBannerRequest', function () { + const bidRequest = { + bidder: 'adrino', + params: { + hash: 'abcdef123456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [970, 250]] + } + }, + sizes: [[300, 250], [970, 250]], + userId: { criteoId: '2xqi3F94aHdwWnM3', pubcid: '3ec0b202-7697' }, + adUnitCode: 'adunit-code-2', + bidId: '12345678901234', + bidderRequestId: '98765432109876', + auctionId: '01234567891234', + }; + + it('should build the request correctly', function () { + const result = spec.buildRequests( + [ bidRequest ], + { refererInfo: { page: 'http://example.com/' } } + ); + expect(result.length).to.equal(1); + expect(result[0].method).to.equal('POST'); + expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('bannerParams'); + expect(result[0].data[0].bannerParams.sizes.length).to.equal(2); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); + }); + }); + + describe('buildNativeRequest', function () { const bidRequest = { bidder: 'adrino', params: { @@ -71,6 +110,15 @@ describe('adrinoBidAdapter', function () { } } }, + nativeParams: { + title: { + required: true + }, + image: { + required: true, + sizes: [[300, 150], [300, 210]] + } + }, userId: { criteoId: '2xqi3F94aHdwWnM3', pubcid: '3ec0b202-7697' }, adUnitCode: 'adunit-code', bidId: '12345678901234', @@ -86,16 +134,16 @@ describe('adrinoBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://stg-prebid-bidder.adrino.io/bidder/bid/'); - expect(result[0].data.bidId).to.equal('12345678901234'); - expect(result[0].data.placementHash).to.equal('abcdef123456'); - expect(result[0].data.referer).to.equal('http://example.com/'); - expect(result[0].data.userAgent).to.equal(navigator.userAgent); - expect(result[0].data).to.have.property('nativeParams'); - expect(result[0].data).not.to.have.property('gdprConsent'); - expect(result[0].data).to.have.property('userId'); - expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); - expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + expect(result[0].url).to.equal('https://stg-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('nativeParams'); + expect(result[0].data[0]).not.to.have.property('gdprConsent'); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); }); it('should build the request correctly with gdpr', function () { @@ -105,16 +153,16 @@ describe('adrinoBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bid/'); - expect(result[0].data.bidId).to.equal('12345678901234'); - expect(result[0].data.placementHash).to.equal('abcdef123456'); - expect(result[0].data.referer).to.equal('http://example.com/'); - expect(result[0].data.userAgent).to.equal(navigator.userAgent); - expect(result[0].data).to.have.property('nativeParams'); - expect(result[0].data).to.have.property('gdprConsent'); - expect(result[0].data).to.have.property('userId'); - expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); - expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('nativeParams'); + expect(result[0].data[0]).to.have.property('gdprConsent'); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); }); it('should build the request correctly without gdpr', function () { @@ -124,22 +172,22 @@ describe('adrinoBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bid/'); - expect(result[0].data.bidId).to.equal('12345678901234'); - expect(result[0].data.placementHash).to.equal('abcdef123456'); - expect(result[0].data.referer).to.equal('http://example.com/'); - expect(result[0].data.userAgent).to.equal(navigator.userAgent); - expect(result[0].data).to.have.property('nativeParams'); - expect(result[0].data).not.to.have.property('gdprConsent'); - expect(result[0].data).to.have.property('userId'); - expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); - expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('nativeParams'); + expect(result[0].data[0]).not.to.have.property('gdprConsent'); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); }); }); describe('interpretResponse', function () { it('should interpret the response correctly', function () { - const response = { + const response1 = { requestId: '31662c69728811', mediaType: 'native', cpm: 0.53, @@ -167,13 +215,44 @@ describe('adrinoBidAdapter', function () { } }; + const response2 = { + requestId: '31662c69728812', + mediaType: 'native', + cpm: 0.77, + currency: 'PLN', + creativeId: '859120', + netRevenue: true, + ttl: 600, + width: 1, + height: 1, + noAd: false, + testAd: false, + native: { + title: 'Ad Title', + body: 'Ad Body', + image: { + url: 'http://emisja.contentstream.pl/_/getImageII/?vid=17180728299&typ=cs_300_150&element=IMAGE&scale=1&prefix=adart&nc=1643878278955', + height: 150, + width: 300 + }, + clickUrl: 'http://emisja.contentstream.pl/_/ctr2/?u=https%3A%2F%2Fonline.efortuna.pl%2Fpage%3Fkey%3Dej0xMzUzMTM1NiZsPTE1Mjc1MzY1JnA9NTMyOTA%253D&e=znU3tABN8K4N391dmUxYfte5G9tBaDXELJVo1_-kvaTJH2XwWRw77fmfL2YjcEmrbqRQ3M0GcJ0vPWcLtZlsrf8dWrAEHNoZKAC6JMnZF_65IYhTPbQIJ-zn3ac9TU7gEZftFKksH1al7rMuieleVv9r6_DtrOk_oZcYAe4rMRQM-TiWvivJRPBchAAblE0cqyG7rCunJFpal43sxlYm4GvcBJaYHzErn5PXjEzNbd3xHjkdiap-xU9y6BbfkUZ1xIMS8QZLvwNrTXMFCSfSRN2tgVfEj7KyGdLCITHSaFtuIKT2iW2pxC7f2RtPHnzsEPXH0SgAfhA3OxZ5jkQjOZy0PsO7MiCv3sJai5ezUAOjFgayU91ZhI0Y9r2YpB1tTGIjnO23wot8PvRENlThHQ%3D%3D&ref=https%3A%2F%2Fbox.adrino.cloud%2Ftmielcarz%2Fadrino_prebid%2Ftest_page3.html%3Fpbjs_debug%3Dtrue', + privacyLink: 'https://adrino.pl/wp-content/uploads/2021/01/POLITYKA-PRYWATNOS%CC%81CI-Adrino-Mobile.pdf', + impressionTrackers: [ + 'https://prd-impression-tracker-producer.adrino.io/impression/eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpbXByZXNzaW9uSWRcIjpcIjMxNjYyYzY5NzI4ODExXCIsXCJkYXRlXCI6WzIwMjIsMiwzXSxcInBsYWNlbWVudEhhc2hcIjpcIjk0NTVjMDQxYzlkMTI1ZmIwNDE4MWVhMGVlZTJmMmFlXCIsXCJjYW1wYWlnbklkXCI6MTc5MjUsXCJhZHZlcnRpc2VtZW50SWRcIjo5MjA3OSxcInZpc3VhbGlzYXRpb25JZFwiOjg1OTExNSxcImNwbVwiOjUzLjB9IiwiZXhwIjoxNjQzOTE2MjUxLCJpYXQiOjE2NDM5MTU2NTF9.0Y_HvInGl6Xo5xP6rDLC8lzQRGvy-wKe0blk1o8ebWyVRFiUY1JGLUeE0k3sCsPNxgdHAv-o6EcbogpUuqlMJA' + ] + } + }; + const serverResponse = { - body: response + body: { bidResponses: [response1, response2] } }; const result = spec.interpretResponse(serverResponse, {}); - expect(result.length).to.equal(1); - expect(result[0]).to.equal(response); + expect(result.length).to.equal(2); + expect(result[0]).to.equal(response1); + expect(result[0].requestId).to.equal('31662c69728811'); + expect(result[1]).to.equal(response2); + expect(result[1].requestId).to.equal('31662c69728812'); }); it('should return empty array of responses', function () { diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index f58e49eb364..6a875feb2a9 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -276,7 +276,7 @@ const SERVER_RESPONSE_VIDEO = { }, } -const WIN_NOTICE = { +const WIN_NOTICE_WEB = { 'adId': '3a20ee5dc78c1e', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'creativeId': '24277955', @@ -297,9 +297,71 @@ const WIN_NOTICE = { 'USD' ], 'mediaType': 'banner', - 'advertiserDomains': [ - 'abc.com' + 'meta': { + 'advertiserDomains': [ + 'cloudflare.com' + ], + 'ext': {} + }, + 'size': '300x250', + 'params': [ + { + 'placementId': '13144370', + 'siteId': '23434', + 'publisherId': '7689670753' + } + ], + 'width': 300, + 'height': 250, + 'status': 'rendered', + 'transactionId': 'ce089116-4251-45c3-bdbb-3a03cb13816b', + 'ttl': 300, + 'requestTimestamp': 1666796241007, + 'responseTimestamp': 1666796241021, + metrics: { + getMetrics() { + return { + + } + } + } +} + +const WIN_NOTICE_APP = { + 'adId': '3a20ee5dc78c1e', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'creativeId': '24277955', + 'cpm': 1, + 'netRevenue': false, + 'adserverTargeting': { + 'hb_bidder': 'aidem', + 'hb_adid': '3a20ee5dc78c1e', + 'hb_pb': '1.00', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + + 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', + 'currency': [ + 'USD' ], + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': [ + 'cloudflare.com' + ], + 'ext': { + 'app': { + 'app_bundle': '{{APP_BUNDLE}}', + 'app_id': '{{APP_ID}}', + 'app_name': '{{APP_NAME}}', + 'app_store_url': '{{APP_STORE_URL}}', + 'inventory_source': '{{INVENTORY_SOURCE}}' + } + } + }, 'size': '300x250', 'params': [ { @@ -549,11 +611,13 @@ describe('Aidem adapter', () => { expect(spec.onBidWon).to.exist.and.to.be.a('function') }); - it(`should send a valid bid won notice`, function () { - spec.onBidWon(WIN_NOTICE); - // server.respondWith('POST', WIN_EVENT_URL, [ - // 400, {'Content-Type': 'application/json'}, ) - // ]); + it(`should send a valid bid won notice from web environment`, function () { + spec.onBidWon(WIN_NOTICE_WEB); + expect(server.requests.length).to.equal(1); + }); + + it(`should send a valid bid won notice from app environment`, function () { + spec.onBidWon(WIN_NOTICE_APP); expect(server.requests.length).to.equal(1); }); }); diff --git a/test/spec/modules/amxIdSystem_spec.js b/test/spec/modules/amxIdSystem_spec.js index dea79e87baa..c1ae2c791d5 100644 --- a/test/spec/modules/amxIdSystem_spec.js +++ b/test/spec/modules/amxIdSystem_spec.js @@ -1,4 +1,4 @@ -import { amxIdSubmodule } from 'modules/amxIdSystem.js'; +import { amxIdSubmodule, storage } from 'modules/amxIdSystem.js'; import { server } from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; @@ -48,38 +48,17 @@ describe('validateConfig', () => { logErrorSpy.restore(); }); - it('should return undefined if config.storage is not present', () => { + it('should allow configuration with no storage', () => { expect( amxIdSubmodule.getId( { ...config, - storage: null, + storage: undefined }, null, null ) - ).to.equal(undefined); - - expect(logErrorSpy.calledOnce).to.be.true; - expect(logErrorSpy.lastCall.lastArg).to.contain('storage is required'); - }); - - it('should return undefined if config.storage.type !== "html5"', () => { - expect( - amxIdSubmodule.getId( - { - ...config, - storage: { - type: 'cookie', - }, - }, - null, - null - ) - ).to.equal(undefined); - - expect(logErrorSpy.calledOnce).to.be.true; - expect(logErrorSpy.lastCall.lastArg).to.contain('cookie'); + ).to.not.equal(undefined); }); it('should return undefined if expires > 30', () => { @@ -111,10 +90,18 @@ describe('getId', () => { }); it('should call the sync endpoint and accept a valid response', () => { + storage.setDataInLocalStorage('__amuidpb', TEST_ID); + const { callback } = amxIdSubmodule.getId(config, null, null); callback(spy); const [request] = server.requests; + expect(request.withCredentials).to.be.true + expect(request.requestHeaders['Content-Type']).to.match(/text\/plain/) + + const { search } = utils.parseUrl(request.url); + expect(search.av).to.equal(amxIdSubmodule.version); + expect(search.am).to.equal(TEST_ID); expect(request.method).to.equal('GET'); request.respond( @@ -187,7 +174,7 @@ describe('getId', () => { ); const [, secondRequest] = server.requests; - expect(secondRequest.url).to.be.equal(intermediateValue); + expect(secondRequest.url).to.match(new RegExp(`^${intermediateValue}\?`)); secondRequest.respond( 200, {}, diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 9e88e2875c7..1603c6e9397 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -257,11 +257,15 @@ describe('AppNexusAdapter', function () { transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' }]; - let types = ['banner', 'video']; + let types = ['banner']; if (FEATURES.NATIVE) { types.push('native'); } + if (FEATURES.VIDEO) { + types.push('video'); + } + types.forEach(type => { getAdUnitsStub.callsFake(function (...args) { return adUnits; @@ -290,122 +294,303 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].ad_types).to.not.exist; }); - it('should populate the ad_types array on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = { context: 'outstream' }; + if (FEATURES.VIDEO) { + it('should populate the ad_types array on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = { context: 'outstream' }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - expect(payload.tags[0].ad_types).to.deep.equal(['video']); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); - }); + it('should attach valid video params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + video: { + id: 123, + minduration: 100, + foobar: 'invalid' + } + } + } + ); - it('should attach valid video params to the tag', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('should include ORTB video values when video params were not set', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + + it('should add video property when adUnit includes a renderer', function () { + const videoData = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + }, params: { placementId: '10433394', video: { - id: 123, - minduration: 100, - foobar: 'invalid' + skippable: true, + playback_method: ['auto_play_sound_off'] } } - } - ); + }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - id: 123, - minduration: 100 - }); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); + let bidRequest1 = deepClone(bidRequests[0]); + bidRequest1 = Object.assign({}, bidRequest1, videoData, { + renderer: { + url: 'https://test.renderer.url', + render: function () { } + } + }); - it('should include ORTB video values when video params were not set', function () { - let bidRequest = deepClone(bidRequests[0]); - bidRequest.params = { - placementId: '1234235', - video: { + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + bidRequest2 = Object.assign({}, bidRequest2, videoData); + + const request = spec.buildRequests([bidRequest1, bidRequest2]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ skippable: true, - playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], - context: 'outstream' - } - }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] - } - }; + playback_method: 2, + custom_renderer_present: true + }); + expect(payload.tags[1].video).to.deep.equal({ + skippable: true, + playback_method: 2 + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + it('should duplicate adpod placements into batches and set correct maxduration', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + + // 300 / 15 = 20 total + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload1.tags[0].video.maxduration).to.equal(30); + + expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload2.tags[0].video.maxduration).to.equal(30); }); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) - }); - it('should add video property when adUnit includes a renderer', function () { - const videoData = { - mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mp4'] + it('should round down adpod placements when numbers are uneven', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 123, + durationRangeSec: [45], + } + } } - }, - params: { - placementId: '10433394', - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(2); + }); + + it('should duplicate adpod placements when requireExactDuration is set', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true, + } + } } - } - }; + ); - let bidRequest1 = deepClone(bidRequests[0]); - bidRequest1 = Object.assign({}, bidRequest1, videoData, { - renderer: { - url: 'https://test.renderer.url', - render: function () { } - } + // 20 total placements with 15 max impressions = 2 requests + const request = spec.buildRequests([bidRequest]); + expect(request.length).to.equal(2); + + // 20 spread over 2 requests = 15 in first request, 5 in second + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + // 10 placements should have max/min at 15 + // 10 placemenst should have max/min at 30 + const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); + const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); + expect(payload1tagsWith15.length).to.equal(10); + expect(payload1tagsWith30.length).to.equal(5); + + // 5 placemenst with min/max at 30 were in the first request + // so 5 remaining should be in the second + const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); + expect(payload2tagsWith30.length).to.equal(5); }); - let bidRequest2 = deepClone(bidRequests[0]); - bidRequest2.adUnitCode = 'adUnit_code_2'; - bidRequest2 = Object.assign({}, bidRequest2, videoData); + it('should set durations for placements when requireExactDuration is set and numbers are uneven', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 105, + durationRangeSec: [15, 30, 60], + requireExactDuration: true, + } + } + } + ); - const request = spec.buildRequests([bidRequest1, bidRequest2]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - skippable: true, - playback_method: 2, - custom_renderer_present: true + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(7); + + const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); + const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); + const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); + expect(tagsWith15.length).to.equal(3); + expect(tagsWith30.length).to.equal(3); + expect(tagsWith60.length).to.equal(1); }); - expect(payload.tags[1].video).to.deep.equal({ - skippable: true, - playback_method: 2 + + it('should break adpod request into batches', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 225, + durationRangeSec: [5], + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + const payload3 = JSON.parse(request[2].data); + + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(15); + expect(payload3.tags.length).to.equal(15); + }); + + it('should contain hb_source value for adpod', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + const request = spec.buildRequests([bidRequest])[0]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(7); }); + } // VIDEO + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); }); it('should attach valid user params to the tag', function () { @@ -486,185 +671,6 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].reserve).to.exist.and.to.equal(3); }); - it('should duplicate adpod placements into batches and set correct maxduration', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - - // 300 / 15 = 20 total - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload1.tags[0].video.maxduration).to.equal(30); - - expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload2.tags[0].video.maxduration).to.equal(30); - }); - - it('should round down adpod placements when numbers are uneven', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 123, - durationRangeSec: [45], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(2); - }); - - it('should duplicate adpod placements when requireExactDuration is set', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - requireExactDuration: true, - } - } - } - ); - - // 20 total placements with 15 max impressions = 2 requests - const request = spec.buildRequests([bidRequest]); - expect(request.length).to.equal(2); - - // 20 spread over 2 requests = 15 in first request, 5 in second - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - // 10 placements should have max/min at 15 - // 10 placemenst should have max/min at 30 - const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); - const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); - expect(payload1tagsWith15.length).to.equal(10); - expect(payload1tagsWith30.length).to.equal(5); - - // 5 placemenst with min/max at 30 were in the first request - // so 5 remaining should be in the second - const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); - expect(payload2tagsWith30.length).to.equal(5); - }); - - it('should set durations for placements when requireExactDuration is set and numbers are uneven', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 105, - durationRangeSec: [15, 30, 60], - requireExactDuration: true, - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(7); - - const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); - const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); - const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); - expect(tagsWith15.length).to.equal(3); - expect(tagsWith30.length).to.equal(3); - expect(tagsWith60.length).to.equal(1); - }); - - it('should break adpod request into batches', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 225, - durationRangeSec: [5], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - const payload3 = JSON.parse(request[2].data); - - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(15); - expect(payload3.tags.length).to.equal(15); - }); - - it('should contain hb_source value for adpod', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - const request = spec.buildRequests([bidRequest])[0]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].hb_source).to.deep.equal(7); - }); - it('should contain hb_source value for other media', function () { let bidRequest = Object.assign({}, bidRequests[0], @@ -1392,27 +1398,31 @@ describe('AppNexusAdapter', function () { }); it('should populate iab_support object at the root level if omid support is detected', function () { - // with bid.params.frameworks - let bidRequest_A = Object.assign({}, bidRequests[0], { - params: { - frameworks: [1, 2, 5, 6], - video: { - frameworks: [1, 2, 5, 6] + let request, payload; + + if (FEATURES.VIDEO) { + // with bid.params.frameworks + let bidRequest_A = Object.assign({}, bidRequests[0], { + params: { + frameworks: [1, 2, 5, 6], + video: { + frameworks: [1, 2, 5, 6] + } } - } - }); - let request = spec.buildRequests([bidRequest_A]); - let payload = JSON.parse(request.data); - expect(payload.iab_support).to.be.an('object'); - expect(payload.iab_support).to.deep.equal({ - omidpn: 'Appnexus', - omidpv: '$prebid.version$' - }); - expect(payload.tags[0].banner_frameworks).to.be.an('array'); - expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video_frameworks).to.be.an('array'); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video.frameworks).to.not.exist; + }); + request = spec.buildRequests([bidRequest_A]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.be.an('object'); + expect(payload.iab_support).to.deep.equal({ + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + }); + expect(payload.tags[0].banner_frameworks).to.be.an('array'); + expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video_frameworks).to.be.an('array'); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video.frameworks).to.not.exist; + } // without bid.params.frameworks const bidRequest_B = Object.assign({}, bidRequests[0]); @@ -1422,19 +1432,21 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].banner_frameworks).to.not.exist; expect(payload.tags[0].video_frameworks).to.not.exist; - // with video.frameworks but it is not an array - const bidRequest_C = Object.assign({}, bidRequests[0], { - params: { - video: { - frameworks: "'1', '2', '3', '6'" + if (FEATURES.VIDEO) { + // with video.frameworks but it is not an array + const bidRequest_C = Object.assign({}, bidRequests[0], { + params: { + video: { + frameworks: "'1', '2', '3', '6'" + } } - } - }); - request = spec.buildRequests([bidRequest_C]); - payload = JSON.parse(request.data); - expect(payload.iab_support).to.not.exist; - expect(payload.tags[0].banner_frameworks).to.not.exist; - expect(payload.tags[0].video_frameworks).to.not.exist; + }); + request = spec.buildRequests([bidRequest_C]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + } }); }) @@ -1591,116 +1603,118 @@ describe('AppNexusAdapter', function () { expect(result.length).to.equal(0); }); - it('handles outstream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' + if (FEATURES.VIDEO) { + it('handles outstream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } } - } - }] - } + }] + } - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastXml'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); - it('handles instream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/vid' - } - }, - 'javascriptTrackers': '' + it('handles instream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/vid' + } + }, + 'javascriptTrackers': '' + }] }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'instream' + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } } - } - }] - } + }] + } - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); - it('handles adpod responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'brand_category_id': 10, - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/adpod', - 'duration_ms': 30000, + it('handles adpod responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'brand_category_id': 10, + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/adpod', + 'duration_ms': 30000, + } + }, + 'viewability': { + 'config': '' } - }, - 'viewability': { - 'config': '' - } + }] }] - }] - }; + }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } } - } - }] - }; - bfStub.returns('1'); + }] + }; + bfStub.returns('1'); - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0].video.context).to.equal('adpod'); - expect(result[0].video.durationSeconds).to.equal(30); - }); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0].video.context).to.equal('adpod'); + expect(result[0].video.durationSeconds).to.equal(30); + }); + } if (FEATURES.NATIVE) { it('handles native responses', function () { @@ -1758,59 +1772,61 @@ describe('AppNexusAdapter', function () { }); } - it('supports configuring outstream renderers', function () { - const outstreamResponse = deepClone(response); - outstreamResponse.tags[0].ads[0].rtb.video = {}; - outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + if (FEATURES.VIDEO) { + it('supports configuring outstream renderers', function () { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - renderer: { - options: { - adText: 'configured' - } - }, - mediaTypes: { - video: { - context: 'outstream' + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + renderer: { + options: { + adText: 'configured' + } + }, + mediaTypes: { + video: { + context: 'outstream' + } } - } - }] - }; + }] + }; - const result = spec.interpretResponse({ body: outstreamResponse }, { bidderRequest }); - expect(result[0].renderer.config).to.deep.equal( - bidderRequest.bids[0].renderer.options - ); - }); + const result = spec.interpretResponse({ body: outstreamResponse }, { bidderRequest }); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); - it('should add deal_priority and deal_code', function () { - let responseWithDeal = deepClone(response); - responseWithDeal.tags[0].ads[0].ad_type = 'video'; - responseWithDeal.tags[0].ads[0].deal_priority = 5; - responseWithDeal.tags[0].ads[0].deal_code = '123'; - responseWithDeal.tags[0].ads[0].rtb.video = { - duration_ms: 1500, - player_width: 640, - player_height: 340, - }; + it('should add deal_priority and deal_code', function () { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].ad_type = 'video'; + responseWithDeal.tags[0].ads[0].deal_priority = 5; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + responseWithDeal.tags[0].ads[0].rtb.video = { + duration_ms: 1500, + player_width: 640, + player_height: 340, + }; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } } - } - }] - } - let result = spec.interpretResponse({ body: responseWithDeal }, { bidderRequest }); - expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); - expect(result[0].video.dealTier).to.equal(5); - }); + }] + } + let result = spec.interpretResponse({ body: responseWithDeal }, { bidderRequest }); + expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + expect(result[0].video.dealTier).to.equal(5); + }); + } it('should add advertiser id', function () { let responseAdvertiserId = deepClone(response); diff --git a/test/spec/modules/appushBidAdapter_spec.js b/test/spec/modules/appushBidAdapter_spec.js index 91c50cc3dd0..e6af98c0f33 100644 --- a/test/spec/modules/appushBidAdapter_spec.js +++ b/test/spec/modules/appushBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('AppushBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js index cae90a19223..2316f96ec8e 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -3,14 +3,14 @@ import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import {server} from '../../mocks/xhr.js'; import {parseBrowser} from '../../../modules/atsAnalyticsAdapter.js'; -import {getStorageManager} from '../../../src/storageManager.js'; +import {getCoreStorageManager, getStorageManager} from '../../../src/storageManager.js'; import {analyticsUrl} from '../../../modules/atsAnalyticsAdapter.js'; let utils = require('src/utils'); let events = require('src/events'); let constants = require('src/constants.json'); -export const storage = getStorageManager(); +const storage = getCoreStorageManager(); let sandbox; let clock; let now = new Date(); diff --git a/test/spec/modules/axonixBidAdapter_spec.js b/test/spec/modules/axonixBidAdapter_spec.js index 37f409e5769..c1cc6d46fb2 100644 --- a/test/spec/modules/axonixBidAdapter_spec.js +++ b/test/spec/modules/axonixBidAdapter_spec.js @@ -286,26 +286,6 @@ describe('AxonixBidAdapter', function () { }); }); - describe.skip('buildRequests: can handle native ad requests', function () { - it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { - // loop: - // set supply id - // set region/endpoint in ssp config - // call buildRequests, validate request (url, method, supply id) - expect.fail('Not implemented'); - }); - - it('creates ServerRequests pointing to default endpoint if missing', function () { - // no endpoint in config means default value openrtb.axonix.com - expect.fail('Not implemented'); - }); - - it('creates ServerRequests pointing to default region if missing', function () { - // no region in config means default value us-east-1 - expect.fail('Not implemented'); - }); - }); - describe('interpretResponse', function () { it('considers corner cases', function() { expect(spec.interpretResponse(null)).to.be.an('array').that.is.empty; @@ -331,13 +311,6 @@ describe('AxonixBidAdapter', function () { expect(response).to.be.an('array').that.is.not.empty; expect(response[0]).to.equal(VIDEO_RESPONSE.body[0]); }); - - it.skip('parses 1 native responses', function () { - // passing 1 valid native in a response generates an array with 1 correct prebid response - // examine mediaType:native, native element - // check nativeBidIsValid from {@link file://./../../../src/native.js} - expect.fail('Not implemented'); - }); }); describe('onBidWon', function () { diff --git a/test/spec/modules/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index a4c1125fa5f..751b3ae1098 100644 --- a/test/spec/modules/beyondmediaBidAdapter_spec.js +++ b/test/spec/modules/beyondmediaBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('AndBeyondMediaBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/big-richmediaBidAdapter_spec.js b/test/spec/modules/big-richmediaBidAdapter_spec.js index b2ee0725d49..f01e261ef9f 100644 --- a/test/spec/modules/big-richmediaBidAdapter_spec.js +++ b/test/spec/modules/big-richmediaBidAdapter_spec.js @@ -92,40 +92,42 @@ describe('bigRichMediaAdapterTests', function () { expect(payload.tags[0].sizes).to.have.lengthOf(3); }); - it('should build video bid request', function() { - const bidRequest = deepClone(bidRequests[0]); - bidRequest.params = { - placementId: '1234235', - video: { - skippable: true, - playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], - context: 'outstream', - format: 'sticky-top' - } - }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] - } - }; + if (FEATURES.VIDEO) { + it('should build video bid request', function() { + const bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream', + format: 'sticky-top' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) }); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) - }); + } }); describe('interpretResponse', function () { @@ -227,42 +229,44 @@ describe('bigRichMediaAdapterTests', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('handles outstream video responses', function () { - const response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' + if (FEATURES.VIDEO) { + it('handles outstream video responses', function () { + const response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] }] - }] - }; - const bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' + }; + const bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } } - } - }] - } + }] + } - const result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).not.to.have.property('vastXml'); - expect(result[0]).not.to.have.property('vastUrl'); - expect(result[0]).to.have.property('width', 1); - expect(result[0]).to.have.property('height', 1); - expect(result[0]).to.have.property('mediaType', 'banner'); - }); + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).not.to.have.property('vastXml'); + expect(result[0]).not.to.have.property('vastUrl'); + expect(result[0]).to.have.property('width', 1); + expect(result[0]).to.have.property('height', 1); + expect(result[0]).to.have.property('mediaType', 'banner'); + }); + } }); describe('getUserSyncs', function() { diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js index 28021c4f7c0..6a761e63ea1 100644 --- a/test/spec/modules/compassBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('CompassBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index d5e7140a9f7..f5c807b4703 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -149,15 +149,10 @@ describe('ConcertAdapter', function () { it('should use sharedid if it exists', function() { storage.removeDataFromLocalStorage('c_nap'); - const request = spec.buildRequests(bidRequests, { - ...bidRequest, - userId: { - _sharedid: { - id: '123abc' - } - } - }); + const bidRequestsWithSharedId = [{ ...bidRequests[0], userId: { sharedid: { id: '123abc' } } }] + const request = spec.buildRequests(bidRequestsWithSharedId, bidRequest); const payload = JSON.parse(request.data); + expect(payload.meta.uid).to.equal('123abc'); }) diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 52639c39baf..72acfa38e0a 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -1,6 +1,10 @@ import {expect} from 'chai'; -import {parseQS} from 'src/utils.js'; -import {connectIdSubmodule} from 'modules/connectIdSystem.js'; +import {connectIdSubmodule, storage} from 'modules/connectIdSystem.js'; +import {server} from '../../mocks/xhr'; +import {parseQS, parseUrl} from 'src/utils.js'; +import {uspDataHandler} from 'src/adapterManager.js'; + +const TEST_SERVER_URL = 'http://localhost:9876/'; describe('Yahoo ConnectID Submodule', () => { const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; @@ -8,6 +12,8 @@ describe('Yahoo ConnectID Submodule', () => { const PIXEL_ID = '1234'; const PROD_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PIXEL_ID}/fed`; const OVERRIDE_ENDPOINT = 'https://foo/bar'; + const STORAGE_KEY = 'connectId'; + const USP_DATA = '1YYY'; it('should have the correct module name declared', () => { expect(connectIdSubmodule.name).to.equal('connectId'); @@ -20,271 +26,440 @@ describe('Yahoo ConnectID Submodule', () => { describe('getId()', () => { let ajaxStub; let getAjaxFnStub; + let getCookieStub; + let setCookieStub; + let getLocalStorageStub; + let setLocalStorageStub; + let cookiesEnabledStub; + let localStorageEnabledStub; + let removeLocalStorageDataStub; + let uspConsentDataStub; + let consentData; beforeEach(() => { ajaxStub = sinon.stub(); getAjaxFnStub = sinon.stub(connectIdSubmodule, 'getAjaxFn'); getAjaxFnStub.returns(ajaxStub); + getCookieStub = sinon.stub(storage, 'getCookie'); + setCookieStub = sinon.stub(storage, 'setCookie'); + cookiesEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + setLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); + localStorageEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + removeLocalStorageDataStub = sinon.stub(storage, 'removeDataFromLocalStorage'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + + cookiesEnabledStub.returns(true); + localStorageEnabledStub.returns(true); + uspConsentDataStub.returns(USP_DATA); consentData = { - gdpr: { - gdprApplies: 1, - consentString: 'GDPR_CONSENT_STRING' - }, - uspConsent: 'USP_CONSENT_STRING', - gppConsent: { - gppString: 'header~section6~section7', - applicableSections: [6, 7] - } + gdprApplies: 1, + consentString: 'GDPR_CONSENT_STRING' }; }); afterEach(() => { getAjaxFnStub.restore(); + getCookieStub.restore(); + setCookieStub.restore(); + getLocalStorageStub.restore(); + setLocalStorageStub.restore(); + cookiesEnabledStub.restore(); + localStorageEnabledStub.restore(); + removeLocalStorageDataStub.restore(); + uspConsentDataStub.restore(); }); function invokeGetIdAPI(configParams, consentData) { let result = connectIdSubmodule.getId({ params: configParams }, consentData); - if (typeof result === 'object') { + if (typeof result === 'object' && result.callback) { result.callback(sinon.stub()); } return result; } - it('returns undefined if he, pixelId and puid params are not passed', () => { - expect(invokeGetIdAPI({}, consentData)).to.be.undefined; - expect(ajaxStub.callCount).to.equal(0); - }); - - it('returns undefined if the pixelId param is not passed', () => { - expect(invokeGetIdAPI({ - he: HASHED_EMAIL, - puid: PUBLISHER_USER_ID - }, consentData)).to.be.undefined; - expect(ajaxStub.callCount).to.equal(0); - }); - - it('returns undefined if the pixelId param is passed, but the he and puid param are not passed', () => { - expect(invokeGetIdAPI({ - pixelId: PIXEL_ID - }, consentData)).to.be.undefined; - expect(ajaxStub.callCount).to.equal(0); - }); - - it('returns an object with the callback function if the endpoint override param and the he params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the endpoint override param and the puid params are passed', () => { - let result = invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the endpoint override param and the puid and he params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - puid: PUBLISHER_USER_ID, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the pixelId and he params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the pixelId and puid params are passed', () => { - let result = invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the pixelId, he and puid params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an undefined if the Yahoo specific opt-out key is present in local storage', () => { - localStorage.setItem('connectIdOptOut', '1'); - expect(invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData)).to.be.undefined; - localStorage.removeItem('connectIdOptOut'); - }); - - it('returns an object with the callback function if the correct params are passed and Yahoo opt-out value is not "1"', () => { - localStorage.setItem('connectIdOptOut', 'true'); - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - localStorage.removeItem('connectIdOptOut'); - }); - - it('Makes an ajax GET request to the production API endpoint with pixelId and he query params', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - - const expectedParams = { - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('Makes an ajax GET request to the production API endpoint with pixelId and puid query params', () => { - invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID - }, consentData); - - const expectedParams = { - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('Makes an ajax GET request to the production API endpoint with pixelId, puid and he query params', () => { - invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - - const expectedParams = { - puid: PUBLISHER_USER_ID, - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('Makes an ajax GET request to the specified override API endpoint with query params', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - - const expectedParams = { - he: HASHED_EMAIL, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('sets the callbacks param of the ajax function call correctly', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + describe('Low level storage functionality', () => { + const storageTestCases = [ + { + detail: 'cookie data over local storage data', + cookie: '{"connectId":"foo"}', + localStorage: {connectId: 'bar'}, + expected: {connectId: 'foo'} + }, + { + detail: 'cookie data if only cookie data exists', + cookie: '{"connectId":"foo"}', + localStorage: undefined, + expected: {connectId: 'foo'} + }, + { + detail: 'local storage data if only it local storage data exists', + cookie: undefined, + localStorage: {connectId: 'bar'}, + expected: {connectId: 'bar'} + }, + { + detail: 'undefined when both cookie and local storage are empty', + cookie: undefined, + localStorage: undefined, + expected: undefined + } + ] - expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); - }); + storageTestCases.forEach(testCase => it(`getId() should return ${testCase.detail}`, function () { + getCookieStub.withArgs(STORAGE_KEY).returns(testCase.cookie); + getLocalStorageStub.withArgs(STORAGE_KEY).returns(testCase.localStorage); - it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + const result = invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams.gdpr).to.equal('1'); - expect(requestQueryParams.gdpr_consent).to.equal(consentData.gdpr.consentString); - }); + expect(result.id).to.be.deep.equal(testCase.expected ? testCase.expected : undefined); + })); + }) - it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { - consentData.gdpr.gdprApplies = false; + describe('with invalid module configuration', () => { + it('returns undefined if he, pixelId and puid params are not passed', () => { + expect(invokeGetIdAPI({}, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + it('returns undefined if the pixelId param is not passed', () => { + expect(invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams.gdpr).to.equal('0'); - expect(requestQueryParams.gdpr_consent).to.equal(''); + it('returns undefined if the pixelId param is passed, but the he and puid param are not passed', () => { + expect(invokeGetIdAPI({ + pixelId: PIXEL_ID + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); }); - [1, '1', true].forEach(firstPartyParamValue => { - it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { - invokeGetIdAPI({ - '1p': firstPartyParamValue, - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + describe('with valid module configuration', () => { + describe('when data is in client storage', () => { + it('returns an object with the stored ID from cookies for valid module configuration', () => { + const cookieData = {connectId: 'foobar'}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('id'); + expect(result.id).to.deep.equal(cookieData); + }); + + it('returns an object with the stored ID from localStorage for valid module configuration', () => { + const localStorageData = {connectId: 'foobarbaz'}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('id'); + expect(result.id).to.deep.equal(localStorageData); + }); + + it('deletes local storage data when expiry has passed', () => { + const localStorageData = {connectId: 'foobarbaz', __expires: Date.now() - 10000}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(removeLocalStorageDataStub.calledOnce).to.be.true; + expect(removeLocalStorageDataStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(result.id).to.equal(undefined); + expect(result.callback).to.be.a('function'); + }); + + it('will not delete local storage data when expiry has not passed', () => { + const localStorageData = {connectId: 'foobarbaz', __expires: Date.now() + 10000}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(removeLocalStorageDataStub.calledOnce).to.be.false; + expect(result.id).to.deep.equal(localStorageData); + }); + }); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams['1p']).to.equal('1'); + describe('when no data in client storage', () => { + it('returns an object with the callback function if the endpoint override param and the he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the endpoint override param and the puid params are passed', () => { + let result = invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the endpoint override param and the puid and he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId and he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId and puid params are passed', () => { + let result = invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId, he and puid params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an undefined if the Yahoo specific opt-out key is present in local storage', () => { + localStorage.setItem('connectIdOptOut', '1'); + expect(invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData)).to.be.undefined; + localStorage.removeItem('connectIdOptOut'); + }); + + it('returns an object with the callback function if the correct params are passed and Yahoo opt-out value is not "1"', () => { + localStorage.setItem('connectIdOptOut', 'true'); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + localStorage.removeItem('connectIdOptOut'); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId and he query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId and puid query params', () => { + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + v: '1', + '1p': '0', + gdpr: '1', + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID, + gdpr_consent: consentData.consentString, + url: TEST_SERVER_URL, + us_privacy: USP_DATA + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId, puid and he query params', () => { + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + puid: PUBLISHER_USER_ID, + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the specified override API endpoint with query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('sets the callbacks param of the ajax function call correctly', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); + }); + + it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('1'); + expect(requestQueryParams.gdpr_consent).to.equal(consentData.consentString); + }); + + it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { + consentData.gdprApplies = false; + + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('0'); + expect(requestQueryParams.gdpr_consent).to.equal(''); + }); + + [1, '1', true].forEach(firstPartyParamValue => { + it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { + invokeGetIdAPI({ + '1p': firstPartyParamValue, + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams['1p']).to.equal('1'); + }); + }); + + it('stores the result in client cookie storage', () => { + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + getAjaxFnStub.restore(); + const upsResponse = {connectid: 'foobarbaz'}; + const expiryDelta = new Date(60 * 60 * 24 * 14 * 1000); + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + let request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(upsResponse) + ); + expect(setCookieStub.calledOnce).to.be.true; + expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(upsResponse)); + expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString()); + expect(setCookieStub.firstCall.args[3]).to.equal(null); + const cookieDomain = parseUrl(TEST_SERVER_URL).hostname; + expect(setCookieStub.firstCall.args[4]).to.equal(`.${cookieDomain}`); + dateNowStub.restore(); + }); + + it('stores the result in localStorage if cookies are not permitted', () => { + getAjaxFnStub.restore(); + cookiesEnabledStub.returns(false); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + const upsResponse = {connectid: 'barfoo'}; + const expectedStoredData = { + connectid: 'barfoo', + __expires: 60 * 60 * 24 * 14 * 1000 + }; + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + let request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(upsResponse) + ); + expect(setLocalStorageStub.calledOnce).to.be.true; + expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(expectedStoredData); + dateNowStub.restore(); + }); }); }); }); @@ -306,6 +481,13 @@ describe('Yahoo ConnectID Submodule', () => { payload: { connectid: '4567' } + }, + { + key: 'connectId', + expected: '9924', + payload: { + connectId: '9924' + } }]; VALID_API_RESPONSES.forEach(responseData => { it('should return a newly constructed object with the connect ID for a payload with ${responseData.key} key(s)', () => { @@ -348,19 +530,15 @@ describe('Yahoo ConnectID Submodule', () => { })).to.be.false; }); - it('should return false if consent data.gdpr.applies is false', () => { + it('should return false if consent consentData.applies is false', () => { expect(connectIdSubmodule.isEUConsentRequired({ - gdpr: { - gdprApplies: false - } + gdprApplies: false })).to.be.false; }); it('should return true if consent data.gdpr.applies is true', () => { expect(connectIdSubmodule.isEUConsentRequired({ - gdpr: { - gdprApplies: true - } + gdprApplies: true })).to.be.true; }); }); diff --git a/test/spec/modules/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js index 368ca8d9e3f..1b3dc4f19c9 100644 --- a/test/spec/modules/contentexchangeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -79,7 +79,8 @@ describe('ContentexchangeBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 261414f336d..bda5d8daca7 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -263,6 +263,7 @@ describe('Conversant adapter tests', function() { const payload = request.data; expect(payload).to.have.property('id', 'req000'); + expect(payload.source).to.have.property('tid', 'req000'); expect(payload).to.have.property('at', 1); expect(payload).to.have.property('imp'); expect(payload.imp).to.be.an('array').with.lengthOf(8); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index e958f1d0019..a1d738c3657 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -728,6 +728,62 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].native).to.equal(true); }); + it('should map ortb native assets to slot ext assets', function () { + const assets = [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, + { + required: 1, + id: 2, + title: { + len: 140, + } + }, + { + required: 1, + id: 3, + data: { + type: 1, + } + }, + { + required: 0, + id: 4, + data: { + type: 2, + } + }, + { + required: 0, + id: 5, + img: { + type: 1, + wmin: 20, + hmin: 20, + } + }]; + const bidRequests = [ + { + nativeOrtbRequest: { + assets: assets + }, + params: { + nativeCallback: function () { } + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].native).to.equal(true); + expect(ortbRequest.slots[0].ext.assets).to.deep.equal(assets); + }); + it('should properly build a networkId request', function () { const bidderRequest = { refererInfo: { @@ -883,6 +939,67 @@ describe('The Criteo bidding adapter', function () { expect(request.data.user.uspIab).to.equal('1YNY'); }); + it('should properly build a request with device sua field', function () { + const sua = {} + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + timeout: 3000, + uspConsent: '1YNY', + ortb2: { + device: { + sua: sua + } + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.user.ext.sua).to.not.be.null; + expect(request.data.user.ext.sua).to.equal(sua); + }); + + it('should properly build a request with gpp consent field', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const ortb2 = { + regs: { + gpp: 'gpp_consent_string', + gpp_sid: [0, 1, 2] + } + }; + + const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); + expect(request.data.regs).to.not.be.null; + expect(request.data.regs.gpp).to.equal('gpp_consent_string'); + expect(request.data.regs.gpp_sid).to.deep.equal([0, 1, 2]); + }); + it('should properly build a request with schain object', function () { const expectedSchain = { someProperty: 'someValue' @@ -1446,7 +1563,7 @@ describe('The Criteo bidding adapter', function () { creativecode: 'test-crId', width: 728, height: 90, - dealCode: 'myDealCode', + deal: 'myDealCode', adomain: ['criteo.com'], }], }, diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 76d5222c8b2..aaf63873d93 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -1,5 +1,6 @@ import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js'; import * as utils from 'src/utils.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; import { server } from '../../mocks/xhr'; const pastDateString = new Date(0).toString() @@ -17,6 +18,9 @@ describe('CriteoId module', function () { let timeStampStub; let parseUrlStub; let triggerPixelStub; + let gdprConsentDataStub; + let uspConsentDataStub; + let gppConsentDataStub; beforeEach(function (done) { getCookieStub = sinon.stub(storage, 'getCookie'); @@ -27,6 +31,9 @@ describe('CriteoId module', function () { timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); parseUrlStub = sinon.stub(utils, 'parseUrl').returns({ protocol: 'https', hostname: 'testdev.com' }) triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); done(); }); @@ -39,6 +46,9 @@ describe('CriteoId module', function () { timeStampStub.restore(); triggerPixelStub.restore(); parseUrlStub.restore(); + gdprConsentDataStub.restore(); + uspConsentDataStub.restore(); + gppConsentDataStub.restore(); }); const storageTestCases = [ @@ -136,11 +146,11 @@ describe('CriteoId module', function () { })); const gdprConsentTestCases = [ - { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: 'expectedConsentString' }, - { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: undefined }, - { consentData: { gdprApplies: true, consentString: undefined }, expected: undefined }, - { consentData: { gdprApplies: 'oui', consentString: 'expectedConsentString' }, expected: undefined }, - { consentData: undefined, expected: undefined } + { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '1' }, + { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '0' }, + { consentData: { gdprApplies: true, consentString: undefined }, expectedGdprConsent: undefined, expectedGdpr: '1' }, + { consentData: { gdprApplies: 'oui', consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '0' }, + { consentData: undefined, expectedGdprConsent: undefined, expectedGdpr: undefined } ]; it('should call sync pixels if request by backend', function () { @@ -185,16 +195,134 @@ describe('CriteoId module', function () { expect(setLocalStorageStub.calledWith('cto_pixel_test', 'ok')).to.be.true; }); + it('should call sync pixels and use error handler', function () { + const expirationTs = new Date(nowTimestamp + cookiesMaxAge).toString(); + + const result = criteoIdSubmodule.getId(); + result.callback((id) => { + }); + + const response = { + pixels: [ + { + pixelUrl: 'pixelUrlWithBundle', + writeBundleInStorage: true, + bundlePropertyName: 'abc', + storageKeyName: 'cto_pixel_test' + } + ] + }; + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(response) + ); + + server.requests[1].respond( + 500, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + abc: 'ok' + }) + ); + + expect(triggerPixelStub.called).to.be.false; + expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.com')).to.be.false; + expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.testdev.com')).to.be.false; + expect(setLocalStorageStub.calledWith('cto_pixel_test', 'ok')).to.be.false; + }); + gdprConsentTestCases.forEach(testCase => it('should call user sync url with the gdprConsent', function () { let callBackSpy = sinon.spy(); - let result = criteoIdSubmodule.getId(undefined, testCase.consentData); + + gdprConsentDataStub.returns(testCase.consentData); + + let result = criteoIdSubmodule.getId(undefined); + result.callback(callBackSpy); + + let request = server.requests[0]; + + if (testCase.expectedGdprConsent) { + expect(request.url).to.have.string(`gdprString=${testCase.expectedGdprConsent}`); + } else { + expect(request.url).to.not.have.string('gdprString='); + } + + if (testCase.expectedGdpr) { + expect(request.url).to.have.string(`gdpr=${testCase.expectedGdpr}`); + } else { + expect(request.url).to.not.have.string('gdpr='); + } + + request.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + expect(callBackSpy.calledOnce).to.be.true; + })); + + [undefined, 'abc'].forEach(usPrivacy => it('should call user sync url with the us privacy string', function () { + let callBackSpy = sinon.spy(); + + uspConsentDataStub.returns(usPrivacy); + + let result = criteoIdSubmodule.getId(undefined); + result.callback(callBackSpy); + + let request = server.requests[0]; + + if (usPrivacy) { + expect(request.url).to.have.string(`us_privacy=${usPrivacy}`); + } else { + expect(request.url).to.not.have.string('us_privacy='); + } + + request.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + expect(callBackSpy.calledOnce).to.be.true; + })); + + [ + { + consentData: { + gppString: 'abc', + applicableSections: [1] + }, + expectedGpp: 'abc', + expectedGppSid: '1' + }, + { + consentData: undefined, + expectedGpp: undefined, + expectedGppSid: undefined + } + ].forEach(testCase => it('should call user sync url with the gpp string', function () { + let callBackSpy = sinon.spy(); + + gppConsentDataStub.returns(testCase.consentData); + + let result = criteoIdSubmodule.getId(undefined); result.callback(callBackSpy); let request = server.requests[0]; - if (testCase.expected) { - expect(request.url).to.have.string(`gdprString=${testCase.expected}`); + + if (testCase.expectedGpp) { + expect(request.url).to.have.string(`gpp=${testCase.expectedGpp}`); + } else { + expect(request.url).to.not.have.string('gpp='); + } + + if (testCase.expectedGppSid) { + expect(request.url).to.have.string(`gpp_sid=${testCase.expectedGppSid}`); } else { - expect(request.url).to.not.have.string('gdprString'); + expect(request.url).to.not.have.string('gpp_sid='); } request.respond( diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 8e203510b10..537b82b0e2e 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -2,7 +2,6 @@ import { expect } from 'chai'; import { spec } from '../../../modules/datablocksBidAdapter.js'; import { BotClientTests } from '../../../modules/datablocksBidAdapter.js'; import { getStorageManager } from '../../../src/storageManager.js'; -export let storage = getStorageManager(); const bid = { bidId: '2dd581a2b6281d', diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index f076866a080..e5e779479cc 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -172,6 +172,36 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('bidswitch', function() { + const userId = { + bidswitch: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'bidswitch.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('medianet', function() { + const userId = { + medianet: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'media.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + it('liveIntentId; getValue call and NO ext', function() { const userId = { lipb: { diff --git a/test/spec/modules/emtvBidAdapter_spec.js b/test/spec/modules/emtvBidAdapter_spec.js new file mode 100644 index 00000000000..4f95a0cc094 --- /dev/null +++ b/test/spec/modules/emtvBidAdapter_spec.js @@ -0,0 +1,400 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/emtvBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'emtv' +const adUrl = 'https://us-east-ep.engagemedia.tv/pbjs'; +const syncUrl = 'https://cs.engagemedia.tv'; + +describe('EMTVBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal(adUrl); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) + }); + }); +}); diff --git a/test/spec/modules/eskimiBidAdapter_spec.js b/test/spec/modules/eskimiBidAdapter_spec.js new file mode 100644 index 00000000000..4622b374de5 --- /dev/null +++ b/test/spec/modules/eskimiBidAdapter_spec.js @@ -0,0 +1,155 @@ +import {expect} from 'chai'; +import {spec} from 'modules/eskimiBidAdapter.js'; + +const REQUEST = { + 'bidderCode': 'eskimi', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', + 'bidderRequestId': 'requestId', + 'bidRequest': [{ + 'bidder': 'eskimi', + 'params': { + 'placementId': 1003000, + 'accId': 123 + }, + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId1', + 'adUnitCode': 'adUnitCode1', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }, + { + 'bidder': 'eskimi', + 'params': { + 'placementId': 1003001, + 'accId': 123 + }, + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId2', + 'adUnitCode': 'adUnitCode2', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'id': 'requestId', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'bidId1', + 'impid': 'bidId1', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'eskimi': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'seat' + } + ] + } +}; + +describe('Eskimi bid adapter', function () { + describe('isBidRequestValid', function () { + it('should accept request if placementId is passed', function () { + let bid = { + bidder: 'eskimi', + params: { + placementId: 123 + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('reject requests without params', function () { + let bid = { + bidder: 'eskimi', + params: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('creates request data', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + expect(request).to.exist.and.to.be.a('object'); + const payload = request.data; + expect(payload.imp[0]).to.have.property('id', REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(REQUEST.bidRequest, req)[0]; + + const payload = request.data; + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + }); + + describe('interpretResponse', function () { + it('has bids', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + let bids = spec.interpretResponse(RESPONSE, request); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].id); + expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); + expect(bids[index]).to.have.property('ttl', 30); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, request); + expect(bids).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index fe07dc1e719..123981825dc 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -252,6 +252,13 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; + it('should return context and placement with default values', () => { + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload.video_context).to.equal('instream'); ; + expect(payload.video_placement).to.equal(1); + }); + it('should add parameters to the tag', () => { const request = spec.buildRequests(bidRequests); const payload = request[0].data; @@ -319,6 +326,36 @@ describe('freewheelSSP BidAdapter Test', () => { }); }) + describe('buildRequestsForVideoWithContextAndPlacement', () => { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'placement': 2, + 'playerSize': [300, 600], + } + }, + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should return input context and placement', () => { + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload.video_context).to.equal('outstream'); ; + expect(payload.video_placement).to.equal(2); + }); + }) + describe('interpretResponseForBanner', () => { let bidRequests = [ { diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 8d58990bb66..941f2b3c8df 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -1,26 +1,33 @@ import { deviceAccessHook, - setEnforcementConfig, - userSyncHook, - userIdHook, - makeBidRequestsHook, - validateRules, + enableAnalyticsHook, enforcementRules, + getGvlid, + getGvlidFromAnalyticsAdapter, + makeBidRequestsHook, purpose1Rule, purpose2Rule, - enableAnalyticsHook, - getGvlid, - internal, STRICT_STORAGE_ENFORCEMENT + setEnforcementConfig, + STRICT_STORAGE_ENFORCEMENT, + userIdHook, + userSyncHook, + validateRules } from 'modules/gdprEnforcement.js'; -import { config } from 'src/config.js'; -import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; +import {config} from 'src/config.js'; +import adapterManager, {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import { validateStorageEnforcement } from 'src/storageManager.js'; +import { + MODULE_TYPE_ANALYTICS, + MODULE_TYPE_BIDDER, + MODULE_TYPE_CORE, + MODULE_TYPE_UID +} from '../../../src/activities/modules.js'; import * as events from 'src/events.js'; import 'modules/appnexusBidAdapter.js'; // some tests expect this to be in the adapter registry -import 'src/prebid.js' +import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; -import {VENDORLESS_GVLID} from '../../../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../../../src/consentHandler.js'; +import {validateStorageEnforcement} from '../../../src/storageManager.js'; describe('gdpr enforcement', function () { let nextFnSpy; @@ -100,6 +107,7 @@ describe('gdpr enforcement', function () { } } }; + let gvlids; before(() => { hook.ready(); @@ -111,31 +119,28 @@ describe('gdpr enforcement', function () { adapterManager.makeBidRequests.getHooks({ hook: makeBidRequestsHook }).remove(); }) - describe('deviceAccessHook', function () { - let adapterManagerStub; + beforeEach(() => { + gvlids = {}; + sinon.stub(GDPR_GVLIDS, 'get').callsFake((name) => ({gvlid: gvlids[name], modules: {}})); + }); - function getBidderSpec(gvlid) { - return { - getSpec: () => { - return { - gvlid - } - } - } - } + afterEach(() => { + GDPR_GVLIDS.get.restore(); + }); + describe('deviceAccessHook', function () { beforeEach(function () { nextFnSpy = sinon.spy(); gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); logWarnSpy = sinon.spy(utils, 'logWarn'); - adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); }); + afterEach(function () { config.resetConfig(); gdprDataHandler.getConsentData.restore(); logWarnSpy.restore(); - adapterManagerStub.restore(); }); + it('should not allow device access when device access flag is set to false', function () { config.setConfig({ deviceAccess: false, @@ -161,8 +166,10 @@ describe('gdpr enforcement', function () { }); it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function () { - adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); - adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(5)); + Object.assign(gvlids, { + appnexus: 1, + rubicon: 5 + }); setEnforcementConfig({ gdpr: { rules: [{ @@ -179,14 +186,16 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 5, 'rubicon'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'rubicon'); expect(logWarnSpy.callCount).to.equal(0); }); it('should check consent for all vendors when enforcePurpose and enforceVendor are true', function () { - adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); - adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(3)); + Object.assign(gvlids, { + appnexus: 1, + rubicon: 3 + }); setEnforcementConfig({ gdpr: { rules: [{ @@ -202,13 +211,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 3, 'rubicon'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'rubicon'); expect(logWarnSpy.callCount).to.equal(1); }); it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function () { - adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + gvlids.appnexus = 1; setEnforcementConfig({ gdpr: { rules: [{ @@ -225,13 +234,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus', result); }); it('should use gvlMapping set by publisher', function() { @@ -256,13 +265,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus', result); config.resetConfig(); }); @@ -291,13 +300,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus', result); config.resetConfig(); curBidderStub.restore(); }); @@ -310,9 +319,9 @@ describe('gdpr enforcement', function () { } gdprDataHandlerStub.returns(consentData); const validate = sinon.stub().callsFake(() => false); - deviceAccessHook(nextFnSpy, VENDORLESS_GVLID, 'mockModule', undefined, {validate}); + deviceAccessHook(nextFnSpy, MODULE_TYPE_CORE, 'mockModule', undefined, {validate}); sinon.assert.callCount(validate, 0); - sinon.assert.calledWith(nextFnSpy, VENDORLESS_GVLID, 'mockModule', {hasEnforcementHook: true, valid: true}); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_CORE, 'mockModule', {hasEnforcementHook: true, valid: true}); }) }); @@ -354,23 +363,11 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); + gvlids.sampleBidder1 = 1; userSyncHook(nextFnSpy); curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); + gvlids.sampleBidder2 = 3; userSyncHook(nextFnSpy); expect(nextFnSpy.calledTwice).to.equal(true); }); @@ -393,23 +390,11 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); + gvlids.sampleBidder1 = 1; userSyncHook(nextFnSpy); curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); + gvlids.sampleBidder2 = 3; userSyncHook(nextFnSpy); expect(nextFnSpy.calledOnce).to.equal(true); expect(logWarnSpy.callCount).to.equal(1); @@ -433,23 +418,11 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); + gvlids.sampleBidder1 = 1; userSyncHook(nextFnSpy); curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); + gvlids.sampleBidder2 = 3; userSyncHook(nextFnSpy); expect(nextFnSpy.calledTwice).to.equal(true); expect(logWarnSpy.callCount).to.equal(0); @@ -486,6 +459,7 @@ describe('gdpr enforcement', function () { name: 'sampleUserId' } }] + gvlids.sampleUserId = 1; userIdHook(nextFnSpy, submodules, consentData); // Should pass back hasValidated flag since version 2 const args = nextFnSpy.getCalls()[0].args; @@ -501,6 +475,7 @@ describe('gdpr enforcement', function () { name: 'sampleUserId' } }]; + gvlids.sampleUserId = 1; let consentData = null; userIdHook(nextFnSpy, submodules, consentData); // Should not pass back hasValidated flag since version 2 @@ -537,6 +512,10 @@ describe('gdpr enforcement', function () { name: 'sampleUserId1' } }] + Object.assign(gvlids, { + sampleUserId: 1, + sampleUserId1: 3 + }); userIdHook(nextFnSpy, submodules, consentData); expect(logWarnSpy.callCount).to.equal(1); let expectedSubmodules = [{ @@ -602,20 +581,9 @@ describe('gdpr enforcement', function () { consentData.gdprApplies = true; gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('bidder_1').returns({ - getSpec: function () { - return { 'gvlid': 4 } - } - }); - adapterManagerStub.withArgs('bidder_2').returns({ - getSpec: function () { - return { 'gvlid': 5 } - } - }); - adapterManagerStub.withArgs('bidder_3').returns({ - getSpec: function () { - return { 'gvlid': undefined } - } + Object.assign(gvlids, { + bidder_1: 4, + bidder_2: 5, }); makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); @@ -660,21 +628,10 @@ describe('gdpr enforcement', function () { consentData.gdprApplies = true; gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('bidder_1').returns({ - getSpec: function () { - return { 'gvlid': 4 } - } - }); - adapterManagerStub.withArgs('bidder_2').returns({ - getSpec: function () { - return { 'gvlid': 5 } - } - }); - adapterManagerStub.withArgs('bidder_3').returns({ - getSpec: function () { - return { 'gvlid': undefined } - } - }); + Object.assign(gvlids, { + bidder_1: 4, + bidder_2: 5, + }) makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); @@ -771,9 +728,11 @@ describe('gdpr enforcement', function () { consentData.gdprApplies = true; gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('analyticsAdapter_A').returns({ gvlid: 3 }); - adapterManagerStub.withArgs('analyticsAdapter_B').returns({ gvlid: 5 }); - adapterManagerStub.withArgs('analyticsAdapter_C').returns({ gvlid: 1 }); + Object.assign(gvlids, { + analyticsAdapter_A: 3, + analyticsAdapter_B: 5, + analyticsAdapter_C: 1 + }); enableAnalyticsHook(nextFnSpy, MOCK_ANALYTICS_ADAPTER_CONFIG); @@ -1142,13 +1101,13 @@ describe('gdpr enforcement', function () { }); describe('getGvlid', function() { - let getGvlidForBidAdapterStub; - let getGvlidForUserIdModuleStub; - let getGvlidForAnalyticsAdapterStub; + const MOCK_MODULE = 'moduleA'; + let entry; + beforeEach(function() { - getGvlidForBidAdapterStub = sandbox.stub(internal, 'getGvlidForBidAdapter'); - getGvlidForUserIdModuleStub = sandbox.stub(internal, 'getGvlidForUserIdModule'); - getGvlidForAnalyticsAdapterStub = sandbox.stub(internal, 'getGvlidForAnalyticsAdapter'); + entry = {modules: {}}; + GDPR_GVLIDS.get.reset(); + GDPR_GVLIDS.get.callsFake((mod) => mod === MOCK_MODULE ? entry : {modules: {}}); }); it('should return "null" if called without passing any argument', function() { @@ -1156,46 +1115,63 @@ describe('gdpr enforcement', function () { expect(gvlid).to.equal(null); }); - it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function() { - getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); - getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); - getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(null); - - const gvlid = getGvlid('moduleA'); + it('should return "null" if no GVL ID was registered', function() { + const gvlid = getGvlid('type', MOCK_MODULE); expect(gvlid).to.equal(null); }); - it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { - config.setConfig({ - gvlMapping: { - moduleA: 1 - } - }); - - // Actual GVL ID for moduleA is 2, as defined on its the bidAdapter.js file. - getGvlidForBidAdapterStub.withArgs('moduleA').returns(2); - - const gvlid = getGvlid('moduleA'); - expect(gvlid).to.equal(1); - }); - - it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function() { - getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); - getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); - getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(7); + it('should return null if the wrong GVL ID was registered', () => { + entry = {gvlid: 123}; + expect(getGvlid('type', 'someOtherModule')).to.equal(null); + }) - expect(getGvlid('moduleA')).to.equal(7); - }); + Object.entries({ + 'without fallback': null, + 'with fallback': () => 'shouldBeIgnored' + }).forEach(([t, fallbackFn]) => { + describe(t, () => { + it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { + config.setConfig({ + gvlMapping: { + [MOCK_MODULE]: 1 + } + }); + + entry = {gvlid: 2}; + + const gvlid = getGvlid('type', MOCK_MODULE, fallbackFn); + expect(gvlid).to.equal(1); + }); + + it('should return the GVL ID that was registered', function() { + entry = {gvlid: 7}; + expect(getGvlid('type', MOCK_MODULE, fallbackFn)).to.equal(7); + }); + + it('should return VENDORLESS_GVLID for core modules', () => { + entry = {gvlid: 123}; + expect(getGvlid(MODULE_TYPE_CORE, MOCK_MODULE, fallbackFn)).to.equal(VENDORLESS_GVLID); + }); + + describe('multiple GVL IDs are found', () => { + it('should use bidder over others', () => { + entry = {modules: {[MODULE_TYPE_BIDDER]: 123, [MODULE_TYPE_UID]: 321}}; + expect(getGvlid(MODULE_TYPE_UID, MOCK_MODULE, fallbackFn)).to.equal(123); + }); + it('should use uid over analytics', () => { + entry = {modules: {[MODULE_TYPE_UID]: 123, [MODULE_TYPE_ANALYTICS]: 321}}; + expect(getGvlid(MODULE_TYPE_ANALYTICS, MOCK_MODULE, fallbackFn)).to.equal(123); + }) + }) + }) + }) - it('should pass extra arguments to analytics\' getGvlid', () => { - getGvlidForAnalyticsAdapterStub.withArgs('analytics').returns(321); - const cfg = {some: 'args'}; - getGvlid('analytics', cfg); - sinon.assert.calledWith(getGvlidForAnalyticsAdapterStub, 'analytics', cfg); + it('should use fallbackFn if no other lookup produces a gvl id', () => { + expect(getGvlid('type', MOCK_MODULE, () => 321)).to.equal(321); }); }); - describe('getGvlidForAnalyticsAdapter', () => { + describe('getGvlidFromAnalyticsConfig', () => { let getAnalyticsAdapter, adapter, adapterEntry; beforeEach(() => { @@ -1207,26 +1183,20 @@ describe('gdpr enforcement', function () { getAnalyticsAdapter.withArgs('analytics').returns(adapterEntry); }); - it('should return gvlid from adapterManager if defined', () => { - adapterEntry.gvlid = 123; - adapter.gvlid = 321 - expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.equal(123); - }); - it('should return gvlid from adapter if defined', () => { adapter.gvlid = 321; - expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.equal(321); + expect(getGvlidFromAnalyticsAdapter('analytics')).to.equal(321); }); it('should invoke adapter.gvlid if it\'s a function', () => { adapter.gvlid = (cfg) => cfg.k const cfg = {k: 231}; - expect(internal.getGvlidForAnalyticsAdapter('analytics', cfg)).to.eql(231); + expect(getGvlidFromAnalyticsAdapter('analytics', cfg)).to.eql(231); }); it('should not choke if adapter gvlid fn throws', () => { adapter.gvlid = () => { throw new Error(); }; - expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.not.be.ok; + expect(getGvlidFromAnalyticsAdapter('analytics')).to.not.be.ok; }); }); }) diff --git a/test/spec/modules/globalsunBidAdapter_spec.js b/test/spec/modules/globalsunBidAdapter_spec.js index 3795f3038a7..0d17c25363d 100644 --- a/test/spec/modules/globalsunBidAdapter_spec.js +++ b/test/spec/modules/globalsunBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('GlobalsunBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 1cafdf6134f..2ef604dd097 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -480,6 +480,14 @@ describe('TheMediaGrid Adapter', function () { 'h': 600, 'protocols': [1, 2, 3], 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + 'context': 'instream', + 'maxduration': 30, + 'minduration': 0, + 'api': [1, 2], + 'skip': 1, + 'placement': 1, + 'playbackmethod': 1, + 'startdelay': 0 } }, { 'id': bidRequests[3].bidId, diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js deleted file mode 100644 index e4f06a451d2..00000000000 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ /dev/null @@ -1,636 +0,0 @@ -import { expect } from 'chai'; -import { spec, resetUserSync, getSyncUrl } from 'modules/gridNMBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('TheMediaGridNM Adapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - const paramsList = [ - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - } - }, - { - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - } - ]; - paramsList.forEach((params) => { - const invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = params; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - }); - - it('should return false when required params has invalid values', function () { - const paramsList = [ - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': '1,2,3,4,5' - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': [1, 2], - 'protocols': [1, 2, 3, 4, 5] - } - }, - { - 'source': 'jwp', - 'secid': 11, - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5] - } - }, - { - 'source': 111, - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5] - } - } - ]; - - paramsList.forEach((params) => { - const invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = params; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - }); - - it('should return true when required params is absent, but available in mediaTypes', function () { - const paramsList = [ - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - } - } - ]; - - const mediaTypes = { - video: { - mimes: ['video/mp4', 'video/x-ms-wmv'], - playerSize: [200, 300], - protocols: [1, 2, 3, 4, 5, 6] - } - }; - - paramsList.forEach((params) => { - const validBid = Object.assign({}, bid); - delete validBid.params; - validBid.params = params; - validBid.mediaTypes = mediaTypes; - expect(spec.isBidRequestValid(validBid)).to.equal(true); - }); - }); - }); - - describe('buildRequests', function () { - function parseRequestUrl(url) { - const res = {}; - url.replace(/^[^\?]+\?/, '').split('&').forEach((it) => { - const couple = it.split('='); - res[couple[0]] = decodeURIComponent(couple[1]); - }); - return res; - } - const bidderRequest = { - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - timeout: 3000, - refererInfo: { page: 'https://example.com' } - }; - const referrer = encodeURIComponent(bidderRequest.refererInfo.page); - let bidRequests = [ - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'floorcpm': 2, - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3], - 'skip': 1 - } - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '3150ccb55da321', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('if content and segment is present in jwTargeting, payload must have right params', function () { - const jsContent = {id: 'test_jw_content_id'}; - const jsSegments = ['test_seg_1', 'test_seg_2']; - const bidRequestsWithJwTargeting = bidRequests.map((bid) => { - return Object.assign({ - rtd: { - jwplayer: { - targeting: { - segments: jsSegments, - content: jsContent - } - } - } - }, bid); - }); - const requests = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); - requests.forEach((req, i) => { - const payload = req.data; - expect(req).to.have.property('data'); - expect(payload).to.have.property('site'); - expect(payload.site.content).to.deep.equal(jsContent); - }); - }); - - it('should attach valid params to the tag', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - const requestsSizes = ['300x250,300x600', '728x90']; - requests.forEach((req, i) => { - expect(req.url).to.be.an('string'); - const payload = parseRequestUrl(req.url); - expect(payload).to.have.property('no_mapping', '1'); - expect(payload).to.have.property('sp', 'jwp'); - - const sizes = { w: bidRequests[i].sizes[0][0], h: bidRequests[i].sizes[0][1] }; - const impObj = { - 'id': bidRequests[i].bidId, - 'tagid': bidRequests[i].params.secid, - 'ext': {'divid': bidRequests[i].adUnitCode}, - 'video': Object.assign(sizes, bidRequests[i].params.video) - }; - - if (bidRequests[i].params.floorcpm) { - impObj.bidfloor = bidRequests[i].params.floorcpm; - } - - expect(req.data).to.deep.equal({ - 'id': bidderRequest.bidderRequestId, - 'site': { - 'page': referrer, - 'publisher': { - 'id': bidRequests[i].params.pubid - } - }, - 'tmax': bidderRequest.timeout, - 'source': { - 'tid': bidderRequest.auctionId, - 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} - }, - 'imp': [impObj] - }); - }); - }); - - it('should attach valid params from mediaTypes', function () { - const mediaTypes = { - video: { - skipafter: 10, - minduration: 10, - maxduration: 100, - protocols: [1, 3, 4], - playerSize: [[300, 250]] - } - }; - const bidRequest = Object.assign({ mediaTypes }, bidRequests[0]); - const req = spec.buildRequests([bidRequest], bidderRequest)[0]; - const expectedVideo = { - 'skipafter': 10, - 'minduration': 10, - 'maxduration': 100, - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6], - 'w': 300, - 'h': 250 - }; - - expect(req.url).to.be.an('string'); - const payload = parseRequestUrl(req.url); - expect(payload).to.have.property('no_mapping', '1'); - expect(payload).to.have.property('sp', 'jwp'); - expect(req.data).to.deep.equal({ - 'id': bidderRequest.bidderRequestId, - 'site': { - 'page': referrer, - 'publisher': { - 'id': bidRequest.params.pubid - } - }, - 'tmax': bidderRequest.timeout, - 'source': { - 'tid': bidderRequest.auctionId, - 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} - }, - 'imp': [{ - 'id': bidRequest.bidId, - 'bidfloor': bidRequest.params.floorcpm, - 'tagid': bidRequest.params.secid, - 'ext': {'divid': bidRequest.adUnitCode}, - 'video': expectedVideo - }] - }); - }); - - it('if gdprConsent is present payload must have gdpr params', function () { - const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); - const request = spec.buildRequests([bidRequests[0]], gdprBidderRequest)[0]; - const payload = request.data; - expect(request).to.have.property('data'); - expect(payload).to.have.property('user'); - expect(payload.user).to.have.property('ext'); - expect(payload.user.ext).to.have.property('consent', 'AAA'); - expect(payload).to.have.property('regs'); - expect(payload.regs).to.have.property('ext'); - expect(payload.regs.ext).to.have.property('gdpr', 1); - }); - - it('if usPrivacy is present payload must have us_privacy param', function () { - const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const request = spec.buildRequests([bidRequests[0]], bidderRequestWithUSP)[0]; - const payload = request.data; - expect(payload).to.have.property('regs'); - expect(payload.regs).to.have.property('ext'); - expect(payload.regs.ext).to.have.property('us_privacy', '1YNN'); - }); - }); - - describe('interpretResponse', function () { - const responses = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '2'}, - {'bid': [{'price': 0.5, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 600, 'w': 300, adomain: ['my_domain.ru']}], 'seat': '2'}, - {'bid': [{'price': 2.00, 'nurl': 'https://some_test_vast_url.com', 'content_type': 'video', 'adomain': ['example.com'], 'w': 300, 'h': 600}], 'seat': '2'}, - {'bid': [{'price': 0, 'h': 250, 'w': 300}], 'seat': '2'}, - {'bid': [{'price': 0, 'adm': '\n<\/Ad>\n<\/VAST>', 'h': 250, 'w': 300}], 'seat': '2'}, - undefined, - {'bid': [], 'seat': '2'}, - {'seat': '2'}, - ]; - - it('should get correct video bid response', function () { - const bidRequests = [ - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '659423fff799cb', - 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }, - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3, 4, 5], - 'skip': 1 - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '2bc598e42b6a', - 'bidderRequestId': '1e8b5a465f404', - 'auctionId': '1cbd2feafe5e8b', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }, - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3], - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '127f4b12a432c', - 'bidderRequestId': 'a75bc868f32', - 'auctionId': '1cbd2feafe5e8b', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - } - ]; - const requests = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '659423fff799cb', - 'cpm': 1.15, - 'creativeId': '5f2009617a7c0a', - 'dealId': 11, - 'width': 300, - 'height': 250, - 'currency': 'USD', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'meta': { - 'advertiserDomains': [] - }, - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - }, - { - 'requestId': '2bc598e42b6a', - 'cpm': 0.5, - 'creativeId': '1e8b5a465f404', - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'currency': 'USD', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'meta': { - 'advertiserDomains': ['my_domain.ru'] - }, - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - }, - { - 'requestId': '127f4b12a432c', - 'cpm': 2.00, - 'creativeId': 'a75bc868f32', - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'currency': 'USD', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'meta': { - advertiserDomains: ['example.com'] - }, - 'vastUrl': 'https://some_test_vast_url.com', - } - ]; - - requests.forEach((req, i) => { - const result = spec.interpretResponse({'body': {'seatbid': [responses[i]]}}, req); - expect(result[0]).to.deep.equal(expectedResponse[i]); - }); - }); - - it('handles wrong and nobid responses', function () { - responses.slice(3).forEach((resp) => { - const request = spec.buildRequests([{ - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3, 4, 5], - 'skip': 1 - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '2bc598e42b6a', - 'bidderRequestId': '39d74f5b71464', - 'auctionId': '1cbd2feafe5e8b', - 'meta': { - 'advertiserDomains': [] - }, - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }]); - const result = spec.interpretResponse({'body': {'seatbid': [resp]}}, request[0]); - expect(result.length).to.equal(0); - }); - }); - }); - - describe('user sync', function () { - const syncUrl = getSyncUrl(); - - beforeEach(function () { - resetUserSync(); - }); - - it('should register the Emily iframe', function () { - let syncs = spec.getUserSyncs({ - pixelEnabled: true - }); - - expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); - }); - - it('should not register the Emily iframe more than once', function () { - let syncs = spec.getUserSyncs({ - pixelEnabled: true - }); - expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); - - // when called again, should still have only been called once - syncs = spec.getUserSyncs(); - expect(syncs).to.equal(undefined); - }); - - it('should pass gdpr params if consent is true', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - gdprApplies: true, consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr=1&gdpr_consent=foo` - }); - }); - - it('should pass gdpr params if consent is false', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - gdprApplies: false, consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr=0&gdpr_consent=foo` - }); - }); - - it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr_consent=foo` - }); - }); - - it('should pass no params if gdpr consentString is not defined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {})).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is a number', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: 0 - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is null', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: null - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is a object', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: {} - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr is not defined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined)).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass usPrivacy param if it is available', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {}, '1YNN')).to.deep.equal({ - type: 'image', url: `${syncUrl}&us_privacy=1YNN` - }); - }); - }); -}); diff --git a/test/spec/modules/growthCodeIdSystem_spec.js b/test/spec/modules/growthCodeIdSystem_spec.js index dce995d25e0..97083047d4e 100644 --- a/test/spec/modules/growthCodeIdSystem_spec.js +++ b/test/spec/modules/growthCodeIdSystem_spec.js @@ -4,12 +4,13 @@ import { server } from 'test/mocks/xhr.js'; import { uspDataHandler } from 'src/adapterManager.js'; import {expect} from 'chai'; import {getStorageManager} from '../../../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; const GCID_EXPIRY = 45; const MODULE_NAME = 'growthCodeId'; const SHAREDID = 'fe9c5c89-7d56-4666-976d-e07e73b3b664'; -export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); const getIdParams = {params: { pid: 'TEST01', diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index a31270c86c7..52e9f9171d6 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -1,9 +1,9 @@ import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; -import {getStorageManager} from '../../../src/storageManager.js'; +import {getCoreStorageManager} from '../../../src/storageManager.js'; -export const storage = getStorageManager(); +const storage = getCoreStorageManager(); const pid = '14'; let defaultConfigParams; diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index de0459f4714..2e920d3b769 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('IQZoneBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/ivsBidAdapter_spec.js b/test/spec/modules/ivsBidAdapter_spec.js new file mode 100644 index 00000000000..79b3d6811f4 --- /dev/null +++ b/test/spec/modules/ivsBidAdapter_spec.js @@ -0,0 +1,197 @@ +import { spec, converter } from 'modules/ivsBidAdapter.js'; +import { assert } from 'chai'; +import { deepClone } from '../../../src/utils'; + +describe('ivsBidAdapter', function () { + describe('isBidRequestValid()', function () { + let validBid = { + bidder: 'ivs', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + params: { + bidderDomain: 'https://www.example.com', + publisherId: '3001234' + } + }; + + it('should return true for a valid bid', function () { + assert.isTrue(spec.isBidRequestValid(validBid)); + }); + + it('should return false if publisherId info is missing', function () { + let bid = deepClone(validBid); + delete bid.params.publisherId; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false for empty video parameters', function () { + let bid = deepClone(validBid); + delete bid.mediaTypes.video; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false for non instream context', function () { + let bid = deepClone(validBid); + bid.mediaTypes.video.context = 'outstream'; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests()', function () { + let validBidRequests, validBidderRequest; + + beforeEach(function () { + validBidRequests = [{ + bidder: 'ivs', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + }, + adUnitCode: 'video1', + transactionId: '1f420478-a3cd-452d-8e33-ac851e7bfba6', + bidId: '2d986cea00fd01', + bidderRequestId: '1022d594d79bf5', + auctionId: '835eacc9-cfe7-4fa2-8738-ab4b5c4f26d2' + }, + params: { + bidderDomain: 'https://www.example.com', + publisherId: '3001234' + } + }]; + + validBidderRequest = { + bidderCode: 'ivs', + auctionId: '409bd13d-d0be-43c4-9c4f-e6f81ecff475', + bidderRequestId: '17bfe74bd98e68', + refererInfo: { + domain: 'example.com', + page: 'https://www.example.com/test.html', + }, + bids: [{ + bidder: 'ivs', + params: { + publisherId: '3001234' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + mimes: ['video/mp4'] + } + }, + adUnitCode: 'video1', + transactionId: '91b1977f-d05c-45c3-af1f-69b4e7d11e86', + sizes: [ + [640, 480] + ], + }], + ortb2: { + site: { + publisher: { + domain: 'example.com', + } + } + } + }; + }); + + it('should return a validate bid request', function () { + const bidRequest = spec.buildRequests(validBidRequests, validBidderRequest); + assert.equal(bidRequest.method, 'POST'); + assert.deepEqual(bidRequest.options, { contentType: 'application/json' }); + assert.ok(bidRequest.data); + }); + + it('should contain the required parameters', function () { + const bidRequest = spec.buildRequests(validBidRequests, validBidderRequest); + const bidderRequest = bidRequest.data; + assert.equal(bidderRequest.id, validBidderRequest.auctionId); + assert.ok(bidderRequest.site); + assert.ok(bidderRequest.source); + assert.lengthOf(bidderRequest.imp, 1); + }); + }); + + describe('interpretResponse()', function () { + let serverResponse, bidderRequest, request; + + beforeEach(function () { + serverResponse = { + body: { + id: '635ba1ed-68ba-47b4-bcec-4a86565f25f9', + seatbid: [{ + bid: [{ + crid: 3715, + id: 'bca9823d-ca7a-4dac-b292-0e1fae5948f8', + impid: '200d1ca23b15a6', + price: 1.5, + nurl: 'https://a.ivstracker.net/dev/getvastxml?ad_creativeid=3715&domain=localhost%3A9999&ip=136.158.51.114&pageurl=http%3A%2F%2Flocalhost%3A9999%2Ftest%2Fpages%2Fivs.html&spid=3001234&adplacement=&brand=unknown&device=desktop&adsclientid=A45-fd46289e-dc60-4be2-a637-4bc8eb953ddf&clientid=3b5e435f-0351-4ba0-bd2d-8d6f3454c5ed&uastring=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F109.0.0.0%20Safari%2F537.36' + }] + }], + cur: 'USD' + }, + headers: {} + }; + + bidderRequest = { + bidderCode: 'ivs', + auctionId: '635ba1ed-68ba-47b4-bcec-4a86565f25f9', + bidderRequestId: '1def3e1d03f5a', + bids: [{ + bidder: 'ivs', + params: { + publisherId: '3001234' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [640, 480] + ], + mimes: ['video/mp4'] + } + }, + adUnitCode: 'video1', + transactionId: '89e5a3e7-df30-4ed6-a130-edfa91941e67', + bidId: '200d1ca23b15a6', + bidderRequestId: '1def3e1d03f5a', + auctionId: '635ba1ed-68ba-47b4-bcec-4a86565f25f9' + }], + }; + + request = { data: converter.toORTB({ bidderRequest }) }; + }); + + if (FEATURES.VIDEO) { + it('should match parsed server response', function () { + const results = spec.interpretResponse(serverResponse, request); + const expected = { + mediaType: 'video', + playerWidth: 640, + playerHeight: 480, + vastUrl: 'https://a.ivstracker.net/dev/getvastxml?ad_creativeid=3715&domain=localhost%3A9999&ip=136.158.51.114&pageurl=http%3A%2F%2Flocalhost%3A9999%2Ftest%2Fpages%2Fivs.html&spid=3001234&adplacement=&brand=unknown&device=desktop&adsclientid=A45-fd46289e-dc60-4be2-a637-4bc8eb953ddf&clientid=3b5e435f-0351-4ba0-bd2d-8d6f3454c5ed&uastring=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F109.0.0.0%20Safari%2F537.36', + requestId: '200d1ca23b15a6', + seatBidId: 'bca9823d-ca7a-4dac-b292-0e1fae5948f8', + cpm: 1.5, + currency: 'USD', + creativeId: 3715, + ttl: 360, + }; + + expect(results.length).to.equal(1); + sinon.assert.match(results[0], expected); + }); + } + + it('should return empty when no response', function () { + assert.ok(!spec.interpretResponse({}, request)); + }); + }); +}); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index d0b2d7344b5..149c4c44c21 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -2,14 +2,14 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { spec, storage, ERROR_CODES, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY } from '../../../modules/ixBidAdapter.js'; +import { spec, storage, ERROR_CODES, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES } from '../../../modules/ixBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; import { deepAccess, deepClone } from '../../../src/utils.js'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/openrtb/pbjs'; - const VIDEO_ENDPOINT_VERSION = 8.1; - const BANNER_ENDPOINT_VERSION = 7.2; + + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = ['test1', 'test2']; const SAMPLE_SCHAIN = { 'ver': '1.0', @@ -1669,27 +1669,6 @@ describe('IndexexchangeAdapter', function () { expect(r.user.testProperty).to.be.undefined; }); - it('should not add fpd data to r object if it exceeds maximum request', function () { - const ortb2 = { - site: { - keywords: 'power tools, drills', - search: 'drill', - }, - user: { - keywords: Array(1200).join('#'), - } - }; - - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; - - const request = spec.buildRequests([bid], { ortb2 })[0]; - const r = extractPayload(request); - - expect(r.site.ref).to.exist; - expect(r.site.keywords).to.be.undefined; - expect(r.user).to.be.undefined; - }); it('should set gpp and gpp_sid field when defined', function () { const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: {gpp: 'gpp', gpp_sid: [1]}} })[0]; const r = extractPayload(request); @@ -1724,6 +1703,63 @@ describe('IndexexchangeAdapter', function () { expect(r.regs.gpp_sid).to.be.an('array'); expect(r.regs.gpp_sid).to.include(1); }); + + it('should add adunit specific data to imp ext for banner', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam banner', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = extractPayload(requests[0]).imp[0]; + expect(deepAccess(imp, 'ext.data.adserver.name')).to.equal('gam banner'); + expect(deepAccess(imp, 'ext.data.adserver.adslot')).to.equal(AD_UNIT_CODE); + }); + + it('should add adunit specific data to imp ext for native', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_NATIVE_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam native', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = extractPayload(requests[0]).imp[0]; + expect(deepAccess(imp, 'ext.data.adserver.name')).to.equal('gam native'); + expect(deepAccess(imp, 'ext.data.adserver.adslot')).to.equal(AD_UNIT_CODE); + }); + + it('should add adunit specific data to imp ext for video', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam video', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = extractPayload(requests[0]).imp[0]; + expect(deepAccess(imp, 'ext.data.adserver.name')).to.equal('gam video'); + expect(deepAccess(imp, 'ext.data.adserver.adslot')).to.equal(AD_UNIT_CODE); + }); }); describe('buildRequests', function () { @@ -2273,7 +2309,7 @@ describe('IndexexchangeAdapter', function () { expect(requests[0].data.sn).to.be.undefined; }); - it('2 requests due to 2 ad units, one larger than url size', function () { + it('1 request, one larger than url size, no splitting', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; bid.params.siteId = '124'; @@ -2283,10 +2319,10 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); expect(requests).to.be.an('array'); - expect(requests).to.have.lengthOf(2); + expect(requests).to.have.lengthOf(1); }); - it('6 ad units should generate only 4 requests', function () { + it('6 ad units should generate only 1 requests', function () { const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid1.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; bid1.params.siteId = '121'; @@ -2312,7 +2348,7 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid1, bid2, bid3, bid4, bid5, bid6], DEFAULT_OPTION); expect(requests).to.be.an('array'); - expect(requests).to.have.lengthOf(4); + expect(requests).to.have.lengthOf(1); for (var i = 0; i < requests.length; i++) { const reqSize = `${requests[i].url}?${utils.parseQueryStringParameters(requests[i].data)}`.length; @@ -3611,7 +3647,7 @@ describe('IndexexchangeAdapter', function () { } } FEATURE_TOGGLES.getFeatureToggles(LOCAL_STORAGE_FEATURE_TOGGLES_KEY); - expect(FEATURE_TOGGLES.isFeatureEnabled('test')).to.be.undefined; + expect(FEATURE_TOGGLES.isFeatureEnabled('test')).to.be.false; expect(FEATURE_TOGGLES.featureToggles).to.deep.equal({}); }); @@ -3672,6 +3708,82 @@ describe('IndexexchangeAdapter', function () { expect(requests).to.be.an('array'); expect(requests).to.have.lengthOf(1); }); + + it('request should have requested feature toggles when local storage is enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.exist; + expect(Object.keys(r.ext.features).length).to.equal(FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES.length); + }); + + it('request should have requested feature toggles when local storage is not enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.exist; + expect(Object.keys(r.ext.features).length).to.equal(FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES.length); + }); + + it('request should not have any feature toggles when there is no requested feature toggle', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = [] + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.be.undefined; + }); + + it('request should not have any feature toggles when there is no requested feature toggle and local storage not enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = [] + expect(r.ext.features).to.be.undefined; + }); + + it('correct activation status of requested feature toggles when local storage not enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = ['test1'] + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.deep.equal({ + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { activated: false } + }); + }); + + it('correct activation status of requested feature toggles', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + serverResponse.body.ext.features = { + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { + activated: true + } + } + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + let bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + let requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + let r = extractPayload(requests[0]); + expect(r.ext.features).to.deep.equal({ + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { activated: true } + }); + + serverResponse.body.ext.features = { + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { + activated: false + } + } + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + r = extractPayload(requests[0]); + expect(r.ext.features).to.deep.equal({ + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { activated: false } + }); + }); }); describe('LocalStorage error codes', () => { @@ -3751,48 +3863,6 @@ describe('IndexexchangeAdapter', function () { expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_FLOOR_INVALID_FORMAT]: 1 } }); }); - it('should log ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - - config.setConfig({ - ix: { - firstPartyData: { - cd: Array(1700).join('#') - } - } - }); - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], {}); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE]: 2 } }); - }); - - it('should log ERROR_CODES.EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.bidderRequestId = Array(8000).join('#'); - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], {}); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.EXCEEDS_MAX_SIZE]: 2 } }); - }); - - it('should log ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - const ortb2 = { - site: { - ext: { - data: { - pageType: Array(5700).join('#') - } - } - } - }; - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], { ortb2 }); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE]: 2 } }); - }); - it('should log ERROR_CODES.VIDEO_DURATION_INVALID in LocalStorage when there is logError called.', () => { const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); bid.params.video.minduration = 1; diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 556e06268b1..d692cc67e26 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -35,7 +35,7 @@ describe('kargo adapter tests', function () { }); describe('build request', function() { - var bids, undefinedCurrency, noAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; + var bids, undefinedCurrency, noAdServerCurrency, nonUSDAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; beforeEach(function () { $$PREBID_GLOBAL$$.bidderSettings = { @@ -45,6 +45,7 @@ describe('kargo adapter tests', function () { }; undefinedCurrency = false; noAdServerCurrency = false; + nonUSDAdServerCurrency = false; sandbox.stub(config, 'getConfig').callsFake(function(key) { if (key === 'currency') { if (undefinedCurrency) { @@ -53,6 +54,9 @@ describe('kargo adapter tests', function () { if (noAdServerCurrency) { return {}; } + if (nonUSDAdServerCurrency) { + return {adServerCurrency: 'EUR'}; + } return {adServerCurrency: 'USD'}; } if (key === 'debug') return true; @@ -63,13 +67,45 @@ describe('kargo adapter tests', function () { bids = [ { params: { - placementId: 'foo' + placementId: 'foo', + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } }, - bidId: 1, + auctionId: '1234098', + bidId: '1', + adUnitCode: '101', + transactionId: '10101', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[320, 50], [300, 50]] + } + }, + bidRequestsCount: 1, + bidderRequestsCount: 2, + bidderWinsCount: 3, userId: { - tdid: 'fake-tdid' + tdid: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c' + }, + userIdAsEids: [ + { + 'source': 'adserver.org', + 'uids': [ + { + 'id': 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + } + ], + floorData: { + floorMin: 1 }, - sizes: [[320, 50], [300, 250], [300, 600]], ortb2: { device: { sua: { @@ -91,25 +127,76 @@ describe('kargo adapter tests', function () { version: [ '99', '0', '0', '0' ] } ], - mobile: 0, - model: '' + mobile: 1, + model: 'model', + source: 1, } } + }, + ortb2Imp: { + ext: { + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } } }, { params: { placementId: 'bar' }, - bidId: 2, - sizes: [[320, 50], [300, 250], [300, 600]] + bidId: '2', + adUnitCode: '202', + transactionId: '20202', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + video: { + sizes: [[320, 50], [300, 50]] + } + }, + bidRequestsCount: 0, + bidderRequestsCount: 0, + bidderWinsCount: 0, + ortb2Imp: { + ext: { + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } }, { params: { placementId: 'bar' }, - bidId: 3, - sizes: [[320, 50], [300, 250], [300, 600]] + bidId: '3', + adUnitCode: '303', + transactionId: '30303', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + native: { + sizes: [[320, 50], [300, 50]] + } + }, + ortb2Imp: { + ext: { + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } + } } ]; }); @@ -160,11 +247,19 @@ describe('kargo adapter tests', function () { function simulateNoCurrencyObject() { undefinedCurrency = true; noAdServerCurrency = false; + nonUSDAdServerCurrency = false; } function simulateNoAdServerCurrency() { undefinedCurrency = false; noAdServerCurrency = true; + nonUSDAdServerCurrency = false; + } + + function simulateNonUSDAdServerCurrency() { + undefinedCurrency = false; + noAdServerCurrency = false; + nonUSDAdServerCurrency = true; } function generateGDPR(applies, haveConsent) { @@ -182,6 +277,32 @@ describe('kargo adapter tests', function () { }; } + function generatePageView() { + return { + id: '112233', + timestamp: frozenNow.getTime(), + url: 'http://pageview.url' + } + } + + function generateRawCRB(rawCRB, rawCRBLocalStorage) { + if (rawCRB == null && rawCRBLocalStorage == null) { + return null + } + + let result = {} + + if (rawCRB != null) { + result.rawCRB = rawCRB + } + + if (rawCRBLocalStorage != null) { + result.rawCRBLocalStorage = rawCRBLocalStorage + } + + return result + } + function getKrgCrb() { return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0='; } @@ -253,6 +374,12 @@ describe('kargo adapter tests', function () { setLocalStorageItem('krg_crb', getEmptyKrgCrb()); } + function initializePageView() { + setLocalStorageItem('pageViewId', 112233); + setLocalStorageItem('pageViewTimestamp', frozenNow.getTime()); + setLocalStorageItem('pageViewUrl', 'http://pageview.url'); + } + function initializeEmptyKrgCrbCookie() { setCookie('krg_crb', getEmptyKrgCrbOldStyle()); } @@ -261,30 +388,20 @@ describe('kargo adapter tests', function () { return spec._getSessionId(); } - function getExpectedKrakenParams(excludeUserIds, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { + function getExpectedKrakenParams(expectedCRB, expectedPage, excludeUserIds, expectedGDPR, currency) { var base = { + pbv: '$prebid.version$', + aid: '1234098', + requestCount: 0, + sid: getSessionId(), + url: 'https://www.prebid.org', timeout: 200, - requestCount: requestCount++, - currency: 'USD', - cpmGranularity: 1, - timestamp: frozenNow.getTime(), - cpmRange: { - floor: 0, - ceil: 20 - }, - bidIDs: { - 1: 'foo', - 2: 'bar', - 3: 'bar' - }, - bidSizes: { - 1: [[320, 50], [300, 250], [300, 600]], - 2: [[320, 50], [300, 250], [300, 600]], - 3: [[320, 50], [300, 250], [300, 600]] - }, + ts: frozenNow.getTime(), device: { - width: screen.width, - height: screen.height, + size: [ + screen.width, + screen.height + ], sua: { platform: { brand: 'macOS', @@ -304,14 +421,61 @@ describe('kargo adapter tests', function () { version: [ '99', '0', '0', '0' ] } ], - mobile: 0, - model: '', + mobile: 1, + model: 'model', + source: 1 + }, + }, + imp: [ + { + code: '101', + id: '1', + pid: 'foo', + tid: '10101', + banner: { + sizes: [[320, 50], [300, 50]] + }, + bidRequestCount: 1, + bidderRequestCount: 2, + bidderWinCount: 3, + floor: 1, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + }, + { + code: '202', + id: '2', + pid: 'bar', + tid: '20202', + video: { + sizes: [[320, 50], [300, 50]] + }, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } }, + { + code: '303', + id: '3', + pid: 'bar', + tid: '30303', + native: { + sizes: [[320, 50], [300, 50]] + }, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + ], + socan: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' }, - userIDs: { + user: { kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f', - tdID: 'fake-tdid', + tdID: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', crbIDs: { 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', @@ -322,86 +486,61 @@ describe('kargo adapter tests', function () { '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' }, optOut: false, - usp: '1---' - }, - pageURL: 'https://www.prebid.org', - prebidVersion: '$prebid.version$', - prebidRawBidRequests: [ - { - bidId: 1, - ortb2: { - device: { - sua: { - platform: { - brand: 'macOS', - version: [ '12', '6', '0' ] - }, - browsers: [ - { - brand: 'Chromium', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Google Chrome', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Not;A=Brand', - version: [ '99', '0', '0', '0' ] - } - ], - mobile: 0, - model: '' + usp: '1---', + sharedIDEids: [ + { + source: 'adserver.org', + uids: [ + { + id: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', + atype: 1, + ext: { + rtiPartner: 'TDID' + } } - } - }, - params: { - placementId: 'foo' - }, - userId: { - tdid: 'fake-tdid' - }, - sizes: [[320, 50], [300, 250], [300, 600]], - }, - { - bidId: 2, - params: { - placementId: 'bar' - }, - sizes: [[320, 50], [300, 250], [300, 600]] - }, - { - bidId: 3, - params: { - placementId: 'bar' - }, - sizes: [[320, 50], [300, 250], [300, 600]] - } - ], - rawCRB: expectedRawCRBCookie, - rawCRBLocalStorage: expectedRawCRB + ] + } + ] + } }; + if (excludeUserIds) { + base.user.crbIDs = {}; + delete base.user.clientID; + delete base.user.kargoID; + delete base.user.optOut; + } + if (expectedGDPR) { - base.userIDs['gdpr'] = expectedGDPR; + base.user.gdpr = expectedGDPR; } - if (excludeUserIds === true) { - base.userIDs = { - crbIDs: {}, - usp: '1---' - }; - delete base.prebidRawBidRequests[0].userId.tdid; + if (expectedPage) { + base.page = expectedPage; + } + + if (currency) { + base.cur = currency; + } + + const reqCount = requestCount++; + base.requestCount = reqCount + + if (expectedCRB != null) { + if (expectedCRB.rawCRB != null) { + base.rawCRB = expectedCRB.rawCRB + } + if (expectedCRB.rawCRBLocalStorage != null) { + base.rawCRBLocalStorage = expectedCRB.rawCRBLocalStorage + } } return base; } - function testBuildRequests(excludeTdid, expected, gdpr) { + function testBuildRequests(expected, gdpr) { var clonedBids = JSON.parse(JSON.stringify(bids)); - if (excludeTdid) { - delete clonedBids[0].userId.tdid; - } + var payload = { timeout: 200, uspConsent: '1---', @@ -415,15 +554,13 @@ describe('kargo adapter tests', function () { } var request = spec.buildRequests(clonedBids, payload); - expected.sessionId = getSessionId(); - sessionIds.push(expected.sessionId); - var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); - expect(request.data.slice(0, 5)).to.equal('json='); - expect(request.url).to.equal('https://krk.kargo.com/api/v2/bid'); - expect(request.method).to.equal('GET'); - expect(request.currency).to.equal('USD'); + var krakenParams = request.data; + + expect(request.url).to.equal('https://krk2.kargo.com/api/v1/prebid'); + expect(request.method).to.equal('POST'); expect(request.timeout).to.equal(200); expect(krakenParams).to.deep.equal(expected); + // Make sure session ID stays the same across requests simulating multiple auctions on one page load for (let i in sessionIds) { if (i == 0) { @@ -436,76 +573,93 @@ describe('kargo adapter tests', function () { it('works when all params and localstorage and cookies are correctly set', function() { initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); }); it('works when all params and cookies are correctly set but no localstorage', function() { initializeKrgCrb(true); - testBuildRequests(false, getExpectedKrakenParams(undefined, null, getKrgCrbOldStyle())); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle()))); }); it('gracefully handles nothing being set', function() { - testBuildRequests(true, getExpectedKrakenParams(true, null, null)); + testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); }); it('gracefully handles browsers without localStorage', function() { simulateNoLocalStorage(); - testBuildRequests(true, getExpectedKrakenParams(true, null, null)); + testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); }); it('handles empty yet valid Kargo CRB', function() { initializeEmptyKrgCrb(); initializeEmptyKrgCrbCookie(); - testBuildRequests(true, getExpectedKrakenParams(true, getEmptyKrgCrb(), getEmptyKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getEmptyKrgCrbOldStyle(), getEmptyKrgCrb()), generatePageView(), true)); }); it('handles broken Kargo CRBs where base64 encoding is invalid', function() { initializeInvalidKrgCrbType1(); - testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType1(), null)); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType1()), generatePageView(), true)); }); it('handles broken Kargo CRBs where top level JSON is invalid on cookie', function() { initializeInvalidKrgCrbType1Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType1())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType1()), generatePageView(), true)); }); it('handles broken Kargo CRBs where decoded JSON is invalid', function() { initializeInvalidKrgCrbType2(); - testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType2(), null)); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType2()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner base 64 is invalid on cookie', function() { initializeInvalidKrgCrbType2Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType2OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType2OldStyle()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner JSON is invalid on cookie', function() { initializeInvalidKrgCrbType3Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType3OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType3OldStyle()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner JSON is falsey', function() { initializeInvalidKrgCrbType4Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType4OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType4OldStyle()), generatePageView(), true)); }); it('handles a non-existant currency object on the config', function() { simulateNoCurrencyObject(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); }); it('handles no ad server currency being set on the currency object in the config', function() { simulateNoAdServerCurrency(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); + }); + + it('handles non-USD ad server currency being set on the currency object in the config', function() { + simulateNonUSDAdServerCurrency(); + initializeKrgCrb(); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView(), undefined, undefined, 'EUR')); }); it('sends gdpr consent', function () { initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(true, true)), generateGDPR(true, true)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, true)), generateGDPR(false, true)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, false)), generateGDPR(false, false)); }); }); @@ -604,11 +758,11 @@ describe('kargo adapter tests', function () { }] }); var expectation = [{ + ad: '
', requestId: '1', cpm: 3, width: 320, height: 50, - ad: '
', ttl: 300, creativeId: 'foo', dealId: undefined, @@ -620,10 +774,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '2', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: 'dmpmptest1234', @@ -637,10 +791,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '3', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: undefined, @@ -652,10 +806,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '4', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: undefined, @@ -670,7 +824,6 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, - ad: '', vastXml: '', ttl: 300, creativeId: 'bar', @@ -686,7 +839,6 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, - ad: '', vastUrl: 'https://foobar.com/vast_adm', ttl: 300, creativeId: 'bar', @@ -740,8 +892,8 @@ describe('kargo adapter tests', function () { }); }); - function getUserSyncsWhenAllowed(gdprConsent, usPrivacy) { - return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy); + function getUserSyncsWhenAllowed(gdprConsent, usPrivacy, gppConsent) { + return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy, gppConsent); } function getUserSyncsWhenForbidden() { @@ -756,17 +908,17 @@ describe('kargo adapter tests', function () { shouldSimulateOutdatedBrowser = true; } - function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy) { + function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { return { type: 'iframe', - url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}` + url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}&gpp=${gpp}&gpp_sid=${gppSid}` }; } - function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy) { + function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { var syncs = []; for (var i = 0; i < 5; i++) { - syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || ''); + syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || '', gpp || '', gppSid || ''); } return syncs; } @@ -803,6 +955,11 @@ describe('kargo adapter tests', function () { safelyRun(() => expect(getUserSyncsWhenAllowed({ gdprApplies: true, consentString: 'consentstring' })).to.deep.equal(getSyncUrls(1, 'consentstring', ''))); }); + it('pass through gpp consent', function () { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, null, { consentString: 'gppString', applicableSections: [-1] })).to.deep.equal(getSyncUrls('', '', '', 'gppString', '-1'))); + }); + it('no user syncs when there is outdated browser', function() { turnOnClientId(); simulateOutdatedBrowser(); @@ -831,7 +988,7 @@ describe('kargo adapter tests', function () { expect(triggerPixelStub.getCall(0)).to.be.null; spec.onTimeout([{ auctionId: '1234', timeout: 2000 }]); expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk.kargo.com/api/v1/event/timeout'); + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk2.kargo.com/api/v1/event/timeout'); expect(triggerPixelStub.getCall(0).args[0]).to.include('aid=1234'); expect(triggerPixelStub.getCall(0).args[0]).to.include('ato=2000'); }); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 5bcf62ff95d..5fb0bef726e 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -366,26 +366,6 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.imp[1].pmp.deals[1].id).to.be.equal(dealIds2[1]); }); - it('should read timeout from config', function () { - const timeout = 4000; - const validBidRequests = [createValidBidRequest()]; - // No timeout field - const bidderRequest = { - auctionId: 'c1243d83-0bed-4fdb-8c76-42b456be17d0', - refererInfo: { - page: 'example.com' - } - }; - config.setConfig({ - bidderTimeout: timeout - }); - - const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); - - expect(openRtbRequest.tmax).to.be.equal(timeout); - }); - it('should read floor price using floors module', function () { const floorPriceFor580x400 = 6.5148; const floorPriceForAnySize = 4.2343; diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 2f48fd6df8c..fc7219d2ee7 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -43,7 +43,12 @@ const BID = { 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER] + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789' + } + } }; const VIDEO_BID = { @@ -315,7 +320,8 @@ describe('KueezRtbBidAdapter', function () { protocols: [2, 3, 5, 6], startdelay: 0 } - } + }, + gpid: '' } }); }); @@ -373,6 +379,7 @@ describe('KueezRtbBidAdapter', function () { schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], + gpid: '0123456789', uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', @@ -448,6 +455,19 @@ describe('KueezRtbBidAdapter', function () { }); }); + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['kueezrtb.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['kueezrtb.com'], + agencyName: 'Agency Name' + }); + }); + it('should return an array of interpreted video responses', function () { const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index af34c209a1d..afbd1566438 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -313,6 +313,16 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar'}}); }); + it('should decode a bidswitch id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar'}}); + }); + + it('should decode a medianet id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar'}}); + }); + it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); expect(result).to.eql({'uid2': {'id': 'bar'}}); diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 9f109ff1892..c8d4c18c407 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -133,7 +133,7 @@ describe('luponmediaBidAdapter', function () { } ], 'auctionStart': 1587413920820, - 'timeout': 2000, + 'timeout': 1500, 'refererInfo': { 'page': 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines', 'reachedTop': true, diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index b27e8f30881..bf9c3050bf6 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -28,7 +28,8 @@ const { BIDDER_DONE, BID_WON, BID_TIMEOUT, - BILLABLE_EVENT + BILLABLE_EVENT, + SEAT_NON_BID } } = CONSTANTS; @@ -160,6 +161,16 @@ const MOCK = { 'status': 'rendered', getStatusCode: () => 1, }, + SEAT_NON_BID: { + auctionId: '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + seatnonbid: [{ + seat: 'rubicon', + nonbid: [{ + status: 1, + impid: 'box' + }] + }] + }, AUCTION_END: { 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', 'auctionEnd': 1658868384019, @@ -2039,4 +2050,121 @@ describe('magnite analytics adapter', function () { } }) }); + + describe('BID_RESPONSE events', () => { + beforeEach(() => { + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + config.setConfig({ rubicon: { updatePageView: true } }); + }); + + it('should add a no-bid bid to the add unit if it recieves one from the server', () => { + const bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + const auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + + bidResponse.requestId = 'fakeId'; + bidResponse.seatBidId = 'fakeId'; + + bidResponse.requestId = 'fakeId'; + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, bidResponse) + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(utils.generateUUID.called).to.equal(true); + + expect(message.auctions[0].adUnits[0].bids[1]).to.deep.equal( + { + bidder: 'rubicon', + source: 'server', + status: 'success', + bidResponse: { + 'bidPriceUSD': 3.4, + 'dimensions': { + 'height': 250, + 'width': 300 + }, + 'mediaType': 'banner' + }, + oldBidId: 'fakeId', + unknownBid: true, + bidId: 'fakeId', + clientLatencyMillis: 271 + } + ); + }); + }); + + describe('SEAT_NON_BID events', () => { + let seatnonbid; + + const runNonBidAuction = () => { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(SEAT_NON_BID, seatnonbid) + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + }; + const checkStatusAgainstCode = (status, code, error, index) => { + seatnonbid.seatnonbid[0].nonbid[0].status = code; + runNonBidAuction(); + let message = JSON.parse(server.requests[index].requestBody); + let bid = message.auctions[0].adUnits[0].bids[1]; + + if (error) { + expect(bid.error).to.deep.equal(error); + } else { + expect(bid.error).to.equal(undefined); + } + expect(bid.source).to.equal('server'); + expect(bid.status).to.equal(status); + expect(bid.isSeatNonBid).to.equal(true); + }; + beforeEach(() => { + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + seatnonbid = utils.deepClone(MOCK.SEAT_NON_BID); + }); + + it('adds seatnonbid info to bids array', () => { + runNonBidAuction(); + let message = JSON.parse(server.requests[0].requestBody); + + expect(message.auctions[0].adUnits[0].bids[1]).to.deep.equal( + { + bidder: 'rubicon', + source: 'server', + status: 'no-bid', + isSeatNonBid: true, + clientLatencyMillis: -139101369960 + } + ); + }); + + it('adjusts the status according to the status map', () => { + const statuses = [ + {code: 0, status: 'no-bid'}, + {code: 100, status: 'error', error: {code: 'request-error', description: 'general error'}}, + {code: 101, status: 'error', error: {code: 'timeout-error', description: 'prebid server timeout'}}, + {code: 200, status: 'rejected'}, + {code: 202, status: 'rejected'}, + {code: 301, status: 'rejected-ipf'} + ]; + statuses.forEach((info, index) => { + checkStatusAgainstCode(info.status, info.code, info.error, index); + }); + }); + }); }); diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index 0f0da6032eb..107906ec83d 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -74,7 +74,8 @@ describe('MathildeAdsBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js index c79cad6245d..28530c4c4b4 100644 --- a/test/spec/modules/mgidBidAdapter_spec.js +++ b/test/spec/modules/mgidBidAdapter_spec.js @@ -1,7 +1,9 @@ -import {assert, expect} from 'chai'; +import {expect} from 'chai'; import { spec, storage } from 'modules/mgidBidAdapter.js'; import { version } from 'package.json'; import * as utils from '../../../src/utils.js'; +import {USERSYNC_DEFAULT_CONFIG} from '../../../src/userSync'; +import {config} from '../../../src/config'; describe('Mgid bid adapter', function () { let sandbox; @@ -21,10 +23,10 @@ describe('Mgid bid adapter', function () { const ua = navigator.userAgent; const screenHeight = screen.height; const screenWidth = screen.width; - const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; + const dnt = (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0; const language = navigator.language ? 'language' : 'userLanguage'; let lang = navigator[language].split('-')[0]; - if (lang.length != 2 && lang.length != 3) { + if (lang.length !== 2 && lang.length !== 3) { lang = ''; } const secure = window.location.protocol === 'https:' ? 1 : 0; @@ -36,7 +38,7 @@ describe('Mgid bid adapter', function () { }); describe('isBidRequestValid', function () { - let bid = { + let sbid = { 'adUnitCode': 'div', 'bidder': 'mgid', 'params': { @@ -46,26 +48,26 @@ describe('Mgid bid adapter', function () { }; it('should not accept bid without required params', function () { - let isValid = spec.isBidRequestValid(bid); + let isValid = spec.isBidRequestValid(sbid); expect(isValid).to.equal(false); }); it('should return false when params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when valid params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '', placementId: ''}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when valid params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = ''; bid.mediaTypes = { @@ -78,7 +80,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when adUnitCode not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = ''; bid.mediaTypes = { @@ -91,7 +93,7 @@ describe('Mgid bid adapter', function () { }); it('should return true when valid params are passed as nums', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = 'div'; bid.mediaTypes = { @@ -104,7 +106,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.mediaTypes = { native: { @@ -116,14 +118,14 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when valid mediaTypes.banner are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -132,7 +134,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes.banner.sizes are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -142,7 +144,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes.banner.sizes are not valid', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -152,7 +154,7 @@ describe('Mgid bid adapter', function () { }); it('should return true when valid params are passed as strings', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = 'div'; bid.params = {accountId: '1', placementId: '1'}; @@ -165,7 +167,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes.native is not object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { native: [] @@ -174,7 +176,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when mediaTypes.native is empty object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -184,7 +186,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when mediaTypes.native is invalid object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -198,7 +200,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when mediaTypes.native has unsupported required asset', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = {accountId: '2', placementId: '1'}; bid.mediaTypes = { native: { @@ -217,7 +219,7 @@ describe('Mgid bid adapter', function () { }); it('should return true when mediaTypes.native all assets needed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.adUnitCode = 'div'; bid.params = {accountId: '2', placementId: '1'}; bid.mediaTypes = { @@ -237,7 +239,7 @@ describe('Mgid bid adapter', function () { }); describe('override defaults', function () { - let bid = { + let sbid = { bidder: 'mgid', params: { accountId: '1', @@ -245,7 +247,7 @@ describe('Mgid bid adapter', function () { }, }; it('should return object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.mediaTypes = { banner: { sizes: [[300, 250]] @@ -257,7 +259,7 @@ describe('Mgid bid adapter', function () { }); it('should return overwrite default bidurl', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = { bidUrl: 'https://newbidurl.com/', accountId: '1', @@ -273,7 +275,7 @@ describe('Mgid bid adapter', function () { expect(request.url).to.include('https://newbidurl.com/1'); }); it('should return overwrite default bidFloor', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = { bidFloor: 1.1, accountId: '1', @@ -294,7 +296,7 @@ describe('Mgid bid adapter', function () { expect(data.imp[0].bidfloor).to.deep.equal(1.1); }); it('should return overwrite default currency', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = { cur: 'GBP', accountId: '1', @@ -323,6 +325,9 @@ describe('Mgid bid adapter', function () { placementId: '2', }, }; + afterEach(function () { + config.setConfig({coppa: undefined}) + }) it('should return undefined if no validBidRequests passed', function () { expect(spec.buildRequests([])).to.be.undefined; @@ -344,6 +349,7 @@ describe('Mgid bid adapter', function () { getDataFromLocalStorageStub.restore(); }); it('should proper handle gdpr', function () { + config.setConfig({coppa: 1}) let bid = Object.assign({}, abid); bid.mediaTypes = { banner: { @@ -351,12 +357,72 @@ describe('Mgid bid adapter', function () { } }; let bidRequests = [bid]; - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'gdpr', gdprApplies: true}}); + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'gdpr', gdprApplies: true}, uspConsent: 'usp', gppConsent: {gppString: 'gpp'}}); expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); expect(request.method).deep.equal('POST'); const data = JSON.parse(request.data); expect(data.user).deep.equal({ext: {consent: 'gdpr'}}); - expect(data.regs).deep.equal({ext: {gdpr: 1}}); + expect(data.regs).deep.equal({ext: {gdpr: 1, us_privacy: 'usp'}, gpp: 'gpp', coppa: 1}); + }); + it('should handle refererInfo', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const domain = 'site.com' + const page = `http://${domain}/site.html` + const ref = 'http://ref.com/ref.html' + const request = spec.buildRequests(bidRequests, {refererInfo: {page, ref}}); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.site.domain).to.deep.equal(domain); + expect(data.site.page).to.deep.equal(page); + expect(data.site.ref).to.deep.equal(ref); + }); + it('should handle schain', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.schain = ['schain1', 'schain2']; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + const data = JSON.parse(request.data); + expect(data.source).to.deep.equal({ext: {schain: bid.schain}}); + }); + it('should handle userId', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const bidderRequest = {userId: 'userid'}; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.user.id).to.deep.equal(bidderRequest.userId); + }); + it('should handle eids', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.userIdAsEids = ['eid1', 'eid2'] + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + const data = JSON.parse(request.data); + expect(data.user.ext.eids).to.deep.equal(bid.userIdAsEids); }); it('should return proper banner imp', function () { let bid = Object.assign({}, abid); @@ -386,7 +452,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"banner":{"w":300,"h":250}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"banner":{"w":300,"h":250}}],"tmax":3000}`, }); }); it('should not return native imp if minimum asset list not requested', function () { @@ -435,7 +501,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":11,"required":0,"data":{"type":1}}]}}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":11,"required":0,"data":{"type":1}}]}}}],"tmax":3000}`, }); }); it('should return proper native imp with image altered', function () { @@ -472,7 +538,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":492,"h":328,"wmin":50,"hmin":50}},{"id":3,"required":0,"img":{"type":1,"w":50,"h":50}},{"id":11,"required":0,"data":{"type":1}}]}}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":492,"h":328,"wmin":50,"hmin":50}},{"id":3,"required":0,"img":{"type":1,"w":50,"h":50}},{"id":11,"required":0,"data":{"type":1}}]}}}],"tmax":3000}`, }); }); it('should return proper native imp with sponsoredBy', function () { @@ -508,7 +574,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":4,"required":0,"data":{"type":1}}]}}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":4,"required":0,"data":{"type":1}}]}}}],"tmax":3000}`, }); }); it('should return proper banner request', function () { @@ -542,7 +608,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}],"tmax":3000}`, }); }); it('should proper handle ortb2 data', function () { @@ -555,7 +621,14 @@ describe('Mgid bid adapter', function () { let bidRequests = [bid]; let bidderRequest = { + gdprConsent: { + consentString: 'consent1', + gdprApplies: false, + }, ortb2: { + bcat: ['bcat1', 'bcat2'], + badv: ['badv1.com', 'badv2.com'], + wlang: ['l1', 'l2'], site: { content: { data: [{ @@ -571,6 +644,9 @@ describe('Mgid bid adapter', function () { } }, user: { + ext: { + consent: 'consent2 ', + }, data: [{ name: 'mgid.com', ext: { @@ -581,6 +657,11 @@ describe('Mgid bid adapter', function () { {'id': '987'}, ], }] + }, + regs: { + ext: { + gdpr: 1, + } } } }; @@ -589,7 +670,13 @@ describe('Mgid bid adapter', function () { expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); expect(request.method).deep.equal('POST'); const data = JSON.parse(request.data); - expect(data.ext).deep.include(bidderRequest.ortb2); + expect(data.bcat).deep.equal(bidderRequest.ortb2.bcat); + expect(data.badv).deep.equal(bidderRequest.ortb2.badv); + expect(data.wlang).deep.equal(bidderRequest.ortb2.wlang); + expect(data.site.content).deep.equal(bidderRequest.ortb2.site.content); + expect(data.regs).deep.equal(bidderRequest.ortb2.regs); + expect(data.user.data).deep.equal(bidderRequest.ortb2.user.data); + expect(data.user.ext).deep.equal(bidderRequest.ortb2.user.ext); }); }); @@ -727,8 +814,69 @@ describe('Mgid bid adapter', function () { }); describe('getUserSyncs', function () { - it('should do nothing on getUserSyncs', function () { - spec.getUserSyncs() + afterEach(function() { + config.setConfig({userSync: {syncsPerBidder: USERSYNC_DEFAULT_CONFIG.syncsPerBidder}}); + }); + it('should do nothing on getUserSyncs without inputs', function () { + expect(spec.getUserSyncs()).to.equal(undefined) + }); + it('should return frame object with empty consents', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&consentData=&gdprApplies=0/) + }); + it('should return frame object with gdpr consent', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}, undefined, {consentString: 'consent', gdprApplies: true}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&consentData=consent&gdprApplies=1/) + }); + it('should return frame object with gdpr + usp', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + }); + it('should return img object with gdpr + usp', function () { + config.setConfig({userSync: {syncsPerBidder: undefined}}); + const sync = spec.getUserSyncs({pixelEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(USERSYNC_DEFAULT_CONFIG.syncsPerBidder) + for (let i = 0; i < USERSYNC_DEFAULT_CONFIG.syncsPerBidder; i++) { + expect(sync[i]).to.have.property('type', 'image') + expect(sync[i]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + } + }); + it('should return frame object with gdpr + usp', function () { + const sync = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + }); + it('should return img (pixels) objects with gdpr + usp', function () { + const response = [{body: {ext: {cm: ['http://cm.mgid.com/i.gif?cdsp=1111', 'http://cm.mgid.com/i.gif']}}}] + const sync = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, response, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(2) + expect(sync[0]).to.have.property('type', 'image') + expect(sync[0]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cdsp=1111&cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + expect(sync[1]).to.have.property('type', 'image') + expect(sync[1]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + }); + }); + + describe('getUserSyncs with img from ext.cm and gdpr + usp + coppa + gpp', function () { + afterEach(function() { + config.setConfig({coppa: undefined}) + }); + it('should return img (pixels) objects with gdpr + usp + coppa + gpp', function () { + config.setConfig({coppa: 1}); + const response = [{body: {ext: {cm: ['http://cm.mgid.com/i.gif?cdsp=1111', 'http://cm.mgid.com/i.gif']}}}] + const sync = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, response, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}, {gppString: 'gpp'}) + expect(sync).to.have.length(2) + expect(sync[0]).to.have.property('type', 'image') + expect(sync[0]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cdsp=1111&cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2&gppString=gpp&coppa=1/) + expect(sync[1]).to.have.property('type', 'image') + expect(sync[1]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2&gppString=gpp&coppa=1/) }); }); diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js index 000ddb2778d..e19323c794f 100644 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -43,7 +43,12 @@ const BID = { 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER] + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '1234567890' + } + } }; const VIDEO_BID = { @@ -315,7 +320,8 @@ describe('MinuteMediaPlus Bid Adapter', function () { protocols: [2, 3, 5, 6], startdelay: 0 } - } + }, + gpid: '' } }); }); @@ -373,6 +379,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], + gpid: '1234567890', uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', @@ -448,6 +455,19 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); }); + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['minutemedia-prebid.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['minutemedia-prebid.com'], + agencyName: 'Agency Name' + }); + }); + it('should return an array of interpreted video responses', function () { const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index 4d70e6f7071..51e78d1f6d6 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -8,6 +8,10 @@ import { getPageUrlFromBidRequest, hasProtocol, addProtocol, + BidRequestDataSource, + RequestData, + UserEIDs, + buildRequestUrl, } from '../../../modules/nativoBidAdapter' describe('bidDataMap', function () { @@ -120,6 +124,7 @@ describe('nativoBidAdapterTests', function () { expect(request.url).to.be.a('string') expect(request.url).to.include('?') + expect(request.url).to.include('ntv_pbv') expect(request.url).to.include('ntv_ptd') expect(request.url).to.include('ntv_pb_rid') expect(request.url).to.include('ntv_ppc') @@ -731,3 +736,99 @@ describe('getPageUrlFromBidRequest', () => { expect(url).not.to.be.undefined }) }) + +describe('RequestData', () => { + describe('addBidRequestDataSource', () => { + it('Adds a BidRequestDataSource', () => { + const requestData = new RequestData() + const testBidRequestDataSource = new BidRequestDataSource() + + requestData.addBidRequestDataSource(testBidRequestDataSource) + + expect(requestData.bidRequestDataSources.length == 1) + }) + + it("Doeasn't add a non BidRequestDataSource", () => { + const requestData = new RequestData() + + requestData.addBidRequestDataSource({}) + requestData.addBidRequestDataSource('test') + requestData.addBidRequestDataSource(1) + requestData.addBidRequestDataSource(true) + + expect(requestData.bidRequestDataSources.length == 0) + }) + }) + + describe('getRequestDataString', () => { + it("Doesn't append empty query strings", () => { + const requestData = new RequestData() + const testBidRequestDataSource = new BidRequestDataSource() + + requestData.addBidRequestDataSource(testBidRequestDataSource) + + let qs = requestData.getRequestDataQueryString() + expect(qs).to.be.empty + + testBidRequestDataSource.getRequestQueryString = () => { + return 'ntv_test=true' + } + qs = requestData.getRequestDataQueryString() + expect(qs).to.be.equal('ntv_test=true') + }) + }) +}) + +describe('UserEIDs', () => { + const userEids = new UserEIDs() + const eids = [{ 'testId': 1111 }] + + describe('processBidRequestData', () => { + it('Processes bid request without eids', () => { + userEids.processBidRequestData({}) + + expect(userEids.eids).to.be.empty + }) + + it('Processed bid request with eids', () => { + userEids.processBidRequestData({ userIdAsEids: eids }) + + expect(userEids.eids).to.not.be.empty + }) + }) + + describe('getRequestQueryString', () => { + it('Correctly prints out QS param string', () => { + const qs = userEids.getRequestQueryString() + const value = qs.slice(11) + + expect(qs).to.include('ntv_pb_eid=') + try { + expect(JSON.parse(value)).to.be.equal(eids) + } catch (err) { } + }) + }) +}) + +describe('buildRequestUrl', () => { + const baseUrl = 'https://www.testExchange.com' + it('Returns baseUrl if no QS strings passed', () => { + const url = buildRequestUrl(baseUrl) + expect(url).to.be.equal(baseUrl) + }) + + it('Returns baseUrl if empty QS strings passed', () => { + const url = buildRequestUrl(baseUrl, ['', '', '']) + expect(url).to.be.equal(baseUrl) + }) + + it('Returns baseUrl + QS params if QS strings passed', () => { + const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', 'ntv_foo=bar']) + expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) + }) + + it('Returns baseUrl + QS params if mixed QS strings passed', () => { + const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', '', '', 'ntv_foo=bar']) + expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) + }) +}) diff --git a/test/spec/modules/neuwoRtdProvider_spec.js b/test/spec/modules/neuwoRtdProvider_spec.js index aed9634cd95..0ad3d7c1f74 100644 --- a/test/spec/modules/neuwoRtdProvider_spec.js +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -60,7 +60,6 @@ describe('neuwoRtdProvider', function () { neuwo.injectTopics(topics, bidsConfig, () => { }) expect(bidsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) expect(bidsConfig.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) - expect(bidsConfig.ortb2Fragments.global.site.cattax, 'category taxonomy code for pagecat').to.equal(6) // CATTAX_IAB expect(bidsConfig.ortb2Fragments.global.site.pagecat[0], 'category taxonomy code for pagecat').to.equal(TAX_ID) }) diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index f7cdcc0d11a..2d48dca5ebb 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -245,6 +245,7 @@ describe('OguryBidAdapter', function () { const stubbedWidth = 200 const stubbedHeight = 600 const stubbedCurrentTime = 1234567890 + const stubbedDevicePixelRatio = 1 const stubbedWidthMethod = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { return stubbedWidth; }); @@ -255,6 +256,10 @@ describe('OguryBidAdapter', function () { return stubbedCurrentTime; }); + const stubbedDevicePixelMethod = sinon.stub(window, 'devicePixelRatio').get(function() { + return stubbedDevicePixelRatio; + }); + const defaultTimeout = 1000; const expectedRequestObject = { id: bidRequests[0].auctionId, @@ -305,11 +310,12 @@ describe('OguryBidAdapter', function () { }, ext: { prebidversion: '$prebid.version$', - adapterversion: '1.4.0' + adapterversion: '1.4.1' }, device: { w: stubbedWidth, - h: stubbedHeight + h: stubbedHeight, + pxratio: stubbedDevicePixelRatio, } }; @@ -317,6 +323,7 @@ describe('OguryBidAdapter', function () { stubbedWidthMethod.restore(); stubbedHeightMethod.restore(); stubbedCurrentTimeMethod.restore(); + stubbedDevicePixelMethod.restore(); }); it('sends bid request to ENDPOINT via POST', function () { @@ -338,6 +345,14 @@ describe('OguryBidAdapter', function () { stubbedTimelineMethod.restore(); }); + it('send device pixel ratio in bid request', function() { + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestObject); + expect(request.data.device.pxratio).to.be.a('number'); + }) + it('bid request object should be conform', function () { const validBidRequests = utils.deepClone(bidRequests) @@ -697,7 +712,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.4.0', + adapterVersion: '1.4.1', prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, @@ -714,7 +729,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.4.0', + adapterVersion: '1.4.1', prebidVersion: '$prebid.version$' }] diff --git a/test/spec/modules/ooloAnalyticsAdapter_spec.js b/test/spec/modules/ooloAnalyticsAdapter_spec.js index a6030e972ce..2515c713b14 100644 --- a/test/spec/modules/ooloAnalyticsAdapter_spec.js +++ b/test/spec/modules/ooloAnalyticsAdapter_spec.js @@ -194,7 +194,7 @@ describe('oolo Prebid Analytic', () => { }) const conf = {} - const pbjsConfig = config.getConfig() + const pbjsConfig = JSON.parse(JSON.stringify(config.getConfig())) Object.keys(pbjsConfig).forEach(key => { if (key[0] !== '_') { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 8fe220aa202..d8ea79ac698 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,204 +1,81 @@ import {expect} from 'chai'; -import {spec, USER_ID_CODE_TO_QUERY_ARG} from 'modules/openxBidAdapter.js'; +import {spec, REQUEST_URL, SYNC_URL, DEFAULT_PH} from 'modules/openxOrtbBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from 'src/mediaTypes.js'; -import {userSync} from 'src/userSync.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; +// load modules that register ORTB processors +import 'src/prebid.js' +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import {deepClone} from 'src/utils.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {hook} from '../../../src/hook.js'; + +const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; + +const BidRequestBuilder = function BidRequestBuilder(options) { + const defaults = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'openx' + }, + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + sizes: [[300, 250], [300, 600]], + }; -const URLBASE = '/w/1.0/arj'; -const URLBASEVIDEO = '/v/1.0/avjp'; - -describe('OpenxAdapter', function () { - const adapter = newBidder(spec); - - /** - * Type Definitions - */ - - /** - * @typedef {{ - * impression: string, - * inview: string, - * click: string - * }} - */ - let OxArjTracking; - /** - * @typedef {{ - * ads: { - * version: number, - * count: number, - * pixels: string, - * ad: Array - * } - * }} - */ - let OxArjResponse; - /** - * @typedef {{ - * adunitid: number, - * adid:number, - * type: string, - * htmlz: string, - * framed: number, - * is_fallback: number, - * ts: string, - * cpipc: number, - * pub_rev: string, - * tbd: ?string, - * adv_id: string, - * deal_id: string, - * auct_win_is_deal: number, - * brand_id: string, - * currency: string, - * idx: string, - * creative: Array - * }} - */ - let OxArjAdUnit; - /** - * @typedef {{ - * id: string, - * width: string, - * height: string, - * target: string, - * mime: string, - * media: string, - * tracking: OxArjTracking - * }} - */ - let OxArjCreative; - - // HELPER METHODS - /** - * @type {OxArjCreative} - */ - const DEFAULT_TEST_ARJ_CREATIVE = { - id: '0', - width: 'test-width', - height: 'test-height', - target: 'test-target', - mime: 'test-mime', - media: 'test-media', - tracking: { - impression: 'test-impression', - inview: 'test-inview', - click: 'test-click' - } + const request = { + ...defaults.request, + ...options }; - /** - * @type {OxArjAdUnit} - */ - const DEFAULT_TEST_ARJ_AD_UNIT = { - adunitid: 0, - type: 'test-type', - html: 'test-html', - framed: 0, - is_fallback: 0, - ts: 'test-ts', - tbd: 'NaN', - deal_id: undefined, - auct_win_is_deal: undefined, - cpipc: 0, - pub_rev: 'test-pub_rev', - adv_id: 'test-adv_id', - brand_id: 'test-brand_id', - currency: 'test-currency', - idx: '0', - creative: [DEFAULT_TEST_ARJ_CREATIVE] + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; }; - /** - * @type {OxArjResponse} - */ - const DEFAULT_ARJ_RESPONSE = { - ads: { - version: 0, - count: 1, - pixels: 'https://testpixels.net', - ad: [DEFAULT_TEST_ARJ_AD_UNIT] + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'openx', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' } }; - // Sample bid requests + const request = { + ...defaults, + ...options + }; - const BANNER_BID_REQUESTS_WITH_MEDIA_TYPES = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - adUnitCode: '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-1' } } }, - }]; - - const VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES = [{ - bidder: 'openx', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', + this.build = () => request; +}; - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }]; +describe('OpenxRtbAdapter', function () { + before(() => { + hook.ready(); + }); - const MULTI_FORMAT_BID_REQUESTS = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - playerSize: [300, 250] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }]; + const adapter = newBidder(spec); describe('inherited functions', function () { it('exists and is a function', function () { @@ -206,7 +83,7 @@ describe('OpenxAdapter', function () { }); }); - describe('isBidRequestValid', function () { + describe('isBidRequestValid()', function () { describe('when request is for a banner ad', function () { let bannerBid; beforeEach(function () { @@ -259,8 +136,28 @@ describe('OpenxAdapter', function () { describe('when request is for a multiformat ad', function () { describe('and request config uses mediaTypes video and banner', () => { + const multiformatBid = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [300, 250] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(MULTI_FORMAT_BID_REQUESTS[0])).to.equal(true); + expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); }); }); }); @@ -349,1509 +246,1035 @@ describe('OpenxAdapter', function () { expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); }); }); - - describe('and request config uses test', () => { - const videoBidWithTest = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain', - test: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - - let mockBidderRequest = {refererInfo: {}}; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(videoBidWithTest)).to.equal(true); - }); - - it('should send video bid request to openx url via GET, with vtest=1 video parameter', function () { - const request = spec.buildRequests([videoBidWithTest], mockBidderRequest); - expect(request[0].data.vtest).to.equal(1); - }); - }); }); }); - describe('buildRequests for banner ads', function () { - const bidRequestsWithMediaTypes = BANNER_BID_REQUESTS_WITH_MEDIA_TYPES; - - const bidRequestsWithPlatform = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }]; - - const mockBidderRequest = {refererInfo: {}}; - - it('should send bid request to openx url via GET, with mediaTypes specified with banner type', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASE); - expect(request[0].data.ph).to.be.undefined; - expect(request[0].method).to.equal('GET'); - }); - - it('should send bid request to openx platform url via GET, if platform is present', function () { - const request = spec.buildRequests(bidRequestsWithPlatform, mockBidderRequest); - expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); - expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); - expect(request[0].method).to.equal('GET'); - }); - - it('should send bid request to openx platform url via GET, if both params present', function () { - const bidRequestsWithPlatformAndDelDomain = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }]; - - const request = spec.buildRequests(bidRequestsWithPlatformAndDelDomain, mockBidderRequest); - expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); - expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); - expect(request[0].method).to.equal('GET'); - }); + describe('buildRequests()', function () { + let bidRequestsWithMediaTypes; + let bidRequestsWithPlatform; + let mockBidderRequest; - it('should send the adunit codes', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.divids).to.equal(`${encodeURIComponent(bidRequestsWithMediaTypes[0].adUnitCode)},${encodeURIComponent(bidRequestsWithMediaTypes[1].adUnitCode)}`); - }); - - it('should send the gpids', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.aucs).to.equal(`${encodeURIComponent('/12345/my-gpt-tag-0')},${encodeURIComponent('/12345/my-gpt-tag-1')}`); - }); + beforeEach(function () { + mockBidderRequest = {refererInfo: {}}; - it('should send ad unit ids when any are defined', function () { - const bidRequestsWithUnitIds = [{ - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' + bidRequestsWithMediaTypes = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', }, - 'adUnitCode': 'adunit-code', + adUnitCode: '/adunit-code/test-path', mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '22', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); - expect(request[0].data.auid).to.equal(`,${bidRequestsWithUnitIds[1].params.unit}`); - }); - - it('should not send any ad unit ids when none are defined', function () { - const bidRequestsWithoutUnitIds = [{ - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + ortb2Imp: { + ext: { + ae: 2 } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' + } }, { - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' + bidder: 'openx', + params: { + unit: '22', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', }, - 'adUnitCode': 'adunit-code', + adUnitCode: 'adunit-code', mediaTypes: { - banner: { - sizes: [[728, 90]] + video: { + playerSize: [640, 480] } }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + transactionId: 'test-transactionId-2' }]; - const request = spec.buildRequests(bidRequestsWithoutUnitIds, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('auid'); }); - it('should send out custom params on bids that have customParams specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customParams': {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} - } + context('common requests checks', function() { + it('should be able to handle multiformat requests', () => { + const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] } - ); + const requests = spec.buildRequests([multiformat], mockBidderRequest); + const outgoingFormats = requests.flatMap(rq => rq.data.imp.flatMap(imp => ['banner', 'video'].filter(k => imp[k] != null))); + const expected = FEATURES.VIDEO ? ['banner', 'video'] : ['banner'] + expect(outgoingFormats).to.have.members(expected); + }) - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; + it('should send bid request to openx url via POST', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].url).to.equal(REQUEST_URL); + expect(request[0].method).to.equal('POST'); + }); - expect(dataParams.tps).to.exist; - expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); - }); + it('should send delivery domain, if available', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.delDomain).to.equal(bidRequestsWithMediaTypes[0].params.delDomain); + expect(request[0].data.ext.platformId).to.be.undefined; + }); - it('should send out custom bc parameter, if override is present', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'bc': 'hb_override' - } - } - ); + it('should send platform id, if available', function () { + bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; + bidRequestsWithMediaTypes[1].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + }); - expect(dataParams.bc).to.exist; - expect(dataParams.bc).to.equal('hb_override'); - }); + it('should send openx adunit codes', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[0].params.unit); + expect(request[1].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit); + }); - it('should not send any consent management properties', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.gdpr).to.equal(undefined); - expect(request[0].data.gdpr_consent).to.equal(undefined); - expect(request[0].data.x_gdpr_f).to.equal(undefined); - }); + it('should send out custom params on bids that have customParams specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customParams: {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} + } + } + ); - describe('when there is a consent management framework', function () { - let bidRequests; - let mockConfig; - let bidderRequest; - const IAB_CONSENT_FRAMEWORK_CODE = 1; + mockBidderRequest.bids = [bidRequest]; + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].ext.customParams).to.equal(bidRequest.params.customParams); + }) - beforeEach(function () { - bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678-banner', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }, { - 'bidder': 'openx', - 'mediaTypes': { - video: { - playerSize: [640, 480] + describe('floors', function () { + it('should send out custom floors on bids that have customFloors, no currency as account currency is used', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customFloor: 1.500 + } } - }, - 'params': { - 'unit': '12345678-video', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', + ); - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - }); + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(bidRequest.params.customFloor); + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined); + }); - afterEach(function () { - config.getConfig.restore(); - }); + context('with floors module', function () { + let adServerCurrencyStub; - describe('when us_privacy applies', function () { - beforeEach(function () { - bidderRequest = { - uspConsent: '1YYN', - refererInfo: {} - }; + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + afterEach(function () { + config.getConfig.restore(); }); - }); - it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.us_privacy).to.equal('1YYN'); - expect(request[1].data.us_privacy).to.equal('1YYN'); - }); - }); + it('should send out floors on bids in USD', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'USD', + floor: 9.99 + } + } + } + ); - describe('when us_privacy does not applies', function () { - beforeEach(function () { - bidderRequest = { - refererInfo: {} - }; + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(9.99); + expect(request[0].data.imp[0].bidfloorcur).to.equal('USD'); + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('should send not send floors', function () { + adServerCurrencyStub.returns('EUR'); + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'BTC', + floor: 9.99 + } + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(undefined) + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined) }); - }); + }) + }) - it('should not send the consent string, when consent string is undefined', function () { - delete bidderRequest.uspConsent; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('us_privacy'); - expect(request[1].data).to.not.have.property('us_privacy'); - }); - }); + describe('FPD', function() { + let bidRequests; + const mockBidderRequest = {refererInfo: {}}; - describe('when GDPR applies', function () { beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: true + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' }, - refererInfo: {} - }; + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('ortb2.site should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + site: { + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'] + } + } }); + let data = request[0].data; + expect(data.site.domain).to.equal('page.example.com'); + expect(data.site.cat).to.deep.equal(['IAB2']); + expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); }); - it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr).to.equal(1); - expect(request[1].data.gdpr).to.equal(1); + it('ortb2.user should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + user: { + yob: 1985 + } + } + }); + let data = request[0].data; + expect(data.user.yob).to.equal(1985); }); - it('should send the consent string', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - }); + describe('ortb2Imp', function() { + describe('ortb2Imp.ext.data.pbadslot', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - it('should send the consent management framework code', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - }); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - describe('when GDPR does not apply', function () { - beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: false - }, - refererInfo: {} - }; - - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + it('should not send if imp[].ext.data.pbadslot is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('should send if imp[].ext.data.pbadslot is string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abcd' + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data).to.have.property('pbadslot'); + expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); + }); }); - }); - it('should not send a signal to specify that GDPR does not apply to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr).to.equal(0); - expect(request[1].data.gdpr).to.equal(0); - }); + describe('ortb2Imp.ext.data.adserver', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - it('should send the consent string', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - it('should send the consent management framework code', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - }); - }); + it('should not send if imp[].ext.data.adserver is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('adserver'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - describe('when GDPR consent has undefined data', function () { - beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: true - }, - refererInfo: {} - }; + it('should send', function() { + let adSlotValue = 'abc'; + bidRequests[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'GAM', + adslot: adSlotValue + } + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); + expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); + }); + }); - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + describe('ortb2Imp.ext.data.other', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); - }); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - it('should not send a signal to specify whether GDPR applies to this request, when GDPR application is undefined', function () { - delete bidderRequest.gdprConsent.gdprApplies; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('gdpr'); - expect(request[1].data).to.not.have.property('gdpr'); - }); + it('should not send if imp[].ext.data.other is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('other'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - it('should not send the consent string, when consent string is undefined', function () { - delete bidderRequest.gdprConsent.consentString; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('gdpr_consent'); - expect(request[1].data).to.not.have.property('gdpr_consent'); + it('ortb2Imp.ext.data.other', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + other: 1234 + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.other).to.equal(1234); + }); + }); }); - it('should not send the consent management framework code, when format is undefined', function () { - delete mockConfig.consentManagement.cmpApi; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('x_gdpr_f'); - expect(request[1].data).to.not.have.property('x_gdpr_f'); + describe('with user agent client hints', function () { + it('should add device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); + expect(request[0].data.device.sua).to.exist; + expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.device?.sua).to.not.exist; + }); }); }); - }); - - it('should not send a coppa query param when there are no coppa param settings in the bid requests', function () { - const bidRequestsWithoutCoppa = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - coppa: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithoutCoppa, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('tfcd'); - }); - - it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { - const bidRequestsWithCoppa = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - coppa: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain', - coppa: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithCoppa, mockBidderRequest); - expect(request[0].data.tfcd).to.equal(1); - }); - it('should not send a "no segmentation" flag there no DoNotTrack setting that is set to true', function () { - const bidRequestsWithoutDnt = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - doNotTrack: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithoutDnt, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('ns'); - }); + context('when there is a consent management framework', function () { + let bidRequests; + let mockConfig; + let bidderRequest; - it('should send a "no segmentation" flag there is any DoNotTrack setting that is set to true', function () { - const bidRequestsWithDnt = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - doNotTrack: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain', - doNotTrack: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithDnt, mockBidderRequest); - expect(request[0].data.ns).to.equal(1); - }); - - describe('when schain is provided', function () { - let bidRequests; - let schainConfig; - const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; - - beforeEach(function () { - schainConfig = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' - // omitted ext + beforeEach(function () { + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' }, - { - 'asi': 'exchange2.com', - 'sid': 'abcd', - 'hp': 1, - 'rid': 'bid-request-2', - // name field missing - 'domain': 'intermediary.com' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } }, - { - 'asi': 'exchange3.com', - 'sid': '4321', - 'hp': 1, - // request id - // name field missing - 'domain': 'intermediary-2.com' - } - ] - }; + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', - bidRequests = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1', - 'schain': schainConfig - }]; - }); + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); - it('should send a schain parameter with the proper delimiter symbols', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - const numNodes = schainConfig.nodes.length; + describe('us_privacy', function () { + beforeEach(function () { + bidderRequest = { + uspConsent: '1YYN', + refererInfo: {} + }; - // each node will have a ! to denote beginning of a new node - expect(dataParams.schain.match(/!/g).length).to.equal(numNodes); + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); - // 1 comma in the front for version - // 5 commas per node - expect(dataParams.schain.match(/,/g).length).to.equal(numNodes * 5 + 1); - }); + afterEach(function () { + config.getConfig.restore(); + }); - it('should send a schain with the right version', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - let version = serializedSupplyChain.shift().split(',')[0]; + it('should send a signal to specify that US Privacy applies to this request', function () { + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); + expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); + }); - expect(version).to.equal(bidRequests[0].schain.ver); - }); + it('should not send the regs object, when consent string is undefined', function () { + delete bidderRequest.uspConsent; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.us_privacy).to.not.exist; + }); + }); - it('should send a schain with the right complete value', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - let isComplete = serializedSupplyChain.shift().split(',')[1]; + describe('GDPR', function () { + beforeEach(function () { + bidderRequest = { + gdprConsent: { + consentString: 'test-gdpr-consent-string', + addtlConsent: 'test-addtl-consent-string', + gdprApplies: true + }, + refererInfo: {} + }; + + mockConfig = { + consentManagement: { + cmpApi: 'iab', + timeout: 1111, + allowAuctionWithoutConsent: 'cancel' + } + }; - expect(isComplete).to.equal(String(bidRequests[0].schain.complete)); - }); + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); - it('should send all available params in the right order', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - serializedSupplyChain.shift(); + afterEach(function () { + config.getConfig.restore(); + }); - serializedSupplyChain.forEach((serializedNode, nodeIndex) => { - let nodeProperties = serializedNode.split(','); + it('should send a signal to specify that GDPR applies to this request', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(1); + expect(request[1].data.regs.ext.gdpr).to.equal(1); + }); - nodeProperties.forEach((nodeProperty, propertyIndex) => { - let node = schainConfig.nodes[nodeIndex]; - let key = supplyChainNodePropertyOrder[propertyIndex]; + it('should send the consent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); - expect(nodeProperty).to.equal(node[key] ? String(node[key]) : '', - `expected node '${nodeIndex}' property '${nodeProperty}' to key '${key}' to be the same value`) + it('should send the addtlConsent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); + expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); }); - }); - }); - }); - describe('when there are userid providers', function () { - const EXAMPLE_DATA_BY_ATTR = { - britepoolid: '1111-britepoolid', - criteoId: '1111-criteoId', - fabrickId: '1111-fabrickid', - haloId: '1111-haloid', - id5id: {uid: '1111-id5id'}, - idl_env: '1111-idl_env', - IDP: '1111-zeotap-idplusid', - idxId: '1111-idxid', - intentIqId: '1111-intentiqid', - lipb: {lipbid: '1111-lipb'}, - lotamePanoramaId: '1111-lotameid', - merkleId: {id: '1111-merkleid'}, - netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, - pubcid: '1111-pubcid', - quantcastId: '1111-quantcastid', - tapadId: '111-tapadid', - tdid: '1111-tdid', - uid2: {id: '1111-uid2'}, - novatiq: {snowflake: '1111-novatiqid'}, - admixerId: '1111-admixerid', - deepintentId: '1111-deepintentid', - dmdId: '111-dmdid', - nextrollId: '1111-nextrollid', - mwOpenLinkId: '1111-mwopenlinkid', - dapId: '1111-dapId', - amxId: '1111-amxid', - kpuid: '1111-kpuid', - publinkId: '1111-publinkid', - naveggId: '1111-naveggid', - imuid: '1111-imuid', - adtelligentId: '1111-adtelligentid' - }; - - // generates the same set of tests for each id provider - utils._each(USER_ID_CODE_TO_QUERY_ARG, (userIdQueryArg, userIdProviderKey) => { - describe(`with userId attribute: ${userIdProviderKey}`, function () { - it(`should not send a ${userIdQueryArg} query param when there is no userId.${userIdProviderKey} defined in the bid requests`, function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys(userIdQueryArg); + it('should send a signal to specify that GDPR does not apply to this request', function () { + bidderRequest.gdprConsent.gdprApplies = false; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(0); + expect(request[1].data.regs.ext.gdpr).to.equal(0); }); - it(`should send a ${userIdQueryArg} query param when userId.${userIdProviderKey} is defined in the bid requests`, function () { - const bidRequestsWithUserId = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - userId: { - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }]; - // enrich bid request with userId key/value - bidRequestsWithUserId[0].userId[userIdProviderKey] = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; - - const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - - let userIdValue; - // handle cases where userId key refers to an object - switch (userIdProviderKey) { - case 'merkleId': - userIdValue = EXAMPLE_DATA_BY_ATTR.merkleId.id; - break; - case 'uid2': - userIdValue = EXAMPLE_DATA_BY_ATTR.uid2.id; - break; - case 'lipb': - userIdValue = EXAMPLE_DATA_BY_ATTR.lipb.lipbid; - break; - case 'parrableId': - userIdValue = EXAMPLE_DATA_BY_ATTR.parrableId.eid; - break; - case 'id5id': - userIdValue = EXAMPLE_DATA_BY_ATTR.id5id.uid; - break; - case 'novatiq': - userIdValue = EXAMPLE_DATA_BY_ATTR.novatiq.snowflake; - break; - default: - userIdValue = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; - } + it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + + 'but can send consent data, ', function () { + delete bidderRequest.gdprConsent.gdprApplies; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); - expect(request[0].data[USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]]).to.equal(userIdValue); + it('when consent string is undefined, should not send the consent string, ', function () { + delete bidderRequest.gdprConsent.consentString; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.imp[0].ext.consent).to.equal(undefined); + expect(request[1].data.imp[0].ext.consent).to.equal(undefined); }); }); }); - }); - - describe('floors', function () { - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); - context('with floors module', function () { - let adServerCurrencyStub; - - beforeEach(function () { - adServerCurrencyStub = sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') + context('coppa', function() { + it('when there are no coppa param settings, should not send a coppa flag', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs?.coppa).to.be.not.ok; }); - afterEach(function () { - config.getConfig.restore(); - }); - - it('should send out floors on bids', function () { - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return { - currency: 'AUS', - floor: 9.99 - } - } - } - ); - - const bidRequest2 = Object.assign({}, - bidRequestsWithMediaTypes[1], - { - getFloor: () => { - return { - currency: 'AUS', - floor: 18.881 - } - } - } - ); + it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { + let mockConfig = { + coppa: true + }; - const request = spec.buildRequests([bidRequest1, bidRequest2], mockBidderRequest); - const dataParams = request[0].data; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('9990,18881'); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send out floors on bids in the default currency', function () { - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return {}; - } - } - ); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); - expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + it('should send a coppa flag there is when there is coppa param settings in the bid params', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + request.params = {coppa: true}; + expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send out floors on bids in the ad server currency if defined', function () { - adServerCurrencyStub.returns('bitcoin'); - - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return {}; - } - } - ); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); - expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + after(function () { + config.getConfig.restore() }); - }) - }) - }); - - describe('buildRequests for video', function () { - const bidRequestsWithMediaTypes = VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES; - const mockBidderRequest = {refererInfo: {}}; - - it('should send bid request to openx url via GET, with mediaTypes having video parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); - expect(request[0].method).to.equal('GET'); - }); - it('should have the correct parameters', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.auid).to.equal('12345678'); - expect(dataParams.vht).to.equal(480); - expect(dataParams.vwd).to.equal(640); - expect(dataParams.aucs).to.equal(encodeURIComponent('/12345/my-gpt-tag-0')); - }); - - it('shouldn\'t have the test parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.vtest).to.be.undefined; - }); - - it('should send a bc parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.bc).to.have.string('hb_pb'); - }); - - describe('when using the video param', function () { - let videoBidRequest; - let mockBidderRequest = {refererInfo: {}}; - - beforeEach(function () { - videoBidRequest = { - 'bidder': 'openx', - 'mediaTypes': { - video: { - context: 'instream', - playerSize: [640, 480] - } - }, - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - mockBidderRequest = {refererInfo: {}}; - }); - - it('should not allow you to set a url', function () { - videoBidRequest.params.video = { - url: 'test-url' - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.url).to.be.undefined; - }); - - it('should not allow you to override the javascript url', function () { - let myUrl = 'my-url'; - videoBidRequest.params.video = { - ju: myUrl - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.ju).to.not.equal(myUrl); }); - describe('when using the openrtb video params', function () { - it('should parse legacy params.video.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + context('do not track (DNT)', function() { + let doNotTrackStub; - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + beforeEach(function () { + doNotTrackStub = sinon.stub(utils, 'getDNT'); }); - - it('should parse legacy params.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.openrtb = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + afterEach(function() { + doNotTrackStub.restore(); }); - it('should parse legacy params.video', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is a do not track, should send a dnt', function () { + doNotTrackStub.returns(1); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(1); }); - it('should parse legacy params.video as full openrtb', function () { - let myOpenRTBObject = {imp: [{video: {mimes: ['application/javascript']}}]}; - videoBidRequest.params.video = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is not do not track, don\'t send dnt', function () { + doNotTrackStub.returns(0); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); }); - it('should parse legacy video.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is no defined do not track, don\'t send dnt', function () { + doNotTrackStub.returns(null); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); }); + }); - it('should omit filtered values for legacy', function () { - let myOpenRTBObject = {mimes: ['application/javascript'], dont: 'use'}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject + context('supply chain (schain)', function () { + let bidRequests; + let schainConfig; + const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + + beforeEach(function () { + schainConfig = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com' + // omitted ext + }, + { + asi: 'exchange2.com', + sid: 'abcd', + hp: 1, + rid: 'bid-request-2', + // name field missing + domain: 'intermediary.com' + }, + { + asi: 'exchange3.com', + sid: '4321', + hp: 1, + // request id + // name field missing + domain: 'intermediary-2.com' + } + ] }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + bidRequests = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + schain: schainConfig + }]; }); - it('should parse mediatypes.video', function () { - videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] - videoBidRequest.mediaTypes.video.minduration = 15 - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); - expect(openRtbRequestParams.imp[0].video.minduration).to.equal(15); + it('should send a supply chain object', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain).to.equal(schainConfig); }); - it('should filter mediatypes.video', function () { - videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] - videoBidRequest.mediaTypes.video.minnothing = 15 - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); - expect(openRtbRequestParams.imp[0].video.minnothing).to.equal(undefined); + it('should send the supply chain object with the right version', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.ver).to.equal(schainConfig.ver); }); - it("should use the bidRequest's playerSize", function () { - const width = 200; - const height = 100; - const myOpenRTBObject = {v: height, w: width}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - - expect(openRtbRequestParams.imp[0].video.w).to.equal(640); - expect(openRtbRequestParams.imp[0].video.h).to.equal(480); + it('should send the supply chain object with the right complete value', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.complete).to.equal(schainConfig.complete); }); }); - }); - describe('floors', function () { - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], + context('when there are userid providers', function () { + const userIdAsEids = [ { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }, + { + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'sharedid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + third: 'some-random-id-value' + } + }] } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); + ]; - context('with floors module', function () { - let adServerCurrencyStub; - function makeBidWithFloorInfo(floorInfo) { - return Object.assign(utils.deepClone(bidRequestsWithMediaTypes[0]), - { - getFloor: () => { - return floorInfo; + it(`should send the user id under the extended ids`, function () { + const bidRequestsWithUserId = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + userId: { + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] } - }); - } - - beforeEach(function () { - adServerCurrencyStub = sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + userIdAsEids: userIdAsEids + }]; + // enrich bid request with userId key/value + + const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); + expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); }); - afterEach(function () { - config.getConfig.restore(); + it(`when no user ids are available, it should not send any extended ids`, function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data).to.not.have.any.keys('user'); }); + }); - it('should send out floors on bids', function () { - const floors = [9.99, 18.881]; - const bidRequests = floors.map(floor => { - return makeBidWithFloorInfo({ - currency: 'AUS', - floor: floor - }); + context('FLEDGE', function() { + it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, { + ...mockBidderRequest, + fledgeEnabled: true }); - const request = spec.buildRequests(bidRequests, mockBidderRequest); - - expect(request[0].data.aumfs).to.exist; - expect(request[0].data.aumfs).to.equal('9990'); - expect(request[1].data.aumfs).to.exist; - expect(request[1].data.aumfs).to.equal('18881'); + expect(request[0].data.imp[0].ext.ae).to.equal(2); }); + }); + }); - it('should send out floors on bids in the default currency', function () { - const bidRequest1 = makeBidWithFloorInfo({}); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + context('banner', function () { + it('should send bid request with a mediaTypes specified with banner type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0]).to.have.any.keys(BANNER); + }); + }); - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); - expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + if (FEATURES.VIDEO) { + context('video', function () { + it('should send bid request with a mediaTypes specified with video type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); }); - it('should send out floors on bids in the ad server currency if defined', function () { - adServerCurrencyStub.returns('bitcoin'); - - const bidRequest1 = makeBidWithFloorInfo({}); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); - expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + it('Update imp.video with OpenRTB options from mimeTypes and params', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + protocols: [8] + } + }, + }).withParams({ + // options in video, will merge + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30 + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: 4, + protocols: [8], + w: 300, + h: 250 + }; + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests).to.have.lengthOf(2); + expect(requests[1].data.imp[0].video).to.deep.equal(expected); }); - }) - }) + }); + } }); - describe('buildRequest for multi-format ad', function () { - const multiformatBid = MULTI_FORMAT_BID_REQUESTS[0]; - let mockBidderRequest = {refererInfo: {}}; + describe('interpretResponse()', function () { + let bidRequestConfigs; + let bidRequest; + let bidResponse; + let bid; - it('should default to a banner request', function () { - const request = spec.buildRequests([multiformatBid], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.divids).to.have.string(multiformatBid.adUnitCode); - }); - }); - - describe('buildRequests for all kinds of ads', function () { - utils._each({ - banner: BANNER_BID_REQUESTS_WITH_MEDIA_TYPES[0], - video: VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES[0], - multi: MULTI_FORMAT_BID_REQUESTS[0] - }, (bidRequest, name) => { - describe('with segments', function () { - const TESTS = [ - { - name: 'should send proprietary segment data from ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - } - } - }, - expect: {sm: 'dmp1/4:foo|bar,dmp2:baz'}, - }, - { - name: 'should send proprietary segment data from ortb2.site.content.data', - config: { - ortb2: { - site: { - content: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - } - } - } - }, - expect: {scsm: 'dmp1/4:foo|bar,dmp2:baz'}, - }, - { - name: 'should send proprietary segment data from both ortb2.site.content.data and ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - }, - site: { - content: { - data: [ - {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, - {name: 'dmp4', segment: [{id: 'baz2'}]}, - ] - } - } - } - }, - expect: { - sm: 'dmp1/4:foo|bar,dmp2:baz', - scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' - }, - }, - { - name: 'should combine same provider segment data from ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, - ] - } - } - }, - expect: {sm: 'dmp1/4:foo|bar,dmp1:baz'}, - }, - { - name: 'should combine same provider segment data from ortb2.site.content.data', - config: { - ortb2: { - site: { - content: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, - ] - } - } - } - }, - expect: {scsm: 'dmp1/4:foo|bar,dmp1:baz'}, - }, - { - name: 'should not send any segment data if first party config is incomplete', - config: { - ortb2: { - user: { - data: [ - {name: 'provider-with-no-segments'}, - {segment: [{id: 'segments-with-no-provider'}]}, - {}, - ] - } - } - } + context('when there is an nbr response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - { - name: 'should send first party data segments and liveintent segments from request', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - }, - site: { - content: { - data: [ - {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, - {name: 'dmp4', segment: [{id: 'baz2'}]}, - ] - } - } - } - }, - request: { - userId: { - lipb: { - lipbid: 'aaa', - segments: ['l1', 'l2'] - }, - }, - }, - expect: { - sm: 'dmp1:foo|bar,dmp2:baz,liveintent:l1|l2', - scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, }, - { - name: 'should send just liveintent segment from request if no first party config', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - segments: ['l1', 'l2'] - }, - }, - }, - expect: {sm: 'liveintent:l1|l2'}, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {nbr: 0}; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); + }); + + context('when no seatbid in response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - { - name: 'should send nothing if lipb section does not contain segments', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - }, - }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, }, - ]; - utils._each(TESTS, (t) => { - context('in ortb2.user.data', function () { - let bidRequests; - beforeEach(function () { - bidRequests = [{...bidRequest, ...t.request}]; - }); + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - const mockBidderRequest = {refererInfo: {}, ortb2: t.config.ortb2}; - it(`${t.name} for type ${name}`, function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest) - expect(request.length).to.equal(1); - if (t.expect) { - for (const key in t.expect) { - expect(request[0].data[key]).to.exist; - expect(request[0].data[key]).to.equal(t.expect[key]); - } - } else { - expect(request[0].data.sm).to.not.exist; - expect(request[0].data.scsm).to.not.exist; - } - }); - }); - }); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {ext: {}, id: 'test-bid-id'}; + bids = spec.interpretResponse({body: bidResponse}, bidRequest); }); - describe('with user agent client hints', function () { - it('should add json query param sua with BidRequest.device.sua if available', function () { - const bidderRequestWithUserAgentClientHints = { refererInfo: {}, - ortb2: { - device: { - sua: { - source: 2, - platform: { - brand: 'macOS', - version: [ '12', '4', '0' ] - }, - browsers: [ - { - brand: 'Chromium', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Google Chrome', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Not;A=Brand', - version: [ '99', '0', '0', '0' ] - }], - mobile: 0, - model: 'Pro', - bitness: '64', - architecture: 'x86' - } - } - }}; - - let request = spec.buildRequests([bidRequest], bidderRequestWithUserAgentClientHints); - expect(request[0].data.sua).to.exist; - const payload = JSON.parse(request[0].data.sua); - expect(payload).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); - const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; - request = spec.buildRequests([bidRequest], bidderRequestWithoutUserAgentClientHints); - expect(request[0].data.sua).to.not.exist; - }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); }); }); - }) - describe('interpretResponse for banner ads', function () { - beforeEach(function () { - sinon.spy(userSync, 'registerSync'); - }); + context('when there is no response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - afterEach(function () { - userSync.registerSync.restore(); + bidResponse = ''; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); }); - describe('when there is a standard response', function () { - const creativeOverride = { - id: 234, - width: '300', - height: '250', - tracking: { - impression: 'https://openx-d.openx.net/v/1.0/ri?ts=ts' + const SAMPLE_BID_REQUESTS = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + const SAMPLE_BID_RESPONSE = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + adomain: ['brand.com'], + ext: { + dsp_id: '123', + buyer_id: '456', + brand_id: '789', + paf: { + content_id: 'paf_content_id' + } + } + }] + }], + cur: 'AUS', + ext: { + paf: { + transmission: {version: '12'} } - }; - - const adUnitOverride = { - ts: 'test-1234567890-ts', - idx: '0', - currency: 'USD', - pub_rev: '10000', - html: '
OpenX Ad
' - }; - let adUnit; - let bidResponse; - - let bid; - let bidRequest; - let bidRequestConfigs; + } + }; + context('when there is a response, the common response properties', function () { beforeEach(function () { - bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); - adUnit = mockAdUnit(adUnitOverride, creativeOverride); - bidResponse = mockArjResponse(undefined, [adUnit]); bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; }); it('should return a price', function () { - expect(bid.cpm).to.equal(parseInt(adUnitOverride.pub_rev, 10) / 1000); + expect(bid.cpm).to.equal(bidResponse.seatbid[0].bid[0].price); }); it('should return a request id', function () { - expect(bid.requestId).to.equal(bidRequest.payload.bids[0].bidId); + expect(bid.requestId).to.equal(bidResponse.seatbid[0].bid[0].impid); }); it('should return width and height for the creative', function () { - expect(bid.width).to.equal(creativeOverride.width); - expect(bid.height).to.equal(creativeOverride.height); + expect(bid.width).to.equal(bidResponse.seatbid[0].bid[0].w); + expect(bid.height).to.equal(bidResponse.seatbid[0].bid[0].h); }); it('should return a creativeId', function () { - expect(bid.creativeId).to.equal(creativeOverride.id); + expect(bid.creativeId).to.equal(bidResponse.seatbid[0].bid[0].crid); }); it('should return an ad', function () { - expect(bid.ad).to.equal(adUnitOverride.html); + expect(bid.ad).to.equal(bidResponse.seatbid[0].bid[0].adm); + }); + + it('should return a deal id if it exists', function () { + expect(bid.dealId).to.equal(bidResponse.seatbid[0].bid[0].dealid); }); it('should have a time-to-live of 5 minutes', function () { @@ -1863,415 +1286,293 @@ describe('OpenxAdapter', function () { }); it('should return a currency', function () { - expect(bid.currency).to.equal(adUnitOverride.currency); - }); - - it('should return a transaction state', function () { - expect(bid.ts).to.equal(adUnitOverride.ts); + expect(bid.currency).to.equal(bidResponse.cur); }); it('should return a brand ID', function () { - expect(bid.meta.brandId).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.brand_id); + expect(bid.meta.brandId).to.equal(bidResponse.seatbid[0].bid[0].ext.brand_id); }); - it('should return an adomain', function () { - expect(bid.meta.advertiserDomains).to.deep.equal([]); + it('should return a dsp ID', function () { + expect(bid.meta.networkId).to.equal(bidResponse.seatbid[0].bid[0].ext.dsp_id); }); - it('should return a dsp ID', function () { - expect(bid.meta.dspid).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.adv_id); + it('should return a buyer ID', function () { + expect(bid.meta.advertiserId).to.equal(bidResponse.seatbid[0].bid[0].ext.buyer_id); }); - }); - describe('when there is a deal', function () { - const adUnitOverride = { - deal_id: 'ox-1000' - }; - let adUnit; - let bidResponse; + it('should return adomain', function () { + expect(bid.meta.advertiserDomains).to.equal(bidResponse.seatbid[0].bid[0].adomain); + }); - let bid; - let bidRequestConfigs; - let bidRequest; + it('should return paf fields', function () { + const paf = { + transmission: {version: '12'}, + content_id: 'paf_content_id' + } + expect(bid.meta.paf).to.deep.equal(paf); + }); + }); + context('when there is more than one response', () => { + let bids; beforeEach(function () { - bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); + bidResponse.seatbid[0].bid.push(deepClone(bidResponse.seatbid[0].bid[0])); + bidResponse.seatbid[0].bid[1].ext.paf.content_id = 'second_paf' - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; - adUnit = mockAdUnit(adUnitOverride); - bidResponse = mockArjResponse(null, [adUnit]); - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - mockArjResponse(); + bids = spec.interpretResponse({body: bidResponse}, bidRequest); }); - it('should return a deal id', function () { - expect(bid.dealId).to.equal(adUnitOverride.deal_id); + it('should not confuse paf content_id', () => { + expect(bids.map(b => b.meta.paf.content_id)).to.eql(['paf_content_id', 'second_paf']); }); - }); - - describe('when there is no bids in the response', function () { - let bidRequest; - let bidRequestConfigs; + }) + context('when the response is a banner', function() { beforeEach(function () { bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' }]; - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; - }); - - it('handles nobid responses', function () { - const bidResponse = { - 'ads': - { - 'version': 1, - 'count': 1, - 'pixels': 'https://testpixels.net', - 'ad': [] - } + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS' }; - const result = spec.interpretResponse({body: bidResponse}, bidRequest); - expect(result.length).to.equal(0); + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; }); - }); - describe('when adunits return out of order', function () { - const bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[100, 111]] - } - }, - bidId: 'test-bid-request-id-1', - bidderRequestId: 'test-request-1', - auctionId: 'test-auction-id-1' - }, { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[200, 222]] - } - }, - bidId: 'test-bid-request-id-2', - bidderRequestId: 'test-request-1', - auctionId: 'test-auction-id-1' - }, { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 333]] - } - }, - 'bidId': 'test-bid-request-id-3', - 'bidderRequestId': 'test-request-1', - 'auctionId': 'test-auction-id-1' - }]; - const bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequests, 'startTime': new Date()} - }; - - let outOfOrderAdunits = [ - mockAdUnit({ - idx: '1' - }, { - width: bidRequests[1].mediaTypes.banner.sizes[0][0], - height: bidRequests[1].mediaTypes.banner.sizes[0][1] - }), - mockAdUnit({ - idx: '2' - }, { - width: bidRequests[2].mediaTypes.banner.sizes[0][0], - height: bidRequests[2].mediaTypes.banner.sizes[0][1] - }), - mockAdUnit({ - idx: '0' - }, { - width: bidRequests[0].mediaTypes.banner.sizes[0][0], - height: bidRequests[0].mediaTypes.banner.sizes[0][1] - }) - ]; - - let bidResponse = mockArjResponse(undefined, outOfOrderAdunits); - - it('should return map adunits back to the proper request', function () { - const bids = spec.interpretResponse({body: bidResponse}, bidRequest); - expect(bids[0].requestId).to.equal(bidRequests[1].bidId); - expect(bids[0].width).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][0]); - expect(bids[0].height).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][1]); - expect(bids[1].requestId).to.equal(bidRequests[2].bidId); - expect(bids[1].width).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][0]); - expect(bids[1].height).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][1]); - expect(bids[2].requestId).to.equal(bidRequests[0].bidId); - expect(bids[2].width).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][0]); - expect(bids[2].height).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][1]); + it('should return the proper mediaType', function () { + it('should return a creativeId', function () { + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); }); }); - }); - - describe('interpretResponse for video ads', function () { - beforeEach(function () { - sinon.spy(userSync, 'registerSync'); - }); - afterEach(function () { - userSync.registerSync.restore(); - }); - - const bidsWithMediaTypes = [{ - 'bidder': 'openx', - 'mediaTypes': {video: {}}, - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - const bidsWithMediaType = [{ - 'bidder': 'openx', - 'mediaType': 'video', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - const bidRequestsWithMediaTypes = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/avjp', - data: {}, - payload: {'bid': bidsWithMediaTypes[0], 'startTime': new Date()} - }; - const bidRequestsWithMediaType = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/avjp', - data: {}, - payload: {'bid': bidsWithMediaType[0], 'startTime': new Date()} - }; - const bidResponse = { - 'pub_rev': '1000', - 'width': '640', - 'height': '480', - 'adid': '5678', - 'currency': 'AUD', - 'vastUrl': 'https://testvast.com', - 'pixels': 'https://testpixels.net' - }; + if (FEATURES.VIDEO) { + context('when the response is a video', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [[640, 360], [854, 480]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 854, + h: 480, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + }] + }], + cur: 'AUS' + }; + }); - it('should return correct bid response with MediaTypes', function () { - const expectedResponse = { - 'requestId': '30b31c1838de1e', - 'cpm': 1, - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'creativeId': '5678', - 'vastUrl': 'https://testvast.com', - 'ttl': 300, - 'netRevenue': true, - 'currency': 'AUD' - }; - - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - expect(result[0]).to.eql(expectedResponse); - }); + it('should return the proper mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); - it('should return correct bid response with MediaType', function () { - const expectedResponse = [ - { - 'requestId': '30b31c1838de1e', - 'cpm': 1, - 'width': '640', - 'height': '480', - 'mediaType': 'video', - 'creativeId': '5678', - 'vastUrl': 'https://testvast.com', - 'ttl': 300, - 'netRevenue': true, - 'currency': 'USD' - } - ]; + it('should return the proper mediaType', function () { + const winUrl = 'https//my.win.url'; + bidResponse.seatbid[0].bid[0].nurl = winUrl + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); - expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); - }); + expect(bid.vastUrl).to.equal(winUrl); + }); + }); + } - it('should return correct bid response with MediaType and deal_id', function () { - const bidResponseOverride = { 'deal_id': 'OX-mydeal' }; - const bidResponseWithDealId = Object.assign({}, bidResponse, bidResponseOverride); - const result = spec.interpretResponse({body: bidResponseWithDealId}, bidRequestsWithMediaType); - expect(result[0].dealId).to.equal(bidResponseOverride.deal_id); - }); + context('when the response contains FLEDGE interest groups config', function() { + let response; - it('should handle nobid responses for bidRequests with MediaTypes', function () { - const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - expect(result.length).to.equal(0); - }); + beforeEach(function () { + sinon.stub(config, 'getConfig') + .withArgs('fledgeEnabled') + .returns(true); - it('should handle nobid responses for bidRequests with MediaType', function () { - const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); - expect(result.length).to.equal(0); - }); - }); + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - describe('user sync', function () { - const syncUrl = 'https://testpixels.net'; + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS', + ext: { + fledge_auction_configs: { + 'test-bid-id': { + seller: 'codinginadtech.com', + interestGroupBuyers: ['somedomain.com'], + sellerTimeout: 0, + perBuyerSignals: { + 'somedomain.com': { + base_bid_micros: 0.1, + disallowed_advertiser_ids: [ + '1234', + '2345' + ], + multiplier: 1.3, + use_bid_multiplier: true, + win_reporting_id: '1234567asdf' + } + } + } + } + } + }; - describe('iframe sync', function () { - it('should register the pixel iframe from banner ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); - it('should register the pixel iframe from video ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {pixels: syncUrl}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + afterEach(function () { + config.getConfig.restore(); }); - it('should register the default iframe if no pixels available', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: 'https://u.openx.net/w/1.0/pd'}]); + it('should return FLEDGE auction_configs alongside bids', function () { + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs.length).to.equal(1); + expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); }); }); + }); - describe('pixel sync', function () { - it('should register the image pixel from banner ad response', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); - }); + describe('user sync', function () { + it('should register the default image pixel if no pixels available', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'image', url: DEFAULT_SYNC}]); + }); - it('should register the image pixel from video ad response', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {pixels: syncUrl}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); - }); + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {delDomain: 'www.url.com'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: 'https://www.url.com/w/1.0/pd'}]); + }); - it('should register the default image pixel if no pixels available', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'image', url: 'https://u.openx.net/w/1.0/pd'}]); - }); + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {platform: 'abc'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: SYNC_URL + '?ph=abc'}]); + }); + + it('when iframe sync is allowed, it should register an iframe sync', function () { + let syncs = spec.getUserSyncs( + {iframeEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); }); it('should prioritize iframe over image for user sync', function () { let syncs = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] + [] ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); }); describe('when gdpr applies', function () { let gdprConsent; let gdprPixelUrl; + const consentString = 'gdpr-pixel-consent'; + const gdprApplies = '1'; beforeEach(() => { gdprConsent = { - consentString: 'test-gdpr-consent-string', + consentString, gdprApplies: true }; - gdprPixelUrl = 'https://testpixels.net?gdpr=1&gdpr_consent=gdpr-pixel-consent' + gdprPixelUrl = `${SYNC_URL}&gdpr=${gdprApplies}&gdpr_consent=${consentString}`; }); it('when there is a response, it should have the gdpr query params', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: gdprPixelUrl}}}], - gdprConsent - ); - - expect(url).to.have.string('gdpr_consent=gdpr-pixel-consent'); - expect(url).to.have.string('gdpr=1'); - }); - - it('when there is no response, it should append gdpr query params', () => { let [{url}] = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, [], gdprConsent ); - expect(url).to.have.string('gdpr_consent=test-gdpr-consent-string'); - expect(url).to.have.string('gdpr=1'); + + expect(url).to.have.string(`gdpr_consent=${consentString}`); + expect(url).to.have.string(`gdpr=${gdprApplies}`); }); it('should not send signals if no consent object is available', function () { @@ -2287,28 +1588,19 @@ describe('OpenxAdapter', function () { describe('when ccpa applies', function () { let usPrivacyConsent; let uspPixelUrl; + const privacyString = 'TEST'; beforeEach(() => { usPrivacyConsent = 'TEST'; - uspPixelUrl = 'https://testpixels.net?us_privacy=AAAA' + uspPixelUrl = `${DEFAULT_SYNC}&us_privacy=${privacyString}` }); - it('when there is a response, it should send the us privacy string from the response, ', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: uspPixelUrl}}}], - undefined, - usPrivacyConsent - ); - - expect(url).to.have.string('us_privacy=AAAA'); - }); - it('when there is no response, it send have the us privacy string', () => { + it('should send the us privacy string, ', () => { let [{url}] = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, [], undefined, usPrivacyConsent ); - expect(url).to.have.string(`us_privacy=${usPrivacyConsent}`); + expect(url).to.have.string(`us_privacy=${privacyString}`); }); it('should not send signals if no consent string is available', function () { @@ -2320,75 +1612,4 @@ describe('OpenxAdapter', function () { }); }); }); - - /** - * Makes sure the override object does not introduce - * new fields against the contract - * - * This does a shallow check in order to make key checking simple - * with respect to what a helper handles. For helpers that have - * nested fields, either check your design on maybe breaking it up - * to smaller, manageable pieces - * - * OR just call this on your nth level field if necessary. - * - * @param {Object} override Object with keys that overrides the default - * @param {Object} contract Original object contains the default fields - * @param {string} typeName Name of the type we're checking for error messages - * @throws {AssertionError} - */ - function overrideKeyCheck(override, contract, typeName) { - expect(contract).to.include.all.keys(Object.keys(override)); - } - - /** - * Creates a mock ArjResponse - * @param {OxArjResponse=} response - * @param {Array=} adUnits - * @throws {AssertionError} - * @return {OxArjResponse} - */ - function mockArjResponse(response, adUnits = []) { - let mockedArjResponse = utils.deepClone(DEFAULT_ARJ_RESPONSE); - - if (response) { - overrideKeyCheck(response, DEFAULT_ARJ_RESPONSE, 'OxArjResponse'); - overrideKeyCheck(response.ads, DEFAULT_ARJ_RESPONSE.ads, 'OxArjResponse'); - Object.assign(mockedArjResponse, response); - } - - if (adUnits.length) { - mockedArjResponse.ads.count = adUnits.length; - mockedArjResponse.ads.ad = adUnits.map((adUnit) => { - overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); - return Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); - }); - } - - return mockedArjResponse; - } - - /** - * Creates a mock ArjAdUnit - * @param {OxArjAdUnit=} adUnit - * @param {OxArjCreative=} creative - * @throws {AssertionError} - * @return {OxArjAdUnit} - */ - function mockAdUnit(adUnit, creative) { - overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); - - let mockedAdUnit = Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); - - if (creative) { - overrideKeyCheck(creative, DEFAULT_TEST_ARJ_CREATIVE); - if (creative.tracking) { - overrideKeyCheck(creative.tracking, DEFAULT_TEST_ARJ_CREATIVE.tracking, 'OxArjCreative'); - } - Object.assign(mockedAdUnit.creative[0], creative); - } - - return mockedAdUnit; - } -}) -; +}); diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index c1eeba62a29..d8ea79ac698 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -19,6 +19,57 @@ import {hook} from '../../../src/hook.js'; const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; +const BidRequestBuilder = function BidRequestBuilder(options) { + const defaults = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'openx' + }, + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + sizes: [[300, 250], [300, 600]], + }; + + const request = { + ...defaults.request, + ...options + }; + + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; + }; + + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'openx', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' + } + }; + + const request = { + ...defaults, + ...options + }; + + this.build = () => request; +}; + describe('OpenxRtbAdapter', function () { before(() => { hook.ready(); @@ -249,6 +300,18 @@ describe('OpenxRtbAdapter', function () { }); context('common requests checks', function() { + it('should be able to handle multiformat requests', () => { + const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] + } + const requests = spec.buildRequests([multiformat], mockBidderRequest); + const outgoingFormats = requests.flatMap(rq => rq.data.imp.flatMap(imp => ['banner', 'video'].filter(k => imp[k] != null))); + const expected = FEATURES.VIDEO ? ['banner', 'video'] : ['banner'] + expect(outgoingFormats).to.have.members(expected); + }) + it('should send bid request to openx url via POST', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].url).to.equal(REQUEST_URL); @@ -789,6 +852,12 @@ describe('OpenxRtbAdapter', function () { expect(request[0].data.regs.coppa).to.equal(1); }); + it('should send a coppa flag there is when there is coppa param settings in the bid params', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + request.params = {coppa: true}; + expect(request[0].data.regs.coppa).to.equal(1); + }); + after(function () { config.getConfig.restore() }); @@ -963,17 +1032,6 @@ describe('OpenxRtbAdapter', function () { }); context('FLEDGE', function() { - it('when FLEDGE is disabled, should not send imp.ext.ae', function () { - const request = spec.buildRequests( - bidRequestsWithMediaTypes, - { - ...mockBidderRequest, - fledgeEnabled: false - } - ); - expect(request[0].data.imp[0].ext).to.not.have.property('ae'); - }); - it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, { ...mockBidderRequest, @@ -997,47 +1055,47 @@ describe('OpenxRtbAdapter', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); }); + + it('Update imp.video with OpenRTB options from mimeTypes and params', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + protocols: [8] + } + }, + }).withParams({ + // options in video, will merge + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30 + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: 4, + protocols: [8], + w: 300, + h: 250 + }; + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests).to.have.lengthOf(2); + expect(requests[1].data.imp[0].video).to.deep.equal(expected); + }); }); } - - it.skip('should send ad unit ids when any are defined', function () { - const bidRequestsWithUnitIds = [{ - bidder: 'openx', - params: { - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - transactionId: 'test-transaction-id-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2', - transactionId: 'test-transaction-id-2' - }]; - mockBidderRequest.bids = bidRequestsWithUnitIds; - const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); - expect(request[0].data.imp[1].tagid).to.equal(bidRequestsWithUnitIds[1].params.unit); - expect(request[0].data.imp[1].ext.divid).to.equal(bidRequestsWithUnitIds[1].params.adUnitCode); - }); }); describe('interpretResponse()', function () { diff --git a/test/spec/modules/pairIdSystem_spec.js b/test/spec/modules/pairIdSystem_spec.js new file mode 100644 index 00000000000..4f75666affe --- /dev/null +++ b/test/spec/modules/pairIdSystem_spec.js @@ -0,0 +1,68 @@ +import { storage, pairIdSubmodule } from 'modules/pairIdSystem.js'; +import * as utils from 'src/utils.js'; + +describe('pairId', function () { + let sandbox; + let logErrorStub; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + logErrorStub = sandbox.stub(utils, 'logError'); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('should log an error if no ID is found when getId', function() { + pairIdSubmodule.getId({ params: {} }); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should read pairId from local storage if exists', function() { + let pairIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('pairId').returns(btoa(JSON.stringify(pairIds))); + + let id = pairIdSubmodule.getId({ params: {} }); + expect(id).to.be.deep.equal({id: pairIds}); + }); + + it('should read pairId from cookie if exists', function() { + let pairIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6']; + sandbox.stub(storage, 'getCookie').withArgs('pairId').returns(btoa(JSON.stringify(pairIds))); + + let id = pairIdSubmodule.getId({ params: {} }); + expect(id).to.be.deep.equal({id: pairIds}); + }); + + it('should read pairId from default liveramp envelope local storage key if configured', function() { + let pairIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': pairIds}))); + let id = pairIdSubmodule.getId({ + params: { + liveramp: {} + }}) + expect(id).to.be.deep.equal({id: pairIds}) + }) + + it('should read pairId from default liveramp envelope cookie entry if configured', function() { + let pairIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': pairIds}))); + let id = pairIdSubmodule.getId({ + params: { + liveramp: {} + }}) + expect(id).to.be.deep.equal({id: pairIds}) + }) + + it('should read pairId from specified liveramp envelope cookie entry if configured with storageKey', function() { + let pairIds = ['test-pair-id7', 'test-pair-id8', 'test-pair-id9']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('lr_pairId_custom').returns(btoa(JSON.stringify({'envelope': pairIds}))); + let id = pairIdSubmodule.getId({ + params: { + liveramp: { + storageKey: 'lr_pairId_custom' + } + }}) + expect(id).to.be.deep.equal({id: pairIds}) + }) +}); diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js index a46ff26c4b8..7c539014cc5 100644 --- a/test/spec/modules/pubCommonId_spec.js +++ b/test/spec/modules/pubCommonId_spec.js @@ -233,23 +233,6 @@ describe('Publisher Common ID', function () { }); }); }); - - it.skip('disable auto create', function() { - setConfig({ - create: false - }); - - const config = getPubcidConfig(); - expect(config.create).to.be.false; - expect(config.typeEnabled).to.equal('html5'); - - let adUnits = getAdUnits(); - let innerAdUnits; - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - const pubcid = localStorage.getItem(ID_NAME); - expect(pubcid).to.be.null; - }); }); describe('Invoking requestBid', function () { diff --git a/test/spec/modules/publinkIdSystem_spec.js b/test/spec/modules/publinkIdSystem_spec.js index 4656afe1585..7d98b724bd8 100644 --- a/test/spec/modules/publinkIdSystem_spec.js +++ b/test/spec/modules/publinkIdSystem_spec.js @@ -1,11 +1,12 @@ import {publinkIdSubmodule} from 'modules/publinkIdSystem.js'; -import {getStorageManager} from '../../../src/storageManager'; +import {getCoreStorageManager, getStorageManager} from '../../../src/storageManager'; import {server} from 'test/mocks/xhr.js'; import sinon from 'sinon'; import {uspDataHandler} from '../../../src/adapterManager'; import {parseUrl} from '../../../src/utils'; -export const storage = getStorageManager({gvlid: 24}); +const storage = getCoreStorageManager(); + const TEST_COOKIE_VALUE = 'cookievalue'; describe('PublinkIdSystem', () => { describe('decode', () => { diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index ed74792e1cd..3198fe406e7 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -194,6 +194,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 1, title: {len: 140} }, + { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 2, required: 1, data: { type: 1 } } + ] + }, bidder: 'pubmatic', params: { publisherId: '5670', @@ -243,6 +251,22 @@ describe('PubMatic adapter', function () { desc2: {required: true, len: 10, ext: {'desc21': 'desc22'}}, displayurl: {required: true, len: 10, ext: {'displayurl1': 'displayurl2'}} }, + nativeOrtbRequest: { + 'ver': '1.2', + 'assets': [ + {'id': 0, 'required': 1, 'title': {'len': 80}}, + {'id': 1, 'required': 1, 'img': {'type': 1, 'w': 50, 'h': 50}}, + {'id': 2, 'required': 1, 'img': {'type': 3, 'w': 728, 'h': 90}}, + {'id': 3, 'required': 1, 'data': {'type': 1, 'len': 10}}, + {'id': 4, 'required': 1, 'data': {'type': 2, 'len': 10}}, + {'id': 5, 'required': 1, 'data': {'type': 3, 'len': 10}}, + {'id': 6, 'required': 1, 'data': {'type': 4, 'len': 10}}, + {'id': 7, 'required': 1, 'data': {'type': 5, 'len': 10}}, + {'id': 8, 'required': 1, 'data': {'type': 6, 'len': 10}}, + {'id': 9, 'required': 1, 'data': {'type': 8, 'len': 10}}, + {'id': 10, 'required': 1, 'data': {'type': 9, 'len': 10}} + ] + }, bidder: 'pubmatic', params: { publisherId: '5670', @@ -301,6 +325,14 @@ describe('PubMatic adapter', function () { image: { required: false, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 0, title: {len: 140} }, + { id: 1, required: 0, img: {type: 3, w: 300, h: 250} }, + { id: 2, required: 1, data: {type: 1} } + ] + }, bidder: 'pubmatic', params: { publisherId: '5670', @@ -389,6 +421,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + {id: 0, required: 1, title: {len: 140}}, + {id: 1, required: 1, img: {type: 3, w: 300, h: 250}}, + {id: 2, required: 1, data: {type: 1}} + ] + }, bidder: 'pubmatic', params: { publisherId: '301', @@ -442,6 +482,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 1, title: {len: 140} }, + { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 2, required: 1, data: { type: 1 } } + ] + }, bidder: 'pubmatic', params: { publisherId: '301', @@ -503,6 +551,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 1, title: {len: 80} }, + { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 2, required: 1, data: { type: 1 } } + ] + }, bidder: 'pubmatic', params: { publisherId: '301', @@ -603,7 +659,7 @@ describe('PubMatic adapter', function () { validnativeBidImpression = { 'native': { - 'request': '{"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":300,"h":250}},{"id":4,"required":1,"data":{"type":1}}]}' + 'request': '{"ver":"1.2","assets":[{"id":0,"required":1,"title":{"len":80}},{"id":1,"required":1,"img":{"type":3,"w":300,"h":250}},{"id":2,"required":1,"data":{"type":1}}]}' } } @@ -615,13 +671,13 @@ describe('PubMatic adapter', function () { validnativeBidImpressionWithRequiredParam = { 'native': { - 'request': '{"assets":[{"id":1,"required":0,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":300,"h":250}},{"id":4,"required":1,"data":{"type":1}}]}' + 'request': '{"ver":"1.2","assets":[{"id":0,"required":0,"title":{"len":80}},{"id":1,"required":0,"img":{"type":3,"w":300,"h":250}},{"id":2,"required":1,"data":{"type":1}}]}' } } validnativeBidImpressionWithAllParams = { native: { - 'request': '{"assets":[{"id":1,"required":1,"title":{"len":80,"ext":{"title1":"title2"}}},{"id":3,"required":1,"img":{"type":1,"w":50,"h":50}},{"id":2,"required":1,"img":{"type":3,"w":728,"h":90,"mimes":["image/png","image/gif"],"ext":{"image1":"image2"}}},{"id":4,"required":1,"data":{"type":1,"len":10,"ext":{"sponsor1":"sponsor2"}}},{"id":5,"required":1,"data":{"type":2,"len":10,"ext":{"body1":"body2"}}},{"id":13,"required":1,"data":{"type":3,"len":10,"ext":{"rating1":"rating2"}}},{"id":14,"required":1,"data":{"type":4,"len":10,"ext":{"likes1":"likes2"}}},{"id":15,"required":1,"data":{"type":5,"len":10,"ext":{"downloads1":"downloads2"}}},{"id":16,"required":1,"data":{"type":6,"len":10,"ext":{"price1":"price2"}}},{"id":17,"required":1,"data":{"type":7,"len":10,"ext":{"saleprice1":"saleprice2"}}},{"id":18,"required":1,"data":{"type":8,"len":10,"ext":{"phone1":"phone2"}}},{"id":19,"required":1,"data":{"type":9,"len":10,"ext":{"address1":"address2"}}},{"id":20,"required":1,"data":{"type":10,"len":10,"ext":{"desc21":"desc22"}}},{"id":21,"required":1,"data":{"type":11,"len":10,"ext":{"displayurl1":"displayurl2"}}}]}' + 'request': '{"ver":"1.2","assets":[{"id":0,"required":1,"title":{"len":80,"ext":{"title1":"title2"}}},{"id":1,"required":1,"img":{"type":1,"w":50,"h":50,"ext":{"icon1":"icon2"}}},{"id":2,"required":1,"img":{"type":3,"w":728,"h":90,"ext":{"image1":"image2"},"mimes":["image/png","image/gif"]}},{"id":3,"required":1,"data":{"type":1,"len":10,"ext":{"sponsor1":"sponsor2"}}},{"id":4,"required":1,"data":{"type":2,"len":10,"ext":{"body1":"body2"}}},{"id":5,"required":1,"data":{"type":3,"len":10,"ext":{"rating1":"rating2"}}},{"id":6,"required":1,"data":{"type":4,"len":10,"ext":{"likes1":"likes2"}}},{"id":7,"required":1,"data":{"type":5,"len":10,"ext":{"downloads1":"downloads2"}}},{"id":8,"required":1,"data":{"type":6,"len":10,"ext":{"price1":"price2"}}},{"id":9,"required":1,"data":{"type":7,"len":10,"ext":{"saleprice1":"saleprice2"}}},{"id":10,"required":1,"data":{"type":8,"len":10,"ext":{"phone1":"phone2"}}},{"id":11,"required":1,"data":{"type":9,"len":10,"ext":{"address1":"address2"}}},{"id":12,"required":1,"data":{"type":10,"len":10,"ext":{"desc21":"desc22"}}},{"id":13,"required":1,"data":{"type":11,"len":10,"ext":{"displayurl1":"displayurl2"}}}]}' } } @@ -794,12 +850,86 @@ describe('PubMatic adapter', function () { expect(isValid).to.equal(true); }); - it('should check for context if video is present', function() { - let bid = { + if (FEATURES.VIDEO) { + it('should check for context if video is present', function() { + let bid = { + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }) + + it('should return false if context is not present in video', function() { + let bid = { + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890' + }, + 'mediaTypes': { + 'video': { + 'w': 640, + 'h': 480, + 'protocols': [1, 2, 5], + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }) + + it('bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array', function() { + let bid = { 'bidder': 'pubmatic', 'params': { 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890' + 'publisherId': '5890', + 'video': {} }, 'mediaTypes': { 'video': { @@ -808,42 +938,6 @@ describe('PubMatic adapter', function () { ], 'protocols': [1, 2, 5], 'context': 'instream', - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }, - isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(true); - }) - - it('should return false if context is not present in video', function() { - let bid = { - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890' - }, - 'mediaTypes': { - 'video': { - 'w': 640, - 'h': 480, - 'protocols': [1, 2, 5], - 'mimes': ['video/flv'], 'skippable': false, 'skip': 1, 'linearity': 2 @@ -861,160 +955,124 @@ describe('PubMatic adapter', function () { 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0 - }, - isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(false); - }) - - it('bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array', function() { - let bid = { - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - 'video': {} - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }; + }; - delete bid.params.video.mimes; // Undefined - bid.mediaTypes.video.mimes = 'string'; // NOT array - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.params.video.mimes; // Undefined + bid.mediaTypes.video.mimes = 'string'; // NOT array + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.params.video.mimes; // Undefined - delete bid.mediaTypes.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.params.video.mimes; // Undefined + delete bid.mediaTypes.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.params.video.mimes; // Undefined - bid.mediaTypes.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.video.mimes; // Undefined + bid.mediaTypes.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - bid.params.video = {mimes: 'string'}; // NOT array - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + bid.params.video = {mimes: 'string'}; // NOT array + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - delete bid.params.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + delete bid.params.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - bid.params.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + bid.params.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.mediaTypes.video.mimes; // Undefined - bid.params.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.mediaTypes.video.mimes; // Undefined + bid.params.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.mediaTypes.video.mimes; // Undefined - delete bid.params.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); + delete bid.mediaTypes.video.mimes; // Undefined + delete bid.params.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - it('checks on bid.params.outstreamAU & bid.renderer & bid.mediaTypes.video.renderer', function() { - const getThebid = function() { - let bid = utils.deepClone(validOutstreamBidRequest.bids[0]); - bid.params.outstreamAU = 'pubmatic-test'; - bid.renderer = ' '; // we are only checking if this key is set or not - bid.mediaTypes.video.renderer = ' '; // we are only checking if this key is set or not - return bid; - } + it('checks on bid.params.outstreamAU & bid.renderer & bid.mediaTypes.video.renderer', function() { + const getThebid = function() { + let bid = utils.deepClone(validOutstreamBidRequest.bids[0]); + bid.params.outstreamAU = 'pubmatic-test'; + bid.renderer = ' '; // we are only checking if this key is set or not + bid.mediaTypes.video.renderer = ' '; // we are only checking if this key is set or not + return bid; + } - // true: when all are present - // mdiatype: outstream - // bid.params.outstreamAU : Y - // bid.renderer : Y - // bid.mediaTypes.video.renderer : Y - let bid = getThebid(); - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : Y - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : Y - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : Y - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // false: none present; only outstream - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - // true: none present; outstream + Banner - // mdiatype: outstream, banner - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - bid.mediaTypes.banner = {sizes: [ [300, 250], [300, 600] ]}; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: none present; outstream + Native - // mdiatype: outstream, native - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - bid.mediaTypes.native = {} - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); + // true: when all are present + // mdiatype: outstream + // bid.params.outstreamAU : Y + // bid.renderer : Y + // bid.mediaTypes.video.renderer : Y + let bid = getThebid(); + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : Y + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : Y + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : Y + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // false: none present; only outstream + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + // true: none present; outstream + Banner + // mdiatype: outstream, banner + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + bid.mediaTypes.banner = {sizes: [ [300, 250], [300, 600] ]}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: none present; outstream + Native + // mdiatype: outstream, native + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + bid.mediaTypes.native = {} + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + } }); describe('Request formation', function () { @@ -1060,18 +1118,6 @@ describe('PubMatic adapter', function () { expect(data.test).to.equal(undefined); }); - // disabled this test case as it refreshes the whole suite when in karma watch mode - // todo: needs a fix - xit('test flag set to 1 when pubmaticTest=true is present in page url', function() { - window.location.href += '#pubmaticTest=true'; - // now all the test cases below will have window.location.href with #pubmaticTest=true - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.test).to.equal(1); - }); - it('Request params check', function () { let request = spec.buildRequests(bidRequests, { auctionId: 'new-auction-id' @@ -1999,29 +2045,31 @@ describe('PubMatic adapter', function () { expect(data.bidfloor).to.equal(undefined); }); - it('ignore floormodule o/p if floor is not number', function() { - floorModuleTestData.banner['300x250'].floor = 'Not-a-Number'; - floorModuleTestData.banner['300x600'].floor = 'Not-a-Number'; - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' + if (FEATURES.VIDEO) { + it('ignore floormodule o/p if floor is not number', function() { + floorModuleTestData.banner['300x250'].floor = 'Not-a-Number'; + floorModuleTestData.banner['300x600'].floor = 'Not-a-Number'; + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(2.5); // video will be lowest now }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(2.5); // video will be lowest now - }); - it('ignore floormodule o/p if currency is not matched', function() { - floorModuleTestData.banner['300x250'].currency = 'INR'; - floorModuleTestData.banner['300x600'].currency = 'INR'; - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' + it('ignore floormodule o/p if currency is not matched', function() { + floorModuleTestData.banner['300x250'].currency = 'INR'; + floorModuleTestData.banner['300x600'].currency = 'INR'; + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(2.5); // video will be lowest now }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(2.5); // video will be lowest now - }); + } it('kadfloor is not passed, use minimum from floorModule', function() { newRequest[0].params.kadfloor = undefined; @@ -2500,114 +2548,6 @@ describe('PubMatic adapter', function () { }); }); - it('Request params check for video ad', function () { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].video).to.exist; - expect(data.imp[0].tagid).to.equal('Div1'); - expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); - expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); - expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); - expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); - expect(data.imp[0]['video']['startdelay']).to.equal(videoBidRequests[0].params.video['startdelay']); - - expect(data.imp[0]['video']['playbackmethod']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['playbackmethod'][0]).to.equal(videoBidRequests[0].params.video['playbackmethod'][0]); - expect(data.imp[0]['video']['playbackmethod'][1]).to.equal(videoBidRequests[0].params.video['playbackmethod'][1]); - - expect(data.imp[0]['video']['api']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['api'][0]).to.equal(videoBidRequests[0].params.video['api'][0]); - expect(data.imp[0]['video']['api'][1]).to.equal(videoBidRequests[0].params.video['api'][1]); - - expect(data.imp[0]['video']['protocols']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['protocols'][0]).to.equal(videoBidRequests[0].params.video['protocols'][0]); - expect(data.imp[0]['video']['protocols'][1]).to.equal(videoBidRequests[0].params.video['protocols'][1]); - - expect(data.imp[0]['video']['battr']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['battr'][0]).to.equal(videoBidRequests[0].params.video['battr'][0]); - expect(data.imp[0]['video']['battr'][1]).to.equal(videoBidRequests[0].params.video['battr'][1]); - - expect(data.imp[0]['video']['linearity']).to.equal(videoBidRequests[0].params.video['linearity']); - expect(data.imp[0]['video']['placement']).to.equal(videoBidRequests[0].params.video['placement']); - expect(data.imp[0]['video']['minbitrate']).to.equal(videoBidRequests[0].params.video['minbitrate']); - expect(data.imp[0]['video']['maxbitrate']).to.equal(videoBidRequests[0].params.video['maxbitrate']); - - expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); - }); - - it('Request params check for 1 banner and 1 video ad', function () { - let request = spec.buildRequests(multipleMediaRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - - expect(data.imp).to.be.an('array') - expect(data.imp).with.length.above(1); - - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(multipleMediaRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(multipleMediaRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(multipleMediaRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(multipleMediaRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude - expect(data.device.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude - expect(data.user.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude - expect(data.user.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude - expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(multipleMediaRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(multipleMediaRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID - - // banner imp object check - expect(data.imp[0].id).to.equal(multipleMediaRequests[0].bidId); // Prebid bid id is passed as id - expect(data.imp[0].bidfloor).to.equal(parseFloat(multipleMediaRequests[0].params.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].ext.pmZoneId).to.equal(multipleMediaRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - - // video imp object check - expect(data.imp[1].video).to.exist; - expect(data.imp[1].tagid).to.equal('Div1'); - expect(data.imp[1]['video']['mimes']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['mimes'][0]).to.equal(multipleMediaRequests[1].params.video['mimes'][0]); - expect(data.imp[1]['video']['mimes'][1]).to.equal(multipleMediaRequests[1].params.video['mimes'][1]); - expect(data.imp[1]['video']['minduration']).to.equal(multipleMediaRequests[1].params.video['minduration']); - expect(data.imp[1]['video']['maxduration']).to.equal(multipleMediaRequests[1].params.video['maxduration']); - expect(data.imp[1]['video']['startdelay']).to.equal(multipleMediaRequests[1].params.video['startdelay']); - - expect(data.imp[1]['video']['playbackmethod']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['playbackmethod'][0]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][0]); - expect(data.imp[1]['video']['playbackmethod'][1]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][1]); - - expect(data.imp[1]['video']['api']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['api'][0]).to.equal(multipleMediaRequests[1].params.video['api'][0]); - expect(data.imp[1]['video']['api'][1]).to.equal(multipleMediaRequests[1].params.video['api'][1]); - - expect(data.imp[1]['video']['protocols']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['protocols'][0]).to.equal(multipleMediaRequests[1].params.video['protocols'][0]); - expect(data.imp[1]['video']['protocols'][1]).to.equal(multipleMediaRequests[1].params.video['protocols'][1]); - - expect(data.imp[1]['video']['battr']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['battr'][0]).to.equal(multipleMediaRequests[1].params.video['battr'][0]); - expect(data.imp[1]['video']['battr'][1]).to.equal(multipleMediaRequests[1].params.video['battr'][1]); - - expect(data.imp[1]['video']['linearity']).to.equal(multipleMediaRequests[1].params.video['linearity']); - expect(data.imp[1]['video']['placement']).to.equal(multipleMediaRequests[1].params.video['placement']); - expect(data.imp[1]['video']['minbitrate']).to.equal(multipleMediaRequests[1].params.video['minbitrate']); - expect(data.imp[1]['video']['maxbitrate']).to.equal(multipleMediaRequests[1].params.video['maxbitrate']); - - expect(data.imp[1]['video']['w']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[0]); - expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); - }); - it('should pass device.sua if present in bidderRequest fpd ortb2 object', function () { const suaObject = {'source': 2, 'platform': {'brand': 'macOS', 'version': ['12', '4', '0']}, 'browsers': [{'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']}], 'mobile': 0, 'model': '', 'bitness': '64', 'architecture': 'x86'}; let request = spec.buildRequests(multipleMediaRequests, { @@ -2667,127 +2607,6 @@ describe('PubMatic adapter', function () { expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpressionWithAllParams.native.request); }); - it('Request params - should handle banner and video format in single adunit', function() { - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length); - - // Case: when size is not present in adslo - bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][0]); - expect(data.banner.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][1]); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length - 1); - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - }); - - it('Request params - banner and video req in single adslot - should ignore banner imp if banner size is set to fluid and send video imp object', function () { - /* Adslot configured for banner and video. - banner size is set to [['fluid'], [300, 250]] - adslot specifies a size as 300x250 - => banner imp object should have primary w and h set to 300 and 250. fluid is ignored - */ - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; - - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format[0].w).to.equal(160); - expect(data.banner.format[0].h).to.equal(600); - - /* Adslot configured for banner and video. - banner size is set to [['fluid'], [300, 250]] - adslot does not specify any size - => banner imp object should have primary w and h set to 300 and 250. fluid is ignored - */ - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; - bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(160); - expect(data.banner.h).to.equal(600); - expect(data.banner.format).to.not.exist; - - /* Adslot configured for banner and video. - banner size is set to [[728 90], ['fluid'], [300, 250]] - adslot does not specify any size - => banner imp object should have primary w and h set to 728 and 90. - banner.format should have 300, 250 set in it - fluid is ignore - */ - - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [[728, 90], ['fluid'], [300, 250]]; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(728); - expect(data.banner.h).to.equal(90); - expect(data.banner.format).to.exist; - expect(data.banner.format[0].w).to.equal(300); - expect(data.banner.format[0].h).to.equal(250); - - /* Adslot configured for banner and video. - banner size is set to [['fluid']] - adslot does not specify any size - => banner object should not be sent in the request. only video should be sent. - */ - - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid']]; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.not.exist; - expect(data.video).to.exist; - }); - - it('Request params - should not contain banner imp if mediaTypes.banner is not present and sizes is specified in bid.sizes', function() { - delete bannerAndVideoBidRequests[0].mediaTypes.banner; - bannerAndVideoBidRequests[0].params.sizes = [300, 250]; - - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.not.exist; - }); - it('Request params - should handle banner and native format in single adunit', function() { let request = spec.buildRequests(bannerAndNativeBidRequests, { auctionId: 'new-auction-id' @@ -2805,58 +2624,6 @@ describe('PubMatic adapter', function () { expect(data.native.request).to.exist; }); - it('Request params - should handle video and native format in single adunit', function() { - let request = spec.buildRequests(videoAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - - it('Request params - should handle banner, video and native format in single adunit', function() { - let request = spec.buildRequests(bannerVideoAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - - it('Request params - should not add banner object if mediaTypes.banner is missing, but adunits.sizes is present', function() { - delete bannerAndNativeBidRequests[0].mediaTypes.banner; - bannerAndNativeBidRequests[0].sizes = [729, 90]; - - let request = spec.buildRequests(bannerAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.not.exist; - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - it('Request params - banner and native multiformat request - should have native object incase of invalid config present', function() { bannerAndNativeBidRequests[0].mediaTypes.native = { title: { required: true }, @@ -2880,115 +2647,399 @@ describe('PubMatic adapter', function () { expect(data.native).to.exist; }); - it('Request params - video and native multiformat request - should have native object incase of invalid config present', function() { - videoAndNativeBidRequests[0].mediaTypes.native = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - }; - videoAndNativeBidRequests[0].nativeParams = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - } - let request = spec.buildRequests(videoAndNativeBidRequests, { + it('Request params - should not add banner object if mediaTypes.banner is missing, but adunits.sizes is present', function() { + delete bannerAndNativeBidRequests[0].mediaTypes.banner; + bannerAndNativeBidRequests[0].sizes = [729, 90]; + + let request = spec.buildRequests(bannerAndNativeBidRequests, { auctionId: 'new-auction-id' }); let data = JSON.parse(request.data); data = data.imp[0]; - expect(data.video).to.exist; + expect(data.banner).to.not.exist; + expect(data.native).to.exist; + expect(data.native.request).to.exist; }); - it('should build video impression if video params are present in adunit.mediaTypes instead of bid.params', function() { - let videoReq = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', - 'sizes': [ - [640, 480] - ], - 'bidId': '21b59b1353ba82', - 'bidderRequestId': '1a08245305e6dd', - 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }] - let request = spec.buildRequests(videoReq, { - auctionId: 'new-auction-id' + if (FEATURES.VIDEO) { + it('Request params - should not contain banner imp if mediaTypes.banner is not present and sizes is specified in bid.sizes', function() { + delete bannerAndVideoBidRequests[0].mediaTypes.banner; + bannerAndVideoBidRequests[0].params.sizes = [300, 250]; + + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.not.exist; }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.video).to.exist; - }); - it('should build video impression with overwriting video params present in adunit.mediaTypes with bid.params', function() { - let videoReq = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', - 'sizes': [ - [640, 480] - ], - 'bidId': '21b59b1353ba82', - 'bidderRequestId': '1a08245305e6dd', - 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }] - let request = spec.buildRequests(videoReq, { - auctionId: 'new-auction-id' + it('Request params check for 1 banner and 1 video ad', function () { + let request = spec.buildRequests(multipleMediaRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + + expect(data.imp).to.be.an('array') + expect(data.imp).with.length.above(1); + + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.page).to.equal(multipleMediaRequests[0].params.kadpageurl); // forced pageURL + expect(data.site.publisher.id).to.equal(multipleMediaRequests[0].params.publisherId); // publisher Id + expect(data.user.yob).to.equal(parseInt(multipleMediaRequests[0].params.yob)); // YOB + expect(data.user.gender).to.equal(multipleMediaRequests[0].params.gender); // Gender + expect(data.device.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version + expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID + expect(data.ext.wrapper.profile).to.equal(parseInt(multipleMediaRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID + expect(data.ext.wrapper.version).to.equal(parseInt(multipleMediaRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + + // banner imp object check + expect(data.imp[0].id).to.equal(multipleMediaRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(multipleMediaRequests[0].params.kadfloor)); // kadfloor + expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.pmZoneId).to.equal(multipleMediaRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid + + // video imp object check + expect(data.imp[1].video).to.exist; + expect(data.imp[1].tagid).to.equal('Div1'); + expect(data.imp[1]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['mimes'][0]).to.equal(multipleMediaRequests[1].params.video['mimes'][0]); + expect(data.imp[1]['video']['mimes'][1]).to.equal(multipleMediaRequests[1].params.video['mimes'][1]); + expect(data.imp[1]['video']['minduration']).to.equal(multipleMediaRequests[1].params.video['minduration']); + expect(data.imp[1]['video']['maxduration']).to.equal(multipleMediaRequests[1].params.video['maxduration']); + expect(data.imp[1]['video']['startdelay']).to.equal(multipleMediaRequests[1].params.video['startdelay']); + + expect(data.imp[1]['video']['playbackmethod']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['playbackmethod'][0]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][0]); + expect(data.imp[1]['video']['playbackmethod'][1]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][1]); + + expect(data.imp[1]['video']['api']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['api'][0]).to.equal(multipleMediaRequests[1].params.video['api'][0]); + expect(data.imp[1]['video']['api'][1]).to.equal(multipleMediaRequests[1].params.video['api'][1]); + + expect(data.imp[1]['video']['protocols']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['protocols'][0]).to.equal(multipleMediaRequests[1].params.video['protocols'][0]); + expect(data.imp[1]['video']['protocols'][1]).to.equal(multipleMediaRequests[1].params.video['protocols'][1]); + + expect(data.imp[1]['video']['battr']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['battr'][0]).to.equal(multipleMediaRequests[1].params.video['battr'][0]); + expect(data.imp[1]['video']['battr'][1]).to.equal(multipleMediaRequests[1].params.video['battr'][1]); + + expect(data.imp[1]['video']['linearity']).to.equal(multipleMediaRequests[1].params.video['linearity']); + expect(data.imp[1]['video']['placement']).to.equal(multipleMediaRequests[1].params.video['placement']); + expect(data.imp[1]['video']['minbitrate']).to.equal(multipleMediaRequests[1].params.video['minbitrate']); + expect(data.imp[1]['video']['maxbitrate']).to.equal(multipleMediaRequests[1].params.video['maxbitrate']); + + expect(data.imp[1]['video']['w']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[0]); + expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.video).to.exist; - expect(data.video.linearity).to.equal(1); - }); + // ================================================ + it('Request params - should handle banner and video format in single adunit', function() { + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length); + + // Case: when size is not present in adslo + bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][0]); + expect(data.banner.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][1]); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length - 1); + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + + it('Request params - should handle banner, video and native format in single adunit', function() { + let request = spec.buildRequests(bannerVideoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - should handle video and native format in single adunit', function() { + let request = spec.buildRequests(videoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - banner and video req in single adslot - should ignore banner imp if banner size is set to fluid and send video imp object', function () { + /* Adslot configured for banner and video. + banner size is set to [['fluid'], [300, 250]] + adslot specifies a size as 300x250 + => banner imp object should have primary w and h set to 300 and 250. fluid is ignored + */ + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; + + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format[0].w).to.equal(160); + expect(data.banner.format[0].h).to.equal(600); + + /* Adslot configured for banner and video. + banner size is set to [['fluid'], [300, 250]] + adslot does not specify any size + => banner imp object should have primary w and h set to 300 and 250. fluid is ignored + */ + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; + bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; + + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(160); + expect(data.banner.h).to.equal(600); + expect(data.banner.format).to.not.exist; + + /* Adslot configured for banner and video. + banner size is set to [[728 90], ['fluid'], [300, 250]] + adslot does not specify any size + => banner imp object should have primary w and h set to 728 and 90. + banner.format should have 300, 250 set in it + fluid is ignore + */ + + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [[728, 90], ['fluid'], [300, 250]]; + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(728); + expect(data.banner.h).to.equal(90); + expect(data.banner.format).to.exist; + expect(data.banner.format[0].w).to.equal(300); + expect(data.banner.format[0].h).to.equal(250); + + /* Adslot configured for banner and video. + banner size is set to [['fluid']] + adslot does not specify any size + => banner object should not be sent in the request. only video should be sent. + */ + + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid']]; + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.not.exist; + expect(data.video).to.exist; + }); + + it('Request params - video and native multiformat request - should have native object incase of invalid config present', function() { + videoAndNativeBidRequests[0].mediaTypes.native = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + }; + videoAndNativeBidRequests[0].nativeParams = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + } + let request = spec.buildRequests(videoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.native).to.exist; + }); + + it('should build video impression if video params are present in adunit.mediaTypes instead of bid.params', function() { + let videoReq = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890', + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', + 'sizes': [ + [640, 480] + ], + 'bidId': '21b59b1353ba82', + 'bidderRequestId': '1a08245305e6dd', + 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + let request = spec.buildRequests(videoReq, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.video).to.exist; + }); + + it('should build video impression with overwriting video params present in adunit.mediaTypes with bid.params', function() { + let videoReq = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890', + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', + 'sizes': [ + [640, 480] + ], + 'bidId': '21b59b1353ba82', + 'bidderRequestId': '1a08245305e6dd', + 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + let request = spec.buildRequests(videoReq, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.video.linearity).to.equal(1); + }); + + it('Request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('Div1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['startdelay']).to.equal(videoBidRequests[0].params.video['startdelay']); + + expect(data.imp[0]['video']['playbackmethod']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['playbackmethod'][0]).to.equal(videoBidRequests[0].params.video['playbackmethod'][0]); + expect(data.imp[0]['video']['playbackmethod'][1]).to.equal(videoBidRequests[0].params.video['playbackmethod'][1]); + + expect(data.imp[0]['video']['api']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['api'][0]).to.equal(videoBidRequests[0].params.video['api'][0]); + expect(data.imp[0]['video']['api'][1]).to.equal(videoBidRequests[0].params.video['api'][1]); + + expect(data.imp[0]['video']['protocols']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['protocols'][0]).to.equal(videoBidRequests[0].params.video['protocols'][0]); + expect(data.imp[0]['video']['protocols'][1]).to.equal(videoBidRequests[0].params.video['protocols'][1]); + + expect(data.imp[0]['video']['battr']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['battr'][0]).to.equal(videoBidRequests[0].params.video['battr'][0]); + expect(data.imp[0]['video']['battr'][1]).to.equal(videoBidRequests[0].params.video['battr'][1]); + + expect(data.imp[0]['video']['linearity']).to.equal(videoBidRequests[0].params.video['linearity']); + expect(data.imp[0]['video']['placement']).to.equal(videoBidRequests[0].params.video['placement']); + expect(data.imp[0]['video']['minbitrate']).to.equal(videoBidRequests[0].params.video['minbitrate']); + expect(data.imp[0]['video']['maxbitrate']).to.equal(videoBidRequests[0].params.video['maxbitrate']); + + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + } }); it('Request params dctr check', function () { @@ -3495,16 +3546,17 @@ describe('PubMatic adapter', function () { data.imp[0].id = '2a5571261281d4'; request.data = JSON.stringify(data); let response = spec.interpretResponse(nativeBidResponse, request); + let assets = response[0].native.ortb.assets; expect(response).to.be.an('array').with.length.above(0); expect(response[0].native).to.exist.and.to.be.an('object'); expect(response[0].mediaType).to.exist.and.to.equal('native'); - expect(response[0].native.title).to.exist.and.to.be.an('string'); - expect(response[0].native.image).to.exist.and.to.be.an('object'); - expect(response[0].native.image.url).to.exist.and.to.be.an('string'); - expect(response[0].native.image.height).to.exist; - expect(response[0].native.image.width).to.exist; - expect(response[0].native.sponsoredBy).to.exist.and.to.be.an('string'); - expect(response[0].native.clickUrl).to.exist.and.to.be.an('string'); + expect(assets).to.be.an('array').with.length.above(0); + expect(assets[0].title).to.exist.and.to.be.an('object'); + expect(assets[1].img).to.exist.and.to.be.an('object'); + expect(assets[1].img.url).to.exist.and.to.be.an('string'); + expect(assets[1].img.h).to.exist; + expect(assets[1].img.w).to.exist; + expect(assets[2].data).to.exist.and.to.be.an('object'); }); it('should check for valid banner mediaType in case of multiformat request', function() { @@ -3516,14 +3568,6 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('banner'); }); - it('should check for valid video mediaType in case of multiformat request', function() { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].mediaType).to.equal('video'); - }); - it('should check for valid native mediaType in case of multiformat request', function() { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' @@ -3533,28 +3577,6 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('native'); }); - it('should assign renderer if bid is video and request is for outstream', function() { - let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); - let response = spec.interpretResponse(outstreamVideoBidResponse, request); - expect(response[0].renderer).to.exist; - }); - - it('should not assign renderer if bidderRequest is not present', function() { - let request = spec.buildRequests(outstreamBidRequest, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(outstreamVideoBidResponse, request); - expect(response[0].renderer).to.not.exist; - }); - - it('should not assign renderer if bid is video and request is for instream', function() { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].renderer).to.not.exist; - }); - it('should not assign renderer if bid is native', function() { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' @@ -3571,154 +3593,186 @@ describe('PubMatic adapter', function () { expect(response[0].renderer).to.not.exist; }); - it('should assign mediaType by reading bid.ext.mediaType', function() { - let newvideoRequests = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5670', - 'video': { - 'mimes': ['video/mp4'], - 'skippable': true, - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }]; - let newvideoBidResponses = { - 'body': { - 'id': '1621441141473', - 'cur': 'USD', - 'customdata': 'openrtb1', - 'ext': { - 'buyid': 'myBuyId' + if (FEATURES.VIDEO) { + it('should check for valid video mediaType in case of multiformat request', function() { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].mediaType).to.equal('video'); + }); + + it('should assign renderer if bid is video and request is for outstream', function() { + let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.exist; + }); + + it('should not assign renderer if bidderRequest is not present', function() { + let request = spec.buildRequests(outstreamBidRequest, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is video and request is for instream', function() { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should assign mediaType by reading bid.ext.mediaType', function() { + let newvideoRequests = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5670', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } }, - 'seatbid': [{ - 'bid': [{ - 'id': '2c95df014cfe97', - 'impid': '2c95df014cfe97', - 'price': 4.2, - 'cid': 'test1', - 'crid': 'test2', - 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", - 'w': 0, - 'h': 0, - 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', - 'ext': { - 'bidtype': 1 - } - }], + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + let newvideoBidResponses = { + 'body': { + 'id': '1621441141473', + 'cur': 'USD', + 'customdata': 'openrtb1', 'ext': { 'buyid': 'myBuyId' - } - }] - }, - 'headers': {} - } - let newrequest = spec.buildRequests(newvideoRequests, { - auctionId: 'new-auction-id' - }); - let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); - expect(newresponse[0].mediaType).to.equal('video') - }) + }, + 'seatbid': [{ + 'bid': [{ + 'id': '2c95df014cfe97', + 'impid': '2c95df014cfe97', + 'price': 4.2, + 'cid': 'test1', + 'crid': 'test2', + 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", + 'w': 0, + 'h': 0, + 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'ext': { + 'bidtype': 1 + } + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + } + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video') + }) - it('should assign mediaType even if bid.ext.mediaType does not exists', function() { - let newvideoRequests = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5670', - 'video': { - 'mimes': ['video/mp4'], - 'skippable': true, - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }]; - let newvideoBidResponses = { - 'body': { - 'id': '1621441141473', - 'cur': 'USD', - 'customdata': 'openrtb1', - 'ext': { - 'buyid': 'myBuyId' + it('should assign mediaType even if bid.ext.mediaType does not exists', function() { + let newvideoRequests = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5670', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } }, - 'seatbid': [{ - 'bid': [{ - 'id': '2c95df014cfe97', - 'impid': '2c95df014cfe97', - 'price': 4.2, - 'cid': 'test1', - 'crid': 'test2', - 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", - 'w': 0, - 'h': 0, - 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420' - }], + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + let newvideoBidResponses = { + 'body': { + 'id': '1621441141473', + 'cur': 'USD', + 'customdata': 'openrtb1', 'ext': { 'buyid': 'myBuyId' - } - }] - }, - 'headers': {} - } - let newrequest = spec.buildRequests(newvideoRequests, { - auctionId: 'new-auction-id' + }, + 'seatbid': [{ + 'bid': [{ + 'id': '2c95df014cfe97', + 'impid': '2c95df014cfe97', + 'price': 4.2, + 'cid': 'test1', + 'crid': 'test2', + 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", + 'w': 0, + 'h': 0, + 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420' + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + } + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video') }); - let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); - expect(newresponse[0].mediaType).to.equal('video') - }); + } }); describe('Preapare metadata', function () { @@ -3898,26 +3952,55 @@ describe('PubMatic adapter', function () { }); }); - describe('Checking for Video.Placement property', function() { - let sandbox, utilsMock; - const adUnit = 'Div1'; - const msg_placement_missing = 'Video.Placement param missing for Div1'; - let videoData = { - battr: [6, 7], - skipafter: 15, - maxduration: 50, - context: 'instream', - playerSize: [640, 480], - skip: 0, - connectiontype: [1, 2, 6], - skipmin: 10, - minduration: 10, - mimes: ['video/mp4', 'video/x-flv'], - } + if (FEATURES.VIDEO) { + describe('Checking for Video.Placement property', function() { + let sandbox, utilsMock; + const adUnit = 'Div1'; + const msg_placement_missing = 'Video.Placement param missing for Div1'; + let videoData = { + battr: [6, 7], + skipafter: 15, + maxduration: 50, + context: 'instream', + playerSize: [640, 480], + skip: 0, + connectiontype: [1, 2, 6], + skipmin: 10, + minduration: 10, + mimes: ['video/mp4', 'video/x-flv'], + } + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }) + + it('should log Video.Placement param missing', function() { + checkVideoPlacement(videoData, adUnit); + sinon.assert.calledWith(utils.logWarn, msg_placement_missing); + }) + it('shoud not log Video.Placement param missing', function() { + videoData['placement'] = 1; + checkVideoPlacement(videoData, adUnit); + sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); + }) + }); + } + }); + + if (FEATURES.VIDEO) { + describe('Video request params', function() { + let sandbox, utilsMock, newVideoRequest; beforeEach(() => { utilsMock = sinon.mock(utils); sandbox = sinon.sandbox.create(); sandbox.spy(utils, 'logWarn'); + newVideoRequest = utils.deepClone(videoBidRequests) }); afterEach(() => { @@ -3925,112 +4008,87 @@ describe('PubMatic adapter', function () { sandbox.restore(); }) - it('should log Video.Placement param missing', function() { - checkVideoPlacement(videoData, adUnit); - sinon.assert.calledWith(utils.logWarn, msg_placement_missing); - }) - it('shoud not log Video.Placement param missing', function() { - videoData['placement'] = 1; - checkVideoPlacement(videoData, adUnit); - sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); - }) - }); - }); + it('Should log warning if video params from mediaTypes and params obj of bid are not present', function () { + delete newVideoRequest[0].mediaTypes.video; + delete newVideoRequest[0].params.video; - describe('Video request params', function() { - let sandbox, utilsMock, newVideoRequest; - beforeEach(() => { - utilsMock = sinon.mock(utils); - sandbox = sinon.sandbox.create(); - sandbox.spy(utils, 'logWarn'); - newVideoRequest = utils.deepClone(videoBidRequests) - }); - - afterEach(() => { - utilsMock.restore(); - sandbox.restore(); - }) - - it('Should log warning if video params from mediaTypes and params obj of bid are not present', function () { - delete newVideoRequest[0].mediaTypes.video; - delete newVideoRequest[0].params.video; + let request = spec.buildRequests(newVideoRequest, { + auctionId: 'new-auction-id' + }); - let request = spec.buildRequests(newVideoRequest, { - auctionId: 'new-auction-id' + sinon.assert.calledOnce(utils.logWarn); + expect(request).to.equal(undefined); }); - sinon.assert.calledOnce(utils.logWarn); - expect(request).to.equal(undefined); - }); - - it('Should consider video params from mediaType object of bid', function () { - delete newVideoRequest[0].params.video; + it('Should consider video params from mediaType object of bid', function () { + delete newVideoRequest[0].params.video; - let request = spec.buildRequests(newVideoRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].video).to.exist; - expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); - expect(data.imp[0]['video']['battr']).to.equal(undefined); - }); - - describe('Assign Deal Tier (i.e. prebidDealPriority)', function () { - let videoSeatBid, request, newBid; - // let data = JSON.parse(request.data); - beforeEach(function () { - videoSeatBid = videoBidResponse.body.seatbid[0].bid[0]; - // const adpodValidOutstreamBidRequest = validOutstreamBidRequest.bids[0].mediaTypes.video.context = 'adpod'; - request = spec.buildRequests(bidRequests, validOutstreamBidRequest); - newBid = { - requestId: '47acc48ad47af5' - }; - videoSeatBid.ext = videoSeatBid.ext || {}; - videoSeatBid.ext.video = videoSeatBid.ext.video || {}; - // videoBidRequests[0].mediaTypes.video = videoBidRequests[0].mediaTypes.video || {}; - }); - - it('should not assign video object if deal priority is missing', function () { - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video).to.equal(undefined); - expect(newBid.video).to.not.exist; - }); - - it('should not assign video object if context is not a adpod', function () { - videoSeatBid.ext.prebiddealpriority = 5; - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video).to.equal(undefined); - expect(newBid.video).to.not.exist; + let request = spec.buildRequests(newVideoRequest, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.imp[0]['video']['battr']).to.equal(undefined); }); - describe('when video deal tier object is present', function () { + describe('Assign Deal Tier (i.e. prebidDealPriority)', function () { + let videoSeatBid, request, newBid; + // let data = JSON.parse(request.data); beforeEach(function () { - videoSeatBid.ext.prebiddealpriority = 5; - request.bidderRequest.bids[0].mediaTypes.video = { - ...request.bidderRequest.bids[0].mediaTypes.video, - context: 'adpod', - maxduration: 50 + videoSeatBid = videoBidResponse.body.seatbid[0].bid[0]; + // const adpodValidOutstreamBidRequest = validOutstreamBidRequest.bids[0].mediaTypes.video.context = 'adpod'; + request = spec.buildRequests(bidRequests, validOutstreamBidRequest); + newBid = { + requestId: '47acc48ad47af5' }; + videoSeatBid.ext = videoSeatBid.ext || {}; + videoSeatBid.ext.video = videoSeatBid.ext.video || {}; + // videoBidRequests[0].mediaTypes.video = videoBidRequests[0].mediaTypes.video || {}; }); - it('should set video deal tier object, when maxduration is present in ext', function () { + it('should not assign video object if deal priority is missing', function () { assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video.durationSeconds).to.equal(50); - expect(newBid.video.context).to.equal('adpod'); - expect(newBid.video.dealTier).to.equal(5); + expect(newBid.video).to.equal(undefined); + expect(newBid.video).to.not.exist; }); - it('should set video deal tier object, when duration is present in ext', function () { - videoSeatBid.ext.video.duration = 20; + it('should not assign video object if context is not a adpod', function () { + videoSeatBid.ext.prebiddealpriority = 5; assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video.durationSeconds).to.equal(20); - expect(newBid.video.context).to.equal('adpod'); - expect(newBid.video.dealTier).to.equal(5); + expect(newBid.video).to.equal(undefined); + expect(newBid.video).to.not.exist; + }); + + describe('when video deal tier object is present', function () { + beforeEach(function () { + videoSeatBid.ext.prebiddealpriority = 5; + request.bidderRequest.bids[0].mediaTypes.video = { + ...request.bidderRequest.bids[0].mediaTypes.video, + context: 'adpod', + maxduration: 50 + }; + }); + + it('should set video deal tier object, when maxduration is present in ext', function () { + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video.durationSeconds).to.equal(50); + expect(newBid.video.context).to.equal('adpod'); + expect(newBid.video.dealTier).to.equal(5); + }); + + it('should set video deal tier object, when duration is present in ext', function () { + videoSeatBid.ext.video.duration = 20; + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video.durationSeconds).to.equal(20); + expect(newBid.video.context).to.equal('adpod'); + expect(newBid.video.dealTier).to.equal(5); + }); }); }); }); - }); + } describe('Marketplace params', function () { let sandbox, utilsMock, newBidRequests, newBidResponses; diff --git a/test/spec/modules/pubxBidAdapter_spec.js b/test/spec/modules/pubxBidAdapter_spec.js index 06bb5b5f638..b387264bf91 100644 --- a/test/spec/modules/pubxBidAdapter_spec.js +++ b/test/spec/modules/pubxBidAdapter_spec.js @@ -39,14 +39,22 @@ describe('pubxAdapter', function () { id: '26c1ee0038ac11', params: { sid: '12345abc' + }, + ortb2: { + site: { + page: `${location.href}?test=1` + } } } ]; const data = { banner: { - sid: '12345abc' - } + sid: '12345abc', + pu: encodeURIComponent( + utils.deepAccess(bidRequests[0], 'ortb2.site.page').replace(/\?.*$/, '') + ), + }, }; it('sends bid request to ENDPOINT via GET', function () { diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index ced2f697649..f9c41b2fda0 100644 --- a/test/spec/modules/realTimeDataModule_spec.js +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -5,6 +5,8 @@ import {default as CONSTANTS} from '../../../src/constants.json'; import * as events from '../../../src/events.js'; import 'src/prebid.js'; import {attachRealTimeDataProvider, onDataDeletionRequest} from 'modules/rtdModule/index.js'; +import {GDPR_GVLIDS} from '../../../src/consentHandler.js'; +import {MODULE_TYPE_RTD} from '../../../src/activities/modules.js'; const getBidRequestDataSpy = sinon.spy(); @@ -84,6 +86,26 @@ describe('Real time module', function () { sandbox.restore(); }); + describe('GVL IDs', () => { + beforeEach(() => { + sinon.stub(GDPR_GVLIDS, 'register'); + }); + + afterEach(() => { + GDPR_GVLIDS.register.restore(); + }); + + it('are registered when RTD module is registered', () => { + let mod; + try { + mod = attachRealTimeDataProvider({name: 'mockRtd', gvlid: 123}); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_RTD, 'mockRtd', 123); + } finally { + mod && mod(); + } + }) + }) + describe('', () => { const PROVIDERS = [validSM, invalidSM, failureSM, nonConfSM, validSMWait]; let _detachers; diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js deleted file mode 100644 index e51a4e2e3a2..00000000000 --- a/test/spec/modules/realvuAnalyticsAdapter_spec.js +++ /dev/null @@ -1,194 +0,0 @@ -import { expect } from 'chai'; -import realvuAnalyticsAdapter, { lib } from 'modules/realvuAnalyticsAdapter.js'; -import CONSTANTS from 'src/constants.json'; - -function addDiv(id) { - let dv = document.createElement('div'); - dv.id = id; - dv.style.width = '728px'; - dv.style.height = '90px'; - dv.style.display = 'block'; - document.body.appendChild(dv); - let f = document.createElement('iframe'); - f.width = 728; - f.height = 90; - dv.appendChild(f); - let d = null; - if (f.contentDocument) d = f.contentDocument; // DOM - else if (f.contentWindow) d = f.contentWindow.document; // IE - d.open() - d.write(''); - d.close(); - return dv; -} - -describe('RealVu', function() { - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - addDiv('ad1'); - addDiv('ad2'); - sandbox.stub(lib, 'scr'); - }); - - afterEach(function () { - let a1 = document.getElementById('ad1'); - document.body.removeChild(a1); - let a2 = document.getElementById('ad2'); - document.body.removeChild(a2); - sandbox.restore(); - realvuAnalyticsAdapter.disableAnalytics(); - }); - - after(function () { - delete window.top1; - delete window.realvu_aa_fifo; - delete window.realvu_aa; - clearInterval(window.boost_poll); - delete window.boost_poll; - }); - - describe('Analytics Adapter.', function () { - it('enableAnalytics', function () { - const config = { - options: { - partnerId: '1Y', - regAllUnits: true - // unitIds: ['ad1', 'ad2'] - } - }; - let p = realvuAnalyticsAdapter.enableAnalytics(config); - expect(p).to.equal('1Y'); - }); - - it('checkIn', function () { - const bid = { - adUnitCode: 'ad1', - sizes: [ - [728, 90], - [970, 250], - [970, 90] - ] - }; - let result = realvuAnalyticsAdapter.checkIn(bid, '1Y'); - const b = Object.assign({}, window.top1.realvu_aa); - let a = b.ads[0]; - // console.log('a: ' + a.x + ', ' + a.y + ', ' + a.w + ', ' + a.h); - // console.log('b: ' + b.x1 + ', ' + b.y1 + ', ' + b.x2 + ', ' + b.y2); - expect(result).to.equal('yes'); - - result = realvuAnalyticsAdapter.checkIn(bid); // test invalid partnerId 'undefined' - result = realvuAnalyticsAdapter.checkIn(bid, ''); // test invalid partnerId '' - }); - - it.skip('isInView returns "yes"', () => { - let inview = realvuAnalyticsAdapter.isInView('ad1'); - expect(inview).to.equal('yes'); - }); - - it('isInView return "NA"', function () { - const adUnitCode = '1234'; - let result = realvuAnalyticsAdapter.isInView(adUnitCode); - expect(result).to.equal('NA'); - }); - - it('bid response event', function () { - const config = { - options: { - partnerId: '1Y', - regAllUnits: true - // unitIds: ['ad1', 'ad2'] - } - }; - realvuAnalyticsAdapter.enableAnalytics(config); - const args = { - 'biddercode': 'realvu', - 'adUnitCode': 'ad1', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '7ba299eba818c1', - 'mediaType': 'banner', - 'creative_id': 85792851, - 'cpm': 0.4308 - }; - realvuAnalyticsAdapter.track({ - eventType: CONSTANTS.EVENTS.BID_RESPONSE, - args: args - }); - const boost = Object.assign({}, window.top1.realvu_aa); - expect(boost.ads[boost.len - 1].bids.length).to.equal(1); - - realvuAnalyticsAdapter.track({ - eventType: CONSTANTS.EVENTS.BID_WON, - args: args - }); - expect(boost.ads[boost.len - 1].bids[0].winner).to.equal(1); - }); - }); - - describe('Boost.', function () { - // const boost = window.top1.realvu_aa; - let boost; - beforeEach(function() { - boost = Object.assign({}, window.top1.realvu_aa); - }); - it('brd', function () { - let a1 = document.getElementById('ad1'); - let p = boost.brd(a1, 'Left'); - expect(typeof p).to.not.equal('undefined'); - }); - - it('addUnitById', function () { - let a1 = document.getElementById('ad1'); - let p = boost.addUnitById('1Y', 'ad1'); - expect(typeof p).to.not.equal('undefined'); - }); - - it('questA', function () { - const dv = document.getElementById('ad1'); - let q = boost.questA(dv); - expect(q).to.not.equal(null); - }); - - it('render', function () { - let dv = document.getElementById('ad1'); - // dv.style.width = '728px'; - // dv.style.height = '90px'; - // dv.style.display = 'block'; - dv.getBoundingClientRect = false; - // document.body.appendChild(dv); - let q = boost.findPosG(dv); - expect(q).to.not.equal(null); - }); - - it('readPos', function () { - const a = boost.ads[boost.len - 1]; - let r = boost.readPos(a); - expect(r).to.equal(true); - }); - - it('send_track', function () { - const a = boost.ads[boost.len - 1]; - boost.track(a, 'show', ''); - boost.sr = 'a'; - boost.send_track(); - expect(boost.beacons.length).to.equal(0); - }); - - it('questA text', function () { - let p = document.createElement('p'); - p.innerHTML = 'ABC'; - document.body.appendChild(p); - let r = boost.questA(p.firstChild); - document.body.removeChild(p); - expect(r).to.not.equal(null); - }); - - it('_f=conf', function () { - const a = boost.ads[boost.len - 1]; - let r = boost.tru(a, 'conf'); - expect(r).to.not.include('_ps='); - }); - }); -}); diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 6a6e79c633d..0f2f9abd583 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -1,13 +1,13 @@ -import { expect } from 'chai'; -import { spec } from 'modules/relaidoBidAdapter.js'; +import {expect} from 'chai'; +import {spec} from 'modules/relaidoBidAdapter.js'; import * as utils from 'src/utils.js'; -import { BANNER, VIDEO } from 'src/mediaTypes.js'; -import { getStorageManager } from '../../../src/storageManager.js'; +import {VIDEO} from 'src/mediaTypes.js'; +import {getCoreStorageManager} from '../../../src/storageManager.js'; const UUID_KEY = 'relaido_uuid'; const relaido_uuid = 'hogehoge'; -const storage = getStorageManager(); +const storage = getCoreStorageManager(); storage.setCookie(UUID_KEY, relaido_uuid); describe('RelaidoAdapter', function () { diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 792d3ab0a9e..78c700f3804 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -282,6 +282,28 @@ describe('RTBHouseAdapter', () => { expect(data.source).to.not.have.property('ext'); }); + it('should include first party data', function () { + const bidRequest = Object.assign([], bidRequests); + const localBidderRequest = { + ...bidderRequest, + ortb2: { + bcat: ['IAB1', 'IAB2-1'], + badv: ['domain1.com', 'domain2.com'], + site: { ext: { data: 'some site data' } }, + device: { ext: { data: 'some device data' } }, + user: { ext: { data: 'some user data' } } + } + }; + + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + expect(data.bcat).to.deep.equal(localBidderRequest.ortb2.bcat); + expect(data.badv).to.deep.equal(localBidderRequest.ortb2.badv); + expect(data.site).to.nested.include({'ext.data': 'some site data'}); + expect(data.device).to.nested.include({'ext.data': 'some device data'}); + expect(data.user).to.nested.include({'ext.data': 'some user data'}); + }); + context('FLEDGE', function() { afterEach(function () { config.resetConfig(); @@ -631,10 +653,10 @@ describe('RTBHouseAdapter', () => { expect(bids[0].meta.advertiserDomains).to.deep.equal(['rtbhouse.com']); expect(bids[0].native).to.deep.equal({ title: 'Title text', - clickUrl: encodeURIComponent('https://example.com'), + clickUrl: encodeURI('https://example.com'), impressionTrackers: ['https://example.com/imptracker'], image: { - url: encodeURIComponent('https://example.com/image.jpg'), + url: encodeURI('https://example.com/image.jpg'), width: 150, height: 50 }, diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 8ef34a1599e..fcfbe5f7c3f 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -91,50 +91,4 @@ describe('SharedId System', function () { expect(result).to.be.undefined; }); }); - - describe('SharedID System domainOverride', () => { - let sandbox, domain, cookies, rejectCookiesFor; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - sandbox.stub(document, 'domain').get(() => domain); - cookies = {}; - sandbox.stub(storage, 'getCookie').callsFake((key) => cookies[key]); - rejectCookiesFor = null; - sandbox.stub(storage, 'setCookie').callsFake((key, value, expires, sameSite, domain) => { - if (domain !== rejectCookiesFor) { - if (expires != null) { - expires = new Date(expires); - } - if (expires == null || expires > Date.now()) { - cookies[key] = value; - } else { - delete cookies[key]; - } - } - }); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return TLD if cookies can be set there', () => { - domain = 'sub.domain.com'; - rejectCookiesFor = 'com'; - expect(sharedIdSystemSubmodule.domainOverride()).to.equal('domain.com'); - }); - - it('should return undefined when cookies cannot be set', () => { - domain = 'sub.domain.com'; - rejectCookiesFor = 'sub.domain.com'; - expect(sharedIdSystemSubmodule.domainOverride()).to.be.undefined; - }); - - it('should return half-way domain if parent domain rejects cookies', () => { - domain = 'inner.outer.domain.com'; - rejectCookiesFor = 'domain.com'; - expect(sharedIdSystemSubmodule.domainOverride()).to.equal('outer.domain.com'); - }); - }); }); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index f635791aeed..fc4fbc86018 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -248,7 +248,8 @@ describe('sharethrough adapter spec', function () { refererInfo: { ref: 'https://referer.com', }, - auctionId: 'auction-id' + auctionId: 'auction-id', + timeout: 242 }; }); diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index e1787dfe880..e01d0c72f6b 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -91,7 +91,8 @@ describe('SmartHubBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { page: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index 0773d29789d..747dc4edc63 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -313,32 +313,6 @@ describe('synacormediaBidAdapter ', function () { expect(req.data.tmax).to.eql(bidderRequestWithTimeout.timeout); }); - it('should return tmax equal to smaller global timeout', function () { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'bidderTimeout': bidderRequestWithTimeout.timeout - 100 - }; - return config[key]; - }); - let req = spec.buildRequests([validBidRequest], bidderRequestWithTimeout); - sandbox.restore(); - expect(req.data.tmax).to.eql(bidderRequestWithTimeout.timeout - 100); - }); - - it('should return tmax equal to smaller callback timeout', function () { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'bidderTimeout': bidderRequestWithTimeout.timeout + 100 - }; - return config[key]; - }); - let req = spec.buildRequests([validBidRequest], bidderRequestWithTimeout); - sandbox.restore(); - expect(req.data.tmax).to.eql(bidderRequestWithTimeout.timeout); - }); - it('should return multiple bids when multiple valid requests with the same seatId are used', function () { let secondBidRequest = { bidId: 'foobar', diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 4debf516f89..718d030be91 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -139,15 +139,13 @@ describe('triplelift adapter', function () { sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', auctionId: '1d1a030790a475', userId: {}, schain, ortb2Imp: { ext: { - data: { - pbAdSlot: 'homepage-top-rect', - adUnitSpecificAttribute: 123 - } + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' } } }, @@ -178,6 +176,15 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + ortb2Imp: { + ext: { + data: { + pbAdSlot: 'homepage-top-rect', + adUnitSpecificAttribute: 123 + }, + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' + } + } }, // banner and outstream video { @@ -245,6 +252,11 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + ortb2Imp: { + misc: { + test: 1 + } + } }, // incomplete banner and incomplete video { @@ -689,6 +701,39 @@ describe('triplelift adapter', function () { expect(payload.imp[13].video.placement).to.equal(3); }); + it('should add tid to imp.ext if transactionId exists', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.tid).to.exist.and.be.a('string'); + expect(request.data.imp[0].ext.tid).to.equal('173f49a8-7549-4218-a23c-e7ba59b47229'); + }); + + it('should not add impression ext object if ortb2Imp does not exist', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[2].ext).to.not.exist; + }); + + it('should not add impression ext object if ortb2Imp.ext does not exist', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[3].ext).to.not.exist; + }); + + it('should copy entire impression ext object', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[1].ext).to.haveOwnProperty('tid'); + expect(request.data.imp[1].ext).to.haveOwnProperty('data'); + expect(request.data.imp[1].ext.data).to.haveOwnProperty('adUnitSpecificAttribute'); + expect(request.data.imp[1].ext.data).to.haveOwnProperty('pbAdSlot'); + expect(request.data.imp[1].ext).to.deep.equal( + { + data: { + pbAdSlot: 'homepage-top-rect', + adUnitSpecificAttribute: 123 + }, + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' + } + ); + }); + it('should add tdid to the payload if included', function () { const id = '6bca7f6b-a98a-46c0-be05-6020f7604598'; bidRequests[0].userId.tdid = id; @@ -1103,10 +1148,10 @@ describe('triplelift adapter', function () { }); it('should send ad unit fpd if kvps are available', function() { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - expect(request.data.imp[0].fpd.context).to.haveOwnProperty('data'); - expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('pbAdSlot'); - expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); - expect(request.data.imp[1].fpd).to.not.exist; + expect(request.data.imp[1].fpd.context).to.haveOwnProperty('data'); + expect(request.data.imp[1].fpd.context.data).to.haveOwnProperty('pbAdSlot'); + expect(request.data.imp[1].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); + expect(request.data.imp[2].fpd).to.not.exist; }); it('should send 1PlusX data as fpd if localStorage is available and no other fpd is defined', function() { sandbox.stub(storage, 'getDataFromLocalStorage').callsFake(() => '{"kid":1,"s":"ySRdArquXuBolr/cVv0UNqrJhTO4QZsbNH/t+2kR3gXjbA==","t":"/yVtBrquXuBolr/cVv0UNtx1mssdLYeKFhWFI3Dq1dJnug=="}'); diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index f9adecabddf..8c0a9db8fbd 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -282,6 +282,21 @@ describe('ttdBidAdapter', function () { expect(requestBody.imp[0].ext.gpid).to.equal(gpid); }); + it('sends rwdd in imp.rwdd if present', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + const gpid = '/1111/home#header'; + const rwdd = 1; + clonedBannerRequests[0].ortb2Imp = { + rwdd: rwdd, + ext: { + gpid: gpid + } + }; + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.imp[0].rwdd).to.be.not.null; + expect(requestBody.imp[0].rwdd).to.equal(1); + }); + it('sends auction id in source.tid', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.source).to.be.not.null; @@ -877,6 +892,14 @@ describe('ttdBidAdapter', function () { const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; expect(requestBody.imp[0].video.placement).to.equal(3); }); + + it('sets plcmt correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.plcmt = 3; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.plcmt).to.equal(3); + }); }); describe('interpretResponse-empty', function () { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index f092486c587..a2f2bfd8713 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -53,7 +53,8 @@ import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import {getPPID} from '../../../src/adserver.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; -import {getCoreStorageManager} from '../../../src/storageManager.js'; +import {GDPR_GVLIDS} from '../../../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -167,6 +168,20 @@ describe('User ID', function () { sandbox.restore(); }); + describe('GVL IDs', () => { + beforeEach(() => { + sinon.stub(GDPR_GVLIDS, 'register'); + }); + afterEach(() => { + GDPR_GVLIDS.register.restore(); + }); + + it('are registered when ID submodule is registered', () => { + attachIdSystem({name: 'gvlidMock', gvlid: 123}); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_UID, 'gvlidMock', 123); + }) + }) + describe('Decorate Ad Units', function () { beforeEach(function () { // reset mockGpt so nothing else interferes diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 97f8af97339..0429a2a51bf 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -477,6 +477,19 @@ describe('VidazooBidAdapter', function () { }); }); + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['vidazoo.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['vidazoo.com'], + agencyName: 'Agency Name' + }); + }); + it('should return an array of interpreted video responses', function () { const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); diff --git a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js index e08353f5b8d..3cede6c8eda 100644 --- a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js @@ -19,7 +19,7 @@ import { PLAYBACK_MODE } from 'libraries/video/constants/constants.js'; function getPlayerMock() { return makePlayerFactoryMock({ getState: function () {}, - setup: function () {}, + setup: function () { return this; }, getViewable: function () {}, getPercentViewable: function () {}, getMute: function () {}, @@ -30,8 +30,8 @@ function getPlayerMock() { getFullscreen: function () {}, getPlaylistItem: function () {}, playAd: function () {}, - on: function () {}, - off: function () {}, + on: function () { return this; }, + off: function () { return this; }, remove: function () {}, getAudioTracks: function () {}, getCurrentAudioTrack: function () {}, @@ -142,7 +142,7 @@ describe('JWPlayerProvider', function () { it('should instantiate the player when uninstantiated', function () { const player = getPlayerMock(); config.playerConfig = {}; - const setupSpy = player.setup = sinon.spy(); + const setupSpy = player.setup = sinon.spy(player.setup); const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); provider.init(); expect(setupSpy.calledOnce).to.be.true; @@ -158,6 +158,19 @@ describe('JWPlayerProvider', function () { expect(setupComplete.calledOnce).to.be.true; }); + it('should support multiple setup complete event handlers', function () { + const player = getPlayerMock(); + player.getState = () => 'idle'; + const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); + const setupComplete = sinon.spy(); + const setupComplete2 = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.onEvent(SETUP_COMPLETE, setupComplete2, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + expect(setupComplete2.calledOnce).to.be.true; + }); + it('should not reinstantiate player', function () { const player = getPlayerMock(); player.getState = () => 'idle'; diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 8a3e61ca43a..38fa872e6b8 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -161,6 +161,113 @@ describe('vidoomyBidAdapter', function() { expect(request[0].data).to.include.any.keys('schain'); expect(request[0].data.schain).to.eq(serializedForm); }); + + it('should return standard json formated eids', function () { + const eids = [{ + source: 'pubcid.org', + uids: [ + { + id: 'some-random-id-value-1', + atype: 1 + } + ] + }, + { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value-2', + atype: 1 + }] + }] + bidRequests[0].userIdAsEids = eids + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + expect(bidRequest[0].data).to.include.any.keys('eids'); + expect(JSON.parse(bidRequest[0].data.eids)).to.eql(eids); + }); + + it('should set the bidfloor if getFloor module is undefined but static bidfloor is present', function () { + const request = { ...bidRequests[0], params: { bidfloor: 2.5 } } + const req = spec.buildRequests([request], bidderRequest)[0]; + expect(req.data).to.include.any.keys('bidfloor'); + expect(req.data.bidfloor).to.equal(2.5); + }); + + describe('floorModule', function () { + const getFloordata = { + 'currency': 'USD', + 'floor': 1.60 + }; + bidRequests[0].getFloor = _ => { + return getFloordata; + }; + it('should return getFloor.floor if present', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.bidfloor).to.equal(getFloordata.floor); + }); + it('should return the getFloor.floor if it is greater than static bidfloor', function () { + const bidfloor = 1.40; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data.bidfloor).to.equal(getFloordata.floor); + }); + it('should return the static bidfloor if it is greater than getFloor.floor', function () { + const bidfloor = 1.90; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data.bidfloor).to.equal(bidfloor); + }); + }); + + describe('badv, bcat, bapp, btype, battr', function () { + const bidderRequestNew = { + ...bidderRequest, + bcat: ['EX1', 'EX2', 'EX3'], + badv: ['site.com'], + bapp: ['app.com'], + btype: [1, 2, 3], + battr: [1, 2, 3] + } + const request = spec.buildRequests(bidRequests, bidderRequestNew); + it('should have badv, bcat, bapp, btype, battr in request', function () { + expect(request[0].data).to.include.any.keys('badv'); + expect(request[0].data).to.include.any.keys('bcat'); + expect(request[0].data).to.include.any.keys('bapp'); + expect(request[0].data).to.include.any.keys('btype'); + expect(request[0].data).to.include.any.keys('battr'); + }) + + it('should have equal badv, bcat, bapp, btype, battr in request', function () { + expect(request[0].badv).to.deep.equal(bidderRequest.refererInfo.badv); + expect(request[0].bcat).to.deep.equal(bidderRequest.refererInfo.bcat); + expect(request[0].bapp).to.deep.equal(bidderRequest.refererInfo.bapp); + expect(request[0].btype).to.deep.equal(bidderRequest.refererInfo.btype); + expect(request[0].battr).to.deep.equal(bidderRequest.refererInfo.battr); + }) + }) + + describe('first party data', function () { + const bidderRequest2 = { + ...bidderRequest, + ortb2: { + bcat: ['EX1', 'EX2', 'EX3'], + badv: ['site.com'], + bapp: ['app.com'], + btype: [1, 2, 3], + battr: [1, 2, 3] + } + } + const request = spec.buildRequests(bidRequests, bidderRequest2); + + it('should have badv, bcat, bapp, btype, battr in request and equal to bidderRequest.ortb2', function () { + expect(request[0].data.bcat).to.deep.equal(bidderRequest2.ortb2.bcat) + expect(request[0].data.badv).to.deep.equal(bidderRequest2.ortb2.badv) + expect(request[0].data.bapp).to.deep.equal(bidderRequest2.ortb2.bapp); + expect(request[0].data.btype).to.deep.equal(bidderRequest2.ortb2.btype); + expect(request[0].data.battr).to.deep.equal(bidderRequest2.ortb2.battr); + }); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js index 93eeb7b556c..ad75e17699f 100644 --- a/test/spec/modules/visiblemeasuresBidAdapter_spec.js +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -78,7 +78,8 @@ describe('VisibleMeasuresBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/vrtcalBidAdapter_spec.js b/test/spec/modules/vrtcalBidAdapter_spec.js index d911745d378..491c96df5e2 100644 --- a/test/spec/modules/vrtcalBidAdapter_spec.js +++ b/test/spec/modules/vrtcalBidAdapter_spec.js @@ -28,7 +28,8 @@ describe('vrtcalBidAdapter', function () { 'bidId': 'bidID0001', 'bidderRequestId': 'br0001', 'auctionId': 'auction0001', - 'userIdAsEids': {} + 'userIdAsEids': {}, + timeout: 435 } ]; @@ -95,7 +96,7 @@ describe('vrtcalBidAdapter', function () { w: 300, h: 250, crid: 'v2_1064_vrt_vrtcaltestdisplay2_300_250', - adomain: ['vrtcal.com'] + adomain: ['vrtcal.com'], }], seat: '16' }], diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index d6bf15ff9d4..3706f770da8 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -212,7 +212,7 @@ describe('YieldmoAdapter', function () { expect(data.hasOwnProperty('h')).to.be.true; expect(data.hasOwnProperty('w')).to.be.true; expect(data.hasOwnProperty('pubcid')).to.be.true; - expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":""}'); + expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":"","gpp":"","gpp_sid":[]}'); expect(data.us_privacy).to.equal(''); }); @@ -262,6 +262,24 @@ describe('YieldmoAdapter', function () { JSON.stringify({ gdprApplies: true, cmp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gpp: '', + gpp_sid: [], + }) + ); + }); + + it('should add gpp information to request if available', () => { + const gppConsent = { + 'gppString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'applicableSections': [8] + }; + const data = buildAndGetData([mockBannerBid()], 0, mockBidderRequest({gppConsent})); + expect(data.userConsent).equal( + JSON.stringify({ + gdprApplies: '', + cmp: '', + gpp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gpp_sid: [8], }) ); }); diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 6494a7cbfef..54483f0c00e 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, getStorage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; import * as storageManager from 'src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; const ZEOTAP_COOKIE = 'THIS-IS-A-DUMMY-COOKIE'; @@ -52,9 +53,9 @@ describe('Zeotap ID System', function() { }); it('when a stored Zeotap ID exists it is added to bids', function() { - let store = getStorage(); + getStorage(); expect(getStorageManagerSpy.calledOnce).to.be.true; - sinon.assert.calledWith(getStorageManagerSpy, {gvlid: 301, moduleName: 'zeotapIdPlus'}); + sinon.assert.calledWith(getStorageManagerSpy, {moduleType: MODULE_TYPE_UID, moduleName: 'zeotapIdPlus'}); }); }); diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 4e18f49c849..c3678427a9a 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -51,6 +51,7 @@ describe('Zeta Ssp Bid Adapter', function () { }, sid: 'publisherId', shortname: 'test_shortname', + tagid: 'test_tag_id', site: { page: 'testPage' }, @@ -396,4 +397,11 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.source.ext.schain).to.eql(schain); }); + + it('Test tagid provided', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].tagid).to.eql(params.tagid); + }); }); diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index c41334f916a..fb1e25d6009 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -52,7 +52,7 @@ describe('Renderer', function () { expect(testRenderer2.getConfig()).to.deep.equal({ test: 'config2' }); }); - it('sets a render function with setRender method', function () { + it('sets a render function with the setRender method', function () { testRenderer1.setRender(spyRenderFn); expect(typeof testRenderer1.render).to.equal('function'); testRenderer1.render(); @@ -110,7 +110,6 @@ describe('Renderer', function () { it('renders immediately when requested', function () { const testRenderer3 = Renderer.install({ - url: 'https://httpbin.org/post', config: { test: 'config2' }, id: 2, renderNow: true diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 58334595d71..8d887474180 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -21,6 +21,8 @@ import {find, includes} from 'src/polyfill.js'; import s2sTesting from 'modules/s2sTesting.js'; import {hook} from '../../../../src/hook.js'; import {auctionManager} from '../../../../src/auctionManager.js'; +import {GDPR_GVLIDS} from '../../../../src/consentHandler.js'; +import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER} from '../../../../src/activities/modules.js'; var events = require('../../../../src/events'); const CONFIG = { @@ -2737,4 +2739,23 @@ describe('adapterManager tests', function () { }) }) }); + + describe('registers GVL IDs', () => { + beforeEach(() => { + sinon.stub(GDPR_GVLIDS, 'register'); + }); + afterEach(() => { + GDPR_GVLIDS.register.restore(); + }); + + it('for bid adapters', () => { + adapterManager.registerBidAdapter({getSpec: () => ({gvlid: 123}), callBids: sinon.stub()}, 'mock'); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_BIDDER, 'mock', 123); + }); + + it('for analytics adapters', () => { + adapterManager.registerAnalyticsAdapter({adapter: {enableAnalytics: sinon.stub()}, code: 'mock', gvlid: 123}); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_ANALYTICS, 'mock', 123); + }); + }); }); diff --git a/test/spec/unit/core/consentHandler_spec.js b/test/spec/unit/core/consentHandler_spec.js index 082ff34f90c..98b317e0d36 100644 --- a/test/spec/unit/core/consentHandler_spec.js +++ b/test/spec/unit/core/consentHandler_spec.js @@ -1,4 +1,4 @@ -import {ConsentHandler} from '../../../../src/consentHandler.js'; +import {ConsentHandler, gvlidRegistry} from '../../../../src/consentHandler.js'; describe('Consent data handler', () => { let handler; @@ -57,3 +57,31 @@ describe('Consent data handler', () => { }) }); }) + +describe('gvlidRegistry', () => { + let registry; + beforeEach(() => { + registry = gvlidRegistry(); + }); + + it('returns undef when id cannoot be found', () => { + expect(registry.get('name')).to.eql({modules: {}}) + }); + + it('does not register null ids', () => { + registry.register('type', 'name', null); + expect(registry.get('type', 'name')).to.eql({modules: {}}); + }) + + it('can retrieve registered GVL IDs', () => { + registry.register('type', 'name', 123); + registry.register('otherType', 'name', 123); + expect(registry.get('name')).to.eql({gvlid: 123, modules: {type: 123, otherType: 123}}); + }); + + it('does not return `gvlid` if there is more than one', () => { + registry.register('type', 'name', 123); + registry.register('otherType', 'name', 321); + expect(registry.get('name')).to.eql({modules: {type: 123, otherType: 321}}) + }); +}) diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index d4b3c8e583e..9e31389d96f 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -1,14 +1,14 @@ import { + getCoreStorageManager, getStorageManager, + newStorageManager, resetData, - getCoreStorageManager, storageCallbacks, - getStorageManager, - newStorageManager, validateStorageEnforcement + validateStorageEnforcement } from 'src/storageManager.js'; -import { config } from 'src/config.js'; +import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {hook} from '../../../../src/hook.js'; -import {VENDORLESS_GVLID} from '../../../../src/consentHandler.js'; +import {MODULE_TYPE_BIDDER} from '../../../../src/activities/modules.js'; describe('storage manager', function() { before(() => { @@ -34,7 +34,7 @@ describe('storage manager', function() { it('should add done callbacks to storageCallbacks array', function() { let noop = sinon.spy(); - const coreStorage = getStorageManager(); + const coreStorage = newStorageManager(); coreStorage.setCookie('foo', 'bar', null, null, null, noop); coreStorage.getCookie('foo', noop); @@ -50,17 +50,16 @@ describe('storage manager', function() { it('should allow bidder to access device if gdpr enforcement module is not included', function() { let deviceAccessSpy = sinon.spy(utils, 'hasDeviceAccess'); - const storage = getStorageManager(); + const storage = newStorageManager(); storage.setCookie('foo1', 'baz1'); expect(deviceAccessSpy.calledOnce).to.equal(true); deviceAccessSpy.restore(); }); - describe(`core storage`, () => { - let storage, validateHook; + describe(`enforcement`, () => { + let validateHook; beforeEach(() => { - storage = getCoreStorageManager(); validateHook = sinon.stub().callsFake(function (next, ...args) { next.apply(this, args); }); @@ -72,15 +71,26 @@ describe('storage manager', function() { config.resetConfig(); }) - it('should respect (vendorless) consent enforcement', () => { - storage.localStorageIsEnabled(); - expect(validateHook.args[0][1]).to.equal(VENDORLESS_GVLID); // gvlid should be set to VENDORLESS_GVLID - }); + Object.entries({ + 'core': () => getCoreStorageManager('mock'), + 'other': () => getStorageManager({moduleType: 'other', moduleName: 'mock'}) + }).forEach(([moduleType, getMgr]) => { + describe(`for ${moduleType} modules`, () => { + let storage; + beforeEach(() => { + storage = getMgr(); + }); + it(`should pass '${moduleType}' module type to consent enforcement`, () => { + storage.localStorageIsEnabled(); + expect(validateHook.args[0][1]).to.equal(moduleType); + }); - it('should respect the deviceAccess flag', () => { - config.setConfig({deviceAccess: false}); - expect(storage.localStorageIsEnabled()).to.be.false - }) + it('should respect the deviceAccess flag', () => { + config.setConfig({deviceAccess: false}); + expect(storage.localStorageIsEnabled()).to.be.false + }); + }); + }); }) describe('localstorage forbidden access in 3rd-party context', function() { @@ -100,7 +110,7 @@ describe('storage manager', function() { }) it('should not throw if the localstorage is not accessible when setting/getting/removing from localstorage', function() { - const coreStorage = getStorageManager(); + const coreStorage = newStorageManager(); coreStorage.setDataInLocalStorage('key', 'value'); const val = coreStorage.getDataFromLocalStorage('key'); @@ -124,7 +134,7 @@ describe('storage manager', function() { }) it('should remove side-effect after checking', function () { - const storage = getStorageManager(); + const storage = newStorageManager(); localStorage.setItem('unrelated', 'dummy'); const val = storage.localStorageIsEnabled(); @@ -206,7 +216,7 @@ describe('storage manager', function() { let mgr; beforeEach(() => { - mgr = newStorageManager({bidderCode: bidderCode}, {bidderSettings: mockBidderSettings(configValue)}); + mgr = newStorageManager({moduleType: MODULE_TYPE_BIDDER, moduleName: bidderCode}, {bidderSettings: mockBidderSettings(configValue)}); }) afterEach(() => { diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index b069b13fc37..820d87ef49c 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -888,16 +888,32 @@ describe('Unit: Prebid Module', function () { it('should only apply price granularity if bid media type matches', function () { initTestConfig({ - adUnits: [ createAdUnit('div-gpt-ad-1460505748561-0', 'video') ], + adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] }); - response = videoResponse; + response = bannerResponse; response.tags[0].ads[0].cpm = 3.4288; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); + + if (FEATURES.VIDEO) { + ajaxStub.restore(); + + initTestConfig({ + adUnits: [createAdUnit('div-gpt-ad-1460505748561-0', 'video')], + adUnitCodes: ['div-gpt-ad-1460505748561-0'] + }); + + response = videoResponse; + response.tags[0].ads[0].cpm = 3.4288; + + auction.callBids(cbTimeout); + let bidTargeting = targeting.getAllTargeting(); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); + } }); }); diff --git a/test/test_index.js b/test/test_index.js index 04d1412860b..ce9b671be89 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,4 +1,3 @@ - [it, describe].forEach((ob) => { ob.only = function () { [ @@ -7,8 +6,19 @@ // eslint-disable-next-line no-console ].forEach(l => console.error(l)) throw new Error('do not use .only()') - } -}) + }; +}); + +[it, describe].forEach((ob) => { + ob.skip = function () { + [ + 'describe.skip and it.skip are disabled,', + 'because they pollute the pipeline test output', + // eslint-disable-next-line no-console + ].forEach(l => console.error(l)) + throw new Error('do not use .skip()') + }; +}); require('./test_deps.js');