diff --git a/integrationExamples/gpt/gdpr_hello_world.html b/integrationExamples/gpt/gdpr_hello_world.html index de0630178f1..2d70af8d34f 100644 --- a/integrationExamples/gpt/gdpr_hello_world.html +++ b/integrationExamples/gpt/gdpr_hello_world.html @@ -82,7 +82,7 @@ - + + + - +

Prebid.js Test

Div-1
diff --git a/integrationExamples/gpt/haloRtdProvider_example.html b/integrationExamples/gpt/haloRtdProvider_example.html index 7f9a34e55ee..14debbd2698 100644 --- a/integrationExamples/gpt/haloRtdProvider_example.html +++ b/integrationExamples/gpt/haloRtdProvider_example.html @@ -40,27 +40,6 @@ googletag.pubads().disableInitialLoad(); }); - var appnexusSegmentHandler = function(bid, segments) { - if (!bid.params) { - bid.params = {}; - } - if (!bid.params.user) { - bid.params.user = {}; - } - - if (!Array.isArray(bid.params.user.segments)) { - bid.params.user.segments = []; - } - - var appnexusSegments = []; - for (var i = 0; i < segments.length; i++) { - var segment = segments[i]; - let appnexusSegment = {'id': segment.id, 'value': segment.value}; - appnexusSegments.push(appnexusSegment); - } - bid.params.user.segments = bid.params.user.segments.concat(appnexusSegments); - }; - pbjs.que.push(function() { pbjs.setConfig({ debug: true, @@ -71,9 +50,6 @@ name: "halo", waitForIt: true, params: { - mapSegments: { - appnexus: appnexusSegmentHandler // pass true to use the builtin handler. here, we will demo overriding the handler with a function - }, segmentCache: false, requestParams: { publisherId: 0 @@ -89,7 +65,7 @@ }); function sendAdserverRequest() { - document.getElementById('audigent_segments').innerHTML = JSON.stringify(adUnits[0].bids[0].params.user.segments); + document.getElementById('audigent_segments').innerHTML = window.localStorage.getItem('auHaloRtd'); document.getElementById('halo_id').innerHTML = testHaloId; if (pbjs.adserverRequestSent) return; @@ -130,7 +106,7 @@ -

Audigent Segments Prebid

+

Halo RTD Prebid

+ + + + + + + + + + +
+

+Give consent or make a choice in Europe. Module will add key/value pairs in ad calls. Check out for sd_rtd key in Google Ad call (https://securepubads.g.doubleclick.net/gampad/ads...) and in the payload sent to Xandr to endpoint https://ib.adnxs.com/ut/v3/prebid : tags[0].keywords.key[sd_rtd] should have an array of string as value. This array will mix user segments and/or page categories based on user's choices. +

+
+

Basic Prebid.js Example

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 973c295325a..72657258ad5 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -229,7 +229,8 @@ }, { name: 'identityLink', params: { - pid: '14' // Set your real identityLink placement ID here + pid: '14', // Set your real identityLink placement ID here + // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this property is undefined and 3p request will be fired. }, storage: { type: 'cookie', diff --git a/integrationExamples/mass/index.html b/integrationExamples/mass/index.html index 80fe4cfb934..3b034957d13 100644 --- a/integrationExamples/mass/index.html +++ b/integrationExamples/mass/index.html @@ -36,8 +36,30 @@ pbjs.setConfig({ mass: { enabled: true, + + // official MASS-supported config: + dealIdPattern: /^MASS/i, renderUrl: 'https://cdn.massplatform.net/bootloader.js', - dealIdPattern: /^MASS/i + + // custom configs: + custom: [ + // simple: + { + dealIdPattern: /^abc/i, + renderUrl: 'https://my.domain.com/script.js' + }, + + // flexible: + { + match: function(bid) { + // return true or false, based on given bid + }, + + render: function(payload) { + // render the ad + } + } + ] } }); }); diff --git a/modules/.submodules.json b/modules/.submodules.json index f71325d38a9..7ad4bedde5c 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -16,6 +16,7 @@ "zeotapIdPlusIdSystem", "haloIdSystem", "quantcastIdSystem", + "deepintentDpesIdSystem", "nextrollIdSystem", "idxIdSystem", "fabrickIdSystem", @@ -36,6 +37,7 @@ "haloRtdProvider", "jwplayerRtdProvider", "reconciliationRtdProvider", - "geoedgeRtdProvider" + "geoedgeRtdProvider", + "sirdataRtdProvider" ] } diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 65df8baad2e..e5a6d148808 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -194,18 +194,36 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl // therefore in ad targetting process ttxRequest.id = bidRequest.bidId; - // Set GDPR related fields - ttxRequest.user = { - ext: { - consent: gdprConsent.consentString - } - }; - ttxRequest.regs = { - ext: { - gdpr: (gdprConsent.gdprApplies === true) ? 1 : 0, - us_privacy: uspConsent || null - } - }; + if (gdprConsent.consentString) { + ttxRequest.user = setExtension( + ttxRequest.user, + 'consent', + gdprConsent.consentString + ) + } + + if (Array.isArray(bidRequest.userIdAsEids) && bidRequest.userIdAsEids.length > 0) { + ttxRequest.user = setExtension( + ttxRequest.user, + 'eids', + bidRequest.userIdAsEids + ) + } + + ttxRequest.regs = setExtension( + ttxRequest.regs, + 'gdpr', + Number(gdprConsent.gdprApplies) + ); + + if (uspConsent) { + ttxRequest.regs = setExtension( + ttxRequest.regs, + 'us_privacy', + uspConsent + ) + } + ttxRequest.ext = { ttx: { prebidStartedAt: Date.now(), @@ -217,11 +235,11 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl }; if (bidRequest.schain) { - ttxRequest.source = { - ext: { - schain: bidRequest.schain - } - } + ttxRequest.source = setExtension( + ttxRequest.source, + 'schain', + bidRequest.schain + ) } // Finally, set the openRTB 'test' param if this is to be a test bid @@ -250,6 +268,15 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } } +// BUILD REQUESTS: SET EXTENSIONS +function setExtension(obj = {}, key, value) { + return Object.assign({}, obj, { + ext: Object.assign({}, obj.ext, { + [key]: value + }) + }); +} + // BUILD REQUESTS: SIZE INFERENCE function _transformSizes(sizes) { if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js index 87c40db51e6..689e7d02124 100644 --- a/modules/adWMGBidAdapter.js +++ b/modules/adWMGBidAdapter.js @@ -6,8 +6,8 @@ import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'adWMG'; -const ENDPOINT = 'https://rtb.adwmg.com/prebid'; -let SYNC_ENDPOINT = 'https://rtb.adwmg.com/cphb.html?'; +const ENDPOINT = 'https://hb.adwmg.com/hb'; +let SYNC_ENDPOINT = 'https://hb.adwmg.com/cphb.html?'; export const spec = { code: BIDDER_CODE, @@ -34,11 +34,21 @@ export const spec = { const additional = spec.parseUserAgent(ua); return validBidRequests.map(bidRequest => { + const checkFloorValue = (value) => { + if (isNaN(parseFloat(value))) { + return 0; + } else return parseFloat(value); + } + const adUnit = { code: bidRequest.adUnitCode, bids: { bidder: bidRequest.bidder, - params: bidRequest.params + params: { + publisherId: bidRequest.params.publisherId, + IABCategories: bidRequest.params.IABCategories || [], + floorCPM: bidRequest.params.floorCPM ? checkFloorValue(bidRequest.params.floorCPM) : 0 + } }, mediaTypes: bidRequest.mediaTypes }; @@ -295,7 +305,11 @@ export const spec = { } } - return {devicetype: detectDevice(), os: detectOs().os, osv: detectOs().osv} + return { + devicetype: detectDevice(), + os: detectOs().os, + osv: detectOs().osv + } } -} +}; registerBidder(spec); diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 16594b3453c..362442015f6 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'adnuntius'; -const ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo=-60&format=json'; +const ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo='; export const spec = { code: BIDDER_CODE, @@ -14,6 +14,7 @@ export const spec = { const networks = {}; const bidRequests = {}; const requests = []; + const tzo = new Date().getTimezoneOffset(); for (var i = 0; i < validBidRequests.length; i++) { const bid = validBidRequests[i] @@ -23,7 +24,7 @@ export const spec = { networks[network] = networks[network] || {}; networks[network].adUnits = networks[network].adUnits || []; - networks[network].adUnits.push({ ...bid.params.targeting, auId: bid.params.auId }); + networks[network].adUnits.push({ ...bid.params.targeting, auId: bid.params.auId, targetId: bid.bidId }); } const networkKeys = Object.keys(networks) @@ -31,7 +32,7 @@ export const spec = { const network = networkKeys[j]; requests.push({ method: 'POST', - url: ENDPOINT_URL, + url: ENDPOINT_URL + tzo + '&format=json', data: JSON.stringify(networks[network]), bid: bidRequests[network] }); @@ -48,13 +49,14 @@ export const spec = { const adUnit = serverBody.adUnits[k] if (adUnit.matchedAdCount > 0) { const bid = adUnit.ads[0]; + const effectiveCpm = (bid.cpc && bid.cpm) ? bid.bid.amount + bid.cpm.amount : (bid.cpc) ? bid.bid.amount : (bid.cpm) ? bid.cpm.amount : 0; bidResponses.push({ requestId: bidRequest.bid[k].bidId, - cpm: (bid.cpm) ? bid.cpm.amount : 0, + cpm: effectiveCpm, width: Number(bid.creativeWidth), height: Number(bid.creativeHeight), creativeId: bid.creativeId, - currency: (bid.cpm) ? bid.cpm.currency : 'EUR', + currency: (bid.bid) ? bid.bid.currency : 'EUR', netRevenue: false, ttl: 360, ad: adUnit.html diff --git a/modules/adponeBidAdapter.js b/modules/adponeBidAdapter.js index f128785afff..49c0365fc87 100644 --- a/modules/adponeBidAdapter.js +++ b/modules/adponeBidAdapter.js @@ -4,34 +4,20 @@ import {triggerPixel} from '../src/utils.js'; const ADPONE_CODE = 'adpone'; const ADPONE_ENDPOINT = 'https://rtb.adpone.com/bid-request'; -const ADPONE_SYNC_ENDPOINT = 'https://eu-ads.adpone.com'; const ADPONE_REQUEST_METHOD = 'POST'; const ADPONE_CURRENCY = 'EUR'; -function _createSync() { - return { - type: 'iframe', - url: ADPONE_SYNC_ENDPOINT - } -} - -function getUserSyncs(syncOptions) { - return (syncOptions && syncOptions.iframeEnabled) ? _createSync() : ([]); -} - export const spec = { code: ADPONE_CODE, supportedMediaTypes: [BANNER], - getUserSyncs, - isBidRequestValid: bid => { return !!bid.params.placementId && !!bid.bidId && bid.bidder === 'adpone' }, - buildRequests: bidRequests => { + buildRequests: (bidRequests, bidderRequest) => { return bidRequests.map(bid => { - const url = ADPONE_ENDPOINT + '?pid=' + bid.params.placementId; + let url = ADPONE_ENDPOINT + '?pid=' + bid.params.placementId; const data = { at: 1, id: bid.bidId, @@ -49,6 +35,11 @@ export const spec = { withCredentials: true }; + if (bidderRequest && bidderRequest.gdprConsent) { + url += '&gdpr_applies=' + bidderRequest.gdprConsent.gdprApplies; + url += '&consentString=' + bidderRequest.gdprConsent.consentString; + } + return { method: ADPONE_REQUEST_METHOD, url, @@ -67,18 +58,27 @@ export const spec = { serverResponse.body.seatbid.forEach(seatbid => { if (seatbid.bid.length) { - answer = [...answer, ...seatbid.bid.filter(bid => bid.price > 0).map(bid => ({ - id: bid.id, - requestId: bidRequest.data.id, - cpm: bid.price, - ad: bid.adm, - width: bid.w || 0, - height: bid.h || 0, - currency: serverResponse.body.cur || ADPONE_CURRENCY, - netRevenue: true, - ttl: 300, - creativeId: bid.crid || 0 - }))]; + answer = [...answer, ...seatbid.bid.filter(bid => bid.price > 0).map(adponeBid => { + const bid = { + id: adponeBid.id, + requestId: bidRequest.data.id, + cpm: adponeBid.price, + ad: adponeBid.adm, + width: adponeBid.w || 0, + height: adponeBid.h || 0, + currency: serverResponse.body.cur || ADPONE_CURRENCY, + netRevenue: true, + ttl: 300, + creativeId: adponeBid.crid || 0 + }; + + if (adponeBid.meta && adponeBid.meta.adomain && adponeBid.meta.adomain.length > 0) { + bid.meta = {}; + bid.meta.advertiserDomains = adponeBid.meta.adomain; + } + + return bid + })]; } }); diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 669feeedb04..9ca4b95dfd2 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -19,6 +19,7 @@ const HOST_GETTERS = { appaloosa: () => 'ghb.hb.appaloosa.media', onefiftytwomedia: () => 'ghb.ads.152media.com', mediafuse: () => 'ghb.hbmp.mediafuse.com', + bidsxchange: () => 'ghb.hbd.bidsxchange.com', } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -34,7 +35,7 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', + aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', { code: 'navelix', gvlid: 380 }, { code: 'mediafuse', @@ -205,6 +206,9 @@ function prepareBidRequests(bidReq) { }; bidReqParams.PlacementId = bidReq.adUnitCode; + if (bidReq.params.iframe) { + bidReqParams.AdmType = 'iframe'; + } if (bidReq.params.vpb_placement_id) { bidReqParams.PlacementId = bidReq.params.vpb_placement_id; } @@ -249,7 +253,8 @@ function createBid(bidResponse, bidRequest) { if (mediaType === BANNER) { return Object.assign(bid, { - ad: bidResponse.ad + ad: bidResponse.ad, + adUrl: bidResponse.adUrl, }); } if (context === ADPOD) { diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index f445c9dfd5d..f6360332368 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -1,5 +1,5 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { getAdUnitSizes, parseSizesInput } from '../src/utils.js'; +import { getAdUnitSizes, parseSizesInput, deepAccess } from '../src/utils.js'; import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'between'; @@ -37,6 +37,8 @@ export const spec = { tz: getTz(), fl: getFl(), rr: getRr(), + shid: getSharedId(i)('id'), + shid3: getSharedId(i)('third'), s: i.params.s, bidid: i.bidId, transactionid: i.transactionId, @@ -144,6 +146,15 @@ export const spec = { } } +function getSharedId(bid) { + const id = deepAccess(bid, 'userId.sharedid.id'); + const third = deepAccess(bid, 'userId.sharedid.third'); + return function(kind) { + if (kind === 'id') return id || ''; + return third || ''; + } +} + function getRr() { try { var td = top.document; diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index afacc48aa8e..cac993b0f8c 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -28,7 +28,7 @@ const BB_CONSTANTS = { const getConfig = config.getConfig; // Helper Functions -export const BB_HELPERS = { +const BB_HELPERS = { addSiteAppDevice: function(request, pageUrl) { if (typeof getConfig('app') === 'object') request.app = getConfig('app'); else { @@ -151,7 +151,7 @@ const BB_RENDERER = { const ele = document.getElementById(bid.adUnitCode); // NB convention const renderer = find(window.bluebillywig.renderers, r => r._id === rendererId); - if (renderer) renderer.bootstrap(config, ele); + if (renderer) renderer.bootstrap(config, ele, bid.rendererSettings || {}); else utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`); }, newRenderer: function(rendererUrl, adUnitCode) { @@ -235,6 +235,11 @@ export const spec = { return false; } + if (bid.params.hasOwnProperty('rendererSettings') && (bid.params.rendererSettings === null || typeof bid.params.rendererSettings !== 'object')) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: params.rendererSettings must be of type object. Rejecting bid: `, bid); + return false; + } + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no context specified in bid. Rejecting bid: `, bid); @@ -330,6 +335,7 @@ export const spec = { bid.publicationName = bidParams.publicationName; bid.rendererCode = bidParams.rendererCode; bid.accountId = bidParams.accountId; + bid.rendererSettings = bidParams.rendererSettings; const rendererUrl = BB_HELPERS.getRendererUrl(bid.publicationName, bid.rendererCode); bid.renderer = BB_RENDERER.newRenderer(rendererUrl, bid.adUnitCode); diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js index aa1076e798a..b57e696148f 100644 --- a/modules/brightMountainMediaBidAdapter.js +++ b/modules/brightMountainMediaBidAdapter.js @@ -2,11 +2,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; -const BIDDER_CODE = 'brightmountainmedia'; -const AD_URL = 'https://console.brightmountainmedia.com/hb/bid'; +const BIDDER_CODE = 'bmtm'; +const AD_URL = 'https://one.elitebidder.com/api/hb'; export const spec = { code: BIDDER_CODE, + aliases: ['brightmountainmedia'], supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: (bid) => { diff --git a/modules/brightMountainMediaBidAdapter.md b/modules/brightMountainMediaBidAdapter.md index a9900b5264d..8dba3a4e6e5 100644 --- a/modules/brightMountainMediaBidAdapter.md +++ b/modules/brightMountainMediaBidAdapter.md @@ -23,7 +23,7 @@ Bright Mountain Media bid adapter currently supports Banner. }, bids: [ { - bidder: 'brightmountainmedia', + bidder: 'bmtm', params: { placement_id: '5f21784949be82079d08c', traffic: 'banner' diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index c4dc23cf912..9ec6c8e5bc2 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -49,6 +49,8 @@ export const spec = { utils.deepSetValue(openRtbBidRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } + injectEids(openRtbBidRequest, validBidRequests); + return { method: 'POST', url: BIDDER_ENDPOINT, @@ -128,6 +130,13 @@ function buildUser(bid) { } } +function injectEids(openRtbBidRequest, validBidRequests) { + const bidUserIdAsEids = utils.deepAccess(validBidRequests, '0.userIdAsEids'); + if (utils.isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + utils.deepSetValue(openRtbBidRequest, 'user.eids', bidUserIdAsEids); + } +} + function buildBanner(bid) { if (utils.deepAccess(bid, 'mediaTypes.banner')) { // Get Sizes from MediaTypes Object, Will always take first size, will be overrided by params for exact w,h diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js new file mode 100644 index 00000000000..375c8c07ed1 --- /dev/null +++ b/modules/deepintentDpesIdSystem.js @@ -0,0 +1,45 @@ +/** + * This module adds DPES to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/deepintentDpesSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'deepintentId'; +export const storage = getStorageManager(null, MODULE_NAME); + +/** @type {Submodule} */ +export const deepintentDpesSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{value:string}} value + * @returns {{deepintentId:Object}} + */ + decode(value, config) { + return value ? { 'deepintentId': value } : undefined; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @param {ConsentData|undefined} consentData + * @param {Object} cacheIdObj - existing id, if any + * @return {{id: string | undefined} | undefined} + */ + getId(config, consentData, cacheIdObj) { + return cacheIdObj; + } + +}; + +submodule('userId', deepintentDpesSubmodule); diff --git a/modules/deepintentDpesIdSystem.md b/modules/deepintentDpesIdSystem.md new file mode 100644 index 00000000000..2af0fe7446e --- /dev/null +++ b/modules/deepintentDpesIdSystem.md @@ -0,0 +1,43 @@ +# Deepintent DPES ID + +The Deepintent Id is a shared, healthcare identifier which helps publisher in absence of the 3rd Party cookie matching. This lets publishers set and bid with healthcare identity . Deepintent lets users protect their privacy through advertising value chain, where Healthcare identity when setting the identity takes in consideration of users choices, as well as when passing identity on the cookie itself privacy consent strings are checked. The healthcare identity when set is not stored on Deepintent's servers but is stored on users browsers itself. User can still opt out of the ads by https://option.deepintent.com/adchoices. + +## Deepintent DPES ID Registration + +The Deepintent DPES ID is free to use, but requires a simple registration with Deepintent. Please reach to prebid@deepintent.com to get started. +Once publisher registers with deepintents platform for healthcare identity Deepintent provides the Tag code to be placed on the page, this tag code works to capture and store information as per publishers and users agreement. DPES User ID module uses this stored id and passes it on the deepintent prebid adapter. + + +## Deepintent DPES ID Configuration + +First, make sure to add the Deepintent submodule to your Prebid.js package with: + +``` +gulp build --modules=deepintentDpesIdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'deepintentId', + storage: { + type: 'cookie', + name: '_dpes_id', + expires: 90 // storage lasts for 90 days, optional if storage type is html5 + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module: `"deepintentId"` | `"deepintentId"` | +| storage | Required | Object | Storage settings for how the User Id module will cache the Deepintent ID locally | | +| storage.type | Required | String | This is where the results of the user ID will be stored. Deepintent`"html5"` or `"cookie"`. | `"html5"` | +| storage.name | Required | String | The name of the local storage where the user ID will be stored. | `"_dpes_id"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. Deepintent recommends `90`. | `90` | \ No newline at end of file diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 3992f2db5e0..54a4ef0c998 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -1,13 +1,13 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER} from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; /** * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.0'; +const VERSION = '1.0.2'; /** * @typedef {object} FeedAdApiBidRequest @@ -61,6 +61,11 @@ const VERSION = '1.0.0'; * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows */ +/** + * The IAB TCF 2.0 vendor ID for the FeedAd GmbH + */ +const TCF_VENDOR_ID = 781; + /** * Bidder network identity code * @type {string} @@ -71,7 +76,7 @@ const BIDDER_CODE = 'feedad'; * The media types supported by FeedAd * @type {MediaType[]} */ -const MEDIA_TYPES = [VIDEO, BANNER]; +const MEDIA_TYPES = [BANNER]; /** * Tag for logging @@ -204,6 +209,10 @@ function buildRequests(validBidRequests, bidderRequest) { referer: data.refererInfo.referer, transactionId: bid.transactionId }); + if (bidderRequest.gdprConsent) { + data.consentIabTcf = bidderRequest.gdprConsent.consentString; + data.gdprApplies = bidderRequest.gdprConsent.gdprApplies; + } return { method: 'POST', url: `${API_ENDPOINT}${API_PATH_BID_REQUEST}`, @@ -279,6 +288,7 @@ function trackingHandlerFactory(klass) { */ export const spec = { code: BIDDER_CODE, + gvlid: TCF_VENDOR_ID, supportedMediaTypes: MEDIA_TYPES, isBidRequestValid, buildRequests, diff --git a/modules/feedadBidAdapter.md b/modules/feedadBidAdapter.md index fd57025c29e..6f705df36b5 100644 --- a/modules/feedadBidAdapter.md +++ b/modules/feedadBidAdapter.md @@ -18,9 +18,6 @@ Prebid.JS adapter that connects to the FeedAd demand sources. mediaTypes: { banner: { // supports all banner sizes sizes: [[300, 250]], - }, - video: { // supports only outstream video - context: 'outstream' } }, bids: [ diff --git a/modules/haloIdSystem.js b/modules/haloIdSystem.js index d0eb79d4ac2..926e6945436 100644 --- a/modules/haloIdSystem.js +++ b/modules/haloIdSystem.js @@ -5,11 +5,15 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js'; import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; +import * as utils from '../src/utils.js'; const MODULE_NAME = 'haloId'; +const AU_GVLID = 561; + +export const storage = getStorageManager(AU_GVLID, 'halo'); /** @type {Submodule} */ export const haloIdSubmodule = { @@ -37,24 +41,30 @@ export const haloIdSubmodule = { const url = `https://id.halo.ad.gt/api/v1/pbhid`; const resp = function (callback) { - const callbacks = { - success: response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); + let haloId = storage.getDataFromLocalStorage('auHaloId'); + if (utils.isStr(haloId)) { + const responseObj = {haloId: haloId}; + callback(responseObj); + } else { + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + } } + callback(responseObj); + }, + error: error => { + utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); } - callback(responseObj); - }, - error: error => { - utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); - callback(); - } - }; - ajax(url, callbacks, undefined, {method: 'GET'}); + }; + ajax(url, callbacks, undefined, {method: 'GET'}); + } }; return {callback: resp}; } diff --git a/modules/haloRtdProvider.js b/modules/haloRtdProvider.js index 1ce44ca6004..055b40c9d09 100644 --- a/modules/haloRtdProvider.js +++ b/modules/haloRtdProvider.js @@ -1,25 +1,31 @@ /** - * This module adds audigent provider to the real time data module + * This module adds the Audigent Halo provider to the real time data module * The {@link module:modules/realTimeData} module is required - * The module will fetch segments from audigent server - * @module modules/audigentRtdProvider + * The module will fetch real-time data from Audigent + * @module modules/haloRtdProvider * @requires module:modules/realTimeData */ +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import * as utils from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; -import {ajax} from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {isFn, isStr, isPlainObject, mergeDeep, logError} from '../src/utils.js'; -export const storage = getStorageManager(); - -/** @type {string} */ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'halo'; +const AU_GVLID = 561; export const HALOID_LOCAL_NAME = 'auHaloId'; -export const SEG_LOCAL_NAME = '__adgntseg'; +export const RTD_LOCAL_NAME = 'auHaloRtd'; +export const storage = getStorageManager(AU_GVLID, SUBMODULE_NAME); +/** + * Deep set an object unless value present. + * @param {Object} obj + * @param {String} path + * @param {Object} val + */ const set = (obj, path, val) => { const keys = path.split('.'); const lastKey = keys.pop(); @@ -27,83 +33,67 @@ const set = (obj, path, val) => { lastObj[lastKey] = lastObj[lastKey] || val; }; -/** bid adapter format segment augmentation functions */ -const segmentMappers = { - appnexus: function(bid, segments) { - set(bid, 'params.user.segments', []); - let appnexusSegments = []; - segments.forEach(segment => { - if (typeof segment.id != 'undefined' && segment.id != null) { - appnexusSegments.push(parseInt(segment.id)); - } - }) - bid.params.user.segments = bid.params.user.segments.concat(appnexusSegments); - }, - generic: function(bid, segments) { - bid.segments = bid.segments || []; - if (Array.isArray(bid.segments)) { - bid.segments = bid.segments.concat(segments); - } +/** + * Lazy merge objects. + * @param {String} target + * @param {String} source + */ +function mergeLazy(target, source) { + if (!isPlainObject(target)) { + target = {}; + } + if (!isPlainObject(source)) { + source = {}; } + return mergeDeep(target, source); } /** - * decorate adUnits with segment data - * @param {adUnit[]} adUnits - * @param {Object} data + * Param or default. + * @param {String} param + * @param {String} defaultVal */ -export function addSegmentData(adUnits, segmentData, config) { - adUnits.forEach(adUnit => { - if (adUnit.hasOwnProperty('bids')) { - adUnit.bids.forEach(bid => { - try { - set(bid, 'fpd.user.data', []); - if (Array.isArray(bid.fpd.user.data)) { - bid.fpd.user.data.forEach(fpdData => { - let segments = segmentData[fpdData.id] || segmentData[fpdData.name] || []; - fpdData.segment = (fpdData.segment || []).concat(segments); - }); - } - } catch (err) { - utils.logError(err.message); - } +function paramOrDefault(param, defaultVal) { + if (isFn(param)) { + return param(); + } else if (isStr(param)) { + return param; + } + return defaultVal; +} - try { - if (config.params.mapSegments && config.params.mapSegments[bid.bidder] && segmentData[bid.bidder]) { - if (typeof config.params.mapSegments[bid.bidder] == 'function') { - config.params.mapSegments[bid.bidder](bid, segmentData[bid.bidder]); - } else if (segmentMappers[bid.bidder]) { - segmentMappers[bid.bidder](bid, segmentData[bid.bidder]); - } - } - } catch (err) { - utils.logError(err.message); - } - }); - } - }); +/** + * Add real-time data & merge segments. + * @param {Object} bidConfig + * @param {Object} rtd + * @param {Object} rtdConfig + */ +export function addRealTimeData(bidConfig, rtd, rtdConfig) { + let ortb2 = config.getConfig('ortb2') || {}; - return adUnits; + if (rtdConfig.params && rtdConfig.params.handleRtd) { + rtdConfig.params.handleRtd(bidConfig, rtd, rtdConfig, config); + } else if (rtd.ortb2) { + config.setConfig({ortb2: mergeLazy(ortb2, rtd.ortb2)}); + } } /** - * segment retrieval from audigent's backends + * Real-time data retrieval from Audigent * @param {Object} reqBidsConfigObj * @param {function} onDone - * @param {Object} config + * @param {Object} rtdConfig * @param {Object} userConsent */ -export function getSegments(reqBidsConfigObj, onDone, config, userConsent) { - const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; - - if (config.params.segmentCache) { - let jsonData = storage.getDataFromLocalStorage(SEG_LOCAL_NAME); +export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + if (rtdConfig && isPlainObject(rtdConfig.params) && rtdConfig.params.segmentCache) { + let jsonData = storage.getDataFromLocalStorage(RTD_LOCAL_NAME); if (jsonData) { let data = JSON.parse(jsonData); - if (data.audigent_segments) { - addSegmentData(adUnits, data.audigent_segments, config); + if (data.rtd) { + addRealTimeData(bidConfig, data.rtd, rtdConfig); onDone(); return; } @@ -113,53 +103,54 @@ export function getSegments(reqBidsConfigObj, onDone, config, userConsent) { const userIds = (getGlobal()).getUserIds(); let haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); - if (haloId) { + if (isStr(haloId)) { userIds.haloId = haloId; - getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); + getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); } else { var script = document.createElement('script') script.type = 'text/javascript'; - script.onload = function() { - userIds.haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); - getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); + window.pubHaloCb = (haloId) => { + userIds.haloId = haloId; + getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); } - script.src = 'https://id.halo.ad.gt/api/v1/haloid'; + const haloIdUrl = rtdConfig.params && rtdConfig.params.haloIdUrl; + script.src = paramOrDefault(haloIdUrl, 'https://id.halo.ad.gt/api/v1/haloid'); document.getElementsByTagName('head')[0].appendChild(script); } } /** - * async segment retrieval from audigent's backends - * @param {adUnit[]} adUnits + * Async rtd retrieval from Audigent * @param {function} onDone - * @param {Object} config + * @param {Object} rtdConfig * @param {Object} userConsent * @param {Object} userIds */ -export function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) { +export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds) { let reqParams = {}; - if (typeof config == 'object' && config != null) { - set(config, 'params.requestParams', {}); - reqParams = config.params.requestParams; + + if (isPlainObject(rtdConfig)) { + set(rtdConfig, 'params.requestParams.ortb2', config.getConfig('ortb2')); + reqParams = rtdConfig.params.requestParams; } - const url = `https://seg.halo.ad.gt/api/v1/rtb_segments`; + const url = `https://seg.halo.ad.gt/api/v1/rtd`; ajax(url, { success: function (response, req) { if (req.status === 200) { try { const data = JSON.parse(response); - if (data && data.audigent_segments) { - addSegmentData(adUnits, data.audigent_segments, config); + if (data && data.rtd) { + addRealTimeData(bidConfig, data.rtd, rtdConfig); onDone(); - storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); + storage.setDataInLocalStorage(RTD_LOCAL_NAME, JSON.stringify(data)); } else { onDone(); } } catch (err) { - utils.logError('unable to parse audigent segment data'); + logError('unable to parse audigent segment data'); onDone(); } } else if (req.status === 204) { @@ -169,7 +160,7 @@ export function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) }, error: function () { onDone(); - utils.logError('unable to get audigent segment data'); + logError('unable to get audigent segment data'); } }, JSON.stringify({'userIds': userIds, 'config': reqParams}), @@ -178,7 +169,7 @@ export function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) } /** - * module init + * Module init * @param {Object} provider * @param {Objkect} userConsent * @return {boolean} @@ -190,7 +181,7 @@ function init(provider, userConsent) { /** @type {RtdSubmodule} */ export const haloSubmodule = { name: SUBMODULE_NAME, - getBidRequestData: getSegments, + getBidRequestData: getRealTimeData, init: init }; diff --git a/modules/haloRtdProvider.md b/modules/haloRtdProvider.md index 2897a5917fa..02dc6577e49 100644 --- a/modules/haloRtdProvider.md +++ b/modules/haloRtdProvider.md @@ -4,13 +4,13 @@ Audigent is a next-generation data management platform and a first-of-a-kind "data agency" containing some of the most exclusive content-consuming audiences across desktop, mobile and social platforms. -This real-time data module provides quality user segmentation that can be -attached to bid request objects destined for different SSPs in order to optimize +This real-time data module provides quality segmentation that can be +provided to bid request objects destined for different SSPs in order to optimize targeting. Audigent maintains a large database of first-party Tradedesk Unified ID, Audigent Halo ID and other id provider mappings to various third-party segment types that are utilizable across different SSPs. With this module, -these segments can be retrieved and supplied to the SSP in real-time during -the bid request cycle. +these segments and other data can be retrieved and supplied to your pages +and the bidstream in real-time during the bid request cycle. ### Publisher Usage @@ -18,15 +18,12 @@ Compile the Halo RTD module into your Prebid build: `gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,appnexusBidAdapter` -Add the Halo RTD provider to your Prebid config. For any adapters -that you would like to retrieve segments for, add a mapping in the 'mapSegments' -parameter. In this example we will configure publisher 1234 to retrieve -appnexus segments from Audigent. See the "Parameter Descriptions" below for -more detailed information of the configuration parameters. Currently, -OpenRTB compatible fpd data will be added for any bid adapter in the -"mapSegments" objects. Automated bid augmentation exists for some bidders. -Please work with your Audigent Prebid support team (prebid@audigent.com) on -which version of Prebid.js supports which bidders automatically. +Add the Halo RTD provider to your Prebid config. In this example we will configure +publisher 1234 to retrieve segments from Audigent. See the +"Parameter Descriptions" below for more detailed information of the +configuration parameters. Please work with your Audigent Prebid support team +(prebid@audigent.com) on which version of Prebid.js supports different bidder +and segment configurations. ``` pbjs.setConfig( @@ -38,9 +35,6 @@ pbjs.setConfig( name: "halo", waitForIt: true, params: { - mapSegments: { - appnexus: true, - }, segmentCache: false, requestParams: { publisherId: 1234 @@ -53,28 +47,27 @@ pbjs.setConfig( } ``` -### Parameter Descriptions for the Halo `dataProviders` Configuration Section +### Parameter Descriptions for the Halo Configuration Section | Name |Type | Description | Notes | | :------------ | :------------ | :------------ |:------------ | | name | String | Real time data module name | Always 'halo' | | waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | | params | Object | | | -| params.mapSegments | Boolean | Dictionary of bidders you would like to supply Audigent segments for. Maps to boolean values, but also allows functions for custom mapping logic. The function signature is (bid, segments) => {}. | Required | +| params.handleRtd | Function | A passable RTD handler that allows custom adunit and ortb2 logic to be configured. The function signature is (bidConfig, rtd, rtdConfig, pbConfig) => {}. | Optional | | params.segmentCache | Boolean | This parameter tells the Halo RTD module to attempt reading segments from a local storage cache instead of always requesting them from the Audigent server. | Optional. Defaults to false. | | params.requestParams | Object | Publisher partner specific configuration options, such as optional publisher id and other segment query related metadata to be submitted to Audigent's backend with each request. Contact prebid@audigent.com for more information. | Optional | +| params.haloIdUrl | String | Parameter to specify alternate haloid endpoint url. | Optional | -### Overriding & Adding Segment Mappers +### Publisher Customized RTD Handling As indicated above, it is possible to provide your own bid augmentation -functions. This is useful if you know a bid adapter's API supports segment -fields which aren't specifically being added to request objects in the Prebid -bid adapter. You can also override segment mappers by passing a function -instead of a boolean to the Halo RTD segment module. This might be useful -if you'd like to use custom logic to determine which segments are sent -to a specific backend. +functions rather than simply merging supplied data. This is useful if you +want to perform custom bid augmentation and logic with Halo real-time data +prior to the bid request being sent. Simply add your custom logic to the +optional handleRtd parameter and provide your custom RTD handling logic there. Please see the following example, which provides a function to modify bids for -a bid adapter called adBuzz and overrides the appnexus segment mapper. +a bid adapter called adBuzz and perform custom logic on bidder parameters. ``` pbjs.setConfig( @@ -86,19 +79,14 @@ pbjs.setConfig( name: "halo", waitForIt: true, params: { - mapSegments: { - // adding an adBuzz segment mapper - adBuzz: function(bid, segments) { - bid.params.adBuzzCustomSegments = []; - for (var i = 0; i < segments.length; i++) { - bid.params.adBuzzCustomSegments.push(segments[i].id); - } - }, - // overriding the appnexus segment mapper to exclude certain segments - appnexus: function(bid, segments) { - for (var i = 0; i < segments.length; i++) { - if (segments[i].id != 'exclude_segment') { - bid.params.user.segments.push(segments[i].id); + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + var adUnits = bidConfig.adUnits; + for (var i = 0; i < adUnits.length; i++) { + var adUnit = adUnits[i]; + for (var j = 0; j < adUnit.bids.length; j++) { + var bid = adUnit.bids[j]; + if (bid.bidder == 'adBuzz' && rtd['adBuzz'][0].value != 'excludeSeg') { + bid.params.adBuzzCustomSegments.push(rtd['adBuzz'][0].id); } } } @@ -115,7 +103,10 @@ pbjs.setConfig( } ``` -More examples can be viewed in the haloRtdAdapter_spec.js tests. +The handleRtd function can also be used to configure custom ortb2 data +processing. Please see the examples available in the haloRtdProvider_spec.js +tests and work with your Audigent Prebid integration team (prebid@audigent.com) +on how to best configure your own Halo RTD & Open RTB data handlers. ### Testing diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index 716929db70a..df7b03b4e6e 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -67,11 +67,11 @@ export const identityLinkSubmodule = { setEnvelopeSource(true); callback(JSON.parse(envelope).envelope); } else { - getEnvelope(url, callback); + getEnvelope(url, callback, configParams); } }); } else { - getEnvelope(url, callback); + getEnvelope(url, callback, configParams); } }; @@ -79,7 +79,7 @@ export const identityLinkSubmodule = { } }; // return envelope from third party endpoint -function getEnvelope(url, callback) { +function getEnvelope(url, callback, configParams) { const callbacks = { success: response => { let responseObj; @@ -98,7 +98,7 @@ function getEnvelope(url, callback) { } }; - if (!storage.getCookie('_lr_retry_request')) { + if (!configParams.notUse3P && !storage.getCookie('_lr_retry_request')) { setRetryCookie(); utils.logInfo('identityLink: A 3P retrieval is attempted!'); setEnvelopeSource(false); diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js new file mode 100644 index 00000000000..b649b5a8a73 --- /dev/null +++ b/modules/impactifyBidAdapter.js @@ -0,0 +1,260 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import {ajax} from '../src/ajax.js'; + +const BIDDER_CODE = 'impactify'; +const BIDDER_ALIAS = ['imp']; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_VIDEO_WIDTH = 640; +const DEFAULT_VIDEO_HEIGHT = 480; +const ORIGIN = 'https://sonic.impactify.media'; +const LOGGER_URI = 'https://logger.impactify.media'; +const AUCTIONURI = '/bidder'; +const COOKIESYNCURI = '/static/cookie_sync.html'; +const GVLID = 606; +const GETCONFIG = config.getConfig; + +const getDeviceType = () => { + // OpenRTB Device type + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { + return 5; + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + return 4; + } + return 2; +} + +const createOpenRtbRequest = (validBidRequests, bidderRequest) => { + // Create request and set imp bids inside + let request = { + id: bidderRequest.auctionId, + validBidRequests, + cur: [DEFAULT_CURRENCY], + imp: [] + }; + + // Force impactify debugging parameter + if (window.localStorage.getItem('_im_db_bidder') == 3) { + request.test = 3; + } + + // Set device/user/site + if (!request.device) request.device = {}; + if (!request.site) request.site = {}; + request.device = { + w: window.innerWidth, + h: window.innerHeight, + devicetype: getDeviceType(), + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en', + }; + request.site = {page: bidderRequest.refererInfo.referer}; + + // Handle privacy settings for GDPR/CCPA/COPPA + if (bidderRequest.gdprConsent) { + let gdprApplies = 0; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + this.syncStore.uspConsent = bidderRequest.uspConsent; + } + + if (GETCONFIG('coppa') == true) utils.deepSetValue(request, 'regs.coppa', 1); + + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // Set buyer uid + utils.deepSetValue(request, 'user.buyeruid', utils.generateUUID()); + + // Create imps with bids + validBidRequests.forEach((bid) => { + let imp = { + id: bid.bidId, + bidfloor: bid.params.bidfloor ? bid.params.bidfloor : 0, + ext: { + impactify: { + appId: bid.params.appId, + format: bid.params.format, + style: bid.params.style + }, + }, + video: { + playerSize: [DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT], + context: 'outstream', + mimes: ['video/mp4'], + }, + }; + if (bid.params.container) { + imp.ext.impactify.container = bid.params.container; + } + request.imp.push(imp); + }); + + return request; +}; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: ['video'], + aliases: BIDDER_ALIAS, + /** + * 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.params.appId || typeof bid.params.appId != 'string' || !bid.params.format || typeof bid.params.format != 'string' || !bid.params.style || typeof bid.params.style != 'string') { + return false; + } + if (bid.params.format != 'screen' && bid.params.format != 'display') { + return false; + } + if (bid.params.style != 'inline' && bid.params.style != 'impact' && bid.params.style != 'static') { + return false; + } + + return true; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - the bidding request + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + // Create a clean openRTB request + let request = createOpenRtbRequest(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: ORIGIN + AUCTIONURI, + data: JSON.stringify(request), + }; + }, + /** + * 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) { + const serverBody = serverResponse.body; + let bidResponses = []; + + if (!serverBody) { + return bidResponses; + } + + if (!serverBody.seatbid || !serverBody.seatbid.length) { + return []; + } + + serverBody.seatbid.forEach((seatbid) => { + if (seatbid.bid.length) { + bidResponses = [ + ...bidResponses, + ...seatbid.bid + .filter((bid) => bid.price > 0) + .map((bid) => ({ + id: bid.id, + requestId: bid.impid, + cpm: bid.price, + currency: serverBody.cur, + netRevenue: true, + ad: bid.adm, + width: bid.w || 0, + height: bid.h || 0, + ttl: 300, + creativeId: bid.crid || 0, + hash: bid.hash, + expiry: bid.expiry + })), + ]; + } + }); + + return bidResponses; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + + if (!syncOptions.iframeEnabled) { + return []; + } + + 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 += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + } + + if (document.location.search.match(/pbs_debug=true/)) params += `&pbs_debug=true`; + + return [{ + type: 'iframe', + url: ORIGIN + COOKIESYNCURI + params + }]; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function(bid) { + ajax(`${LOGGER_URI}/log/bidder/won`, null, JSON.stringify(bid), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + onTimeout: function(data) { + ajax(`${LOGGER_URI}/log/bidder/timeout`, null, JSON.stringify(data[0]), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + } +}; +registerBidder(spec); diff --git a/modules/impactifyBidAdapter.md b/modules/impactifyBidAdapter.md new file mode 100644 index 00000000000..3de9a8cfb84 --- /dev/null +++ b/modules/impactifyBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Impactify Bidder Adapter +Module Type: Bidder Adapter +Maintainer: thomas.destefano@impactify.io +``` + +# Description + +Module that connects to the Impactify solution. +The impactify bidder need 3 parameters: + - appId : This is your unique publisher identifier + - format : This is the ad format needed, can be : screen or display + - style : This is the ad style needed, can be : inline, impact or static + +# Test Parameters +``` + var adUnits = [{ + code: 'your-slot-div-id', // This is your slot div id + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [{ + bidder: 'impactify', + params: { + appId: 'example.com', + format: 'screen', + style: 'inline' + } + }] + }]; +``` diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 7b972aa37e6..0f7f967ef6b 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -492,7 +492,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { msd = impressions[transactionIds[i]].missingCount; } - trimImpressions(impressions[transactionIds[i]], MAX_REQ_SIZE - BASE_REQ_SIZE); + if (BASE_REQ_SIZE < MAX_REQ_SIZE) { + trimImpressions(impressions[transactionIds[i]], MAX_REQ_SIZE - BASE_REQ_SIZE); + } else { + utils.logError('ix bidder: Base request size has exceeded maximum request size.'); + } if (impressions[transactionIds[i]].hasOwnProperty('missingImps')) { msi = impressions[transactionIds[i]].missingImps.length; diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 2840da8dda6..93552638007 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -81,6 +81,8 @@ export const spec = { version: VERSION, gdprApplies: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : undefined, gdprConsent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : undefined, + coppa: getCoppa(), + usPrivacy: bidderRequest.uspConsent, cookieSupport: !utils.isSafariBrowser() && storage.cookiesAreEnabled(), rcv: getAdblockerRecovered(), adRequests: [...adRequests], @@ -309,4 +311,9 @@ function getDeviceHeight() { return window.innerHeight; } +function getCoppa() { + if (typeof config.getConfig('coppa') === 'boolean') { + return config.getConfig('coppa'); + } +} registerBidder(spec); diff --git a/modules/logicadBidAdapter.js b/modules/logicadBidAdapter.js index 0bf6b886dee..2c919f9c157 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -62,6 +62,7 @@ function newBidRequest(bid, bidderRequest) { prebidJsVersion: '$prebid.version$', referrer: bidderRequest.refererInfo.referer, auctionStartTime: bidderRequest.auctionStart, + eids: bid.userIdAsEids, }; } diff --git a/modules/mass.js b/modules/mass.js index 14fe556a466..01135e7ddff 100644 --- a/modules/mass.js +++ b/modules/mass.js @@ -6,15 +6,16 @@ import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; import find from 'core-js-pure/features/array/find.js'; -export let listenerAdded = false; -export let massEnabled = false; - const defaultCfg = { dealIdPattern: /^MASS/i }; let cfg; -const massBids = {}; +export let listenerAdded = false; +export let isEnabled = false; + +const matchedBids = {}; +let renderers; init(); config.getConfig('mass', config => init(config.mass)); @@ -22,68 +23,113 @@ config.getConfig('mass', config => init(config.mass)); /** * Module init. */ -export function init(customCfg) { - cfg = Object.assign({}, defaultCfg, customCfg); +export function init(userCfg) { + cfg = Object.assign({}, defaultCfg, window.massConfig && window.massConfig.mass, userCfg); if (cfg.enabled === false) { - if (massEnabled) { - massEnabled = false; + if (isEnabled) { getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + isEnabled = false; } } else { - if (!massEnabled) { + if (!isEnabled) { getHook('addBidResponse').before(addBidResponseHook); - massEnabled = true; + isEnabled = true; } } + + if (isEnabled) { + updateRenderers(); + } } /** - * Before hook for 'addBidResponse'. + * Update the list of renderers based on current config. */ -export function addBidResponseHook(next, adUnitCode, bid) { - if (!isMassBid(bid) || !cfg.renderUrl) { - return next(adUnitCode, bid); +export function updateRenderers() { + renderers = []; + + // official MASS renderer: + if (cfg.dealIdPattern && cfg.renderUrl) { + renderers.push({ + match: isMassBid, + render: useDefaultRender(cfg.renderUrl, 'mass') + }); } - const bidRequest = find(this.bidderRequest.bids, bidRequest => - bidRequest.bidId === bid.requestId - ); - - massBids[bid.requestId] = { - bidRequest, - bid, - adm: bid.ad - }; + // add any custom renderer defined in the config: + (cfg.custom || []).forEach(renderer => { + if (!renderer.match && renderer.dealIdPattern) { + renderer.match = useDefaultMatch(renderer.dealIdPattern); + } - bid.ad = '`; + break; + } + }); + } + return result; +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = utils.deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} + +function getNativeAssets(bid) { + return utils._map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin, w, h; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } + }).filter(Boolean); +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + if (!utils.isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (utils.isArray(requestSizes[0])) { + return requestSizes.map(item => + ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + }) + ); + } + + return []; +} diff --git a/modules/outbrainBidAdapter.md b/modules/outbrainBidAdapter.md new file mode 100644 index 00000000000..32df22ddad8 --- /dev/null +++ b/modules/outbrainBidAdapter.md @@ -0,0 +1,111 @@ +# Overview + +``` +Module Name: Outbrain Adapter +Module Type: Bidder Adapter +Maintainer: prog-ops-team@outbrain.com +``` + +# Description + +Module that connects to Outbrain bidder to fetch bids. +Both native and display formats are supported but not at the same time. Using OpenRTB standard. + +# Configuration + +## Bidder and usersync URLs + +The Outbrain adapter does not work without setting the correct bidder and usersync URLs. +You will receive the URLs when contacting us. + +``` +pbjs.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + usersyncUrl: 'https://usersync-url.com' + } +}); +``` + + +# Test Native Parameters +``` + var adUnits = [ + code: '/19968336/prebid_native_example_1', + mediaTypes: { + native: { + image: { + required: false, + sizes: [100, 50] + }, + title: { + required: false, + len: 140 + }, + sponsoredBy: { + required: false + }, + clickUrl: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'outbrain', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] + } + }] + ]; + + pbjs.setConfig({ + outbrain: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` + +# Test Display Parameters +``` + var adUnits = [ + code: '/19968336/prebid_display_example_1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'outbrain', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] + }, + }] + ]; + + pbjs.setConfig({ + outbrain: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index 56e4fdfbfef..3a8f0b05f43 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -13,5 +13,12 @@ export const S2S_VENDORS = { endpoint: 'https://prebid-server.rubiconproject.com/openrtb2/auction', syncEndpoint: 'https://prebid-server.rubiconproject.com/cookie_sync', timeout: 500 + }, + 'openx': { + adapter: 'prebidServer', + enabled: true, + endpoint: 'https://prebid.openx.net/openrtb2/auction', + syncEndpoint: 'https://prebid.openx.net/cookie_sync', + timeout: 1000 } } diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index 0b553ce995b..ff07ee9ede9 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -1,5 +1,4 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; - const BIDDER_CODE = 'proxistore'; const PROXISTORE_VENDOR_ID = 418; @@ -27,41 +26,38 @@ function _createServerRequest(bidRequests, bidderRequest) { language: bidRequests[0].params.language, gdpr: { applies: false, + consentGiven: false }, }; if (bidderRequest && bidderRequest.gdprConsent) { - if ( - typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && - bidderRequest.gdprConsent.gdprApplies - ) { + const { gdprConsent } = bidderRequest; + if (typeof gdprConsent.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { payload.gdpr.applies = true; } - if ( - typeof bidderRequest.gdprConsent.consentString === 'string' && - bidderRequest.gdprConsent.consentString - ) { + if (typeof gdprConsent.consentString === 'string' && gdprConsent.consentString) { payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; } - - if ( - bidderRequest.gdprConsent.vendorData && - bidderRequest.gdprConsent.vendorData.vendorConsents && - typeof bidderRequest.gdprConsent.vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined' - ) { - payload.gdpr.consentGiven = !!bidderRequest.gdprConsent.vendorData - .vendorConsents[PROXISTORE_VENDOR_ID.toString(10)]; + if (gdprConsent.vendorData) { + const {vendorData} = gdprConsent; + const {apiVersion} = gdprConsent; + if (apiVersion === 2 && vendorData.vendor && vendorData.vendor.consents && typeof vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined') { + payload.gdpr.consentGiven = !!vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)]; + } else if (apiVersion === 1 && vendorData.vendorConsents && typeof vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined') { + payload.gdpr.consentGiven = !!vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)]; + } } } const options = { contentType: 'application/json', - withCredentials: !!payload.gdpr.consentGiven, + withCredentials: payload.gdpr.consentGiven, }; + const endPointUri = payload.gdpr.consentGiven || !payload.gdpr.applies ? `https://abs.proxistore.com/${payload.language}/v3/rtb/prebid/multi` - : `https://abs.proxistore.com/${payload.language}/v3/rtb/prebid/multi/cookieless`; + : `https://abs.cookieless-proxistore.com/${payload.language}/v3/rtb/prebid/multi`; return { method: 'POST', diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 5c750e66c25..2df2d25f627 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -7,8 +7,8 @@ import { deepSetValue, inIframe, isArrayOfNums, + isFn, isInteger, - isNumber, isStr, logError, parseQueryStringParameters, @@ -185,9 +185,16 @@ function buildImp(bid) { imp.video = buildVideoParams(bid.mediaTypes.video, bid.params.video); } - const bidFloor = bid.params.bidFloor; - if (isNumber(bidFloor)) { - imp.bidfloor = bidFloor; + if (isFn(bid.getFloor)) { + const { floor } = bid.getFloor({ + mediaType: bid.mediaTypes.banner ? 'banner' : 'video', + size: '*', + currency: 'USD', + }); + + if (floor) { + imp.bidfloor = floor; + } } const pos = bid.params.position; diff --git a/modules/pubgeniusBidAdapter.md b/modules/pubgeniusBidAdapter.md index ff23a433331..66e0c382285 100644 --- a/modules/pubgeniusBidAdapter.md +++ b/modules/pubgeniusBidAdapter.md @@ -12,8 +12,6 @@ Module that connects to pubGENIUS's demand sources # Test Parameters -Test bids have $0.01 CPM by default. Use `bidFloor` in bidder params to control CPM for testing purposes. - ``` var adUnits = [ { @@ -45,7 +43,6 @@ var adUnits = [ bidder: 'pubgenius', params: { adUnitId: '1000', - bidFloor: 0.5, test: true } } @@ -66,7 +63,6 @@ var adUnits = [ bidder: 'pubgenius', params: { adUnitId: '1001', - bidFloor: 1, test: true, // other video parameters as in OpenRTB v2.5 spec diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js index 2f4173f240b..31e430d79f9 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -46,6 +46,19 @@ export const spec = { } }; + if (bidderRequest.gdprConsent) { + request.user = { + ext: { + consent: bidderRequest.gdprConsent.consentString || '' + }, + }; + request.regs = { + ext: { + gdpr: bidderRequest.gdprConsent.gdprApplies !== undefined ? bidderRequest.gdprConsent.gdprApplies : true + } + }; + } + return { method: 'POST', url: ENDPOINT, @@ -87,6 +100,11 @@ function bidResponseAvailable(bidRequest, bidResponse) { currency: bidResponse.cur, native: nativeResponse(idToImpMap[id], idToBidMap[id]) }; + if (idToBidMap[id].adomain) { + bid.meta = { + advertiserDomains: idToBidMap[id].adomain + } + } bids.push(bid); } }); @@ -94,11 +112,20 @@ function bidResponseAvailable(bidRequest, bidResponse) { } function impression(slot) { + let bidFloorFromModule + if (typeof slot.getFloor === 'function') { + const floorInfo = slot.getFloor({ + currency: 'USD', + mediaType: 'native', + size: '\*' + }); + bidFloorFromModule = floorInfo.currency === 'USD' ? floorInfo.floor : undefined; + } return { id: slot.bidId, native: nativeImpression(slot), - bidfloor: slot.params.bidfloor || 0, - bidfloorcur: slot.params.bidfloorcur || 'USD', + bidfloor: bidFloorFromModule || slot.params.bidfloor || 0, + bidfloorcur: (bidFloorFromModule && 'USD') || slot.params.bidfloorcur || 'USD', tagId: slot.params.tagId || '0' }; } diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 37a9554e9a4..5e2a5e1bff5 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -31,7 +31,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bid => { var payload = { - bidfloor: bid.params.bidfloor, + bidfloor: raiGetFloor(bid, config), ifa: bid.params.ifa, pid: bid.params.pid, supplyType: bid.params.supplyType, @@ -141,11 +141,15 @@ export const spec = { var syncUrl = ''; var consent = ''; + var raiSync = {}; + + raiSync = raiGetSyncInclude(config); + if (gdprConsent && typeof gdprConsent.consentString === 'string' && typeof gdprConsent.consentString != 'undefined') { consent = `consentString=${gdprConsent.consentString}` } - if (syncOptions.iframeEnabled) { + if (syncOptions.iframeEnabled && raiSync.raiIframe != 'exclude') { syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand if (consent != '') { syncUrl += `&${consent}` @@ -156,7 +160,7 @@ export const spec = { }); } - if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0) { + if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0 && raiSync.raiImage != 'exclude') { syncUrl = `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=${REFERER}`; if (consent != '') { syncUrl += `&${consent}` @@ -263,3 +267,42 @@ function raiGetResolution() { } return resolution; } + +function raiGetSyncInclude(config) { + try { + let raConfig = null; + let raiSync = {}; + if (config.getConfig('userSync').filterSettings != null && typeof config.getConfig('userSync').filterSettings != 'undefined') { + raConfig = config.getConfig('userSync').filterSettings + if (raConfig.iframe != null && typeof raConfig.iframe != 'undefined') { + raiSync.raiIframe = raConfig.iframe.bidders == 'richaudience' || raConfig.iframe.bidders == '*' ? raConfig.iframe.filter : 'exclude'; + } + if (raConfig.image != null && typeof raConfig.image != 'undefined') { + raiSync.raiImage = raConfig.image.bidders == 'richaudience' || raConfig.image.bidders == '*' ? raConfig.image.filter : 'exclude'; + } + } + return raiSync; + } catch (e) { + return null; + } +} + +function raiGetFloor(bid, config) { + try { + let raiFloor; + if (bid.params.bidfloor != null) { + raiFloor = bid.params.bidfloor; + } else if (typeof bid.getFloor == 'function') { + let floorSpec = bid.getFloor({ + currency: config.getConfig('currency.adServerCurrency'), + mediaType: bid.mediaType.banner ? 'banner' : 'video', + size: '*' + }) + + raiFloor = floorSpec.floor; + } + return raiFloor + } catch (e) { + return 0 + } +} diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 90575cf8cf1..74f098c0292 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -363,8 +363,9 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { const height = bid.height || bid.playerHeight; return (width && height) ? {width, height} : undefined; }, - 'pbsBidId', - 'seatBidId', + // Handling use case where pbs sends back 0 or '0' bidIds + 'pbsBidId', pbsBidId => pbsBidId == 0 ? utils.generateUUID() : pbsBidId, + 'seatBidId', seatBidId => seatBidId == 0 ? utils.generateUUID() : seatBidId, 'floorValue', () => utils.deepAccess(bid, 'floorData.floorValue'), 'floorRuleValue', () => utils.deepAccess(bid, 'floorData.floorRuleValue'), 'floorRule', () => utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined, diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 24be8673615..eef18288b17 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,7 +1,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; -const VERSION = '3.3.1'; +const VERSION = '3.3.2'; const BIDDER_CODE = 'sharethrough'; const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1'; const DEFAULT_SIZE = [1, 1]; @@ -48,6 +49,10 @@ export const sharethroughAdapterSpec = { query.us_privacy = bidderRequest.uspConsent } + if (config.getConfig('coppa') === true) { + query.coppa = true + } + if (bidRequest.schain) { query.schain = JSON.stringify(bidRequest.schain); } diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js new file mode 100644 index 00000000000..373468b2f14 --- /dev/null +++ b/modules/sirdataRtdProvider.js @@ -0,0 +1,402 @@ +/** + * This module adds Sirdata provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch segments (user-centric) and categories (page-centric) from Sirdata server + * The module will automatically handle user's privacy and choice in California (IAB TL CCPA Framework) and in Europe (IAB EU TCF FOR GDPR) + * @module modules/sirdataRtdProvider + * @requires module:modules/realTimeData + */ +import {getGlobal} from '../src/prebidGlobal.js'; +import * as utils from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import findIndex from 'core-js-pure/features/array/find-index.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { config } from '../src/config.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'SirdataRTDModule'; + +export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + moduleConfig.params = moduleConfig.params || {}; + + var tcString = (userConsent && userConsent.gdpr && userConsent.gdpr.consentString ? userConsent.gdpr.consentString : ''); + var gdprApplies = (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies ? userConsent.gdpr.gdprApplies : ''); + + moduleConfig.params.partnerId = moduleConfig.params.partnerId ? moduleConfig.params.partnerId : 1; + moduleConfig.params.key = moduleConfig.params.key ? moduleConfig.params.key : 1; + + var sirdataDomain; + var sendWithCredentials; + + if (userConsent.coppa || (userConsent.usp && (userConsent.usp[0] == '1' && (userConsent.usp[1] == 'N' || userConsent.usp[2] == 'Y')))) { + // if children or "Do not Sell" management in California, no segments, page categories only whatever TCF signal + sirdataDomain = 'cookieless-data.com'; + sendWithCredentials = false; + gdprApplies = null; + tcString = ''; + } else if (getGlobal().getConfig('consentManagement.gdpr')) { + // Default endpoint is cookieless if gdpr management is set. Needed because the cookie-based endpoint will fail and return error if user is located in Europe and no consent has been given + sirdataDomain = 'cookieless-data.com'; + sendWithCredentials = false; + } + + // default global endpoint is cookie-based if no rules falls into cookieless or consent has been given or GDPR doesn't apply + if (!sirdataDomain || !gdprApplies || (utils.deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { + sirdataDomain = 'sddan.com'; + sendWithCredentials = true; + } + + var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().referer; + + const url = 'https://kvt.' + sirdataDomain + '/api/v1/public/p/' + moduleConfig.params.partnerId + '/d/' + moduleConfig.params.key + '/s?callback=&gdpr=' + gdprApplies + '&gdpr_consent=' + tcString + (actualUrl ? '&url=' + actualUrl : ''); + ajax(url, { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.segments) { + addSegmentData(adUnits, data, moduleConfig, onDone); + } else { + onDone(); + } + } catch (e) { + onDone(); + utils.logError('unable to parse Sirdata data' + e); + } + } else if (req.status === 204) { + onDone(); + } + }, + error: function () { + onDone(); + utils.logError('unable to get Sirdata data'); + } + }, + null, + { + contentType: 'text/plain', + method: 'GET', + withCredentials: sendWithCredentials, + referrerPolicy: 'unsafe-url', + crossOrigin: true + }); +} + +export function setGlobalOrtb2(segments, categories) { + try { + let addOrtb2 = {}; + let testGlobal = getGlobal().getConfig('ortb2') || {}; + if (!utils.deepAccess(testGlobal, 'user.ext.data.sd_rtd') || !utils.deepEqual(testGlobal.user.ext.data.sd_rtd, segments)) { + utils.deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + } + if (!utils.deepAccess(testGlobal, 'site.ext.data.sd_rtd') || !utils.deepEqual(testGlobal.site.ext.data.sd_rtd, categories)) { + utils.deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); + } + if (!utils.isEmpty(addOrtb2)) { + let ortb2 = {ortb2: utils.mergeDeep({}, testGlobal, addOrtb2)}; + getGlobal().setConfig(ortb2); + } + } catch (e) { + utils.logError(e) + } + + return true; +} + +export function setBidderOrtb2(bidder, segments, categories) { + try { + let addOrtb2 = {}; + let testBidder = utils.deepAccess(config.getBidderConfig(), bidder + '.ortb2') || {}; + if (!utils.deepAccess(testBidder, 'user.ext.data.sd_rtd') || !utils.deepEqual(testBidder.user.ext.data.sd_rtd, segments)) { + utils.deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + } + if (!utils.deepAccess(testBidder, 'site.ext.data.sd_rtd') || !utils.deepEqual(testBidder.site.ext.data.sd_rtd, categories)) { + utils.deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); + } + if (!utils.isEmpty(addOrtb2)) { + let ortb2 = {ortb2: utils.mergeDeep({}, testBidder, addOrtb2)}; + getGlobal().setBidderConfig({ bidders: [bidder], config: ortb2 }); + } + } catch (e) { + utils.logError(e) + } + + return true; +} + +export function loadCustomFunction (todo, adUnit, list, data, bid) { + try { + if (typeof todo == 'function') { + todo(adUnit, list, data, bid); + } + } catch (e) { utils.logError(e); } + return true; +} + +export function getSegAndCatsArray(data, minScore) { + var sirdataData = {'segments': [], 'categories': []}; + minScore = minScore && typeof minScore == 'number' ? minScore : 30; + try { + if (data && data.contextual_categories) { + for (let catId in data.contextual_categories) { + let value = data.contextual_categories[catId]; + if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { + sirdataData.categories.push(catId.toString()); + } + } + } + } catch (e) { utils.logError(e); } + try { + if (data && data.segments) { + for (let segId in data.segments) { + sirdataData.segments.push(data.segments[segId].toString()); + } + } + } catch (e) { utils.logError(e); } + return sirdataData; +} + +export function addSegmentData(adUnits, data, moduleConfig, onDone) { + moduleConfig = moduleConfig || {}; + moduleConfig.params = moduleConfig.params || {}; + const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30; + var sirdataData = getSegAndCatsArray(data, globalMinScore); + + if (!sirdataData || (sirdataData.segments.length < 1 && sirdataData.categories.length < 1)) { utils.logError('no cats'); onDone(); return adUnits; } + + const sirdataList = sirdataData.segments.concat(sirdataData.categories); + + var curationData = {'segments': [], 'categories': []}; + var curationId = '1'; + const biddersParamsExist = (!!(moduleConfig.params && moduleConfig.params.bidders)); + + // Global ortb2 + if (!biddersParamsExist) { + setGlobalOrtb2(sirdataData.segments, sirdataData.categories); + } + + // Google targeting + if (typeof window.googletag !== 'undefined' && (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues'))) { + try { + // For curation Google is pid 27449 + curationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : '27449'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], globalMinScore); + } + window.googletag.pubads().getSlots().forEach(function(n) { + if (typeof n.setTargeting !== 'undefined') { + n.setTargeting('sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + }) + } catch (e) { utils.logError(e); } + } + + // Bid targeting level for FPD non-generic biders + var bidderIndex = ''; + var indexFound = false; + + adUnits.forEach(adUnit => { + if (!biddersParamsExist && !utils.deepAccess(adUnit, 'ortb2Imp.ext.data.sd_rtd')) { + utils.deepSetValue(adUnit, 'ortb2Imp.ext.data.sd_rtd', sirdataList); + } + + adUnit.hasOwnProperty('bids') && adUnit.bids.forEach(bid => { + bidderIndex = (moduleConfig.params.hasOwnProperty('bidders') ? findIndex(moduleConfig.params.bidders, function(i) { return i.bidder === bid.bidder; }) : false); + indexFound = (!!(typeof bidderIndex == 'number' && bidderIndex >= 0)); + try { + curationData = {'segments': [], 'categories': []}; + let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore) + + if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { + switch (bid.bidder) { + case 'appnexus': + case 'appnexusAst': + case 'brealtime': + case 'emxdigital': + case 'pagescience': + case 'gourmetads': + case 'matomy': + case 'featureforward': + case 'oftmedia': + case 'districtm': + case 'adasta': + case 'beintoo': + case 'gravity': + case 'msq_classic': + case 'msq_max': + case '366_apx': + // For curation Xandr is pid 27446 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27446'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + utils.deepSetValue(bid, 'params.keywords.sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'smartadserver': + case 'smart': + var target = []; + if (bid.hasOwnProperty('params') && bid.params.hasOwnProperty('target')) { + target.push(bid.params.target); + } + // For curation Smart is pid 27440 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27440'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) { + if (target.indexOf('sd_rtd=' + entry) === -1) { + target.push('sd_rtd=' + entry); + } + }); + utils.deepSetValue(bid, 'params.target', target.join(';')); + } + break; + + case 'rubicon': + // For curation Magnite is pid 27518 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27452'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'ix': + var ixConfig = getGlobal().getConfig('ix.firstPartyData.sd_rtd'); + if (!ixConfig) { + // For curation index is pid 27248 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27248'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + var cappIxCategories = []; + var ixLength = 0; + var ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); + // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters + sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) { + if (ixLength < ixLimit) { + cappIxCategories.push(entry); + ixLength += entry.toString().length; + } + }); + getGlobal().setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); + } + } + break; + + case 'proxistore': + // For curation Proxistore is pid 27484 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27484'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } else { + data.shared_taxonomy[curationId] = {contextual_categories: {}}; + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + utils.deepSetValue(bid, 'ortb2.user.ext.data', {segments: sirdataData.segments.concat(curationData.segments), contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories}}); + } + break; + + case 'criteo': + // For curation Smart is pid 27443 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27443'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, sirdataList.concat(curationData.segments).concat(curationData.categories), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'triplelift': + // For curation Triplelift is pid 27518 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27518'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'avct': + case 'avocet': + // For curation Avocet is pid 27522 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27522'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'smaato': + // For curation Smaato is pid 27520 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27520'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + default: + if (!biddersParamsExist || indexFound) { + if (!utils.deepAccess(bid, 'ortb2.site.ext.data.sd_rtd')) { + utils.deepSetValue(bid, 'ortb2.site.ext.data.sd_rtd', sirdataData.categories); + } + if (!utils.deepAccess(bid, 'ortb2.user.ext.data.sd_rtd')) { + utils.deepSetValue(bid, 'ortb2.user.ext.data.sd_rtd', sirdataData.segments); + } + } + } + } + } catch (e) { utils.logError(e) } + }) + }); + + onDone(); + return adUnits; +} + +export function init(config) { + return true; +} + +export const sirdataSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: getSegmentsAndCategories +}; + +submodule(MODULE_NAME, sirdataSubmodule); diff --git a/modules/sirdataRtdProvider.md b/modules/sirdataRtdProvider.md new file mode 100644 index 00000000000..f67e34db43a --- /dev/null +++ b/modules/sirdataRtdProvider.md @@ -0,0 +1,169 @@ +# Sirdata Real-Time Data Submodule + +Module Name: Sirdata Rtd Provider +Module Type: Rtd Provider +Maintainer: bob@sirdata.com + +# Description + +Sirdata provides a disruptive API that allows its partners to leverage its +cutting-edge contextualization technology and its audience segments based on +cookies and consent or without cookies nor consent! + +User-based segments and page-level automatic contextual categories will be +attached to bid request objects sent to different SSPs in order to optimize +targeting. + +Automatic integration with Google Ad Manager and major bidders like Xandr/Appnexus, +Smartadserver, Index Exchange, Proxistore, Magnite/Rubicon or Triplelift ! + +User's country and choice management are included in the module, so it's 100% +compliant with local and regional laws like GDPR and CCPA/CPRA. + +ORTB2 compliant and FPD support for Prebid versions < 4.29 + +Contact bob@sirdata.com for information. + +### Publisher Usage + +Compile the Sirdata RTD module into your Prebid build: + +`gulp build --modules=rtdModule,sirdataRtdProvider` + +Add the Sirdata RTD provider to your Prebid config. + +Segments ids (user-centric) and category ids (page-centric) will be provided +salted and hashed : you can use them with a dedicated and private matching table. +Should you want to allow a SSP or a partner to curate your media and operate +cross-publishers campaigns with our data, please ask Sirdata (bob@sirdata.com) to +open it for you account. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "SirdataRTDModule", + waitForIt: true, + params: { + partnerId: 1, + key: 1, + setGptKeyValues: true, + contextualMinRelevancyScore: 50, //Min score to filter contextual category globally (0-100 scale) + actualUrl: actual_url, //top location url, for contextual categories + bidders: [{ + bidder: 'appnexus', + adUnitCodes: ['adUnit-1','adUnit-2'], + customFunction: overrideAppnexus, + curationId: '111', + },{ + bidder: 'ix', + sizeLimit: 1200 //specific to Index Exchange, + contextualMinRelevancyScore: 50, //Min score to filter contextual category for curation in the bidder (0-100 scale) + }] + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the Sirdata Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Mandatory. Always 'SirdataRTDModule' | +| waitForIt | Boolean | Mandatory. Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false but recommended to true | +| params | Object | | Optional | +| params.partnerId | Integer | Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. | +| params.key | Integer | Key linked to Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. | +| params.setGptKeyValues | Boolean | This parameter Sirdata to set Targeting for GPT/GAM | Optional. Defaults to true. | +| params.contextualMinRelevancyScore | Integer | Min score to keep filter category in the bidders (0-100 scale). Optional. Defaults to 30. | +| params.bidders | Object | Dictionary of bidders you would like to supply Sirdata data for. | Optional. In case no bidder is specified Sirdata will atend to ad data custom and ortb2 to all bidders, adUnits & Globalconfig | + +Bidders can receive common setting : +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| bidder | String | Bidder name | Mandatory if params.bidders are specified | +| adUnitCodes | Array of String | Use if you want to limit data injection to specified adUnits for the bidder | Optional. Default is false and data shared with the bidder isn't filtered | +| customFunction | Function | Use it to override the way data is shared with a bidder | Optional. Default is false | +| curationId | String | Specify the curation ID of the bidder. Provided by Sirdata, request it at bob@sirdata.com | Optional. Default curation ids are specified for main bidders | +| contextualMinRelevancyScore | Integer | Min score to filter contextual categories for curation in the bidder (0-100 scale). Optional. Defaults to 30 or global params.contextualMinRelevancyScore if exits. | +| sizeLimit | Integer | used only for bidder 'ix' to limit the size of the get parameter in Index Exchange ad call | Optional. Default is 1000 | + + +### Overriding data sharing function +As indicated above, it is possible to provide your own bid augmentation +functions. This is useful if you know a bid adapter's API supports segment +fields which aren't specifically being added to request objects in the Prebid +bid adapter. + +Please see the following example, which provides a function to modify bids for +a bid adapter called ix and overrides the appnexus. + +data Object format for usage in this kind of function : +{ + "segments":[111111,222222], + "contextual_categories":{"333333":100}, + "shared_taxonomy":{ + "27446":{ //CurationId + "segments":[444444,555555], + "contextual_categories":{"666666":100} + } + } +} + +``` +function overrideAppnexus (adUnit, segmentsArray, dataObject, bid) { + for (var i = 0; i < segmentsArray.length; i++) { + if (segmentsArray[i]) { + bid.params.user.segments.push(segmentsArray[i]); + } + } +} + +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "SirdataRTDModule", + waitForIt: true, + params: { + partnerId: 1, + key: 1, + setGptKeyValues: true, + contextualMinRelevancyScore: 50, //Min score to keep contextual category in the bidders (0-100 scale) + actualUrl: actual_url, //top location url, for contextual categories + bidders: [{ + bidder: 'appnexus', + customFunction: overrideAppnexus, + curationId: '111' + },{ + bidder: 'ix', + sizeLimit: 1200, //specific to Index Exchange + customFunction: function(adUnit, segmentsArray, dataObject, bid) { + bid.params.contextual.push(dataObject.contextual_categories); + }, + }] + } + } + ] + } + ... +} +``` + +### Testing + +To view an example of available segments returned by Sirdata's backends: + +`gulp serve --modules=rtdModule,sirdataRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/sirdataRtdProvider_example.html` \ No newline at end of file diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 57c54cb5090..6b34e499a99 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -149,6 +149,7 @@ export const spec = { } const language = navigator.language ? 'language' : 'userLanguage'; + const device = { h: screen.height, w: screen.width, @@ -157,8 +158,11 @@ export const spec = { make: navigator.vendor ? navigator.vendor : '', ua: navigator.userAgent }; + const at = utils.getBidIdParameter('at', bid.params) || 2; + const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR']; + const requestPayload = { id: utils.generateUUID(), imp: smartxReq, @@ -176,6 +180,7 @@ export const spec = { at: at, cur: cur }; + const userExt = {}; // Add GDPR flag and consent string @@ -271,6 +276,7 @@ export const spec = { serverResponseBody.cur = pmb.currency; } }); + const bid = { requestId: currentBidRequest.bidId, currency: serverResponseBody.cur || 'USD', @@ -284,7 +290,14 @@ export const spec = { width: smartxBid.w, height: smartxBid.h }; + + bid.meta = bid.meta || {}; + if (smartxBid && smartxBid.adomain && smartxBid.adomain.length > 0) { + bid.meta.advertiserDomains = smartxBid.adomain; + } + const context = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); + if (context === 'outstream') { const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ @@ -366,7 +379,7 @@ function createOutstreamScript(bid) { smartPlayObj.adResponse = bid.vastContent; - const divID = '#' + elementId; + const divID = '[id="' + elementId + '"]'; var script = document.createElement('script'); script.src = 'https://dco.smartclip.net/?plc=7777778'; script.type = 'text/javascript'; diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index f965310abdd..fb05298a230 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -108,16 +108,31 @@ export const spec = { * @param {*} serverResponses A successful response from the server. * @return {Syncs[]} An array of syncs that should be executed. */ - getUserSyncs: function(syncOptions, serverResponses) { - const syncs = [] - if (syncOptions.iframeEnabled && serverResponses.length > 0) { - if (serverResponses[0].body.cSyncUrl === 'https://csync.smilewanted.com') { - syncs.push({ - type: 'iframe', - url: serverResponses[0].body.cSyncUrl - }); + getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { + let params = ''; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `?gdpr_consent=${gdprConsent.consentString}`; } } + + if (uspConsent) { + params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + } + + const syncs = [] + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://csync.smilewanted.com' + params + }); + } + return syncs; } } diff --git a/modules/sortableBidAdapter.js b/modules/sortableBidAdapter.js index f5d3a6b1bb5..6989abff143 100644 --- a/modules/sortableBidAdapter.js +++ b/modules/sortableBidAdapter.js @@ -2,6 +2,7 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'sortable'; const SERVER_URL = 'https://c.deployads.com'; @@ -218,6 +219,8 @@ export const spec = { return rv; }); const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const bidUserId = validBidReqs[0].userId; + const eids = createEidsArray(bidUserId); const sortableBidReq = { id: utils.getUniqueIdentifierStr(), imp: sortableImps, @@ -241,6 +244,9 @@ export const spec = { h: screen.height }, }, + user: { + ext: {} + } }; if (bidderRequest && bidderRequest.timeout > 0) { sortableBidReq.tmax = bidderRequest.timeout; @@ -255,6 +261,9 @@ export const spec = { sortableBidReq.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0 } } + if (eids.length) { + sortableBidReq.user.ext.eids = eids; + } if (bidderRequest.uspConsent) { sortableBidReq.regs.ext.us_privacy = bidderRequest.uspConsent; } diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 05e4e0ba1ef..b60d25db4d6 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -291,9 +291,10 @@ export const spec = { if (!utils.isEmpty(userExt)) { requestPayload.user = { ext: userExt }; } + const urlQueryParams = 'src_sys=prebid' return { method: 'POST', - url: URL + channelId, + url: URL + channelId + '?' + urlQueryParams, data: requestPayload, bidRequest: bidderRequest }; @@ -352,6 +353,7 @@ export const spec = { } else { bid.cache_key = spotxBid.ext.cache_key; bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key + bid.videoCacheKey = spotxBid.ext.cache_key; } bid.meta = bid.meta || {}; @@ -365,7 +367,7 @@ export const spec = { const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: '//', + url: '/', config: { adText: 'SpotX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 7782f151802..1228fbafaad 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -2,18 +2,22 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'tappx'; const TTL = 360; const CUR = 'USD'; +const TAPPX_BIDDER_VERSION = '0.1.10413'; +const TYPE_CNN = 'prebidjs'; +const VIDEO_SUPPORT = ['instream']; + var HOST; var hostDomain; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -22,11 +26,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - if ((bid.params == null) || (bid.params.endpoint == null) || (bid.params.tappxkey == null)) { - utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters. ${JSON.stringify(bid)}`); - return false; - } - return true; + return validBasic(bid) && validMediaType(bid) }, /** @@ -61,7 +61,7 @@ export const spec = { const bids = []; responseBody.seatbid.forEach(serverSeatBid => { serverSeatBid.bid.forEach(serverBid => { - bids.push(interpretBannerBid(serverBid, originalRequest)); + bids.push(interpretBid(serverBid, originalRequest)); }); }); @@ -104,25 +104,66 @@ export const spec = { } } +function validBasic(bid) { + if ( + (bid.params == null) || + (bid.params.endpoint == null) || + (bid.params.tappxkey == null)) { + utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters.`); + return false; + } + return true; +} + +function validMediaType(bid) { + const video = utils.deepAccess(bid, 'mediaTypes.video'); + + // Video validations + if (typeof video != 'undefined') { + if (VIDEO_SUPPORT.indexOf(video.context) === -1) { + utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters for Video. Only "instream" is suported.`); + return false; + } + } + + return true; +} + /** * Parse the response and generate one bid object. * * @param {object} serverBid Bid by OpenRTB 2.5 * @returns {object} Prebid banner bidObject */ -function interpretBannerBid(serverBid, request) { - return { +function interpretBid(serverBid, request) { + let bidReturned = { requestId: request.bids.bidId, cpm: serverBid.price, currency: serverBid.cur ? serverBid.cur : CUR, width: serverBid.w, height: serverBid.h, - ad: serverBid.adm, ttl: TTL, creativeId: serverBid.crid, netRevenue: true, - mediaType: BANNER, } + + if (typeof serverBid.dealId != 'undefined') { bidReturned.dealId = serverBid.dealId } + + if (typeof request.bids.mediaTypes != 'undefined' && typeof request.bids.mediaTypes.video != 'undefined') { + bidReturned.vastXml = serverBid.adm; + bidReturned.vastUrl = serverBid.lurl; + bidReturned.ad = serverBid.adm; + bidReturned.mediaType = VIDEO; + } else { + bidReturned.ad = serverBid.adm; + bidReturned.mediaType = BANNER; + } + + if (typeof bidReturned.adomain != 'undefined' || bidReturned.adomain != null) { + bidReturned.meta = { advertiserDomains: request.bids.adomain }; + } + + return bidReturned; } /** @@ -134,12 +175,14 @@ function interpretBannerBid(serverBid, request) { */ function buildOneRequest(validBidRequests, bidderRequest) { HOST = utils.deepAccess(validBidRequests, 'params.host'); - hostDomain = HOST.split('/', 1)[0]; + let hostInfo = getHostInfo(HOST); + hostDomain = hostInfo.domain; const ENDPOINT = utils.deepAccess(validBidRequests, 'params.endpoint'); const TAPPXKEY = utils.deepAccess(validBidRequests, 'params.tappxkey'); const BIDFLOOR = utils.deepAccess(validBidRequests, 'params.bidfloor'); const bannerMediaType = utils.deepAccess(validBidRequests, 'mediaTypes.banner'); + const videoMediaType = utils.deepAccess(validBidRequests, 'mediaTypes.video'); const { refererInfo } = bidderRequest; // let requests = []; @@ -201,11 +244,31 @@ function buildOneRequest(validBidRequests, bidderRequest) { imp.banner = banner; } + if (videoMediaType) { + let video = {}; + w = videoMediaType.playerSize[0][0]; + h = videoMediaType.playerSize[0][1]; + video.w = w; + video.h = h; + + video.mimes = videoMediaType.mimes; + + imp.video = video; + } + imp.id = validBidRequests.bidId; imp.tagid = tagid; imp.secure = 1; imp.bidfloor = utils.deepAccess(validBidRequests, 'params.bidfloor'); + + let bidder = {}; + bidder.tappxkey = TAPPXKEY; + bidder.endpoint = ENDPOINT; + bidder.host = hostInfo.url; + + imp.ext = {}; + imp.ext.bidder = bidder; // < Imp object // > Device object @@ -230,8 +293,6 @@ function buildOneRequest(validBidRequests, bidderRequest) { // > Params let params = {}; params.host = 'tappx.com'; - params.tappxkey = TAPPXKEY; - params.endpoint = ENDPOINT; params.bidfloor = BIDFLOOR; // < Params @@ -253,6 +314,14 @@ function buildOneRequest(validBidRequests, bidderRequest) { if (config.getConfig('coppa') === true) { regs.coppa = config.getConfig('coppa') === true ? 1 : 0; } + + // Universal ID + const eidsArr = utils.deepAccess(validBidRequests, 'userIdAsEids'); + payload.user = { + ext: { + eids: eidsArr + } + }; // < GDPR // > Payload @@ -270,7 +339,7 @@ function buildOneRequest(validBidRequests, bidderRequest) { return { method: 'POST', - url: `https://${HOST}/${ENDPOINT}?type_cnn=prebidjs`, + url: `https://${HOST}/${ENDPOINT}?type_cnn=${TYPE_CNN}&v=${TAPPX_BIDDER_VERSION}`, data: JSON.stringify(payload), bids: validBidRequests }; @@ -286,4 +355,26 @@ function getOs() { if (ua == null) { return 'unknown'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'ios'; } else if (ua.match(/Android/)) { return 'android'; } else if (ua.match(/Window/)) { return 'windows'; } else { return 'unknown'; } } +function getHostInfo(hostParam) { + let domainInfo = {}; + + domainInfo.domain = hostParam.split('/', 1)[0]; + domainInfo.url = hostParam; + + let regexNewEndpoints = new RegExp(`^(zz.*|testing)\.ssp\.tappx\.com$`, 'i'); + let regexClassicEndpoints = new RegExp(`^[a-z]{3}\.[a-z]{3}\.tappx\.com$`, 'i'); + + if (regexNewEndpoints.test(domainInfo.domain)) { + let endpoint = domainInfo.domain.split('.', 1)[0] + if (endpoint.toUpperCase().indexOf('TESTING') === -1) { + domainInfo.endpoint = endpoint + domainInfo.new_endpoint = true; + } + } else if (regexClassicEndpoints.test(domainInfo.domain)) { + domainInfo.new_endpoint = false; + } + + return domainInfo; +} + registerBidder(spec); diff --git a/modules/tappxBidAdapter.md b/modules/tappxBidAdapter.md index d9ffd98b6c5..e6581a67d06 100644 --- a/modules/tappxBidAdapter.md +++ b/modules/tappxBidAdapter.md @@ -7,6 +7,7 @@ Maintainer: prebid@tappx.com # Description Module that connects to :tappx demand sources. +Suppots Banner and Instream Video. Please use ```tappx``` as the bidder code. Ads sizes available: [320,50], [300,250], [320,480], [1024,768], [728,90] @@ -35,3 +36,35 @@ Ads sizes available: [320,50], [300,250], [320,480], [1024,768], [728,90] } ]; ``` + + +# Video Test Parameters +``` + var adUnits = [ + { + code: 'video-ad-div', + renderer: { + options: { + text: "Tappx instream Video" + } + }, + mediaTypes: { + video: { + context: "instream", + mimes : [ "video/mp4", "application/javascript" ], + playerSize: [320, 250] + } + }, + bids: [{ + bidder: 'tappx', + params: { + host: "testing.ssp.tappx.com/rtb/v2/", + tappxkey: "pub-1234-desktop-1234", + endpoint: "VZ12TESTCTV", + bidfloor: 0.005, + test: true + } + }] + } + ]; +``` \ No newline at end of file diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index a86ac1f2874..3116400edf7 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -2,9 +2,11 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = 'https://sofia.trustx.org/hb'; +const NEW_ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; const TIME_TO_LIVE = 360; const ADAPTER_SYNC_URL = 'https://sofia.trustx.org/push_sync'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -40,98 +42,30 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; const bids = validBidRequests || []; - let priceType = 'net'; - let pageKeywords; - let reqId; - + const newFormatBids = []; + const oldFormatBids = []; + const requests = []; bids.forEach(bid => { - if (bid.params.priceType === 'gross') { - priceType = 'gross'; - } - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); - - if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - const keywords = utils.transformBidderParamKeywords(bid.params.keywords); - - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } - pageKeywords = keywords; - } - - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; + if (bid.params.useNewFormat) { + newFormatBids.push(bid); } else { - slotsMap[adUnitCode].bids.push(bid); + oldFormatBids.push(bid); } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); }); - - const payload = { - pt: priceType, - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (pageKeywords) { - payload.keywords = JSON.stringify(pageKeywords); - } - - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; + if (newFormatBids.length) { + const newFormatRequests = newFormatRequest(newFormatBids, bidderRequest); + if (newFormatRequests) { + requests.push(newFormatRequests); } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; - } - if (bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; + } + if (oldFormatBids.length) { + const oldFormatRequests = oldFormatRequest(oldFormatBids, bidderRequest); + if (oldFormatRequests) { + requests.push(oldFormatRequests); } } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap, - }; + return requests; }, /** * Unpack the response from the server into a list of bids. @@ -143,8 +77,6 @@ export const spec = { interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; - const bidsMap = bidRequest.bidsMap; - const priceType = bidRequest.data.pt; let errorMessage; @@ -155,7 +87,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses, RendererConst); + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst); }); } if (errorMessage) utils.logError(errorMessage); @@ -192,66 +124,75 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererConst) { +function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { - const awaitingBids = bidsMap[serverBid.auid]; - if (awaitingBids) { - const sizeId = `${serverBid.w}x${serverBid.h}`; - if (awaitingBids[sizeId]) { - const slot = awaitingBids[sizeId][0]; - - const bid = slot.bids.shift(); - const bidResponse = { - requestId: bid.bidId, // bid.bidderRequestId, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid, // bid.bidId, - currency: 'USD', - netRevenue: priceType !== 'gross', - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid - }; - if (serverBid.content_type === 'video') { - bidResponse.vastXml = serverBid.adm; - bidResponse.mediaType = VIDEO; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }, RendererConst); - } - } else { - bidResponse.ad = serverBid.adm; - bidResponse.mediaType = BANNER; + const { bidsMap, priceType, newFormat } = bidRequest; + let bid; + let slot; + if (newFormat) { + bid = bidsMap[serverBid.impid]; + } else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + const sizeId = `${serverBid.w}x${serverBid.h}`; + if (awaitingBids[sizeId]) { + slot = awaitingBids[sizeId][0]; + bid = slot.bids.shift(); } + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } - bidResponses.push(bidResponse); - - if (!slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!utils.getKeys(parent).length) { - delete bidsMap[uid]; - } - } - }); + if (!errorMessage && bid) { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: newFormat ? false : priceType !== 'gross', + ttl: TIME_TO_LIVE, + dealId: serverBid.dealid + }; + if (serverBid.content_type === 'video') { + bidResponse.vastXml = serverBid.adm; + bidResponse.mediaType = VIDEO; + bidResponse.adResponse = { + content: bidResponse.vastXml + }; + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL + }, RendererConst); } + } else { + bidResponse.ad = serverBid.adm; + bidResponse.mediaType = BANNER; } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + + bidResponses.push(bidResponse); + } + + if (slot && !slot.bids.length) { + slot.parents.forEach(({parent, key, uid}) => { + const index = parent[key].indexOf(slot); + if (index > -1) { + parent[key].splice(index, 1); + } + if (!parent[key].length) { + delete parent[key]; + if (!utils.getKeys(parent).length) { + delete bidsMap[uid]; + } + } + }); } } if (errorMessage) { @@ -284,4 +225,343 @@ function createRenderer (bid, rendererParams, RendererConst) { return rendererInst; } +function createVideoRequest(bid, mediaType) { + const {playerSize, mimes, durationRangeSec, protocols} = mediaType; + const size = (playerSize || bid.sizes || [])[0]; + if (!size) return; + + let result = utils.parseGPTSingleSizeArrayToRtbSize(size); + + if (mimes) { + result.mimes = mimes; + } + + if (durationRangeSec && durationRangeSec.length === 2) { + result.minduration = durationRangeSec[0]; + result.maxduration = durationRangeSec[1]; + } + + if (protocols && protocols.length) { + result.protocols = protocols; + } + + return result; +} + +function createBannerRequest(bid, mediaType) { + const sizes = mediaType.sizes || bid.sizes; + if (!sizes || !sizes.length) return; + + let format = sizes.map((size) => utils.parseGPTSingleSizeArrayToRtbSize(size)); + let result = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]); + + if (format.length) { + result.format = format + } + return result; +} + +/** + * Gets bidfloor + * @param {Object} mediaTypes + * @param {Object} bid + * @returns {Number} floor + */ +function _getFloor (mediaTypes, bid) { + const curMediaType = mediaTypes.video ? 'video' : 'banner'; + let floor = bid.params.bidFloor || 0; + + 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 newFormatRequest(validBidRequests, bidderRequest) { + if (!validBidRequests.length) { + return null; + } + let pageKeywords = null; + let jwpseg = null; + let content = null; + let schain = null; + let userId = null; + let userIdAsEids = null; + let user = null; + let userExt = null; + let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; + + const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + const imp = []; + const bidsMap = {}; + + validBidRequests.forEach((bid) => { + if (!bidderRequestId) { + bidderRequestId = bid.bidderRequestId; + } + if (!auctionId) { + auctionId = bid.auctionId; + } + if (!schain) { + schain = bid.schain; + } + if (!userId) { + userId = bid.userId; + } + if (!userIdAsEids) { + userIdAsEids = bid.userIdAsEids; + } + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; + bidsMap[bidId] = bid; + if (!pageKeywords && !utils.isEmpty(keywords)) { + pageKeywords = utils.transformBidderParamKeywords(keywords); + } + const bidFloor = _getFloor(mediaTypes || {}, bid); + const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; + if (jwTargeting) { + if (!jwpseg && jwTargeting.segments) { + jwpseg = jwTargeting.segments; + } + if (!content && jwTargeting.content) { + content = jwTargeting.content; + } + } + let impObj = { + id: bidId, + tagid: uid.toString(), + ext: { + divid: adUnitCode + } + }; + + if (bidFloor) { + impObj.bidfloor = bidFloor; + } + + if (!mediaTypes || mediaTypes[BANNER]) { + const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); + if (banner) { + impObj.banner = banner; + } + } + if (mediaTypes && mediaTypes[VIDEO]) { + const video = createVideoRequest(bid, mediaTypes[VIDEO]); + if (video) { + impObj.video = video; + } + } + + if (impObj.banner || impObj.video) { + imp.push(impObj); + } + }); + + const source = { + tid: auctionId, + ext: { + wrapper: 'Prebid_js', + wrapper_version: '$prebid.version$' + } + }; + + if (schain) { + source.ext.schain = schain; + } + + const bidderTimeout = config.getConfig('bidderTimeout') || timeout; + const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; + + let request = { + id: bidderRequestId, + site: { + page: referer + }, + tmax, + source, + imp + }; + + if (content) { + request.site.content = content; + } + + if (jwpseg && jwpseg.length) { + user = { + data: [{ + name: 'iow_labs_pub_data', + segment: jwpseg.map((seg) => { + return {name: 'jwpseg', value: seg}; + }) + }] + }; + } + + 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; + } + + if (user) { + request.user = user; + } + + const configKeywords = utils.transformBidderParamKeywords({ + 'user': utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('ortb2.site'), 'keywords') || null + }); + + if (configKeywords.length) { + pageKeywords = (pageKeywords || []).concat(configKeywords); + } + + if (pageKeywords && pageKeywords.length > 0) { + pageKeywords.forEach(deleteValues); + } + + if (pageKeywords) { + request.ext = { + keywords: pageKeywords + }; + } + + 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; + } + + return { + method: 'POST', + url: NEW_ENDPOINT_URL, + data: JSON.stringify(request), + newFormat: true, + bidsMap + }; +} + +function oldFormatRequest(validBidRequests, bidderRequest) { + const auids = []; + const bidsMap = {}; + const slotsMapByUid = {}; + const sizeMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + let pageKeywords; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + reqId = bid.bidderRequestId; + const {params: {uid}, adUnitCode} = bid; + auids.push(uid); + const sizesId = utils.parseSizesInput(bid.sizes); + + if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { + const keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + pageKeywords = keywords; + } + + if (!slotsMapByUid[uid]) { + slotsMapByUid[uid] = {}; + } + const slotsMap = slotsMapByUid[uid]; + if (!slotsMap[adUnitCode]) { + slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; + } else { + slotsMap[adUnitCode].bids.push(bid); + } + const slot = slotsMap[adUnitCode]; + + sizesId.forEach((sizeId) => { + sizeMap[sizeId] = true; + if (!bidsMap[uid]) { + bidsMap[uid] = {}; + } + + if (!bidsMap[uid][sizeId]) { + bidsMap[uid][sizeId] = [slot]; + } else { + bidsMap[uid][sizeId].push(slot); + } + slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); + }); + }); + + const payload = { + pt: priceType, + auids: auids.join(','), + sizes: utils.getKeys(sizeMap).join(','), + r: reqId, + wrapperType: 'Prebid_js', + wrapperVersion: '$prebid.version$' + }; + + if (pageKeywords) { + payload.keywords = JSON.stringify(pageKeywords); + } + + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + payload.u = bidderRequest.refererInfo.referer; + } + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap, + priceType + }; +} + registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md index a72f1ba85aa..e891df8f161 100644 --- a/modules/trustxBidAdapter.md +++ b/modules/trustxBidAdapter.md @@ -52,6 +52,18 @@ TrustX Bid Adapter supports Banner and Video (instream and outstream). } } ] + },{ + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "trustx", + params: { + uid: '58851', + useNewFormat: true + } + } + ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/userId/eids.js b/modules/userId/eids.js index a38417683ba..93d2ead15ea 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -207,7 +207,10 @@ const USER_IDS_CONFIG = { return data.id; } }, - + 'deepintentId': { + source: 'deepintent.com', + atype: 3 + }, // Admixer Id 'admixerId': { source: 'admixer.net', diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 93783a2db4d..a00aedcc52e 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -184,6 +184,13 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 3 }] + }, + { + source: 'deepintent.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] } ] ``` diff --git a/modules/userId/index.js b/modules/userId/index.js index be9883dae9c..8822e2e11ea 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -101,6 +101,7 @@ * @property {(LiveIntentCollectConfig|undefined)} liCollectConfig - the config for LiveIntent's collect requests * @property {(string|undefined)} pd - publisher provided data for reconciling ID5 IDs * @property {(string|undefined)} emailHash - if provided, the hashed email address of a user + * @property {(string|undefined)} notUse3P - use to retrieve envelope from 3p endpoint */ /** diff --git a/modules/userId/userId.md b/modules/userId/userId.md index aa72146c47c..aef50eeccdf 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -43,7 +43,8 @@ pbjs.setConfig({ }, { name: 'identityLink', params: { - pid: '999' // Set your real identityLink placement ID here + pid: '999', // Set your real identityLink placement ID here + // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this property is undefined and 3p request will be fired. }, storage: { type: 'cookie', @@ -144,7 +145,8 @@ pbjs.setConfig({ }, { name: 'identityLink', params: { - pid: '999' // Set your real identityLink placement ID here + pid: '999', // Set your real identityLink placement ID here + // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this property is undefined and 3p request will be fired. }, storage: { type: 'html5', @@ -220,6 +222,20 @@ pbjs.setConfig({ name: 'admixerId', expires: 30 } + },{ + name: "deepintentId", + storage: { + type: "html5", + name: "_dpes_id", + expires: 90 + } + },{ + name: "deepintentId", + storage: { + type: "cookie", + name: "_dpes_id", + expires: 90 + } }], syncDelay: 5000 } diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index 725482d07c3..a5829b9cd9c 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -2,10 +2,14 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'visx'; -const ENDPOINT_URL = 'https://t.visx.net/hb'; +const BASE_URL = 'https://t.visx.net'; +const ENDPOINT_URL = BASE_URL + '/hb'; const TIME_TO_LIVE = 360; const DEFAULT_CUR = 'EUR'; -const ADAPTER_SYNC_URL = 'https://t.visx.net/push_sync'; +const ADAPTER_SYNC_URL = BASE_URL + '/push_sync'; +const TRACK_WIN_URL = BASE_URL + '/track/win'; +const TRACK_PENDING_URL = BASE_URL + '/track/pending'; +const TRACK_TIMEOUT_URL = BASE_URL + '/track/bid_timeout'; const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -170,6 +174,18 @@ export const spec = { url: ADAPTER_SYNC_URL + (query.length ? '?' + query.join('&') : '') }]; } + }, + onSetTargeting: function(bid) { + // Call '/track/pending' with the corresponding bid.requestId + utils.triggerPixel(TRACK_PENDING_URL + '?requestId=' + bid.requestId); + }, + onBidWon: function(bid) { + // Call '/track/win' with the corresponding bid.requestId + utils.triggerPixel(TRACK_WIN_URL + '?requestId=' + bid.requestId); + }, + onTimeout: function(timeoutData) { + // Call '/track/bid_timeout' with timeout data + utils.triggerPixel(TRACK_TIMEOUT_URL + '?data=' + JSON.stringify(timeoutData)); } }; diff --git a/modules/welectBidAdapter.js b/modules/welectBidAdapter.js index f9fc57a4834..467b628c944 100644 --- a/modules/welectBidAdapter.js +++ b/modules/welectBidAdapter.js @@ -38,7 +38,7 @@ export const spec = { let domain = bidRequest.params.domain || DEFAULT_DOMAIN; - let url = `https://${domain}/api/v2/preflight/by_alias/${bidRequest.params.placementId}`; + let url = `https://${domain}/api/v2/preflight/${bidRequest.params.placementId}`; let gdprConsent = null; diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index c1151617ac4..9bd0d1c7f6a 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,8 +1,8 @@ import * as utils from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; import includes from 'core-js-pure/features/array/includes'; -import find from 'core-js-pure/features/array/find.js'; const BIDDER_CODE = 'yieldmo'; const CURRENCY = 'USD'; @@ -10,10 +10,14 @@ const TIME_TO_LIVE = 300; const NET_REVENUE = true; const BANNER_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; const VIDEO_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo'; +const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js'; const OPENRTB_VIDEO_BIDPARAMS = ['placement', 'startdelay', 'skipafter', 'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos']; const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords']; -const localWindow = utils.getWindowTop(); +const LOCAL_WINDOW = utils.getWindowTop(); +const DEFAULT_PLAYBACK_METHOD = 2; +const DEFAULT_START_DELAY = 0; +const VAST_TIMEOUT = 15000; export const spec = { code: BIDDER_CODE, @@ -48,12 +52,12 @@ export const spec = { page_url: bidderRequest.refererInfo.referer, bust: new Date().getTime().toString(), pr: bidderRequest.refererInfo.referer, - scrd: localWindow.devicePixelRatio || 0, + scrd: LOCAL_WINDOW.devicePixelRatio || 0, dnt: getDNT(), description: getPageDescription(), - title: localWindow.document.title || '', - w: localWindow.innerWidth, - h: localWindow.innerHeight, + title: LOCAL_WINDOW.document.title || '', + w: LOCAL_WINDOW.innerWidth, + h: LOCAL_WINDOW.innerHeight, userConsent: JSON.stringify({ // case of undefined, stringify will remove param gdprApplies: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', @@ -167,8 +171,9 @@ function addPlacement(request) { if (request.params.placementId) { placementInfo.ym_placement_id = request.params.placementId; } - if (request.params.bidFloor) { - placementInfo.bidFloor = request.params.bidFloor; + const bidfloor = getBidFloor(request, BANNER); + if (bidfloor) { + placementInfo.bidFloor = bidfloor; } } return JSON.stringify(placementInfo); @@ -189,6 +194,10 @@ function createNewBannerBid(response) { netRevenue: NET_REVENUE, ttl: TIME_TO_LIVE, ad: response.ad, + meta: { + advertiserDomains: response.adomain || [], + mediaType: BANNER, + }, }; } @@ -198,8 +207,9 @@ function createNewBannerBid(response) { * @param bidRequest server request */ function createNewVideoBid(response, bidRequest) { - const imp = find((utils.deepAccess(bidRequest, 'data.imp') || []), imp => imp.id === response.impid); - return { + const imp = (utils.deepAccess(bidRequest, 'data.imp') || []).find(imp => imp.id === response.impid); + + let result = { requestId: imp.id, cpm: response.price, width: imp.video.w, @@ -209,8 +219,41 @@ function createNewVideoBid(response, bidRequest) { netRevenue: NET_REVENUE, mediaType: VIDEO, ttl: TIME_TO_LIVE, - vastXml: response.adm + vastXml: response.adm, + meta: { + advertiserDomains: response.adomain || [], + mediaType: VIDEO, + }, }; + + if (imp.placement && imp.placement !== 1) { + const renderer = Renderer.install({ + url: OUTSTREAM_VIDEO_PLAYER_URL, + config: { + width: result.width, + height: result.height, + vastTimeout: VAST_TIMEOUT, + maxAllowedVastTagRedirects: 5, + allowVpaid: true, + autoPlay: true, + preload: true, + mute: true + }, + id: imp.tagid, + loaded: false, + }); + + renderer.setRender(function (bid) { + bid.renderer.push(() => { + const { id, config } = bid.renderer; + window.YMoutstreamPlayer(bid, id, config); + }); + }); + + result.renderer = renderer; + } + + return result; } /** @@ -280,7 +323,7 @@ function openRtbImpression(bidRequest) { const imp = { id: bidRequest.bidId, tagid: bidRequest.adUnitCode, - bidfloor: bidRequest.params.bidfloor || 0, + bidfloor: getBidFloor(bidRequest, VIDEO), ext: { placement_id: bidRequest.params.placementId }, @@ -297,13 +340,27 @@ function openRtbImpression(bidRequest) { .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) .forEach(param => imp.video[param] = videoParams[param]); - if (videoParams.skippable) { - imp.video.skip = 1; + if (videoParams.skippable) imp.video.skip = 1; + if (videoParams.placement !== 1) { + imp.video = { + ...imp.video, + startdelay: DEFAULT_START_DELAY, + playbackmethod: [ DEFAULT_PLAYBACK_METHOD ] + } } - return imp; } +function getBidFloor(bidRequest, mediaType) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ currency: CURRENCY, mediaType, size: '*' }); + } + + return floorInfo.floor || bidRequest.params.bidfloor || bidRequest.params.bidFloor || 0; +} + /** * @param {BidRequest} bidRequest bidder request object. * @return [number, number] || null Player's width and height, or undefined otherwise. diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md index 1b8b7b1b741..54be295a1a1 100644 --- a/modules/yieldmoBidAdapter.md +++ b/modules/yieldmoBidAdapter.md @@ -69,3 +69,30 @@ var adUnits = [{ // Video adUnit }] }]; ``` + +Sample out-stream video ad unit config: +```javascript +var videoAdUnit = [{ + code: 'div-video-ad-1234567890', + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'outstream', + mimes: ['video/mp4'] // required, array of strings + } + }, + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1524592390382976659', // required + video: { + placement: 3, // required, integer ( 3,4,5 ) + maxduration: 30, // required, integer + protocols: [2, 3], // required, array of integers + api: [2, 3], // required, array of integers + playbackmethod: [1,2] // required, array of integers + } + } + }] +}]; +``` diff --git a/modules/zemantaBidAdapter.js b/modules/zemantaBidAdapter.js index b6dafcaed77..b2c00b69c24 100644 --- a/modules/zemantaBidAdapter.js +++ b/modules/zemantaBidAdapter.js @@ -167,7 +167,11 @@ export const spec = { return syncs; }, onBidWon: (bid) => { - ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server + // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute + if (bid.nurl) { + ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + } } }; diff --git a/modules/zetaSspBidAdapter.js b/modules/zetaSspBidAdapter.js new file mode 100644 index 00000000000..d6f2039dff1 --- /dev/null +++ b/modules/zetaSspBidAdapter.js @@ -0,0 +1,158 @@ +import * as utils 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 = 'zeta_global_ssp'; +const ENDPOINT_URL = 'https:/ssp.disqus.com/bid'; +const USER_SYNC_URL = 'https:/ssp.disqus.com/match'; +const DEFAULT_CUR = 'USD'; +const TTL = 200; +const NET_REV = true; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * 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) { + // check for all required bid fields + if (!(bid && + bid.bidId && + bid.params)) { + utils.logWarn('Invalid bid request - missing required bid data'); + return false; + } + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {Bids[]} validBidRequests - an array of bidRequest objects + * @param {BidderRequest} bidderRequest - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const secure = 1; // treat all requests as secure + const request = validBidRequests[0]; + const params = request.params; + let impData = { + id: request.bidId, + secure: secure, + banner: buildBanner(request) + }; + const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + let payload = { + id: bidderRequest.auctionId, + cur: [DEFAULT_CUR], + imp: [impData], + site: params.site ? params.site : {}, + device: fpd.device ? fpd.device : {}, + user: params.user ? params.user : {}, + app: params.app ? params.app : {}, + ext: { + tags: params.tags ? params.tags : {} + } + }; + + payload.device.ua = navigator.userAgent; + payload.site.page = bidderRequest.refererInfo.referer; + payload.site.mobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; + + if (params.test) { + payload.test = params.test; + } + if (request.gdprConsent) { + payload.regs = { + ext: { + gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + } + if (request.gdprConsent && request.gdprConsent.gdprApplies) { + payload.user = { + ext: { + consent: request.gdprConsent.consentString + } + }; + } + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(payload), + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest The payload from the server's response. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + let bidResponse = []; + if (Object.keys(serverResponse.body).length !== 0) { + let zetaResponse = serverResponse.body; + let zetaBid = zetaResponse.seatbid[0].bid[0]; + let bid = { + requestId: zetaBid.impid, + cpm: zetaBid.price, + currency: zetaResponse.cur, + width: zetaBid.w, + height: zetaBid.h, + ad: zetaBid.adm, + ttl: TTL, + creativeId: zetaBid.crid, + netRevenue: NET_REV, + }; + if (zetaBid.adomain && zetaBid.adomain.length) { + bid.meta = {}; + bid.meta.advertiserDomains = zetaBid.adomain; + } + bidResponse.push(bid); + } + return bidResponse; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @param gdprConsent The GDPR consent parameters + * @param uspConsent The USP consent parameters + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: USER_SYNC_URL + }); + } + return syncs; + } +} + +function buildBanner(request) { + let sizes = request.sizes; + if (request.mediaTypes && + request.mediaTypes.banner && + request.mediaTypes.banner.sizes) { + sizes = request.mediaTypes.banner.sizes; + } + return { + w: sizes[0][0], + h: sizes[0][1] + }; +} + +registerBidder(spec); diff --git a/modules/zetaSspBidAdapter.md b/modules/zetaSspBidAdapter.md new file mode 100644 index 00000000000..332f4b302d0 --- /dev/null +++ b/modules/zetaSspBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +``` +Module Name: Zeta Ssp Bidder Adapter +Module Type: Bidder Adapter +Maintainer: miakovlev@zetaglobal.com +``` + +# Description + +Module that connects to Zeta's SSP + +# Test Parameters +``` + var adUnits = [ + { + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: 'zeta_global_ssp', + bidId: 12345, + params: { + placement: 12345, + user: { + uid: 12345, + buyeruid: 12345 + }, + tags: { + someTag: 123 + }, + test: 1 + } + } + ] + } + ]; +``` diff --git a/package-lock.json b/package-lock.json index 330298cbaac..5131b5f4d23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24613,4 +24613,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 60c295f53ec..58ea0d7ba30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.34.0-pre", + "version": "4.36.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/auctionManager.js b/src/auctionManager.js index 3d4bd0afe99..1aca1277aa8 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -9,6 +9,7 @@ * * @property {function(): Array} getBidsRequested - returns consolidated bid requests * @property {function(): Array} getBidsReceived - returns consolidated bid received + * @property {function(): Array} getAllBidsForAdUnitCode - returns consolidated bid received for a given adUnit * @property {function(): Array} getAdUnits - returns consolidated adUnits * @property {function(): Array} getAdUnitCodes - returns consolidated adUnitCodes * @property {function(): Object} createAuction - creates auction instance and stores it for future reference @@ -66,6 +67,13 @@ export function newAuctionManager() { .filter(bid => bid); }; + auctionManager.getAllBidsForAdUnitCode = function(adUnitCode) { + return _auctions.map((auction) => { + return auction.getBidsReceived(); + }).reduce(flatten, []) + .filter(bid => bid && bid.adUnitCode === adUnitCode) + }; + auctionManager.getAdUnits = function() { return _auctions.map(auction => auction.getAdUnits()) .reduce(flatten, []); diff --git a/src/prebid.js b/src/prebid.js index 6565c1610d8..7211371366e 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,12 +1,12 @@ /** @module pbjs */ import { getGlobal } from './prebidGlobal.js'; -import { adUnitsFilter, flatten, isArrayOfNums, isGptPubadsDefined, uniques } from './utils.js'; +import { adUnitsFilter, flatten, getHighestCpm, isArrayOfNums, isGptPubadsDefined, uniques } from './utils.js'; import { listenMessagesFromCreative } from './secureCreatives.js'; import { userSync } from './userSync.js'; import { config } from './config.js'; import { auctionManager } from './auctionManager.js'; -import { targeting } from './targeting.js'; +import { filters, targeting } from './targeting.js'; import { hook } from './hook.js'; import { sessionLoader } from './debugging.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -206,6 +206,24 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { } }; +/** + * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * @param adUnitCode {string} adUnitCode to get the bid responses for + * @alias module:pbjs.getHighestUnusedBidResponseForAdUnitCode + * @returns {Object} returnObj return bid + */ +$$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCode) { + if (adunitCode) { + const bid = auctionManager.getAllBidsForAdUnitCode(adunitCode) + .filter(filters.isUnusedBid) + .filter(filters.isBidNotExpired) + + return bid.length ? bid.reduce(getHighestCpm) : {} + } else { + utils.logMessage('Need to call getHighestUnusedBidResponseForAdUnitCode with adunitCode'); + } +}; + /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param adUnitCode {string} adUnitCode to get the bid responses for diff --git a/src/userSync.js b/src/userSync.js index fceeb1d722d..f653880fa29 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -225,7 +225,7 @@ export function newUserSync(userSyncDependencies) { } return checkForFiltering[filterType](biddersToFilter, bidder); } - return false; + return !permittedPixels[type]; } /** diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index 6d67565056f..cb6c7aa87a3 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -82,5 +82,9 @@ describe('Publisher API', function () { it('should have function $$PREBID_GLOBAL$$.getAllWinningBids', function () { assert.isFunction($$PREBID_GLOBAL$$.getAllWinningBids); }); + + it('should have function $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode', function () { + assert.isFunction($$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode); + }); }); }); diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index edc7b7a2767..c16e3fffd03 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -5,6 +5,14 @@ import { config } from 'src/config.js'; import { spec } from 'modules/33acrossBidAdapter.js'; +function validateBuiltServerRequest(builtReq, expectedReq) { + expect(builtReq.url).to.equal(expectedReq.url); + expect(builtReq.options).to.deep.equal(expectedReq.options); + expect(JSON.parse(builtReq.data)).to.deep.equal( + JSON.parse(expectedReq.data) + ) +} + describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; const SITE_ID = 'sample33xGUID123456789'; @@ -22,14 +30,9 @@ describe('33acrossBidAdapter:', function () { id: SITE_ID }, id: 'b1', - user: { - ext: { - } - }, regs: { ext: { - gdpr: 0, - us_privacy: null + gdpr: 0 } }, ext: { @@ -194,6 +197,18 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withUserIds = (eids) => { + Object.assign(ttxRequest, { + user: { + ext: { + eids + } + } + }); + + return this; + } + this.build = () => ttxRequest; } @@ -270,6 +285,12 @@ describe('33acrossBidAdapter:', function () { return this; } + this.withUserIds = (eids) => { + bidRequests[0].userIdAsEids = eids; + + return this; + }; + this.build = () => bidRequests; } @@ -568,7 +589,8 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 600, height: 400 }); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -585,7 +607,8 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -602,7 +625,8 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 800, height: 800 }); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -621,7 +645,8 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 0, height: 0 }); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -643,7 +668,8 @@ describe('33acrossBidAdapter:', function () { sandbox.stub(utils, 'getWindowTop').returns({}); sandbox.stub(utils, 'getWindowSelf').returns(win); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -664,7 +690,8 @@ describe('33acrossBidAdapter:', function () { win.document.visibilityState = 'hidden'; sandbox.stub(utils, 'getWindowTop').returns(win); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -689,9 +716,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with gdpr consent data', function() { @@ -710,9 +737,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -731,9 +758,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with default gdpr consent data', function() { @@ -751,9 +778,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -775,9 +802,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with us_privacy consent data', function() { @@ -796,9 +823,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -817,9 +844,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with default us_privacy consent data', function() { @@ -837,9 +864,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -860,9 +887,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -880,9 +907,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -934,9 +961,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); }); @@ -952,9 +979,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -967,9 +994,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -984,9 +1011,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('sets bidfloors in ttxRequest if there is a floor', function() { @@ -1009,9 +1036,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -1034,9 +1061,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds instream request with params passed', function() { @@ -1051,9 +1078,9 @@ describe('33acrossBidAdapter:', function () { .withProduct('instream') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(JSON.parse(builtServerRequests[0].data)).to.deep.equal(ttxRequest); + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); }); @@ -1075,9 +1102,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds siab request with video params passed', function() { @@ -1095,9 +1122,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -1117,9 +1144,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds default inview request when product is set as such', function() { @@ -1138,9 +1165,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -1162,9 +1189,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds siab request with banner and outstream video even when context is instream', function() { @@ -1186,9 +1213,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -1207,9 +1234,9 @@ describe('33acrossBidAdapter:', function () { .withProduct() .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(JSON.parse(builtServerRequests[0].data)).to.deep.equal(ttxRequest); + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); it('sets bidfloors in video if there is a floor', function() { @@ -1235,9 +1262,118 @@ describe('33acrossBidAdapter:', function () { .withFloors('video', [ 1.0 ]) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + }); + + context('when user ID data exists as userIdAsEids Array in bidRequest', function() { + it('passes userIds in eids field in ORTB request', function() { + const eids = [ + { + 'source': 'x-device-vendor-x.com', + 'uids': [ + { + 'id': 'yyy', + 'atype': 1 + }, + { + 'id': 'zzz', + 'atype': 1 + }, + { + 'id': 'DB700403-9A24-4A4B-A8D5-8A0B4BE777D2', + 'atype': 2 + } + ], + 'ext': { + 'foo': 'bar' + } + } + ]; + + const bidRequests = ( + new BidRequestsBuilder() + .withUserIds(eids) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withUserIds(eids) + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + + it('does not validate eids ORTB', function() { + const eids = [1, 2, 3]; + + const bidRequests = ( + new BidRequestsBuilder() + .withUserIds(eids) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withUserIds(eids) + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + }); + + context('when user IDs do not exist under the userIdAsEids field in bidRequest as a non-empty Array', function() { + it('does not pass user IDs in the bidRequest ORTB', function() { + const eidsScenarios = [ + 'foo', + [], + {foo: 1} + ]; - expect(JSON.parse(builtServerRequests[0].data)).to.deep.equal(ttxRequest); + eidsScenarios.forEach((eids) => { + const bidRequests = ( + new BidRequestsBuilder() + .withUserIds(eids) + .build() + ); + bidRequests.userId = { + 'vendorx': { + 'source': 'x-device-vendor-x.com', + 'uids': [ + { + 'id': 'yyy', + 'atype': 1 + }, + { + 'id': 'zzz', + 'atype': 1 + }, + { + 'id': 'DB700403-9A24-4A4B-A8D5-8A0B4BE777D2', + 'atype': 2 + } + ], + 'ext': { + 'foo': 'bar' + } + } + }; + + const ttxRequest = new TtxRequestBuilder() + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); }); }); }); diff --git a/test/spec/modules/adWMGBidAdapter_spec.js b/test/spec/modules/adWMGBidAdapter_spec.js index 1f881897fd8..8b927ace84c 100644 --- a/test/spec/modules/adWMGBidAdapter_spec.js +++ b/test/spec/modules/adWMGBidAdapter_spec.js @@ -168,8 +168,8 @@ describe('adWMGBidAdapter', function () { it('should have an url that match the default endpoint', function() { let requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[0].url).to.equal('https://rtb.adwmg.com/prebid'); - expect(requests[1].url).to.equal('https://rtb.adwmg.com/prebid'); + expect(requests[0].url).to.equal('https://hb.adwmg.com/hb'); + expect(requests[1].url).to.equal('https://hb.adwmg.com/hb'); }); it('should contain GDPR consent data if GDPR set', function() { @@ -258,7 +258,7 @@ describe('adWMGBidAdapter', function () { let syncs = spec.getUserSyncs(syncOptions); expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).includes('https://rtb.adwmg.com/cphb.html?'); + expect(syncs[0].url).includes('https://hb.adwmg.com/cphb.html?'); }); it('should register iframe sync when iframe and image are enabled', function () { @@ -269,7 +269,7 @@ describe('adWMGBidAdapter', function () { let syncs = spec.getUserSyncs(syncOptions); expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).includes('https://rtb.adwmg.com/cphb.html?'); + expect(syncs[0].url).includes('https://hb.adwmg.com/cphb.html?'); }); it('should send GDPR consent if enabled', function() { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 54ff038c083..c2aa09fafaa 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -4,7 +4,8 @@ import { spec } from 'modules/adnuntiusBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; describe('adnuntiusBidAdapter', function () { - const ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo=-60&format=json'; + const tzo = new Date().getTimezoneOffset(); + const ENDPOINT_URL = `https://delivery.adnuntius.com/i?tzo=${tzo}&format=json`; const adapter = newBidder(spec); const bidRequests = [ { @@ -96,7 +97,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\"}]}'); + expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\",\"targetId\":\"123\"}]}'); }); }); @@ -105,12 +106,14 @@ describe('adnuntiusBidAdapter', function () { const request = spec.buildRequests(bidRequests); const interpretedResponse = spec.interpretResponse(serverResponse, request[0]); const ad = serverResponse.body.adUnits[0].ads[0] + const cpm = (ad.cpc && ad.cpm) ? ad.bid.amount + ad.cpm.amount : (ad.cpm) ? ad.cpm.amount : 0; + expect(interpretedResponse).to.have.lengthOf(1); expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - expect(interpretedResponse[0].currency).to.equal(ad.cpm.currency); + expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); expect(interpretedResponse[0].netRevenue).to.equal(false); expect(interpretedResponse[0].ad).to.equal(serverResponse.body.adUnits[0].html); expect(interpretedResponse[0].ttl).to.equal(360); diff --git a/test/spec/modules/adponeBidAdapter_spec.js b/test/spec/modules/adponeBidAdapter_spec.js index 737f1c284e1..92fd672df47 100644 --- a/test/spec/modules/adponeBidAdapter_spec.js +++ b/test/spec/modules/adponeBidAdapter_spec.js @@ -110,122 +110,109 @@ describe('adponeBidAdapter', function () { expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); -}); - -describe('interpretResponse', function () { - let serverResponse; - let bidRequest = { data: {id: '1234'} }; - - beforeEach(function () { - serverResponse = { - body: { - id: '2579e20c0bb89', - seatbid: [ - { - bid: [ - { - id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', - impid: '2579e20c0bb89_0', - price: 1, - adm: '', - adomain: [ - 'www.addomain.com' - ], - iurl: 'https://localhost11', - crid: 'creative111', - h: 250, - w: 300, - ext: { - dspid: 6 + describe('interpretResponse', function () { + let serverResponse; + let bidRequest = { data: {id: '1234'} }; + + beforeEach(function () { + serverResponse = { + body: { + id: '2579e20c0bb89', + seatbid: [ + { + bid: [ + { + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + impid: '2579e20c0bb89_0', + price: 1, + adm: '', + meta: { + adomain: [ + 'adpone.com' + ] + }, + iurl: 'https://localhost11', + crid: 'creative111', + h: 250, + w: 300, + ext: { + dspid: 6 + } } - } - ], - seat: 'adpone' - } - ], - cur: 'USD' - }, - }; - }); - - it('validate_response_params', function() { - const newResponse = spec.interpretResponse(serverResponse, bidRequest); - expect(newResponse[0].id).to.be.equal('613673EF-A07C-4486-8EE9-3FC71A7DC73D'); - expect(newResponse[0].requestId).to.be.equal('1234'); - expect(newResponse[0].cpm).to.be.equal(1); - expect(newResponse[0].width).to.be.equal(300); - expect(newResponse[0].height).to.be.equal(250); - expect(newResponse[0].currency).to.be.equal('USD'); - expect(newResponse[0].netRevenue).to.be.equal(true); - expect(newResponse[0].ttl).to.be.equal(300); - expect(newResponse[0].ad).to.be.equal(''); - }); + ], + seat: 'adpone' + } + ], + cur: 'USD' + }, + }; + }); - it('should correctly reorder the server response', function () { - const newResponse = spec.interpretResponse(serverResponse, bidRequest); - expect(newResponse.length).to.be.equal(1); - expect(newResponse[0]).to.deep.equal({ - id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', - requestId: '1234', - cpm: 1, - width: 300, - height: 250, - creativeId: 'creative111', - currency: 'USD', - netRevenue: true, - ttl: 300, - ad: '' + it('validate_response_params', function() { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse[0].id).to.be.equal('613673EF-A07C-4486-8EE9-3FC71A7DC73D'); + expect(newResponse[0].requestId).to.be.equal('1234'); + expect(newResponse[0].cpm).to.be.equal(1); + expect(newResponse[0].width).to.be.equal(300); + expect(newResponse[0].height).to.be.equal(250); + expect(newResponse[0].currency).to.be.equal('USD'); + expect(newResponse[0].netRevenue).to.be.equal(true); + expect(newResponse[0].ttl).to.be.equal(300); + expect(newResponse[0].ad).to.be.equal(''); }); - }); - it('should not add responses if the cpm is 0 or null', function () { - serverResponse.body.seatbid[0].bid[0].price = 0; - let response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.deep.equal([]); + it('should correctly reorder the server response', function () { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + meta: { + advertiserDomains: [ + 'adpone.com' + ] + }, + requestId: '1234', + cpm: 1, + width: 300, + height: 250, + creativeId: 'creative111', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '' + }); + }); - serverResponse.body.seatbid[0].bid[0].price = null; - response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.deep.equal([]) - }); - it('should add responses if the cpm is valid', function () { - serverResponse.body.seatbid[0].bid[0].price = 0.5; - let response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.not.deep.equal([]); - }); -}); + it('should not add responses if the cpm is 0 or null', function () { + serverResponse.body.seatbid[0].bid[0].price = 0; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]); -describe('getUserSyncs', function () { - it('Verifies that getUserSyncs is a function', function () { - expect((typeof (spec.getUserSyncs)).should.equals('function')); - }); - it('Verifies getUserSyncs returns expected result', function () { - expect((typeof (spec.getUserSyncs)).should.equals('function')); - expect(spec.getUserSyncs({iframeEnabled: true})).to.deep.equal({ - type: 'iframe', - url: 'https://eu-ads.adpone.com' + serverResponse.body.seatbid[0].bid[0].price = null; + response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]) + }); + it('should add responses if the cpm is valid', function () { + serverResponse.body.seatbid[0].bid[0].price = 0.5; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.not.deep.equal([]); }); }); - it('Verifies that iframeEnabled: false returns an empty array', function () { - expect(spec.getUserSyncs({iframeEnabled: false})).to.deep.equal(EMPTY_ARRAY); - }); - it('Verifies that iframeEnabled: null returns an empty array', function () { - expect(spec.getUserSyncs(null)).to.deep.equal(EMPTY_ARRAY); - }); -}); -describe('test onBidWon function', function () { - beforeEach(function() { - sinon.stub(utils, 'triggerPixel'); - }); - afterEach(function() { - utils.triggerPixel.restore(); - }); - it('exists and is a function', () => { - expect(spec.onBidWon).to.exist.and.to.be.a('function'); - }); - it('should return nothing', function () { - var response = spec.onBidWon({}); - expect(response).to.be.an('undefined') - expect(utils.triggerPixel.called).to.equal(true); + describe('test onBidWon function', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onBidWon({}); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); + }); }); }); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 67372216e96..e6916997133 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -16,6 +16,7 @@ const aliasEP = { onefiftytwomedia: 'https://ghb.ads.152media.com/v2/auction/', mediafuse: 'https://ghb.hbmp.mediafuse.com/v2/auction/', navelix: 'https://ghb.hb.navelix.com/v2/auction/', + bidsxchange: 'https://ghb.hbd.bidsxchange.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; @@ -88,6 +89,7 @@ const SERVER_DISPLAY_RESPONSE = { 'source': { 'aid': 12345, 'pubId': 54321 }, 'bids': [{ 'ad': '', + 'adUrl': 'adUrl', 'requestId': '2e41f65424c87c', 'creative_id': 342516, 'cmpId': 342516, @@ -171,6 +173,7 @@ const displayEqResponse = [{ netRevenue: true, currency: 'USD', ad: '', + adUrl: 'adUrl', height: 250, width: 300, ttl: 300, diff --git a/test/spec/modules/betweenBidAdapter_spec.js b/test/spec/modules/betweenBidAdapter_spec.js index 44a8d2cc733..1b1ccd9efd2 100644 --- a/test/spec/modules/betweenBidAdapter_spec.js +++ b/test/spec/modules/betweenBidAdapter_spec.js @@ -221,4 +221,50 @@ describe('betweenBidAdapterTests', function () { expect(req_data.sizes).to.deep.equal(['970x250', '240x400', '728x90']) }); + + it('check sharedId with id and third', function() { + const bidRequestData = [{ + bidId: 'bid123', + bidder: 'between', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + s: 1112, + }, + userId: { + sharedid: { + id: '01EXQE7JKNDRDDVATB0S2GX1NT', + third: '01EXQE7JKNDRDDVATB0S2GX1NT' + } + } + }]; + const shid = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid; + const shid3 = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid3; + expect(shid).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT') && expect(shid3).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT'); + }); + it('check sharedId with only id', function() { + const bidRequestData = [{ + bidId: 'bid123', + bidder: 'between', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + s: 1112, + }, + userId: { + sharedid: { + id: '01EXQE7JKNDRDDVATB0S2GX1NT', + } + } + }]; + const shid = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid; + const shid3 = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid3; + expect(shid).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT') && expect(shid3).to.equal(''); + }); }); diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index 56ea29d6d73..3aee6c69438 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -210,6 +210,25 @@ describe('BlueBillywigAdapter', () => { bid.params.video = void (0); expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should fail if rendererSettings is specified but is not an object', () => { + const bid = deepClone(baseValidBid); + + bid.params.rendererSettings = null; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = 'string'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', () => { diff --git a/test/spec/modules/brightMountainMediaBidAdapter_spec.js b/test/spec/modules/brightMountainMediaBidAdapter_spec.js index f6312253722..bc7f69f0baf 100644 --- a/test/spec/modules/brightMountainMediaBidAdapter_spec.js +++ b/test/spec/modules/brightMountainMediaBidAdapter_spec.js @@ -4,7 +4,7 @@ import { spec } from '../../../modules/brightMountainMediaBidAdapter.js'; describe('brightMountainMediaBidAdapter_spec', function () { let bid = { bidId: '2dd581a2b6281d', - bidder: 'brightmountainmedia', + bidder: 'bmtm', bidderRequestId: '145e1d6a7837c9', params: { placement_id: '123qwerty' @@ -19,7 +19,7 @@ describe('brightMountainMediaBidAdapter_spec', function () { transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', }; let bidderRequest = { - bidderCode: 'brightmountainmedia', + bidderCode: 'bmtm', auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', bidderRequestId: 'ffffffffffffff', start: 1472239426002, @@ -55,7 +55,7 @@ describe('brightMountainMediaBidAdapter_spec', function () { expect(serverRequest.method).to.equal('POST'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://console.brightmountainmedia.com/hb/bid'); + expect(serverRequest.url).to.equal('https://one.elitebidder.com/api/hb'); }); it('Returns valid data if array of bids is valid', function () { diff --git a/test/spec/modules/deepintentDpesIdsystem_spec.js b/test/spec/modules/deepintentDpesIdsystem_spec.js new file mode 100644 index 00000000000..7ea5553393c --- /dev/null +++ b/test/spec/modules/deepintentDpesIdsystem_spec.js @@ -0,0 +1,76 @@ +import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { storage, deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { config } from 'src/config.js'; + +const DI_COOKIE_NAME = '_dpes_id'; +const DI_COOKIE_STORED = '{"id":"2cf40748c4f7f60d343336e08f80dc99"}'; +const DI_COOKIE_OBJECT = {id: '2cf40748c4f7f60d343336e08f80dc99'}; + +const cookieConfig = { + name: 'deepintentId', + storage: { + type: 'cookie', + name: '_dpes_id', + expires: 28 + } +}; + +const html5Config = { + name: 'deepintentId', + storage: { + type: 'html5', + name: '_dpes_id', + expires: 28 + } +} + +describe('Deepintent DPES System', () => { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let getCookieStub, cookiesAreEnabledStub; + + beforeEach(() => { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + }); + + afterEach(() => { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + getCookieStub.restore(); + cookiesAreEnabledStub.restore(); + }); + + describe('Deepintent Dpes Sytsem: test "getId" method', () => { + it('Wrong config should fail the tests', () => { + // no config + expect(deepintentDpesSubmodule.getId()).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({ })).to.be.eq(undefined); + + expect(deepintentDpesSubmodule.getId({params: {}, storage: {}})).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({params: {}, storage: {type: 'cookie'}})).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({params: {}, storage: {name: '_dpes_id'}})).to.be.eq(undefined); + }); + + it('Get value stored in cookie for getId', () => { + getCookieStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); + let diId = deepintentDpesSubmodule.getId(cookieConfig, undefined, DI_COOKIE_OBJECT); + expect(diId).to.deep.equal(DI_COOKIE_OBJECT); + }); + + it('provides the stored deepintentId if cookie is absent but present in local storage', () => { + getDataFromLocalStorageStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); + let idx = deepintentDpesSubmodule.getId(html5Config, undefined, DI_COOKIE_OBJECT); + expect(idx).to.deep.equal(DI_COOKIE_OBJECT); + }); + }); + + describe('Deepintent Dpes System : test "decode" method', () => { + it('Get the correct decoded value for dpes id', () => { + expect(deepintentDpesSubmodule.decode(DI_COOKIE_OBJECT, cookieConfig)).to.deep.equal({'deepintentId': {'id': '2cf40748c4f7f60d343336e08f80dc99'}}); + }); + }); +}); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 86a6dff2205..1ccaab2b302 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -189,6 +189,18 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('deepintentId', function() { + const userId = { + deepintentId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'deepintent.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + it('NetId', function() { const userId = { netId: 'some-random-id-value' diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 066ab6b21f6..6b75af0d55d 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -13,7 +13,7 @@ describe('FeedAdAdapter', function () { it('should only support video and banner ads', function () { expect(spec.supportedMediaTypes).to.be.a('array'); expect(spec.supportedMediaTypes).to.include(BANNER); - expect(spec.supportedMediaTypes).to.include(VIDEO); + expect(spec.supportedMediaTypes).not.to.include(VIDEO); expect(spec.supportedMediaTypes).not.to.include(NATIVE); }); it('should export the BidderSpec functions', function () { @@ -23,6 +23,9 @@ describe('FeedAdAdapter', function () { expect(spec.onTimeout).to.be.a('function'); expect(spec.onBidWon).to.be.a('function'); }); + it('should export the TCF vendor ID', function () { + expect(spec.gvlid).to.equal(781); + }) }); describe('isBidRequestValid', function () { @@ -248,6 +251,40 @@ describe('FeedAdAdapter', function () { let result = spec.buildRequests([bid, bid, bid]); expect(result).to.be.empty; }); + it('should not include GDPR data if the bidder request has none available', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.gdprApplies).to.be.undefined; + expect(result.data.consentIabTcf).to.be.undefined; + }); + it('should include GDPR data if the bidder requests contains it', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let request = Object.assign({}, bidderRequest, { + gdprConsent: { + consentString: 'the consent string', + gdprApplies: true + } + }); + let result = spec.buildRequests([bid], request); + expect(result.data.gdprApplies).to.equal(request.gdprConsent.gdprApplies); + expect(result.data.consentIabTcf).to.equal(request.gdprConsent.consentString); + }); }); describe('interpretResponse', function () { @@ -404,7 +441,7 @@ describe('FeedAdAdapter', function () { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: '1.0.0' + sdk_version: '1.0.2' }; subject(data); expect(server.requests.length).to.equal(1); diff --git a/test/spec/modules/haloIdSystem_spec.js b/test/spec/modules/haloIdSystem_spec.js new file mode 100644 index 00000000000..240a83c3c83 --- /dev/null +++ b/test/spec/modules/haloIdSystem_spec.js @@ -0,0 +1,31 @@ +import { haloIdSubmodule, storage } from 'modules/haloIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import * as utils from 'src/utils.js'; + +describe('HaloIdSystem', function () { + describe('getId', function() { + it('gets a haloId', function() { + const config = { + params: {} + }; + const callbackSpy = sinon.spy(); + const callback = haloIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq(`https://id.halo.ad.gt/api/v1/pbhid`); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ haloId: 'testHaloId1' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'testHaloId1'}); + }); + + it('gets a cached haloid', function() { + const config = { + params: {} + }; + storage.setDataInLocalStorage('auHaloId', 'tstCachedHaloId1'); + const callbackSpy = sinon.spy(); + const callback = haloIdSubmodule.getId(config).callback; + callback(callbackSpy); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'tstCachedHaloId1'}); + }); + }); +}); diff --git a/test/spec/modules/haloRtdProvider_spec.js b/test/spec/modules/haloRtdProvider_spec.js index 69ea4bc997c..3052441a00d 100644 --- a/test/spec/modules/haloRtdProvider_spec.js +++ b/test/spec/modules/haloRtdProvider_spec.js @@ -1,220 +1,374 @@ -import { HALOID_LOCAL_NAME, SEG_LOCAL_NAME, addSegmentData, getSegments, haloSubmodule, storage } from 'modules/haloRtdProvider.js'; -import { server } from 'test/mocks/xhr.js'; +import {config} from 'src/config.js'; +import {HALOID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, haloSubmodule, storage} from 'modules/haloRtdProvider.js'; +import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; describe('haloRtdProvider', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + describe('haloSubmodule', function() { it('successfully instantiates', function () { - expect(haloSubmodule.init()).to.equal(true); + expect(haloSubmodule.init()).to.equal(true); }); }); - describe('Add Segment Data', function() { - it('adds segment data', function() { - const config = { - params: { - mapSegments: { - 'appnexus': true, - 'generic': true - } - } + describe('Add Real-Time Data', function() { + it('merges ortb2 data', function() { + let rtdConfig = {}; + let bidConfig = {}; + + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] }; - let adUnits = [ - { - bids: [ - // bid with existing segment data in bid obj and fpd - { - bidder: 'appnexus', - fpd: { - user: { - data: [ - { - id: 'appnexus', - segment: [ - { - id: '0' - } - ] - } - ] - } - }, - params: { - user: { - segments: [0] - } - } - } - ] + const setConfigUserObj2 = { + name: 'www.dataprovider2.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1914' + }] + }; + + const setConfigSiteObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + } - // bids with fpd data definitions but without existing segment data - { - bids: [ - { - bidder: 'appnexus', - fpd: { - user: { - data: [ - { - id: 'appnexus' - } - ] - } - } - }, - { - bidder: 'generic', - fpd: { - user: { - data: [ - { - id: 'generic' - } - ] - } - } + config.setConfig({ + ortb2: { + user: { + data: [setConfigUserObj1, setConfigUserObj2] + }, + site: { + content: { + data: [setConfigSiteObj1] } - ] + } } - ]; + }); - const data = { - appnexus: [{id: '1'}, {id: '2'}, {id: '3'}], - generic: [{id: 'seg1'}, {id: 'seg2'}, {id: 'seg3'}] + const rtdUserObj1 = { + name: 'www.dataprovider4.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] }; - addSegmentData(adUnits, data, config); + const rtdSiteObj1 = { + name: 'www.dataprovider5.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1945' + }, + { + id: '2003' + } + ] + }; - expect(adUnits[0].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', '0'); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', '1'); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', '2'); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[3]).to.have.deep.property('id', '3'); - expect(adUnits[0].bids[0].params.user).to.have.deep.property('segments', [0, 1, 2, 3]); + const rtd = { + ortb2: { + user: { + data: [rtdUserObj1] + }, + site: { + content: { + data: [rtdSiteObj1] + } + } + } + }; + + let pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); - expect(adUnits[1].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', '1'); - expect(adUnits[1].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', '2'); - expect(adUnits[1].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', '3'); - expect(adUnits[1].bids[0].params.user).to.have.deep.property('segments', [1, 2, 3]); + let ortb2Config = config.getConfig().ortb2; - expect(adUnits[1].bids[1].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'seg1'); - expect(adUnits[1].bids[1].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'seg2'); - expect(adUnits[1].bids[1].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'seg3'); - expect(adUnits[1].bids[1].segments[0]).to.have.deep.property('id', 'seg1'); - expect(adUnits[1].bids[1].segments[1]).to.have.deep.property('id', 'seg2'); - expect(adUnits[1].bids[1].segments[2]).to.have.deep.property('id', 'seg3'); + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1, setConfigUserObj2, rtdUserObj1]); + expect(ortb2Config.site.content.data).to.deep.include.members([setConfigSiteObj1, rtdSiteObj1]); }); - it('allows mapper extensions and overrides', function() { - const config = { + it('allows publisher defined rtd ortb2 logic', function() { + const rtdConfig = { params: { - mapSegments: { - generic: (bid, segments) => { - bid.overrideSegments = segments; - }, - newBidder: (bid, segments) => { - bid.newBidderSegments = segments; + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + if (rtd.ortb2.user.data[0].segment[0].id == '1776') { + pbConfig.setConfig({ortb2: rtd.ortb2}); + } else { + pbConfig.setConfig({ortb2: {}}); } } } }; - let adUnits = [ - { - bids: [ {bidder: 'newBidder'}, {bidder: 'generic'} ] + let bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.dataprovider.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + let rtd = { + ortb2: { + user: { + data: [rtdUserObj1] + } } - ]; + }; - const data = { - newBidder: [{id: 'nbseg1', name: 'New Bidder Segment 1'}, {id: 'nbseg2', name: 'New Bidder Segment 2'}, {id: 'nbseg3', name: 'New Bidder Segment 3'}], - generic: [{id: 'seg1'}, {id: 'seg2'}, {id: 'seg3'}] + config.resetConfig(); + + let pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + + const rtdUserObj2 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [{ + id: 'pubseg1' + }] }; - addSegmentData(adUnits, data, config); + rtd = { + ortb2: { + user: { + data: [rtdUserObj2] + } + } + }; - expect(adUnits[0].bids[0].newBidderSegments[0]).to.have.deep.property('id', 'nbseg1'); - expect(adUnits[0].bids[0].newBidderSegments[0]).to.have.deep.property('name', 'New Bidder Segment 1'); - expect(adUnits[0].bids[0].newBidderSegments[1]).to.have.deep.property('id', 'nbseg2'); - expect(adUnits[0].bids[0].newBidderSegments[1]).to.have.deep.property('name', 'New Bidder Segment 2'); - expect(adUnits[0].bids[0].newBidderSegments[2]).to.have.deep.property('id', 'nbseg3'); - expect(adUnits[0].bids[0].newBidderSegments[2]).to.have.deep.property('name', 'New Bidder Segment 3'); + config.resetConfig(); - expect(adUnits[0].bids[1].overrideSegments[0]).to.have.deep.property('id', 'seg1'); - expect(adUnits[0].bids[1].overrideSegments[1]).to.have.deep.property('id', 'seg2'); - expect(adUnits[0].bids[1].overrideSegments[2]).to.have.deep.property('id', 'seg3'); + pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); + expect(config.getConfig().ortb2).to.deep.equal({}); }); - }); - describe('Get Segments', function() { - it('gets segment data from local storage cache', function() { - const config = { + it('allows publisher defined adunit logic', function() { + const rtdConfig = { params: { - segmentCache: true, - mapSegments: { - 'generic': true + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + var adUnits = bidConfig.adUnits; + for (var i = 0; i < adUnits.length; i++) { + var adUnit = adUnits[i]; + for (var j = 0; j < adUnit.bids.length; j++) { + var bid = adUnit.bids[j]; + if (bid.bidder == 'adBuzz') { + for (var k = 0; k < rtd.adBuzz.length; k++) { + bid.adBuzzData.segments.adBuzz.push(rtd.adBuzz[k]); + } + } else if (bid.bidder == 'trueBid') { + for (var k = 0; k < rtd.trueBid.length; k++) { + bid.trueBidSegments.push(rtd.trueBid[k]); + } + } + } + } } } }; - let reqBidsConfigObj = { + let bidConfig = { adUnits: [ { - bids: [{bidder: 'generic'}] + bids: [ + { + bidder: 'adBuzz', + adBuzzData: { + segments: { + adBuzz: [ + { + id: 'adBuzzSeg1' + } + ] + } + } + }, + { + bidder: 'trueBid', + trueBidSegments: [] + } + ] } ] }; - const data = { - audigent_segments: { - generic: [{id: 'seg1'}] + const rtd = { + adBuzz: [{id: 'adBuzzSeg2'}, {id: 'adBuzzSeg3'}], + trueBid: [{id: 'truebidSeg1'}, {id: 'truebidSeg2'}, {id: 'truebidSeg3'}] + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[0].id).to.equal('adBuzzSeg1'); + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[1].id).to.equal('adBuzzSeg2'); + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[2].id).to.equal('adBuzzSeg3'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[0].id).to.equal('truebidSeg1'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[1].id).to.equal('truebidSeg2'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[2].id).to.equal('truebidSeg3'); + }); + }); + + describe('Get Real-Time Data', function() { + it('gets rtd from local storage cache', function() { + const rtdConfig = { + params: { + segmentCache: true } }; - storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); + const bidConfig = {}; - getSegments(reqBidsConfigObj, () => {}, config, {}); + const rtdUserObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; - expect(reqBidsConfigObj.adUnits[0].bids[0].segments[0]).to.have.deep.property('id', 'seg1'); + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + } + }; + + getDataFromLocalStorageStub.withArgs(RTD_LOCAL_NAME).returns(JSON.stringify(cachedRtd)); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); }); - it('gets segment data via async request', function() { - const config = { + it('gets real-time data via async request', function() { + const setConfigSiteObj1 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [ + { + id: 'pubseg1' + }, + { + id: 'pubseg2' + } + ] + } + + config.setConfig({ + ortb2: { + site: { + content: { + data: [setConfigSiteObj1] + } + } + } + }); + + const rtdConfig = { params: { segmentCache: false, - mapSegments: { - 'generic': true - }, + usePubHalo: true, requestParams: { - 'publisherId': 1234 + publisherId: 'testPub1' } } }; - let reqBidsConfigObj = { - adUnits: [ + let bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [ + { + id: 'pubseg1' + }, { - bids: [{bidder: 'generic'}] + id: 'pubseg2' } ] }; + const data = { - audigent_segments: { - generic: [{id: 'seg1'}] + rtd: { + ortb2: { + user: { + data: [rtdUserObj1] + } + } } }; - storage.setDataInLocalStorage(HALOID_LOCAL_NAME, 'haloid'); - getSegments(reqBidsConfigObj, () => {}, config, {}); + getDataFromLocalStorageStub.withArgs(HALOID_LOCAL_NAME).returns('testHaloId1'); + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); let request = server.requests[0]; let postData = JSON.parse(request.requestBody); - expect(postData.config).to.have.deep.property('publisherId', 1234); + expect(postData.config).to.have.deep.property('publisherId', 'testPub1'); + expect(postData.userIds).to.have.deep.property('haloId', 'testHaloId1'); request.respond(200, responseHeader, JSON.stringify(data)); - expect(reqBidsConfigObj.adUnits[0].bids[0].segments[0]).to.have.deep.property('id', 'seg1'); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); }); }); }); diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index 3c544fa8d08..a31270c86c7 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -6,19 +6,21 @@ import {getStorageManager} from '../../../src/storageManager.js'; export const storage = getStorageManager(); const pid = '14'; -const defaultConfigParams = { params: {pid: pid} }; +let defaultConfigParams; const responseHeader = {'Content-Type': 'application/json'} describe('IdentityLinkId tests', function () { let logErrorStub; beforeEach(function () { + defaultConfigParams = { params: {pid: pid} }; logErrorStub = sinon.stub(utils, 'logError'); // remove _lr_retry_request cookie before test storage.setCookie('_lr_retry_request', 'true', 'Thu, 01 Jan 1970 00:00:01 GMT'); }); afterEach(function () { + defaultConfigParams = {}; logErrorStub.restore(); }); @@ -141,7 +143,7 @@ describe('IdentityLinkId tests', function () { expect(request).to.be.eq(undefined); }); - it('should call the LiveRamp envelope endpoint if cookie _lr_retry_request does not exist', function () { + it('should call the LiveRamp envelope endpoint if cookie _lr_retry_request does not exist and notUse3P config property was not set', function () { let callBackSpy = sinon.spy(); let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); @@ -154,4 +156,13 @@ describe('IdentityLinkId tests', function () { ); expect(callBackSpy.calledOnce).to.be.true; }); + + it('should not call the LiveRamp envelope endpoint if config property notUse3P is set to true', function () { + defaultConfigParams.params.notUse3P = true; + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request).to.be.eq(undefined); + }); }); diff --git a/test/spec/modules/impactifyBidAdapter_spec.js b/test/spec/modules/impactifyBidAdapter_spec.js new file mode 100644 index 00000000000..d14cea8cad3 --- /dev/null +++ b/test/spec/modules/impactifyBidAdapter_spec.js @@ -0,0 +1,398 @@ +import { expect } from 'chai'; +import { spec } from 'modules/impactifyBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const BIDDER_CODE = 'impactify'; +const BIDDER_ALIAS = ['imp']; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_VIDEO_WIDTH = 640; +const DEFAULT_VIDEO_HEIGHT = 480; +const ORIGIN = 'https://sonic.impactify.media'; +const LOGGER_URI = 'https://logger.impactify.media'; +const AUCTIONURI = '/bidder'; +const COOKIESYNCURI = '/static/cookie_sync.html'; +const GVLID = 606; + +var gdprData = { + 'consentString': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'gdprApplies': true +}; + +describe('ImpactifyAdapter', function () { + describe('isBidRequestValid', function () { + let validBid = { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + } + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, validBid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when appId is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.appId; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when appId is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.appId = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.format; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.format = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is not equals to screen or display', () => { + const bid = utils.deepClone(validBid); + if (bid.params.format != 'screen' && bid.params.format != 'display') { + expect(spec.isBidRequestValid(bid)).to.equal(false); + } + }); + + it('should return false when style is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.style; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when style is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.style = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + describe('buildRequests', function () { + let videoBidRequests = [ + { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + transactionId: 'f7b2c372-7a7b-11eb-9439-0242ac130002' + } + ]; + let videoBidderRequest = { + bidderRequestId: '98845765110', + auctionId: '165410516454', + bidderCode: 'impactify', + bids: [ + { + ...videoBidRequests[0] + } + ], + refererInfo: { + referer: 'https://impactify.io' + } + }; + + it('sends video bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(videoBidRequests, videoBidderRequest); + expect(request.url).to.equal(ORIGIN + AUCTIONURI); + expect(request.method).to.equal('POST'); + }); + }); + describe('interpretResponse', function () { + it('should get correct bid response', function () { + let response = { + id: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + seatbid: [ + { + bid: [ + { + id: '65820304700829014', + impid: '462c08f20d428', + price: 3.40, + adm: '', + adid: '97517771', + adomain: [ + '' + ], + iurl: 'https://fra1-ib.adnxs.com/cr?id=97517771', + cid: '9325', + crid: '97517771', + w: 1, + h: 1, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + prebid: { + type: 'video', + video: { + duration: 30, + primary_category: '' + } + }, + bidder: { + appnexus: { + brand_id: 182979, + auction_id: 8657683934873599656, + bidder_id: 2, + bid_ad_type: 1, + creative_info: { + video: { + duration: 30, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'impactify' + } + ], + cur: DEFAULT_CURRENCY, + ext: { + responsetimemillis: { + impactify: 114 + }, + prebid: { + auctiontimestamp: 1614587024591 + } + } + }; + let bidderRequest = { + bids: [ + { + bidId: '462c08f20d428', + adUnitCode: '/19968336/header-bid-tag-1', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + bidder: 'impactify', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + mediaTypes: { + video: { + context: 'outstream' + } + } + }, + ] + } + let expectedResponse = [ + { + id: '65820304700829014', + requestId: '462c08f20d428', + cpm: 3.40, + currency: DEFAULT_CURRENCY, + netRevenue: true, + ad: '', + width: 1, + height: 1, + hash: 'test', + expiry: 166192938, + ttl: 300, + creativeId: '97517771' + } + ]; + let result = spec.interpretResponse({ body: response }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }); + describe('getUserSyncs', function () { + let videoBidRequests = [ + { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + transactionId: 'f7b2c372-7a7b-11eb-9439-0242ac130002' + } + ]; + let videoBidderRequest = { + bidderRequestId: '98845765110', + auctionId: '165410516454', + bidderCode: 'impactify', + bids: [ + { + ...videoBidRequests[0] + } + ], + refererInfo: { + referer: 'https://impactify.io' + } + }; + let validResponse = { + id: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + seatbid: [ + { + bid: [ + { + id: '65820304700829014', + impid: '462c08f20d428', + price: 3.40, + adm: '', + adid: '97517771', + adomain: [ + '' + ], + iurl: 'https://fra1-ib.adnxs.com/cr?id=97517771', + cid: '9325', + crid: '97517771', + w: 1, + h: 1, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + prebid: { + type: 'video', + video: { + duration: 30, + primary_category: '' + } + }, + bidder: { + appnexus: { + brand_id: 182979, + auction_id: 8657683934873599656, + bidder_id: 2, + bid_ad_type: 1, + creative_info: { + video: { + duration: 30, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'impactify' + } + ], + cur: DEFAULT_CURRENCY, + ext: { + responsetimemillis: { + impactify: 114 + }, + prebid: { + auctiontimestamp: 1614587024591 + } + } + }; + it('should return empty response if server response is false', function () { + const result = spec.getUserSyncs('bad', false, gdprData); + expect(result).to.be.empty; + }); + it('should return empty response if server response is empty', function () { + const result = spec.getUserSyncs('bad', [], gdprData); + expect(result).to.be.empty; + }); + it('should append the various values if they exist', function() { + const result = spec.getUserSyncs({iframeEnabled: true}, validResponse, gdprData); + expect(result[0].url).to.include('gdpr=1'); + expect(result[0].url).to.include('gdpr_consent=BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); + }); + }); + + describe('On winning bid', function () { + const bid = { + ad: '', + cpm: '2' + }; + const result = spec.onBidWon(bid); + assert.ok(result); + }); + + describe('On bid Time out', function () { + const bid = { + ad: '', + cpm: '2' + }; + const result = spec.onTimeout(bid); + assert.ok(result); + }); +}) diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index fa00a359191..9435d9131fa 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -625,6 +625,79 @@ describe('Livewrapped adapter tests', function () { expect(data).to.deep.equal(expectedQuery); }); + it('should pass us privacy parameter', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + let testRequest = clone(bidderRequest); + testRequest.uspConsent = '1---'; + let result = spec.buildRequests(testRequest.bids, testRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + usPrivacy: '1---', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should pass coppa parameter', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'coppa') { + return true; + } + return origGetConfig.apply(config, arguments); + }); + + let result = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + coppa: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + it('should pass no cookie support', function() { sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => false); sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); diff --git a/test/spec/modules/logicadBidAdapter_spec.js b/test/spec/modules/logicadBidAdapter_spec.js index c4c06630a2b..6eb8dc1c043 100644 --- a/test/spec/modules/logicadBidAdapter_spec.js +++ b/test/spec/modules/logicadBidAdapter_spec.js @@ -19,7 +19,23 @@ describe('LogicadAdapter', function () { banner: { sizes: [[300, 250], [300, 600]] } - } + }, + userId: { + sharedid: { + id: 'fakesharedid', + third: 'fakesharedid' + } + }, + userIdAsEids: [{ + source: 'sharedid.org', + uids: [{ + id: 'fakesharedid', + atype: 1, + ext: { + third: 'fakesharedid' + } + }] + }] }]; const nativeBidRequests = [{ bidder: 'logicad', @@ -45,7 +61,23 @@ describe('LogicadAdapter', function () { required: true } } - } + }, + userId: { + sharedid: { + id: 'fakesharedid', + third: 'fakesharedid' + } + }, + userIdAsEids: [{ + source: 'sharedid.org', + uids: [{ + id: 'fakesharedid', + atype: 1, + ext: { + third: 'fakesharedid' + } + }] + }] }]; const bidderRequest = { refererInfo: { @@ -141,6 +173,11 @@ describe('LogicadAdapter', function () { expect(request.method).to.equal('POST'); expect(request.url).to.equal('https://pb.ladsp.com/adrequest/prebid'); expect(request.data).to.exist; + + const data = JSON.parse(request.data); + expect(data.auctionId).to.equal('18fd8b8b0bd757'); + expect(data.eids[0].source).to.equal('sharedid.org'); + expect(data.eids[0].uids[0].id).to.equal('fakesharedid'); }); }); diff --git a/test/spec/modules/mass_spec.js b/test/spec/modules/mass_spec.js index d8d27348bde..a4a87ce113f 100644 --- a/test/spec/modules/mass_spec.js +++ b/test/spec/modules/mass_spec.js @@ -3,10 +3,12 @@ import { init, addBidResponseHook, addListenerOnce, - getRenderPayload, - render, + isMassBid, + useDefaultMatch, + useDefaultRender, + updateRenderers, listenerAdded, - massEnabled + isEnabled } from 'modules/mass'; import { logInfo } from 'src/utils.js'; @@ -70,16 +72,16 @@ describe('MASS Module', function() { let bidderRequest = Object.assign({}, mockedBidderRequest); it('should be enabled by default', function() { - expect(massEnabled).to.equal(true); + expect(isEnabled).to.equal(true); }); it('can be disabled', function() { init({enabled: false}); - expect(massEnabled).to.equal(false); + expect(isEnabled).to.equal(false); }); it('should only affect MASS bids', function() { - init({renderUrl: 'http://...'}); + init({renderUrl: 'https://...'}); mockedNonMassBids.forEach(function(mockedBid) { const originalBid = Object.assign({}, mockedBid); const bid = Object.assign({}, originalBid); @@ -93,7 +95,7 @@ describe('MASS Module', function() { }); it('should only update the ad markup field', function() { - init({renderUrl: 'http://...'}); + init({renderUrl: 'https://...'}); mockedMassBids.forEach(function(mockedBid) { const originalBid = Object.assign({}, mockedBid); const bid = Object.assign({}, originalBid); @@ -116,15 +118,34 @@ describe('MASS Module', function() { expect(listenerAdded).to.equal(true); }); - it('should get correct bid in render payload', function() { - const payload = getRenderPayload({data: {massBidId: 'mass-bid-1'}}); - expect(payload.type).to.equal('prebid'); - expect(payload.bid.bidId).to.equal('mass-bid-1'); + it('should support custom renderers', function() { + init({ + renderUrl: 'https://...', + custom: [ + { + dealIdPattern: /abc/, + render: function() {} + } + ] + }); + + const renderers = updateRenderers(); + + expect(renderers.length).to.equal(2); }); - it('should load the bootloader on rendering', function() { + it('should match bids by deal ID with the default matcher', function() { + const match = useDefaultMatch(/abc/); + + expect(match({dealId: 'abc'})).to.equal(true); + expect(match({dealId: 'xyz'})).to.equal(false); + }); + + it('should have a default renderer', function() { + const render = useDefaultRender('https://example.com/render.js', 'abc'); render({}); - expect(window.mass).to.be.an('object'); - expect(window.mass.bootloader.loaded).to.equal(true); + + expect(window.abc.loaded).to.equal(true); + expect(window.abc.queue.length).to.equal(1); }); }); diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js new file mode 100644 index 00000000000..e1132bf1b74 --- /dev/null +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -0,0 +1,227 @@ +import { expect } from 'chai' +import { spec } from 'modules/nativoBidAdapter.js' +// import { newBidder } from 'src/adapters/bidderFactory.js' +// import * as bidderFactory from 'src/adapters/bidderFactory.js' +// import { deepClone } from 'src/utils.js' +// import { config } from 'src/config.js' + +describe('nativoBidAdapterTests', function () { + describe('isBidRequestValid', function () { + let bid = { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + } + + 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 () { + let bid2 = Object.assign({}, bid) + delete bid2.params + bid2.params = {} + expect(spec.isBidRequestValid(bid2)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests = [ + { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + transactionId: '3b36e7e0-0c3e-4006-a279-a741239154ff', + }, + ] + + it('url should contain query string parameters', function () { + const request = spec.buildRequests(bidRequests, { + bidderRequestId: 123456, + refererInfo: { + referer: 'https://www.test.com', + }, + }) + + expect(request.url).to.exist + expect(request.url).to.be.a('string') + + expect(request.url).to.include('?') + expect(request.url).to.include('ntv_url') + expect(request.url).to.include('ntv_ptd') + }) + }) +}) + +describe('interpretResponse', function () { + let response = { + id: '126456', + seatbid: [ + { + seat: 'seat_0', + bid: [ + { + id: 'f70362ac-f3cf-4225-82a5-948b690927a6', + impid: '1', + price: 3.569, + adm: '', + h: 300, + w: 250, + cat: [], + adomain: ['test.com'], + crid: '1060_72_6760217', + }, + ], + }, + ], + cur: 'USD', + } + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '1F254428-AB11-4D5E-9887-567B3F952CA5', + cpm: 3.569, + currency: 'USD', + width: 300, + height: 250, + creativeId: '1060_72_6760217', + dealId: 'f70362ac-f3cf-4225-82a5-948b690927a6', + netRevenue: true, + ttl: 360, + ad: '', + meta: { + advertiserDomains: ['test.com'], + }, + }, + ] + + let bidderRequest = { + id: 123456, + bids: [ + { + params: { + placementId: 1 + } + }, + ], + } + + // mock + spec.getRequestId = () => 123456 + + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(Object.keys(result[0])).to.have.deep.members( + Object.keys(expectedResponse[0]) + ) + }) + + it('handles nobid responses', function () { + let response = {} + let bidderRequest + + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(result.length).to.equal(0) + }) +}) + +describe('getUserSyncs', function () { + const response = [ + { + body: { + cur: 'USD', + id: 'a136dbd8-4387-48bf-b8e4-ff9c1d6056ee', + seatbid: [ + { + bid: [{}], + seat: 'seat_0', + syncUrls: [ + { + type: 'image', + url: 'pixel-tracker-test-url/?{GDPR_params}', + }, + { + type: 'iframe', + url: 'iframe-tracker-test-url/?{GDPR_params}', + }, + ], + }, + ], + }, + }, + ] + + const gdprConsent = { + gdprApplies: true, + consentString: '111111' + } + + const uspConsent = { + uspConsent: '1YYY' + } + + it('Returns empty array if no supported user syncs', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(0) + }) + + it('Returns valid iframe user sync', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: true, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(1) + expect(userSync[0].type).to.exist + expect(userSync[0].url).to.exist + expect(userSync[0].type).to.be.equal('iframe') + expect(userSync[0].url).to.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + }) + + it('Returns valid URL and type', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: true, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(1) + expect(userSync[0].type).to.exist + expect(userSync[0].url).to.exist + expect(userSync[0].type).to.be.equal('image') + expect(userSync[0].url).to.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + }) +}) diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index c8c92392ff2..580fd3dec93 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1854,6 +1854,13 @@ describe('OpenxAdapter', function () { expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); }); + 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); + }); + 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); diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js new file mode 100644 index 00000000000..9671adf826e --- /dev/null +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -0,0 +1,558 @@ +import {expect} from 'chai'; +import {spec} from 'modules/outbrainBidAdapter.js'; +import {config} from 'src/config.js'; +import {server} from 'test/mocks/xhr'; + +describe('Outbrain Adapter', function () { + describe('Bid request and response', function () { + const commonBidRequest = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id' + }, + }, + bidId: '2d6815a92ba1ba', + auctionId: '12043683-3254-4f74-8934-f941b085579e', + } + const nativeBidRequestParams = { + nativeParams: { + image: { + required: true, + sizes: [ + 120, + 100 + ], + sendId: true + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false + } + }, + } + + const displayBidRequestParams = { + sizes: [ + [ + 300, + 250 + ] + ] + } + + describe('isBidRequestValid', function () { + before(() => { + config.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should fail when bid is invalid', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should succeed when bid contains native params', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should succeed when bid contains sizes', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail if publisher id is not set', function () { + const bid = { + bidder: 'outbrain', + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should succeed with outbrain config', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + config.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + }) + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail if bidder url is not set', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + before(() => { + config.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/' + } + } + + it('should build native request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const expectedNativeAssets = { + assets: [ + { + required: 1, + id: 3, + img: { + type: 3, + w: 120, + h: 100 + } + }, + { + required: 1, + id: 0, + title: {} + }, + { + required: 0, + id: 5, + data: { + type: 1 + } + } + ] + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + native: { + request: JSON.stringify(expectedNativeAssets) + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }); + + it('should build display request', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + + it('should pass optional parameters in request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + bidRequest.params.tagid = 'test-tag' + bidRequest.params.publisher.name = 'test-publisher' + bidRequest.params.publisher.domain = 'test-publisher.com' + bidRequest.params.bcat = ['bad-category'] + bidRequest.params.badv = ['bad-advertiser'] + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.imp[0].tagid).to.equal('test-tag') + expect(resData.site.publisher.name).to.equal('test-publisher') + expect(resData.site.publisher.domain).to.equal('test-publisher.com') + expect(resData.bcat).to.deep.equal(['bad-category']) + expect(resData.badv).to.deep.equal(['bad-advertiser']) + }); + + it('should pass bidder timeout', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.tmax).to.equal(500) + }); + + it('should pass GDPR consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + uspConsent: 'consentString' + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.us_privacy).to.equal('consentString') + }); + + it('should pass coppa consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + config.setConfig({coppa: true}) + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) + + describe('interpretResponse', function () { + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse({}, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should interpret native response', function () { + const serverResponse = { + body: { + id: '0a73e68c-9967-4391-b01b-dda2d9fc54e4', + seatbid: [ + { + bid: [ + { + id: '82822cf5-259c-11eb-8a52-f29e5275aa57', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '{"ver":"1.2","assets":[{"id":3,"required":1,"img":{"url":"http://example.com/img/url","w":120,"h":100}},{"id":0,"required":1,"title":{"text":"Test title"}},{"id":5,"data":{"value":"Test sponsor"}}],"link":{"url":"http://example.com/click/url"},"eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + adomain: [ + 'example.com' + ], + cid: '3487171', + crid: '28023739', + cat: [ + 'IAB10-2' + ] + } + ], + seat: 'acc-5537' + } + ], + bidid: '82822cf5-259c-11eb-8a52-b48e7518c657', + cur: 'USD' + }, + } + const request = { + bids: [ + { + ...commonBidRequest, + ...nativeBidRequestParams, + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '28023739', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'native', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + native: { + clickTrackers: undefined, + clickUrl: 'http://example.com/click/url', + image: { + url: 'http://example.com/img/url', + width: 120, + height: 100 + }, + title: 'Test title', + sponsoredBy: 'Test sponsor', + impressionTrackers: [ + 'http://example.com/impression', + ] + } + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + + it('should interpret display response', function () { + const serverResponse = { + body: { + id: '6b2eedc8-8ff5-46ef-adcf-e701b508943e', + seatbid: [ + { + bid: [ + { + id: 'd90fe7fa-28d7-11eb-8ce4-462a842a7cf9', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '
ad
', + adomain: [ + 'example.com' + ], + cid: '3865084', + crid: '29998660', + cat: [ + 'IAB10-2' + ], + w: 300, + h: 250 + } + ], + seat: 'acc-6536' + } + ], + bidid: 'd90fe7fa-28d7-11eb-8ce4-13d94bfa26f9', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'banner', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + ad: '
ad
', + width: 300, + height: 250, + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + }) + + describe('getUserSyncs', function () { + const usersyncUrl = 'https://usersync-url.com'; + beforeEach(() => { + config.setConfig({ + outbrain: { + usersyncUrl: usersyncUrl, + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should return user sync if pixel enabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.deep.equal([{type: 'image', url: 'https://usersync-url.com'}]) + }) + it('should return user sync if pixel enabled with outbrain config', function () { + config.resetConfig() + config.setConfig({ + outbrain: { + usersyncUrl: 'https://usersync-url.com', + } + }) + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.deep.equal([{type: 'image', url: 'https://usersync-url.com'}]) + }) + + it('should not return user sync if pixel disabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: false}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should not return user sync if url is not set', function () { + config.resetConfig() + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should pass GDPR consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&gdpr=1&gdpr_consent=` + }]); + }); + + it('should pass US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&us_privacy=1NYN` + }]); + }); + + it('should pass GDPR and US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN` + }]); + }); + }) + + describe('onBidWon', function () { + it('should make an ajax call with the original cpm', function () { + const bid = { + nurl: 'http://example.com/win/${AUCTION_PRICE}', + cpm: 2.1, + originalCpm: 1.1, + } + spec.onBidWon(bid) + expect(server.requests[0].url).to.equals('http://example.com/win/1.1') + }); + }) +}) diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index f98d9633320..bdcdca06183 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -2,16 +2,20 @@ import { expect } from 'chai'; let { spec } = require('modules/proxistoreBidAdapter'); const BIDDER_CODE = 'proxistore'; describe('ProxistoreBidAdapter', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; const bidderRequest = { bidderCode: BIDDER_CODE, auctionId: '1025ba77-5463-4877-b0eb-14b205cb9304', bidderRequestId: '10edf38ec1a719', gdprConsent: { + apiVersion: 2, gdprApplies: true, - consentString: 'CONSENT_STRING', + consentString: consentString, vendorData: { - vendorConsents: { - 418: true, + vendor: { + consents: { + 418: true, + }, }, }, }, @@ -50,7 +54,7 @@ describe('ProxistoreBidAdapter', function () { const url = { cookieBase: 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi', cookieLess: - 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi/cookieless', + 'https://abs.cookieless-proxistore.com/fr/v3/rtb/prebid/multi', }; let request = spec.buildRequests([bid], bidderRequest); it('should return a valid object', function () { @@ -75,6 +79,29 @@ describe('ProxistoreBidAdapter', function () { expect(request.url).equal(url.cookieBase); // doens't have gpdr consent bidderRequest.gdprConsent.vendorData = null; + + request = spec.buildRequests([bid], bidderRequest); + expect(request.url).equal(url.cookieLess); + + // api v2 + bidderRequest.gdprConsent = { + gdprApplies: true, + allowAuctionWithoutConsent: true, + consentString: consentString, + vendorData: { + vendor: { + consents: { + '418': true + } + }, + }, + apiVersion: 2 + }; + // has gdpr consent + request = spec.buildRequests([bid], bidderRequest); + expect(request.url).equal(url.cookieBase); + + bidderRequest.gdprConsent.vendorData.vendor = {}; request = spec.buildRequests([bid], bidderRequest); expect(request.url).equal(url.cookieLess); }); @@ -105,26 +132,29 @@ describe('ProxistoreBidAdapter', function () { expect(data.bids[0].floor).to.be.null; }); }); - describe('interpretResponse', function() { - const emptyResponseParam = {body: []}; - const fakeResponseParam = {body: [ - { ad: '', - cpm: 6.25, - creativeId: '22c3290b-8cd5-4cd6-8e8c-28a2de180ccd', - currency: 'EUR', - dealId: '2021-03_a63ec55e-b9bb-4ca4-b2c9-f456be67e656', - height: 600, - netRevenue: true, - requestId: '3543724f2a033c9', - segments: [], - ttl: 10, - vastUrl: null, - vastXml: null, - width: 300} - ] + describe('interpretResponse', function () { + const emptyResponseParam = { body: [] }; + const fakeResponseParam = { + body: [ + { + ad: '', + cpm: 6.25, + creativeId: '22c3290b-8cd5-4cd6-8e8c-28a2de180ccd', + currency: 'EUR', + dealId: '2021-03_a63ec55e-b9bb-4ca4-b2c9-f456be67e656', + height: 600, + netRevenue: true, + requestId: '3543724f2a033c9', + segments: [], + ttl: 10, + vastUrl: null, + vastXml: null, + width: 300, + }, + ], }; - it('should always return an array', function() { + it('should always return an array', function () { let response = spec.interpretResponse(emptyResponseParam, bid); expect(response).to.be.an('array'); expect(response.length).equal(0); diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 382199dcffc..57b83fced06 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -225,8 +225,8 @@ describe('pubGENIUS adapter', () => { expect(buildRequests([bidRequest, bidRequest1], bidderRequest)).to.deep.equal(expectedRequest); }); - it('should take bid floor in bidder params', () => { - bidRequest.params.bidFloor = 0.5; + it('should take bid floor from getFloor interface', () => { + bidRequest.getFloor = () => ({ floor: 0.5, currency: 'USD' }); expectedRequest.data.imp[0].bidfloor = 0.5; expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js index d5a877f6221..eefd7792a7c 100644 --- a/test/spec/modules/readpeakBidAdapter_spec.js +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -189,6 +189,73 @@ describe('ReadPeakAdapter', function() { language: navigator.language }); expect(data.cur).to.deep.equal(['EUR']); + expect(data.user).to.be.undefined; + expect(data.regs).to.be.undefined; + }); + + it('should get bid floor from module', function() { + const floorModuleData = { + currency: 'USD', + floor: 3.2, + } + bidRequest.getFloor = function () { + return floorModuleData + } + const request = spec.buildRequests([bidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal('$prebid.version$'); + expect(data.id).to.equal(bidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(floorModuleData.floor); + expect(data.imp[0].bidfloorcur).to.equal(floorModuleData.currency); + }); + + it('should send gdpr data when gdpr does not apply', function() { + const gdprData = { + gdprConsent: { + gdprApplies: false, + consentString: undefined, + } + } + const request = spec.buildRequests([bidRequest], {...bidderRequest, ...gdprData}); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: '' + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: false + } + }); + }); + + it('should send gdpr data when gdpr applies', function() { + const tcString = 'sometcstring'; + const gdprData = { + gdprConsent: { + gdprApplies: true, + consentString: tcString + } + } + const request = spec.buildRequests([bidRequest], {...bidderRequest, ...gdprData}); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: tcString + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: true + } + }); }); }); @@ -213,6 +280,9 @@ describe('ReadPeakAdapter', function() { currency: serverResponse.cur }); + expect(bidResponse.meta).to.deep.equal({ + advertiserDomains: ['readpeak.com'], + }) expect(bidResponse.native.title).to.equal('Title'); expect(bidResponse.native.body).to.equal('Description'); expect(bidResponse.native.image).to.deep.equal({ diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 5deb2463523..72410b71fb2 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -788,76 +788,343 @@ describe('Richaudience adapter tests', function () { })).to.equal(true); }); - it('Verifies user syncs iframe', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true + describe('userSync', function () { + it('Verifies user syncs iframe include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); }); + it('Verifies user syncs iframe exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}}} + }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - syncs = spec.getUserSyncs({ - iframeEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); }); - expect(syncs).to.have.lengthOf(0); - syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(1); + it('Verifies user syncs image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); - syncs = spec.getUserSyncs({ - iframeEnabled: false - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); - config.setConfig({ - consentManagement: { - cmpApi: 'iab', - timeout: 5000, - allowAuctionWithoutConsent: true, + syncs = spec.getUserSyncs({ + iframeEnabled: false, pixelEnabled: true - } + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); }); - }); - it('Verifies user syncs image', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: '', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [], { - consentString: null, - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - }); + it('Verifies user syncs image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe exclude / image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe include / image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + }) }); diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 0239eff5883..84315dd1eba 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1773,6 +1773,40 @@ describe('rubicon analytics adapter', function () { expect(message.bidsWon[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); }); + it('should correctly generate new bidId if it is 0', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let seatBidResponse = utils.deepClone(BID4); + seatBidResponse.pbsBidId = '0'; + + const setTargeting = { + [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting + }; + + const bidWon = Object.assign({}, seatBidResponse, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(server.requests[0].requestBody); + + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal(STUBBED_UUID); + expect(message.bidsWon[0].bidId).to.equal(STUBBED_UUID); + }); + it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { // Only want one bid request in our mock auction let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index b3451a09dde..57306580ecc 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from '../../../src/utils.js'; +import { config } from 'src/config'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); const bidRequests = [ @@ -441,6 +442,29 @@ describe('sharethrough adapter spec', function() { const builtBidRequest = spec.buildRequests([bidRequest])[0]; expect(builtBidRequest.data).to.not.include.any.keys('bidfloor'); }); + + describe('coppa', function() { + it('should add coppa to request if enabled', function() { + config.setConfig({coppa: true}); + const bidRequest = Object.assign({}, bidRequests[0]); + const builtBidRequest = spec.buildRequests([bidRequest])[0]; + expect(builtBidRequest.data.coppa).to.eq(true); + }); + + it('should not add coppa to request if disabled', function() { + config.setConfig({coppa: false}); + const bidRequest = Object.assign({}, bidRequests[0]); + const builtBidRequest = spec.buildRequests([bidRequest])[0]; + expect(builtBidRequest.data.coppa).to.be.undefined; + }); + + it('should not add coppa to request if unknown value', function() { + config.setConfig({coppa: 'something'}); + const bidRequest = Object.assign({}, bidRequests[0]); + const builtBidRequest = spec.buildRequests([bidRequest])[0]; + expect(builtBidRequest.data.coppa).to.be.undefined; + }); + }); }); describe('.interpretResponse', function() { diff --git a/test/spec/modules/sirdataRtdProvider_spec.js b/test/spec/modules/sirdataRtdProvider_spec.js new file mode 100644 index 00000000000..a16359c50cb --- /dev/null +++ b/test/spec/modules/sirdataRtdProvider_spec.js @@ -0,0 +1,94 @@ +import { addSegmentData, getSegmentsAndCategories, sirdataSubmodule } from 'modules/sirdataRtdProvider.js'; +import { server } from 'test/mocks/xhr.js'; + +const responseHeader = {'Content-Type': 'application/json'}; + +describe('sirdataRtdProvider', function() { + describe('sirdataSubmodule', function() { + it('successfully instantiates', function () { + expect(sirdataSubmodule.init()).to.equal(true); + }); + }); + + describe('Add Segment Data', function() { + it('adds segment data', function() { + const config = { + params: { + setGptKeyValues: false, + contextualMinRelevancyScore: 50, + bidders: [{ + bidder: 'appnexus' + }, { + bidder: 'other' + }] + } + }; + + let adUnits = [ + { + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }, { + bidder: 'other' + }] + } + ]; + + let data = { + segments: [111111, 222222], + contextual_categories: {'333333': 100} + }; + + addSegmentData(adUnits, data, config, () => {}); + expect(adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); + expect(adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); + expect(adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); + }); + }); + + describe('Get Segments And Categories', function() { + it('gets data from async request and adds segment data', function() { + const config = { + params: { + setGptKeyValues: false, + contextualMinRelevancyScore: 50, + bidders: [{ + bidder: 'appnexus' + }, { + bidder: 'other' + }] + } + }; + + let reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }, { + bidder: 'other' + }] + }] + }; + + let data = { + segments: [111111, 222222], + contextual_categories: {'333333': 100} + }; + + getSegmentsAndCategories(reqBidsConfigObj, () => {}, config, {}); + + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(reqBidsConfigObj.adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); + expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); + expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); + }); + }); +}); diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index abc06b48ce7..3c871c6f88b 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -423,6 +423,7 @@ describe('The smartx adapter', function () { expect(responses).to.be.an('array').with.length(2); expect(bidderRequestObj).to.be.an('Object'); expect(bidderRequestObj.bidRequest.bids).to.be.an('array').with.length(2); + expect(responses[0].meta.advertiserDomains[0]).to.equal('abc.com'); expect(responses[0].requestId).to.equal(123); expect(responses[0].currency).to.equal('USD'); expect(responses[0].cpm).to.equal(12); @@ -432,6 +433,7 @@ describe('The smartx adapter', function () { expect(responses[0].mediaType).to.equal('video'); expect(responses[0].width).to.equal(400); expect(responses[0].height).to.equal(300); + expect(responses[1].meta.advertiserDomains[0]).to.equal('def.com'); expect(responses[1].requestId).to.equal(124); expect(responses[1].currency).to.equal('USD'); expect(responses[1].cpm).to.equal(13); diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index 0ac242ce0e1..d0d8b65a42f 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -305,12 +305,12 @@ describe('smilewantedBidAdapterTests', function () { }); it('SmileWanted - Verify user sync', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [BID_RESPONSE_DISPLAY]); + var syncs = spec.getUserSyncs({iframeEnabled: true}, {}, { + consentString: 'foo' + }, '1NYN'); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://csync.smilewanted.com'); + expect(syncs[0].url).to.equal('https://csync.smilewanted.com?gdpr_consent=foo&us_privacy=1NYN'); syncs = spec.getUserSyncs({ iframeEnabled: false @@ -320,6 +320,6 @@ describe('smilewantedBidAdapterTests', function () { syncs = spec.getUserSyncs({ iframeEnabled: true }, []); - expect(syncs).to.have.lengthOf(0); + expect(syncs).to.have.lengthOf(1); }); }); diff --git a/test/spec/modules/sortableBidAdapter_spec.js b/test/spec/modules/sortableBidAdapter_spec.js index 8d2b4eea87d..97f80e8dfeb 100644 --- a/test/spec/modules/sortableBidAdapter_spec.js +++ b/test/spec/modules/sortableBidAdapter_spec.js @@ -329,7 +329,40 @@ describe('sortableBidAdapter', function() { const gdprRequest = spec.buildRequests(gdprBidRequests, {refererInfo: { referer: 'http://localhost:9876/' }}); const gdprRequestBody = JSON.parse(gdprRequest.data); expect(gdprRequestBody.regs).to.deep.equal({ext: {}}); - expect(gdprRequestBody.user).to.equal(undefined); + expect(gdprRequestBody.user.ext.consent).to.equal(undefined); + }) + + const eidsBidRequests = [{ + 'bidder': 'sortable', + 'params': { + 'tagId': '403370', + 'siteId': 'example.com', + 'floor': 0.21, + 'keywords': {}, + 'floorSizeMap': {} + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + it('should not set user ids when none present', function() { + const eidsRequest = spec.buildRequests(eidsBidRequests, {refererInfo: { + referer: 'http://localhost:9876/' + }}); + const eidsRequestBody = JSON.parse(eidsRequest.data); + + expect(eidsRequestBody.user.ext.eids).to.equal(undefined); + }) + + it('should set user ids when present', function() { + eidsBidRequests[0].userId = { criteoId: 'sample-userid' }; + const eidsRequest = spec.buildRequests(eidsBidRequests, {refererInfo: { + referer: 'http://localhost:9876/' + }}); + const eidsRequestBody = JSON.parse(eidsRequest.data); + + expect(eidsRequestBody.user.ext.eids.length).to.equal(1); }) }); diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index 873914441aa..5d7b32eaeeb 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -102,7 +102,7 @@ describe('the spotx adapter', function () { it('should build a very basic request', function() { var request = spec.buildRequests([bid], bidRequestObj)[0]; expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://search.spotxchange.com/openrtb/2.3/dados/12345'); + expect(request.url).to.equal('https://search.spotxchange.com/openrtb/2.3/dados/12345?src_sys=prebid'); expect(request.bidRequest).to.equal(bidRequestObj); expect(request.data.id).to.equal(12345); expect(request.data.ext.wrap_response).to.equal(1); @@ -495,6 +495,7 @@ describe('the spotx adapter', function () { expect(responses[0].requestId).to.equal(123); expect(responses[0].ttl).to.equal(360); expect(responses[0].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); + expect(responses[0].videoCacheKey).to.equal('cache123'); expect(responses[0].width).to.equal(400); expect(responses[1].cache_key).to.equal('cache124'); expect(responses[1].channel_id).to.equal(12345); @@ -508,6 +509,7 @@ describe('the spotx adapter', function () { expect(responses[1].requestId).to.equal(124); expect(responses[1].ttl).to.equal(360); expect(responses[1].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache124'); + expect(responses[1].videoCacheKey).to.equal('cache124'); expect(responses[1].width).to.equal(200); }); }); diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index c4410d8ce5e..378b391a4eb 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -1,36 +1,168 @@ import { assert } from 'chai'; -import {spec} from 'modules/tappxBidAdapter'; +import { spec } from 'modules/tappxBidAdapter'; -describe('Tappx adapter tests', function () { - describe('isBidRequestValid', function () { - let bid = { bidder: 'tappx', params: { host: 'testing.ssp.tappx.com', tappxkey: 'pub-1234-test-1234', endpoint: 'ZZ1234PBJS', bidfloor: 0.005 } }; +const c_BIDREQUEST = { + data: { + }, + bids: [ + { + bidder: 'tappx', + params: { + host: 'testing.ssp.tappx.com\/rtb\/v2\/', + tappxkey: 'pub-1234-android-1234', + endpoint: 'ZZ1234PBJS', + bidfloor: 0.05 + }, + crumbs: { + pubcid: 'df2144f7-673f-4440-83f5-cd4a73642d99' + }, + fpd: { + context: { + adServer: { + name: 'gam', + adSlot: '/19968336/header-bid-tag-0' + }, + pbAdSlot: '/19968336/header-bid-tag-0', + }, + }, + mediaTypes: { + banner: { + sizes: [ + [ + 320, + 480 + ] + ] + } + }, + adUnitCode: 'div-1', + transactionId: '47dd44e8-e7db-417c-a8f1-621a2e1a117d', + sizes: [ + [ + 320, + 480 + ] + ], + bidId: '2170932097e505', + bidderRequestId: '140ba7a1ab7aeb', + auctionId: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + } + ] +}; +const c_SERVERRESPONSE_B = { + body: { + id: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + bidid: 'bid3811165568213389257', + seatbid: [ + { + seat: '1', + group: 0, + bid: [ + { + id: '3811165568213389257', + impid: 1, + price: 0.05, + adm: "\t", + w: 320, + h: 480, + lurl: 'http://testing.ssp.tappx.com/rtb/RTBv2Loss?id=3811165568213389257&ep=ZZ1234PBJS&au=test&bu=localhost&sz=320x480&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', + cid: '01744fbb521e9fb10ffea926190effea', + crid: 'a13cf884e66e7c660afec059c89d98b6', + adomain: [ + ], + }, + ], + }, + ], + cur: 'USD', + }, + headers: {} +}; + +const c_SERVERRESPONSE_V = { + body: { + id: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + bidid: 'bid3811165568213389257', + seatbid: [ + { + seat: '1', + group: 0, + bid: [ + { + id: '3811165568213389257', + impid: 1, + price: 0.05, + adm: "Tappx<\/AdSystem>Tappx<\/AdTitle><\/Impression><\/Error>00:00:22<\/Duration><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/TrackingEvents><\/ClickThrough><\/ClickTracking><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + 'lurl': 'https:\/\/ssp.api.tappx.com\/rtb\/RTBv2Loss?id=5001829913749291152&ep=VZ12TESTCTV&au=test&bu=localhost&sz=6x6&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', + cid: '01744fbb521e9fb10ffea926190effea', + crid: 'a13cf884e66e7c660afec059c89d98b6', + adomain: [ + ], + }, + ], + }, + ], + cur: 'USD', + }, + headers: {} +}; + +const c_CONSENTSTRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; +const c_VALIDBIDREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000x179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6eFJ7otPYix179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6e', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_rEXbz6UYtYEJelYrDaZOLkh8WcF9J0ZHmEHFKZEBlLXsgP6xqXU3BCj4Ay0Z6fw_jSOaHxMHwd-voRHqFA4Q9NwAxFcVLyPWnNGZ9VbcSAPos1wupq7Xu3MIm-Bw_0vxjhZdWNy4chM9x3i', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': 'xTtLUY7GwqX2MMqSHo9RQ2YUOIBFhlASOR43I9KjvgtcrxIys3RxME96M02LTjWR', 'parrableId': {'eid': '02.YoqC9lWZh8.C8QTSiJTNgI6Pp0KCM5zZgEgwVMSsVP5W51X8cmiUHQESq9WRKB4nreqZJwsWIcNKlORhG4u25Wm6lmDOBmQ0B8hv0KP6uVQ97aouuH52zaz2ctVQTORUKkErPRPcaCJ7dKFcrNoF2i6WOR0S5Nk'}, 'pubcid': 'b1254-152f-12F5-5698-dI1eljK6C7WA', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_VALIDBIDAPPREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1, 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}, {'source': 'intentiq.com', 'uids': [{'id': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'atype': 1}]}, {'source': 'crwdcntrl.net', 'uids': [{'id': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'atype': 1}]}, {'source': 'parrable.com', 'uids': [{'id': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0', 'atype': 1}]}, {'source': 'pubcid.org', 'uids': [{'id': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'atype': 1}]}, {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_BIDDERREQUEST_B = {'bidderCode': 'tappx', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'bidderRequestId': '1c674c14a3889c', 'bids': [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1617088922120, 'timeout': 700, 'refererInfo': {'referer': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': c_CONSENTSTRING, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; +const c_BIDDERREQUEST_V = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"e807363f-3095-43a8-a4a6-f44196cb7318","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"w":320,"h":250,"mimes":["video/mp4","application/javascript"]},"id":"28f49c71b13f2f","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"VZ12TESTCTV","host":"testing.ssp.tappx.com/rtb/v2/"}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'context': 'instream', 'mimes': ['video/mp4', 'application/javascript'], 'playerSize': [[320, 250]]}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} +describe('Tappx bid adapter', function () { + /** + * IS REQUEST VALID + */ + describe('isBidRequestValid', function () { it('should return true when required params found', function () { - assert(spec.isBidRequestValid(bid)); + assert.isTrue(spec.isBidRequestValid(c_BIDREQUEST.bids[0]), JSON.stringify(c_BIDREQUEST)); }); it('should return false when required params are missing', function () { - const bid = { - host: 'testing.ssp.tappx.com' - }; - assert.isFalse(spec.isBidRequestValid(bid)); + let badBidRequest = c_BIDREQUEST; + delete badBidRequest.bids[0].params.tappxkey; + delete badBidRequest.bids[0].params.endpoint; + assert.isFalse(spec.isBidRequestValid(badBidRequest.bids[0])); + }); + + it('should return false for not instream requests', function () { + let badBidRequest_v = c_BIDDERREQUEST_V; + delete badBidRequest_v.bids.mediaTypes.banner; + badBidRequest_v.bids.mediaTypes.video = {}; + badBidRequest_v.bids.mediaTypes.video.context = 'outstream'; + badBidRequest_v.bids.mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; + badBidRequest_v.bids.mediaTypes.video.playerSize = [320, 250]; + assert.isFalse(spec.isBidRequestValid(badBidRequest_v.bids)); }); }); + /** + * BUILD REQUEST TEST + */ describe('buildRequest', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; // Web Test - let validBidRequests = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'api': [3, 5]}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'div-1', 'transactionId': '713f2c01-61e3-45b5-9e4e-2b163033f3d6', 'sizes': [[320, 480]], 'bidId': '27818d05971607', 'bidderRequestId': '1320551a307df5', 'auctionId': '3f1281d3-9860-4657-808d-3c1d42231ef3', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}] + let validBidRequests = c_VALIDBIDREQUESTS; + let validBidRequests_V = c_VALIDBIDREQUESTS; // App Test - let validAppBidRequests = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'api': [3, 5], 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 50]]}}, 'adUnitCode': 'div-1', 'transactionId': '713f2c01-61e3-45b5-9e4e-2b163033f3d6', 'sizes': [[320, 50]], 'bidId': '27818d05971607', 'bidderRequestId': '1320551a307df5', 'auctionId': '3f1281d3-9860-4657-808d-3c1d42231ef3', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}] - let bidderRequest = {'bidderCode': 'tappx', 'auctionId': '6cca2192-2262-468b-8a3c-d00c58a5d911', 'bidderRequestId': '1ae5c6a02684df', 'bids': [{'bidder': 'tappx', 'params': {'host': 'tests.tappx.com', 'tappxkey': 'pub-1234-test-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'banner-ad-div', 'transactionId': 'c44cdbde-ab6d-47a0-8dde-6b4ff7909a35', 'sizes': [[320, 50]], 'bidId': '2e3a5feb30cfe4', 'bidderRequestId': '1ae5c6a02684df', 'auctionId': '6cca2192-2262-468b-8a3c-d00c58a5d911', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1611308859094, 'timeout': 700, 'refererInfo': {'referer': 'http://tappx.local:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://tappx.local:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': consentString, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; + let validAppBidRequests = c_VALIDBIDAPPREQUESTS; + + let bidderRequest = c_BIDDERREQUEST_B; + let bidderRequest_V = c_BIDDERREQUEST_V; it('should add gdpr/usp consent information to the request', function () { const request = spec.buildRequests(validBidRequests, bidderRequest); const payload = JSON.parse(request[0].data); expect(payload.regs.gdpr).to.exist.and.to.be.true; - expect(payload.regs.consent).to.exist.and.to.equal(consentString); + expect(payload.regs.consent).to.exist.and.to.equal(c_CONSENTSTRING); expect(payload.regs.ext.us_privacy).to.exist; }); @@ -48,6 +180,28 @@ describe('Tappx adapter tests', function () { expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); }); + it('should properly build a video request', function () { + const request = spec.buildRequests(validBidRequests_V, bidderRequest_V); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + + it('should set user eids array', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + const data = JSON.parse(request[0].data); + expect(data.user.ext.eids, data).to.not.be.null; + expect(data.user.ext.eids[0]).to.have.keys(['source', 'uids']); + }); + it('should properly build a banner request with app params', function () { const request = spec.buildRequests(validAppBidRequests, bidderRequest); expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); @@ -63,24 +217,55 @@ describe('Tappx adapter tests', function () { }); }); + /** + * INTERPRET RESPONSE TESTS + */ describe('interpretResponse', function () { - const bidRequest = { - data: {}, - bids: [ {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.05, 'api': [3, 5]}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'div-1', 'transactionId': '47dd44e8-e7db-417c-a8f1-621a2e1a117d', 'sizes': [[320, 480]], 'bidId': '2170932097e505', 'bidderRequestId': '140ba7a1ab7aeb', 'auctionId': '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0} ] - }; - - const serverResponse = {'body': {'id': '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', 'bidid': 'bid3811165568213389257', 'seatbid': [{'seat': '1', 'group': 0, 'bid': [{'id': '3811165568213389257', 'impid': 1, 'price': 0.05, 'adm': "\t", 'w': 320, 'h': 480, 'lurl': 'http://testing.ssp.tappx.com/rtb/RTBv2Loss?id=3811165568213389257&ep=ZZ1234PBJS&au=test&bu=localhost&sz=320x480&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', 'cid': '01744fbb521e9fb10ffea926190effea', 'crid': 'a13cf884e66e7c660afec059c89d98b6', 'adomain': []}]}], 'cur': 'USD'}, 'headers': {}}; - it('receive reponse with single placement', function () { - const bids = spec.interpretResponse(serverResponse, bidRequest); + it('receive banner reponse with single placement', function () { + const bids = spec.interpretResponse(c_SERVERRESPONSE_B, c_BIDDERREQUEST_B); const bid = bids[0]; expect(bid.cpm).to.exist; expect(bid.ad).to.match(/^