diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index d28bf391aa5..404a8c04d14 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -244,10 +244,12 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; + addUserId(eids, utils.deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); addUserId(eids, utils.deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); addUserId(eids, utils.deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); addUserId(eids, utils.deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); addUserId(eids, utils.deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); + addUserId(eids, utils.deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); if (eids.length) { payload.eids = eids; diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 806f276fb72..5af399365bd 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -62,7 +62,7 @@ export const spec = { let bidurl = URL; const conversantImps = validBidRequests.map(function(bid) { - const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + const bidfloor = getBidFloor(bid); siteId = utils.getBidIdParameter('site_id', bid.params) || siteId; pubcidName = utils.getBidIdParameter('pubcid_name', bid.params) || pubcidName; @@ -378,4 +378,28 @@ function readStoredValue(key) { return storedValue; } +/** + * Get the floor price from bid.params for backward compatibility. + * If not found, then check floor module. + * @param bid A valid bid object + * @returns {*|number} floor price + */ +function getBidFloor(bid) { + let floor = utils.getBidIdParameter('bidfloor', bid.params); + + if (!floor && utils.isFn(bid.getFloor)) { + const floorObj = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (utils.isPlainObject(floorObj) && !isNaN(floorObj.floor) && floorObj.currency === 'USD') { + floor = floorObj.floor; + } + } + + return floor +} + registerBidder(spec); diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index 41a5af6d703..bcdcd7393f7 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -223,7 +223,7 @@ export const spec = { utils._each(validBidRequests, function (bid) { let tagid = utils.getBidIdParameter('tagid', bid.params); - let bidfloor = parseFloat(utils.getBidIdParameter('bidfloor', bid.params)) || 0; + let bidfloor = parseFloat(getBidFloor(bid)) || 0; let isVideo = !!bid.mediaTypes.video; let data = { id: bid.bidId, @@ -287,6 +287,14 @@ export const spec = { bidResponse = emxAdapter.formatVideoResponse(bidResponse, Object.assign({}, emxBid), bidRequest); } bidResponse.mediaType = (isVideo ? VIDEO : BANNER); + + // support for adomain in prebid 5.0 + if (emxBid.adomain && emxBid.adomain.length) { + bidResponse.meta = { + advertiserDomains: emxBid.adomain + }; + } + emxBidResponses.push(bidResponse); }); } @@ -312,4 +320,22 @@ export const spec = { return syncs; } }; + +// support floors module in prebid 5.0 +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return parseFloat(utils.getBidIdParameter('bidfloor', bid.params)); + } + + let floor = bid.getFloor({ + currency: DEFAULT_CUR, + mediaType: '*', + size: '*' + }); + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index d4d26b1e017..7d2942eea55 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -8,10 +8,10 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 5, + PREBID_VERSION: 6, METHOD: 'GET', INVIBES_VENDOR_ID: 436, - USERID_PROVIDERS: ['pubcid', 'pubProvidedId'] + USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'] }; const storage = getStorageManager(CONSTANTS.INVIBES_VENDOR_ID); @@ -168,12 +168,6 @@ function handleResponse(responseObj, bidRequests) { responseObj = responseObj.body || responseObj; responseObj = responseObj.videoAdContentResult || responseObj; - let bidModel = responseObj.BidModel; - if (typeof bidModel !== 'object') { - utils.logInfo('Invibes Adapter - Bidding is not configured'); - return []; - } - if (typeof invibes.bidResponse === 'object') { utils.logInfo('Invibes Adapter - Bid response already received. Invibes only responds to one bid request per user visit'); return []; @@ -181,51 +175,69 @@ function handleResponse(responseObj, bidRequests) { invibes.bidResponse = responseObj; - let ads = responseObj.Ads; + const bidResponses = []; + for (let i = 0; i < bidRequests.length; i++) { + let bidRequest = bidRequests[i]; - if (!Array.isArray(ads) || ads.length < 1) { - if (responseObj.AdReason != null) { - utils.logInfo('Invibes Adapter - ' + responseObj.AdReason); + let requestPlacement = null; + if (responseObj.AdPlacements != null) { + for (let j = 0; j < responseObj.AdPlacements.length; j++) { + let bidModel = responseObj.AdPlacements[j].BidModel; + if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + requestPlacement = responseObj.AdPlacements[j]; + break; + } + } + } else { + let bidModel = responseObj.BidModel; + if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + requestPlacement = responseObj; + } } - utils.logInfo('Invibes Adapter - No ads available'); - return []; + let bid = createBid(bidRequest, requestPlacement); + if (bid !== null) { + bidResponses.push(bid); + } } - let ad = ads[0]; + return bidResponses; +} - if (bidModel.PlacementId == null) { - utils.logInfo('Invibes Adapter - No Placement Id in response'); - return []; +function createBid(bidRequest, requestPlacement) { + if (requestPlacement === null || requestPlacement.BidModel === null) { + utils.logInfo('Invibes Adapter - Placement not configured for bidding ' + bidRequest.params.placementId); + return null; } - const bidResponses = []; - for (let i = 0; i < bidRequests.length; i++) { - let bidRequest = bidRequests[i]; - - if (bidModel.PlacementId == bidRequest.params.placementId) { - let size = getBiggerSize(bidRequest.sizes); - - bidResponses.push({ - requestId: bidRequest.bidId, - cpm: ad.BidPrice, - width: bidModel.Width || size[0], - height: bidModel.Height || size[1], - creativeId: ad.VideoExposedId, - currency: bidModel.Currency || CONSTANTS.DEFAULT_CURRENCY, - netRevenue: true, - ttl: CONSTANTS.TIME_TO_LIVE, - ad: renderCreative(bidModel) - }); - - const now = Date.now(); - ivLogger.info('Bid auction started at ' + bidModel.AuctionStartTime + ' . Invibes registered the bid at ' + now + ' ; bid request took a total of ' + (now - bidModel.AuctionStartTime) + ' ms.'); - } else { - utils.logInfo('Invibes Adapter - Incorrect Placement Id: ' + bidRequest.params.placementId); + let bidModel = requestPlacement.BidModel; + let ads = requestPlacement.Ads; + if (!Array.isArray(ads) || ads.length < 1) { + if (requestPlacement.AdReason != null) { + utils.logInfo('Invibes Adapter - No ads ' + requestPlacement.AdReason); } + + utils.logInfo('Invibes Adapter - No ads available'); + return null; } - return bidResponses; + let ad = ads[0]; + let size = getBiggerSize(bidRequest.sizes); + + const now = Date.now(); + utils.logInfo('Bid auction started at ' + bidModel.AuctionStartTime + ' . Invibes registered the bid at ' + now + ' ; bid request took a total of ' + (now - bidModel.AuctionStartTime) + ' ms.'); + + return { + requestId: bidRequest.bidId, + cpm: ad.BidPrice, + width: bidModel.Width || size[0], + height: bidModel.Height || size[1], + creativeId: ad.VideoExposedId, + currency: bidModel.Currency || CONSTANTS.DEFAULT_CURRENCY, + netRevenue: true, + ttl: CONSTANTS.TIME_TO_LIVE, + ad: renderCreative(bidModel) + }; } function generateRandomId() { @@ -357,17 +369,6 @@ function getCappedCampaignsAsString() { .join(','); } -const noop = function () { -}; - -function initLogger() { - if (storage.hasLocalStorage() && localStorage.InvibesDEBUG) { - return window.console; - } - - return {info: noop, error: noop, log: noop, warn: noop, debug: noop}; -} - function buildSyncUrl() { let syncUrl = _customUserSync || CONSTANTS.SYNC_ENDPOINT; syncUrl += '?visitId=' + invibes.visitId; @@ -392,7 +393,7 @@ function readGdprConsent(gdprConsent) { if (!gdprConsent.vendorData.gdprApplies || gdprConsent.vendorData.hasGlobalConsent) { var index; - for (index = 0; index < invibes.purposes; ++index) { + for (index = 0; index < invibes.purposes.length; ++index) { invibes.purposes[index] = true; } @@ -448,7 +449,13 @@ function tryCopyValueToArray(value, target, length) { } if (value.hasOwnProperty(prop)) { - target[i] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + let parsedProp = parseInt(prop); + if (isNaN(parsedProp)) { + target[i] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + } else { + target[parsedProp - 1] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + } + i++; } } @@ -515,8 +522,6 @@ function getVendorLegitimateInterest(vendorData) { return {}; } -const ivLogger = initLogger(); - /// Local domain cookie management ===================== invibes.Uid = { generate: function () { diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 64a3237e02a..6970eefeac2 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -2,7 +2,6 @@ import * as utils from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import find from 'core-js-pure/features/array/find.js'; -import isInteger from 'core-js-pure/features/number/is-integer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'ix'; @@ -23,6 +22,33 @@ const PRICE_TO_DOLLAR_FACTOR = { const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; +// determines which eids we send and the rtiPartner field in ext +const SOURCE_RTI_MAPPING = { + 'liveramp.com': 'idl', + 'netid.de': 'NETID', + 'neustar.biz': 'fabrickId', + 'zeotap.com': 'zeotapIdPlus', + 'uidapi.com': 'UID2', + 'adserver.org': 'TDID' +}; + +const PROVIDERS = [ + 'britepoolid', + 'id5id', + 'lipbid', + 'haloId', + 'criteoId', + 'lotamePanoramaId', + 'merkleId', + 'parrableId', + 'connectid', + 'sharedid', + 'tapadId', + 'quantcastId', + 'pubcid', + 'TDID', + 'flocId' +] /** * Transform valid bid request config object to banner impression object that will be sent to ad server. @@ -179,6 +205,8 @@ function _applyFloor(bid, imp, mediaType) { */ function parseBid(rawBid, currency, bidRequest) { const bid = {}; + const isValidExpiry = !!((utils.deepAccess(rawBid, 'exp') && utils.isInteger(rawBid.exp))); + const dealID = utils.deepAccess(rawBid, 'dealid') || utils.deepAccess(rawBid, 'ext.dealid'); if (PRICE_TO_DOLLAR_FACTOR.hasOwnProperty(currency)) { bid.cpm = rawBid.price / PRICE_TO_DOLLAR_FACTOR[currency]; @@ -188,7 +216,10 @@ function parseBid(rawBid, currency, bidRequest) { bid.requestId = rawBid.impid; - bid.dealId = utils.deepAccess(rawBid, 'ext.dealid'); + if (dealID) { + bid.dealId = dealID; + } + bid.netRevenue = NET_REVENUE; bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; @@ -199,13 +230,13 @@ function parseBid(rawBid, currency, bidRequest) { bid.width = bidRequest.video.w; bid.height = bidRequest.video.h; bid.mediaType = VIDEO; - bid.ttl = VIDEO_TIME_TO_LIVE; + bid.ttl = isValidExpiry ? rawBid.exp : VIDEO_TIME_TO_LIVE; } else { bid.ad = rawBid.adm; bid.width = rawBid.w; bid.height = rawBid.h; bid.mediaType = BANNER; - bid.ttl = BANNER_TIME_TO_LIVE; + bid.ttl = isValidExpiry ? rawBid.exp : BANNER_TIME_TO_LIVE; } bid.meta = {}; @@ -226,7 +257,7 @@ function parseBid(rawBid, currency, bidRequest) { * @return {boolean} True if this is a valid size format, and false otherwise. */ function isValidSize(size) { - return Array.isArray(size) && size.length === 2 && isInteger(size[0]) && isInteger(size[1]); + return Array.isArray(size) && size.length === 2 && utils.isInteger(size[0]) && utils.isInteger(size[1]); } /** @@ -285,35 +316,37 @@ function getBidRequest(id, impressions) { * From the userIdAsEids array, filter for the ones our adserver can use, and modify them * for our purposes, e.g. add rtiPartner * @param {array} allEids userIdAsEids passed in by prebid + * @param {object} flocId flocId passed in by prebid * @return {object} contains toSend (eids to send to the adserver) and seenSources (used to filter * identity info from IX Library) */ -function getEidInfo(allEids) { - // determines which eids we send and the rtiPartner field in ext - var sourceRTIMapping = { - 'liveramp.com': 'idl', - 'netid.de': 'NETID', - 'neustar.biz': 'fabrickId', - 'zeotap.com': 'zeotapIdPlus', - 'uidapi.com': 'UID2' - }; - var toSend = []; - var seenSources = {}; +function getEidInfo(allEids, flocData) { + let toSend = []; + let seenSources = {}; if (utils.isArray(allEids)) { - for (var i = 0; i < allEids.length; i++) { - if (sourceRTIMapping[allEids[i].source] && utils.deepAccess(allEids[i], 'uids.0')) { - seenSources[allEids[i].source] = 1; - allEids[i].uids[0].ext = { - rtiPartner: sourceRTIMapping[allEids[i].source] + for (const eid of allEids) { + if (SOURCE_RTI_MAPPING[eid.source] && utils.deepAccess(eid, 'uids.0')) { + seenSources[eid.source] = true; + eid.uids[0].ext = { + rtiPartner: SOURCE_RTI_MAPPING[eid.source] }; - delete allEids[i].uids[0].atype; - toSend.push(allEids[i]); + delete eid.uids[0].atype; + toSend.push(eid); } } } - return { toSend: toSend, seenSources: seenSources }; -} + const isValidFlocId = flocData && flocData.id && flocData.version; + if (isValidFlocId) { + const flocEid = { + 'source': 'chrome.com', + 'uids': [{ 'id': flocData.id, 'ext': { 'rtiPartner': 'flocId', 'ver': flocData.version } }] + }; + toSend.push(flocEid); + seenSources['chrome.com'] = true; + } + return { toSend, seenSources }; +} /** * Builds a request object to be sent to the ad server based on bid requests. * @@ -327,10 +360,9 @@ function getEidInfo(allEids) { function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Always use secure HTTPS protocol. let baseUrl = SECURE_BID_URL; - // Get ids from Prebid User ID Modules - var eidInfo = getEidInfo(utils.deepAccess(validBidRequests, '0.userIdAsEids')); - var userEids = eidInfo.toSend; + let eidInfo = getEidInfo(utils.deepAccess(validBidRequests, '0.userIdAsEids'), utils.deepAccess(validBidRequests, '0.userId.flocId')); + let userEids = eidInfo.toSend; // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist @@ -357,7 +389,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const r = {}; // Since bidderRequestId are the same for different bid request, just use the first one. - r.id = validBidRequests[0].bidderRequestId; + r.id = validBidRequests[0].bidderRequestId.toString(); r.site = {}; r.ext = {}; @@ -424,6 +456,10 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } } + if (config.getConfig('coppa')) { + utils.deepSetValue(r, 'regs.coppa', 1); + } + const payload = {}; // Parse additional runtime configs. @@ -570,23 +606,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { function _getUserIds(bidRequest) { const userIds = bidRequest.userId || {}; - const PROVIDERS = [ - 'britepoolid', - 'id5id', - 'lipbid', - 'haloId', - 'criteoId', - 'lotamePanoramaId', - 'merkleId', - 'parrableId', - 'connectid', - 'sharedid', - 'tapadId', - 'quantcastId', - 'pubcid' - ] - - return PROVIDERS.filter(provider => utils.deepAccess(userIds, provider)) + return PROVIDERS.filter(provider => userIds[provider]); } /** diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 0723e8cbb6c..7fb1bb13d74 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -2,7 +2,7 @@ The purpose of this Real Time Data Provider is to allow publishers to target aga having to integrate with the Player Bidding product. This prebid module makes JW Player's video ad targeting information accessible to Bid Adapters. -#Usage for Publishers: +# Usage for Publishers: Compile the JW Player RTD Provider into your Prebid build: @@ -30,7 +30,7 @@ Lastly, include the content's media ID and/or the player's ID in the matching Ad ```javascript const adUnit = { code: '/19968336/prebid_native_example_1', - ... + ..., ortb2Imp: { ext: { data: { @@ -51,10 +51,12 @@ pbjs.que.push(function() { }); }); ``` +**Note**: The player ID is the ID of the HTML div element used when instantiating the player. +You can retrieve this ID by calling `player.id`, where player is the JW Player instance variable. **Note**: You may also include `jwTargeting` information in the prebid config's `ortb2.site.ext.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. -##Prefetching +## Prefetching In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var and set `waitForIt` to `true`: ```javascript @@ -76,7 +78,7 @@ realTimeData = { }; ``` -#Usage for Bid Adapters: +# Usage for Bid Adapters: Implement the `buildRequests` function. When it is called, the `bidRequests` param will be an array of bids. Each bid for which targeting information was found will conform to the following object structure: @@ -118,6 +120,6 @@ To view an example: **Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. -#Maintainer info +# Maintainer info Maintained by JW Player. For any questions, comments or feedback please contact Karim Mourra, karim@jwplayer.com diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 8a455f6fa25..a398a20a5c5 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -2,8 +2,12 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import includes from 'core-js-pure/features/array/includes.js' const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', + 'startdelay', 'skippable', 'playbackmethod', 'api', 'protocols', 'boxingallowed', + 'linearity', 'delivery', 'protocol', 'placement', 'minbitrate', 'maxbitrate']; const BIDDER_CODE = 'openx'; const BIDDER_CONFIG = 'hb_pb'; const BIDDER_VERSION = '3.0.3'; @@ -326,7 +330,12 @@ function buildOXBannerRequest(bids, bidderRequest) { let auids = utils._map(bids, bid => bid.params.unit); queryParams.aus = utils._map(bids, bid => utils.parseSizesInput(bid.mediaTypes.banner.sizes).join(',')).join('|'); - queryParams.divIds = utils._map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); + queryParams.divids = utils._map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); + // gpid + queryParams.aucs = utils._map(bids, function (bid) { + let gpid = utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + return encodeURIComponent(gpid || '') + }).join(','); if (auids.some(auid => auid)) { queryParams.auid = auids.join(','); @@ -382,6 +391,7 @@ function buildOXVideoRequest(bid, bidderRequest) { } function generateVideoParameters(bid, bidderRequest) { + const videoMediaType = utils.deepAccess(bid, `mediaTypes.video`); let queryParams = buildCommonQueryParamsFromBids([bid], bidderRequest); let oxVideoConfig = utils.deepAccess(bid, 'params.video') || {}; let context = utils.deepAccess(bid, 'mediaTypes.video.context'); @@ -401,16 +411,35 @@ function generateVideoParameters(bid, bidderRequest) { height = parseInt(playerSize[1], 10); } - Object.keys(oxVideoConfig).forEach(function (key) { - if (key === 'openrtb') { - oxVideoConfig[key].w = width || oxVideoConfig[key].w; - oxVideoConfig[key].v = height || oxVideoConfig[key].v; - queryParams[key] = JSON.stringify(oxVideoConfig[key]); - } else if (!(key in queryParams) && key !== 'url') { - // only allow video-related attributes - queryParams[key] = oxVideoConfig[key]; - } - }); + let openRtbParams = {w: width, h: height}; + + // legacy openrtb params could be in video, openrtb, or video.openrtb + let legacyParams = bid.params.video || bid.params.openrtb || {}; + if (legacyParams.openrtb) { + legacyParams = legacyParams.openrtb; + } + // support for video object or full openrtb object + if (utils.isArray(legacyParams.imp)) { + legacyParams = legacyParams.imp[0].video; + } + Object.keys(legacyParams) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => openRtbParams[param] = legacyParams[param]); + + // 5.0 openrtb video params + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => openRtbParams[param] = videoMediaType[param]); + + let openRtbReq = { + imp: [ + { + video: openRtbParams + } + ] + } + + queryParams['openrtb'] = JSON.stringify(openRtbReq); queryParams.auid = bid.params.unit; // override prebid config with openx config if available @@ -429,6 +458,11 @@ function generateVideoParameters(bid, bidderRequest) { queryParams.vtest = 1; } + let gpid = utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + queryParams.aucs = encodeURIComponent(gpid) + } + // each video bid makes a separate request enrichQueryWithFloors(queryParams, VIDEO, [bid]); diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 965b8ee1948..68e41a93b18 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -98,7 +98,7 @@ pbjs.setConfig({ ``` # Additional Details -[Banner Ads](https://docs.openx.com/Content/developers/containers/prebid-adapter.html) +[Banner Ads](https://docs.openx.com/publishers/prebid-adapter-web/) -[Video Ads](https://docs.openx.com/Content/developers/containers/prebid-video-adapter.html) +[Video Ads](https://docs.openx.com/publishers/prebid-adapter-video/) diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 3738c6e8be7..39f9a2aaaa5 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -20,7 +20,7 @@ pbjs.setConfig({ name: 'permutive', waitForIt: true, // should be true if there's an `auctionDelay` params: { - acBidders: ['appnexus', 'rubicon', 'ozone'] + acBidders: ['appnexus'] } }] }, @@ -31,16 +31,16 @@ pbjs.setConfig({ ## Supported Bidders The below bidders are currently support by the Permutive RTD module. Please reach out to your Permutive Account Manager to request support for any additional bidders. -| Bidder | ID | First-party segments | Audience Connector | +| Bidder | ID | Custom First-Party Segments | Audience Connector ("acBidders") | | ----------- | ---------- | -------------------- | ------------------ | | Xandr | `appnexus` | Yes | Yes | -| Magnite | `rubicon` | Yes | Yes | +| Magnite | `rubicon` | Yes | No | | Ozone | `ozone` | No | Yes | | TrustX | `trustx` | No | Yes | * **First-party segments:** When enabling the respective Activation for a segment in Permutive, this module will automatically attach that segment to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in Permutive. Permutive segments will be sent in the `permutive` key-value. -* **Audience Connector:** You'll need to define which bidder should receive Audience Connector segments. You need to include the `ID` of any bidder in the `acBidders` array. Audience Connector segments will be sent in the `p_standard` key-value. +* **Audience Connector:** You'll need to define which bidder should receive Audience Connector segments. You need to include the `ID` of any bidder in the `acBidders` array. Audience Connector segments will be sent in the `p_standard` key-value. The segments produced by Audience Connector are not supported for PMPs at this time. ## Parameters @@ -66,7 +66,7 @@ pbjs.setConfig({ name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon'], + acBidders: ['appnexus'], maxSegs: 450, overwrites: { rubicon: function (bid, data, acEnabled, utils, defaultFn) { diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index aaff637c790..7694f5d838e 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -14,6 +14,12 @@ const BLOCKED_AD_SIZES = [ '1x1', '1x2' ]; +const SUPPORTED_USER_ID_SOURCES = [ + 'liveramp.com', // Liveramp IdentityLink + 'nextroll.com', // NextRoll XID + 'verizonmedia.com', // Verizon Media ConnectID + 'pubcid.org' // PubCommon ID +]; export const spec = { code: 'synacormedia', supportedMediaTypes: [ BANNER, VIDEO ], @@ -91,6 +97,14 @@ export const spec = { deepSetValue(openRtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + // User ID + if (validBidReqs[0] && validBidReqs[0].userIdAsEids && Array.isArray(validBidReqs[0].userIdAsEids)) { + const eids = this.processEids(validBidReqs[0].userIdAsEids); + if (eids.length) { + deepSetValue(openRtbBidRequest, 'user.ext.eids', eids); + } + } + if (openRtbBidRequest.imp.length && seatId) { return { method: 'POST', @@ -104,6 +118,16 @@ export const spec = { } }, + processEids: function(userIdAsEids) { + const eids = []; + userIdAsEids.forEach(function(eid) { + if (SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) > -1) { + eids.push(eid); + } + }); + return eids; + }, + buildBannerImpressions: function (adSizes, bid, tagIdOrPlacementId, pos, bidFloor, videoOrBannerKey) { let format = []; let imps = []; diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index c875cba12bc..4baecdd1ba8 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -873,9 +873,14 @@ describe('AppNexusAdapter', function () { const bidRequest = Object.assign({}, bidRequests[0], { userId: { tdid: 'sample-userid', + uid2: { id: 'sample-uid2-value' }, criteoId: 'sample-criteo-userid', netId: 'sample-netId-userid', - idl_env: 'sample-idl-userid' + idl_env: 'sample-idl-userid', + flocId: { + id: 'sample-flocid-value', + version: 'chrome.1.0' + } } }); @@ -892,6 +897,11 @@ describe('AppNexusAdapter', function () { id: 'sample-criteo-userid', }); + expect(payload.eids).to.deep.include({ + source: 'chrome.com', + id: 'sample-flocid-value' + }); + expect(payload.eids).to.deep.include({ source: 'netid.de', id: 'sample-netId-userid', @@ -900,7 +910,13 @@ describe('AppNexusAdapter', function () { expect(payload.eids).to.deep.include({ source: 'liveramp.com', id: 'sample-idl-userid' - }) + }); + + expect(payload.eids).to.deep.include({ + source: 'uidapi.com', + id: 'sample-uid2-value', + rti_partner: 'UID2' + }); }); it('should populate iab_support object at the root level if omid support is detected', function () { diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 1c24fb7694a..96a7f419341 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -588,4 +588,77 @@ describe('Conversant adapter tests', function() { expect(payload).to.have.deep.nested.property('user.ext.fpc', 'fghijk'); }); }); + + describe('price floor module', function() { + let bidRequest; + beforeEach(function() { + bidRequest = [utils.deepClone(bidRequests[0])]; + delete bidRequest[0].params.bidfloor; + }); + + it('obtain floor from getFloor', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'USD', + floor: 3.21 + }; + }; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 3.21); + }); + + it('obtain floor from params', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'USD', + floor: 3.21 + }; + }; + bidRequest[0].params.bidfloor = 0.6; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0.6); + }); + + it('unsupported currency', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'EUR', + floor: 1.23 + }; + }; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0); + }); + + it('bad floor value', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'USD', + floor: 'test' + }; + }; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0); + }); + + it('empty floor object', function() { + bidRequest[0].getFloor = () => { + return {}; + }; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0); + }); + + it('undefined floor result', function() { + bidRequest[0].getFloor = () => {}; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0); + }); + }); }); diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index 855ffa0a0b7..0f82122b9c1 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -253,12 +253,38 @@ describe('emx_digital Adapter', function () { expect(queryParams[1]).to.match(new RegExp('^ts=\d*', 'g')); }); - it('builds with bid floor', function () { - const bidRequestWithBidFloor = utils.deepClone(bidderRequest.bids); - bidRequestWithBidFloor[0].params.bidfloor = 1; - const requestWithFloor = spec.buildRequests(bidRequestWithBidFloor, bidderRequest); + it('builds bidfloor value from bid param when getFloor function does not exist', function () { + const bidRequestWithFloor = utils.deepClone(bidderRequest.bids); + bidRequestWithFloor[0].params.bidfloor = 1; + const requestWithFloor = spec.buildRequests(bidRequestWithFloor, bidderRequest); const data = JSON.parse(requestWithFloor.data); - expect(data.imp[0].bidfloor).to.equal(bidRequestWithBidFloor[0].params.bidfloor); + expect(data.imp[0].bidfloor).to.equal(bidRequestWithFloor[0].params.bidfloor); + }); + + it('builds bidfloor value from getFloor function when it exists', function () { + const floorResponse = { currency: 'USD', floor: 3 }; + const bidRequestWithGetFloor = utils.deepClone(bidderRequest.bids); + bidRequestWithGetFloor[0].getFloor = () => floorResponse; + const requestWithGetFloor = spec.buildRequests(bidRequestWithGetFloor, bidderRequest); + const data = JSON.parse(requestWithGetFloor.data); + expect(data.imp[0].bidfloor).to.equal(3); + }); + + it('builds bidfloor value from getFloor when both floor and getFloor function exists', function () { + const floorResponse = { currency: 'USD', floor: 3 }; + const bidRequestWithBothFloors = utils.deepClone(bidderRequest.bids); + bidRequestWithBothFloors[0].params.bidfloor = 1; + bidRequestWithBothFloors[0].getFloor = () => floorResponse; + const requestWithBothFloors = spec.buildRequests(bidRequestWithBothFloors, bidderRequest); + const data = JSON.parse(requestWithBothFloors.data); + expect(data.imp[0].bidfloor).to.equal(3); + }); + + it('empty bidfloor value when floor and getFloor is not defined', function () { + const bidRequestWithoutFloor = utils.deepClone(bidderRequest.bids); + const requestWithoutFloor = spec.buildRequests(bidRequestWithoutFloor, bidderRequest); + const data = JSON.parse(requestWithoutFloor.data); + expect(data.imp[0].bidfloor).to.not.exist; }); it('builds request properly', function () { @@ -470,7 +496,8 @@ describe('emx_digital Adapter', function () { 'id': '987654321cba', 'price': 0.5, 'ttl': 300, - 'w': 300 + 'w': 300, + 'adomain': ['example.com'] }], 'seat': '1356' }, { @@ -498,7 +525,10 @@ describe('emx_digital Adapter', function () { 'netRevneue': true, 'mediaType': 'banner', 'ad': '', - 'ttl': 300 + 'ttl': 300, + 'meta': { + 'advertiserDomains': ['example.com'] + } }, { 'requestId': '12819a18-56e1-4256-b836-b69a10202668', 'cpm': 0.7, @@ -630,6 +660,14 @@ describe('emx_digital Adapter', function () { body: badAdmServerResponse })); }); + + it('returns valid advertiser domain', function () { + const bidResponse = utils.deepClone(serverResponse); + let result = spec.interpretResponse({body: bidResponse}); + expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); + // case where adomains are not in request + expect(result[1].meta).to.not.exist; + }); }); describe('getUserSyncs', function () { diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index ee3624b5b16..6a59bf98dad 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -736,6 +736,30 @@ describe('invibesBidAdapter:', function () { ` }]; + let multiResponse = { + AdPlacements: [{ + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }], + BidModel: { + BidVersion: 1, + PlacementId: '12345', + AuctionStartTime: Date.now(), + CreativeHtml: '' + } + }] + }; + + let invalidResponse = { + AdPlacements: [{ + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }] + }] + }; + context('when the response is not valid', function () { it('handles response with no bids requested', function () { let emptyResult = spec.interpretResponse({body: response}); @@ -781,24 +805,25 @@ describe('invibesBidAdapter:', function () { let emptyResult = spec.interpretResponse({BidModel: {}, Ads: [{BidPrice: 1}]}, {bidRequests}); expect(emptyResult).to.be.empty; }); + + it('handles response when bid model is missing', function () { + let emptyResult = spec.interpretResponse(invalidResponse); + expect(emptyResult).to.be.empty; + }); }); - context('when the response is valid', function () { - it('responds with a valid bid', function () { - // top.window.invibes.setCookie('a', 'b', 370); - // top.window.invibes.setCookie('c', 'd', 0); - let result = spec.interpretResponse({body: response}, {bidRequests}); + context('when the multiresponse is valid', function () { + it('responds with a valid multiresponse bid', function () { + let result = spec.interpretResponse({body: multiResponse}, {bidRequests}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('responds with a valid bid and uses logger', function () { - localStorage.InvibesDEBUG = true; + it('responds with a valid singleresponse bid', function () { let result = spec.interpretResponse({body: response}, {bidRequests}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); it('does not make multiple bids', function () { - localStorage.InvibesDEBUG = false; let result = spec.interpretResponse({body: response}, {bidRequests}); let secondResult = spec.interpretResponse({body: response}, {bidRequests}); expect(secondResult).to.be.empty; diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index e0d45913fd9..c026dfd3efc 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -462,9 +462,23 @@ describe('IndexexchangeAdapter', function () { ]; const DEFAULT_USERID_BID_DATA = { - lotamePanoramaId: 'bd738d136bdaa841117fe9b331bb4' + lotamePanoramaId: 'bd738d136bdaa841117fe9b331bb4', + flocId: {id: '1234', version: 'chrome.1.2'} }; + const DEFAULT_FLOC_USERID_PAYLOAD = [ + { + source: 'chrome.com', + uids: [{ + id: DEFAULT_USERID_BID_DATA.flocId.id, + ext: { + rtiPartner: 'flocId', + ver: DEFAULT_USERID_BID_DATA.flocId.version + } + }] + } + ]; + describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); @@ -903,6 +917,96 @@ describe('IndexexchangeAdapter', function () { expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); }); + it('IX adapter reads floc id from prebid userId and adds it to eids when there is not other eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(1); + expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc id from prebid userId and appends it to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(6); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads empty floc obj from prebid userId it, floc is not added to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = {'flocId': {}} + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc obj from prebid userId it version is missing, floc is not added to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = {'flocId': {'id': 'abcd'}} + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc obj from prebid userId it ID is missing, floc is not added to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = {'flocId': {'version': 'chrome.a.b.c'}} + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc id with empty id from prebid userId and it does not added to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = {flocID: {id: '', ver: 'chrome.1.2.3'}}; + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { validIdentityResponse = { AdserverOrgIp: { @@ -1117,7 +1221,7 @@ describe('IndexexchangeAdapter', function () { }); describe('buildRequests', function () { - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + let request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const requestUrl = request.url; const requestMethod = request.method; const query = request.data; @@ -1150,6 +1254,7 @@ describe('IndexexchangeAdapter', function () { it('payload should have correct format and value', function () { const payload = JSON.parse(query.r); expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); + expect(payload.id).to.be.a('string'); expect(payload.site).to.exist; expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.referer); expect(payload.site.ref).to.equal(document.referrer); @@ -1161,6 +1266,18 @@ describe('IndexexchangeAdapter', function () { expect(payload.imp).to.have.lengthOf(2); }); + it('payload should have correct format and value for r.id when bidderRequestId is a number ', function () { + const bidWithIntId = utils.deepClone(DEFAULT_BANNER_VALID_BID); + bidWithIntId[0].bidderRequestId = 123456; + + request = spec.buildRequests(bidWithIntId, DEFAULT_OPTION)[0]; + + const payload = JSON.parse(request.data.r); + expect(bidWithIntId[0].bidderRequestId).to.be.a('number'); + expect(payload.id).to.equal(bidWithIntId[0].bidderRequestId.toString()); + expect(payload.id).to.be.a('string'); + }); + it('payload should not include schain when not provided', function () { const payload = JSON.parse(queryWithoutSchain.r); expect(payload.source).to.not.exist; // source object currently only written for schain @@ -1859,7 +1976,6 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 300, netRevenue: true, - dealId: undefined, meta: { networkId: 50, brandId: 303325, @@ -1885,7 +2001,6 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 300, netRevenue: true, - dealId: undefined, meta: { networkId: 50, brandId: 303325, @@ -1912,7 +2027,6 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 300, netRevenue: true, - dealId: undefined, meta: { networkId: 50, brandId: 303325, @@ -1939,7 +2053,6 @@ describe('IndexexchangeAdapter', function () { currency: 'JPY', ttl: 300, netRevenue: true, - dealId: undefined, meta: { networkId: 50, brandId: 303325, @@ -1952,9 +2065,38 @@ describe('IndexexchangeAdapter', function () { expect(result[0]).to.deep.equal(expectedParse[0]); }); - it('should set dealId correctly', function () { + it('should prioritize bid[].dealid over bid[].ext.dealid ', function () { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + bidResponse.seatbid[0].bid[0].ext.dealid = 'ext-deal'; + bidResponse.seatbid[0].bid[0].dealid = 'outter-deal'; + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + currency: 'USD', + ttl: 300, + netRevenue: true, + dealId: 'outter-deal', + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] + } + } + ]; + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + + expect(result[0].dealId).to.equal(expectedParse[0].dealId); + }); + + it('should not set bid[].dealid if dealid is not present', function () { const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); - bidResponse.seatbid[0].bid[0].ext.dealid = 'deal'; const expectedParse = [ { requestId: '1a2b3c4d', @@ -1967,7 +2109,6 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 300, netRevenue: true, - dealId: 'deal', meta: { networkId: 50, brandId: 303325, @@ -1980,6 +2121,34 @@ describe('IndexexchangeAdapter', function () { expect(result[0]).to.deep.equal(expectedParse[0]); }); + it('should use set bid[].ext.dealid if bid[].dealid is not present', function () { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + bidResponse.seatbid[0].bid[0].ext.dealid = 'ext-deal'; + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + currency: 'USD', + ttl: 300, + dealId: 'ext-deal', + netRevenue: true, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] + } + } + ]; + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + expect(result[0].dealId).to.deep.equal(expectedParse[0].dealId); + }); + it('should get correct bid response for video ad', function () { const expectedParse = [ { @@ -1992,7 +2161,6 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 3600, netRevenue: true, - dealId: undefined, vastUrl: 'www.abcd.com/vast', meta: { networkId: 51, @@ -2038,6 +2206,26 @@ describe('IndexexchangeAdapter', function () { expect(requestWithoutreferInfo.site.page).to.equal(options.refererInfo.referer); expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); }); + + it('should set bid[].ttl to seatbid[].bid[].exp value from response', function () { + const BANNER_RESPONSE_WITH_EXP = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + const VIDEO_RESPONSE_WITH_EXP = utils.deepClone(DEFAULT_VIDEO_BID_RESPONSE); + VIDEO_RESPONSE_WITH_EXP.seatbid[0].bid[0].exp = 200; + BANNER_RESPONSE_WITH_EXP.seatbid[0].bid[0].exp = 100; + const bannerResult = spec.interpretResponse({ body: BANNER_RESPONSE_WITH_EXP }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + const videoResult = spec.interpretResponse({ body: VIDEO_RESPONSE_WITH_EXP }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + + expect(bannerResult[0].ttl).to.equal(100); + expect(videoResult[0].ttl).to.equal(200); + }); + + it('should default bid[].ttl if seat[].bid[].exp is not in the resposne', function () { + const bannerResult = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + const videoResult = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + + expect(bannerResult[0].ttl).to.equal(300); + expect(videoResult[0].ttl).to.equal(3600); + }); }); describe('bidrequest consent', function () { @@ -2144,5 +2332,27 @@ describe('IndexexchangeAdapter', function () { expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings')).to.not.exist; expect(utils.deepAccess(requestWithConsent, 'user.ext.consent')).to.not.exist; }); + + it('should set coppa to 1 in config when enabled', () => { + config.setConfig({ coppa: true }) + const bid = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + const r = JSON.parse(bid[0].data.r); + + expect(r.regs.coppa).to.equal(1); + }); + it('should not set coppa in config when disabled', () => { + config.setConfig({ coppa: false }) + const bid = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + const r = JSON.parse(bid[0].data.r); + + expect(r.regs.coppa).to.be.undefined; + }); + it('should not set coppa when not specified in config', () => { + config.resetConfig(); + const bid = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + const r = JSON.parse(bid[0].data.r); + + expect(r.regs.coppa).to.be.undefined; + }); }); }); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 7391c8826bc..2a380277e55 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -341,7 +341,8 @@ describe('OpenxAdapter', function () { }, 'bidId': 'test-bid-id-1', 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' + 'auctionId': 'test-auction-1', + 'ortb2Imp': { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } } }, { 'bidder': 'openx', 'params': { @@ -356,7 +357,8 @@ describe('OpenxAdapter', function () { }, 'bidId': 'test-bid-id-2', 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' + 'auctionId': 'test-auction-2', + 'ortb2Imp': { ext: { data: { pbadslot: '/12345/my-gpt-tag-1' } } } }]; const bidRequestsWithPlatform = [{ @@ -450,7 +452,12 @@ describe('OpenxAdapter', function () { it('should send the adunit codes', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.divIds).to.equal(`${encodeURIComponent(bidRequestsWithMediaTypes[0].adUnitCode)},${encodeURIComponent(bidRequestsWithMediaTypes[1].adUnitCode)}`); + expect(request[0].data.divids).to.equal(`${encodeURIComponent(bidRequestsWithMediaTypes[0].adUnitCode)},${encodeURIComponent(bidRequestsWithMediaTypes[1].adUnitCode)}`); + }); + + it('should send the gpids', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.aucs).to.equal(`${encodeURIComponent('/12345/my-gpt-tag-0')},${encodeURIComponent('/12345/my-gpt-tag-1')}`); }); it('should send ad unit ids when any are defined', function () { @@ -1238,7 +1245,8 @@ describe('OpenxAdapter', function () { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e', + 'ortb2Imp': { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } } }]; const mockBidderRequest = {refererInfo: {}}; @@ -1254,6 +1262,7 @@ describe('OpenxAdapter', function () { expect(dataParams.auid).to.equal('12345678'); expect(dataParams.vht).to.equal(480); expect(dataParams.vwd).to.equal(640); + expect(dataParams.aucs).to.equal(encodeURIComponent('/12345/my-gpt-tag-0')); }); it('shouldn\'t have the test parameter', function () { @@ -1313,45 +1322,97 @@ describe('OpenxAdapter', function () { expect(request[0].data.ju).to.not.equal(myUrl); }); - describe('when using the openRtb param', function () { - it('should covert the param to a JSON string', function () { - let myOpenRTBObject = {}; + describe('when using the openrtb video params', function () { + it('should parse legacy params.video.openrtb', function () { + let myOpenRTBObject = {mimes: ['application/javascript']}; videoBidRequest.params.video = { openrtb: myOpenRTBObject }; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - expect(request[0].data.openrtb).to.equal(JSON.stringify(myOpenRTBObject)); + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); }); - it("should use the bidRequest's playerSize when it is available", function () { - const width = 200; - const height = 100; - const myOpenRTBObject = {v: height, w: width}; + it('should parse legacy params.openrtb', function () { + let myOpenRTBObject = {mimes: ['application/javascript']}; + videoBidRequest.params.openrtb = myOpenRTBObject; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should parse legacy params.video', function () { + let myOpenRTBObject = {mimes: ['application/javascript']}; + videoBidRequest.params.video = myOpenRTBObject; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should parse legacy params.video as full openrtb', function () { + let myOpenRTBObject = {imp: [{video: {mimes: ['application/javascript']}}]}; + videoBidRequest.params.video = myOpenRTBObject; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should parse legacy video.openrtb', function () { + let myOpenRTBObject = {mimes: ['application/javascript']}; + videoBidRequest.params.video = { + openrtb: myOpenRTBObject + }; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should omit filtered values for legacy', function () { + let myOpenRTBObject = {mimes: ['application/javascript'], dont: 'use'}; videoBidRequest.params.video = { openrtb: myOpenRTBObject }; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should parse mediatypes.video', function () { + videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] + videoBidRequest.mediaTypes.video.minduration = 15 const request = spec.buildRequests([videoBidRequest], mockBidderRequest); const openRtbRequestParams = JSON.parse(request[0].data.openrtb); + expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); + expect(openRtbRequestParams.imp[0].video.minduration).to.equal(15); + }); - expect(openRtbRequestParams.w).to.not.equal(width); - expect(openRtbRequestParams.v).to.not.equal(height); + it('should filter mediatypes.video', function () { + videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] + videoBidRequest.mediaTypes.video.minnothing = 15 + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + const openRtbRequestParams = JSON.parse(request[0].data.openrtb); + expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); + expect(openRtbRequestParams.imp[0].video.minnothing).to.equal(undefined); }); - it('should use the the openRTB\'s sizing when the bidRequest\'s playerSize is not available', function () { + it("should use the bidRequest's playerSize", function () { const width = 200; const height = 100; const myOpenRTBObject = {v: height, w: width}; videoBidRequest.params.video = { openrtb: myOpenRTBObject }; - videoBidRequest.mediaTypes.video.playerSize = undefined; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.w).to.equal(width); - expect(openRtbRequestParams.v).to.equal(height); + expect(openRtbRequestParams.imp[0].video.w).to.equal(640); + expect(openRtbRequestParams.imp[0].video.h).to.equal(480); }); }); }); @@ -1465,7 +1526,7 @@ describe('OpenxAdapter', function () { const request = spec.buildRequests([multiformatBid], mockBidderRequest); const dataParams = request[0].data; - expect(dataParams.divIds).to.have.string(multiformatBid.adUnitCode); + expect(dataParams.divids).to.have.string(multiformatBid.adUnitCode); }); }); diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index 688f1c2c090..5f3633ec311 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -191,6 +191,62 @@ describe('synacormediaBidAdapter ', function () { uspConsent: '1YYY' }; + let validBidRequestWithUserIds = { + bidId: '9876abcd', + sizes: [[300, 250], [300, 600]], + params: { + seatId: 'prebid', + tagId: '1234', + bidfloor: '0.50' + }, + userIdAsEids: [ + { + source: 'pubcid.org', + uids: [{ + id: 'cid0032l2344jskdsl3', + atype: 1 + }] + }, + { + source: 'liveramp.com', + uids: [{ + id: 'lrv39010k42dl', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }, + { + source: 'neustar.biz', + uids: [{ + id: 'neustar809-044-23njhwer3', + atype: 1 + }] + } + ] + }; + + let expectedEids = [ + { + source: 'pubcid.org', + uids: [{ + id: 'cid0032l2344jskdsl3', + atype: 1 + }] + }, + { + source: 'liveramp.com', + uids: [{ + id: 'lrv39010k42dl', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + let expectedDataImp1 = { banner: { format: [ @@ -631,7 +687,21 @@ describe('synacormediaBidAdapter ', function () { expect(req.data.id).to.equal('xyz123'); expect(req.data.regs.ext.us_privacy).to.equal('1YYY'); expect(req.data.imp).to.eql([expectedDataImp1]); - }) + }); + it('should contain user object when user ids are present in the bidder request', function () { + let req = spec.buildRequests([validBidRequestWithUserIds], bidderRequest); + expect(req).be.an('object'); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('https://prebid.technoratimedia.com/openrtb/bids/prebid?'); + expect(req.data).to.exist.and.to.be.an('object'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.user).be.an('object'); + expect(req.data.user).to.have.property('ext'); + expect(req.data.user.ext).to.have.property('eids'); + expect(req.data.user.ext.eids).to.eql(expectedEids); + expect(req.data.imp).to.eql([expectedDataImp1]); + }); }); describe('Bid Requests with placementId should be backward compatible ', function () {