diff --git a/modules/51DegreesRtdProvider.md b/modules/51DegreesRtdProvider.md index 991e756a0d7..424a559797d 100644 --- a/modules/51DegreesRtdProvider.md +++ b/modules/51DegreesRtdProvider.md @@ -49,7 +49,7 @@ In order to use the module please first obtain a Resource Key using the [Configu * ScreenInchesWidth * PixelRatio (optional) -PixelRatio is desirable, but it's a paid property requiring a paid license. Also free API service is limited to 500,000 requests per month - consider picking a [51Degrees pricing plan](https://51degrees.com/pricing) that fits your needs. +PixelRatio is desirable, but it's a paid property requiring a paid license. Free API service is limited. Please check [51Degrees pricing](https://51degrees.com/pricing) to choose a plan that suits your needs. #### User Agent Client Hint (UA-CH) Permissions diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 0c353e4332a..151e08bd2bb 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -94,7 +94,8 @@ export const spec = { {code: 'adpluto'}, {code: 'headbidder'}, {code: 'digiad'}, - {code: 'monetix'} + {code: 'monetix'}, + {code: 'hyperbrainz'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 060f87c0f9c..189e2287571 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -130,31 +130,6 @@ const storageTool = (function () { return (meta && meta.usi) ? meta.usi : false } - const getSegmentsFromOrtb = function (ortb2) { - const userData = deepAccess(ortb2, 'user.data'); - let segments = []; - if (userData) { - userData.forEach(userdat => { - if (userdat.segment) { - segments.push(...userdat.segment.map((segment) => { - if (isStr(segment)) return segment; - if (isStr(segment.id)) return segment.id; - }).filter((seg) => !!seg)); - } - }); - } - return segments - } - - const getKvsFromOrtb = function (ortb2) { - const siteData = deepAccess(ortb2, 'site.ext.data'); - if (siteData) { - return siteData - } else { - return null - } - } - return { refreshStorage: function (bidderRequest) { const ortb2 = bidderRequest.ortb2 || {}; @@ -171,25 +146,69 @@ const storageTool = (function () { return voidAuId.auId; }); } - metaInternal.segments = getSegmentsFromOrtb(ortb2); - metaInternal.kv = getKvsFromOrtb(ortb2); }, saveToStorage: function (serverData, network) { setMetaInternal(serverData, network); }, getUrlRelatedData: function () { // getting the URL information is theoretically not network-specific - const { segments, kv, usi, voidAuIdsArray } = metaInternal; - return { segments, kv, usi, voidAuIdsArray }; + const { usi, voidAuIdsArray } = metaInternal; + return { usi, voidAuIdsArray }; }, getPayloadRelatedData: function (network) { // getting the payload data should be network-specific - const { segments, kv, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); + const { segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); return payloadRelatedData; } }; })(); +const targetingTool = (function() { + const getSegmentsFromOrtb = function(bidderRequest) { + const userData = deepAccess(bidderRequest.ortb2 || {}, 'user.data'); + let segments = []; + if (userData) { + userData.forEach(userdat => { + if (userdat.segment) { + segments.push(...userdat.segment.map((segment) => { + if (isStr(segment)) return segment; + if (isStr(segment.id)) return segment.id; + }).filter((seg) => !!seg)); + } + }); + } + return segments + }; + + const getKvsFromOrtb = function(bidderRequest) { + return deepAccess(bidderRequest.ortb2 || {}, 'site.ext.data'); + }; + + return { + addSegmentsToUrlData: function (validBids, bidderRequest, existingUrlRelatedData) { + let segments = getSegmentsFromOrtb(bidderRequest || {}); + + for (let i = 0; i < validBids.length; i++) { + const bid = validBids[i]; + const targeting = bid.params.targeting || {}; + if (Array.isArray(targeting.segments)) { + segments = segments.concat(targeting.segments); + delete bid.params.targeting.segments; + } + } + + existingUrlRelatedData.segments = segments; + }, + mergeKvsFromOrtb: function(bidTargeting, bidderRequest) { + const kv = getKvsFromOrtb(bidderRequest || {}); + if (!kv) { + return; + } + bidTargeting.kv = {...kv, ...bidTargeting.kv}; + } + } +})(); + const validateBidType = function (bidTypeOption) { return VALID_BID_TYPES.indexOf(bidTypeOption || '') > -1 ? bidTypeOption : 'bid'; } @@ -227,6 +246,7 @@ export const spec = { storageTool.refreshStorage(bidderRequest); const urlRelatedMetaData = storageTool.getUrlRelatedData(); + targetingTool.addSegmentsToUrlData(validBidRequests, bidderRequest, urlRelatedMetaData); if (urlRelatedMetaData.segments.length > 0) queryParamsAndValues.push('segments=' + urlRelatedMetaData.segments.join(',')); if (urlRelatedMetaData.usi) queryParamsAndValues.push('userId=' + urlRelatedMetaData.usi); @@ -261,9 +281,9 @@ export const spec = { networks[network].metaData = payloadRelatedData; } - const targeting = bid.params.targeting || {}; - if (urlRelatedMetaData.kv) targeting.kv = urlRelatedMetaData.kv; - const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId }; + const bidTargeting = {...bid.params.targeting || {}}; + targetingTool.mergeKvsFromOrtb(bidTargeting, bidderRequest); + const adUnit = { ...bidTargeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId }; const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); if (maxDeals > 0) { adUnit.maxDeals = maxDeals; diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index c6a5cd96fb6..0730149e909 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -59,7 +59,7 @@ const converter = ortbConverter({ const request = buildRequest(imps, bidderRequest, context); deepSetValue(request, 'at', 1); setPrebidRequestEnvironment(request); - deepSetValue(request, 'regs', getRegs()); + deepSetValue(request, 'regs', getRegs(bidderRequest)); deepSetValue(request, 'site.publisher.id', bidderRequest.bids[0].params.publisherId); deepSetValue(request, 'site.id', bidderRequest.bids[0].params.siteId); return request; @@ -106,22 +106,22 @@ function recur(obj) { return result; } -function getRegs() { +function getRegs(bidderRequest) { let regs = {}; - const consentManagement = config.getConfig('consentManagement'); + const euConsentManagement = bidderRequest.gdprConsent; + const usConsentManagement = bidderRequest.uspConsent; const coppa = config.getConfig('coppa'); - if (consentManagement && !!(consentManagement.gdpr)) { - deepSetValue(regs, 'gdpr_applies', !!consentManagement.gdpr); + if (euConsentManagement && euConsentManagement.consentString) { + deepSetValue(regs, 'gdpr_applies', !!euConsentManagement.consentString); } else { deepSetValue(regs, 'gdpr_applies', false); } - if (consentManagement && deepAccess(consentManagement, 'usp.cmpApi') === 'static') { - deepSetValue(regs, 'usp_applies', !!deepAccess(consentManagement, 'usp')); - deepSetValue(regs, 'us_privacy', deepAccess(consentManagement, 'usp.consentData.getUSPData.uspString')); + if (usConsentManagement) { + deepSetValue(regs, 'usp_applies', true); + deepSetValue(regs, 'us_privacy', bidderRequest.uspConsent); } else { deepSetValue(regs, 'usp_applies', false); } - if (isBoolean(coppa)) { deepSetValue(regs, 'coppa_applies', !!coppa); } else { diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 658fc30b43b..8f4a9e3e46d 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -15,7 +15,7 @@ import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; -const ADAPTER_VERSION = '1.20'; +const ADAPTER_VERSION = '1.21'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; const CURRENCY = 'USD'; @@ -26,7 +26,7 @@ export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/l export const SYNC_IFRAME_ENDPOINT = 'https://sync.bfmio.com/sync_iframe'; export const SYNC_IMAGE_ENDPOINT = 'https://sync.bfmio.com/syncb'; -export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'skip', 'skipmin', 'skipafter']; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'plcmt', 'skip', 'skipmin', 'skipafter']; export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; export const SUPPORTED_USER_IDS = [ diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 01a38c63b69..46393b37a05 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -13,6 +13,7 @@ * @property {string} pubKey * @property {string} url * @property {?string} keyName + * @property {?string} splitKey */ import {deepClone, deepSetValue, isFn, isGptPubadsDefined, isNumber, logError, logInfo, generateUUID} from '../src/utils.js'; @@ -33,6 +34,7 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'browsi'; const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME}); +const RANDOM = Math.floor(Math.random() * 10) + 1; /** @type {ModuleParams} */ let _moduleParams = {}; @@ -58,12 +60,22 @@ export function addBrowsiTag(data) { script.setAttribute('id', 'browsi-tag'); script.setAttribute('src', data.u); script.prebidData = deepClone(typeof data === 'string' ? Object(data) : data) + script.brwRandom = RANDOM; if (_moduleParams.keyName) { script.prebidData.kn = _moduleParams.keyName; } return script; } +export function setKeyValue(key) { + if (!key || typeof key !== 'string') return false; + window.googletag = window.googletag || {cmd: []}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + window.googletag.pubads().setTargeting(key, RANDOM.toString()); + }); +} + export function sendPageviewEvent(eventType) { if (eventType === 'PAGEVIEW') { window.addEventListener('browsi_pageview', () => { @@ -380,6 +392,7 @@ function init(moduleConfig) { _moduleParams = moduleConfig.params; if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { collectData(); + setKeyValue(_moduleParams.splitKey); } else { logError('missing params for Browsi provider'); } diff --git a/modules/cadentApertureMXBidAdapter.js b/modules/cadentApertureMXBidAdapter.js index e73564dacdb..97283952888 100644 --- a/modules/cadentApertureMXBidAdapter.js +++ b/modules/cadentApertureMXBidAdapter.js @@ -282,12 +282,13 @@ export const spec = { }; // adding gpid support - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot'); - if (!gpid) { - gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - } + let gpid = + deepAccess(bid, 'ortb2Imp.ext.gpid') || + deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot') || + deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { - data.ext = {gpid: gpid.toString()}; + data.ext = { gpid: gpid.toString() }; } let typeSpecifics = isVideo ? { video: cadentAdapter.buildVideo(bid) } : { banner: cadentAdapter.buildBanner(bid) }; let bidfloorObj = bidfloor > 0 ? { bidfloor, bidfloorcur: DEFAULT_CUR } : {}; diff --git a/modules/ceeIdSystem.js b/modules/ceeIdSystem.js new file mode 100644 index 00000000000..9c8f1409fd3 --- /dev/null +++ b/modules/ceeIdSystem.js @@ -0,0 +1,62 @@ +/** + * This module adds ceeId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/ceeId + * @requires module:modules/userId + */ + +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { submodule } from '../src/hook.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + +const MODULE_NAME = 'ceeId'; +const ID_TOKEN = 'WPxid'; +export const storage = getStorageManager({ moduleName: MODULE_NAME, moduleType: MODULE_TYPE_UID }); + +/** + * Reads the ID token from local storage or cookies. + * @returns {string|undefined} The ID token, or undefined if not found. + */ +export const readId = () => storage.getDataFromLocalStorage(ID_TOKEN) || storage.getCookie(ID_TOKEN); + +/** @type {Submodule} */ +export const ceeIdSubmodule = { + name: MODULE_NAME, + gvlid: 676, + + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {string} value + * @returns {(Object|undefined)} + */ + decode(value) { + return typeof value === 'string' ? { 'ceeId': value } : undefined; + }, + + /** + * performs action to obtain id and return a value + * @function + * @returns {(IdResponse|undefined)} + */ + getId() { + const ceeIdToken = readId(); + + return ceeIdToken ? { id: ceeIdToken } : undefined; + }, + domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME), + eids: { + 'ceeId': { + source: 'ceeid.eu', + atype: 1 + }, + }, +}; + +submodule('userId', ceeIdSubmodule); diff --git a/modules/ceeIdSystem.md b/modules/ceeIdSystem.md new file mode 100644 index 00000000000..811efe08069 --- /dev/null +++ b/modules/ceeIdSystem.md @@ -0,0 +1,27 @@ +# Overview + +Module Name: ceeIdSystem +Module Type: UserID Module +Maintainer: pawel.grudzien@grupawp.pl + +# Description + +User identification system for WPM + +### Prebid Params example + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360 + } + }] + } +}); +``` diff --git a/modules/colombiaBidAdapter.js b/modules/colombiaBidAdapter.js new file mode 100644 index 00000000000..0d25ca6cb60 --- /dev/null +++ b/modules/colombiaBidAdapter.js @@ -0,0 +1,107 @@ +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +const BIDDER_CODE = 'colombia'; +const ENDPOINT_URL = 'https://ade.clmbtech.com/cde/prebid.htm'; +const HOST_NAME = document.location.protocol + '//' + window.location.host; + +export const spec = { + code: BIDDER_CODE, + aliases: ['clmb'], + supportedMediaTypes: [BANNER], + isBidRequestValid: function(bid) { + return !!(bid.params.placementId); + }, + buildRequests: function(validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return []; + } + let payloadArr = [] + let ctr = 1; + validBidRequests = validBidRequests.map(bidRequest => { + const params = bidRequest.params; + const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + const width = sizes.split('x')[0]; + const height = sizes.split('x')[1]; + const placementId = params.placementId; + const cb = Math.floor(Math.random() * 99999999999); + const bidId = bidRequest.bidId; + const referrer = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : ''; + let mediaTypes = {} + let payload = { + v: 'hb1', + p: placementId, + pos: '~' + ctr, + w: width, + h: height, + cb: cb, + r: referrer, + uid: bidId, + t: 'i', + d: HOST_NAME, + fpc: params.fpc, + _u: window.location.href, + mediaTypes: Object.assign({}, mediaTypes, bidRequest.mediaTypes) + }; + if (params.keywords) payload.keywords = params.keywords; + if (params.category) payload.cat = params.category; + if (params.pageType) payload.pgt = params.pageType; + if (params.incognito) payload.ic = params.incognito; + if (params.dsmi) payload.smi = params.dsmi; + if (params.optout) payload.out = params.optout; + if (bidRequest && bidRequest.hasOwnProperty('ortb2Imp') && bidRequest.ortb2Imp.hasOwnProperty('ext')) { + payload.ext = bidRequest.ortb2Imp.ext; + if (bidRequest.ortb2Imp.ext.hasOwnProperty('gpid')) payload.pubAdCode = bidRequest.ortb2Imp.ext.gpid.split('#')[0]; + } + payloadArr.push(payload); + ctr++; + }); + return [{ + method: 'POST', + url: ENDPOINT_URL, + data: payloadArr, + }] + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const res = serverResponse.body || serverResponse; + if (!res || res.length === 0) { + return bidResponses; + } + try { + res.forEach(response => { + const crid = response.creativeId || 0; + const width = response.width || 0; + const height = response.height || 0; + let cpm = response.cpm || 0; + if (cpm <= 0) { + return bidResponses; + } + if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'USD'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const bidResponse = { + requestId: response.requestId, + cpm: cpm.toFixed(2), + width: response.width, + height: response.height, + creativeId: crid, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout') || 300, + referrer: bidRequest.data.r, + ad: response.ad + }; + bidResponses.push(bidResponse); + } + }); + } catch (error) { + utils.logError(error); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/colombiaBidAdapter.md b/modules/colombiaBidAdapter.md new file mode 100644 index 00000000000..c6ef5e6b749 --- /dev/null +++ b/modules/colombiaBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: COLOMBIA Bidder Adapter +Module Type: Bidder Adapter +Maintainer: colombiaonline@timesinteret.in +``` + +# Description + +Connect to COLOMBIA for bids. + +COLOMBIA adapter requires setup and approval from the COLOMBIA team. Please reach out to your account team or colombiaonline@timesinteret.in for more information. + +# Test Parameters +``` + var adUnits = [{ + code: 'test-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250],[728,90],[320,50]] + } + }, + bids: [{ + bidder: 'colombia', + params: { + placementId: '540799' + } + }] + }]; +``` \ No newline at end of file diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index b5a096521a1..2f8e397d959 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -227,7 +227,6 @@ function buildVideoRequest(bidRequest, bidderRequest) { maxbitrate: deepAccess(bidRequest, 'mediaTypes.video.maxbitrate'), delivery: deepAccess(bidRequest, 'mediaTypes.video.delivery'), linearity: deepAccess(bidRequest, 'mediaTypes.video.linearity'), - placement: deepAccess(bidRequest, 'mediaTypes.video.placement'), skip: deepAccess(bidRequest, 'mediaTypes.video.skip'), skipafter: deepAccess(bidRequest, 'mediaTypes.video.skipafter') }; diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index 0a64ed88ca5..f0c76ae557b 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -14,7 +14,7 @@ export const ORTB_VIDEO_PARAMS = { 'w': (value) => isInteger(value), 'h': (value) => isInteger(value), 'startdelay': (value) => isInteger(value), - 'placement': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 5), + 'plcmt': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 5), 'linearity': (value) => [1, 2].indexOf(value) !== -1, 'skip': (value) => [0, 1].indexOf(value) !== -1, 'skipmin': (value) => isInteger(value), diff --git a/modules/deepintentBidAdapter.md b/modules/deepintentBidAdapter.md index 84c375d69a4..0f017402d1d 100644 --- a/modules/deepintentBidAdapter.md +++ b/modules/deepintentBidAdapter.md @@ -66,7 +66,7 @@ var adVideoAdUnits = [ protocols: [ 2, 3 ], // optional battr: [ 13, 14 ], // optional linearity: 1, // optional - placement: 2, // optional + plcmt: 2, // optional minbitrate: 10, // optional maxbitrate: 10 // optional } diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index ce01abb9e71..02568c01014 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -9,7 +9,6 @@ import {getBidIdParameter} from '../src/utils.js'; */ const BIDDER_CODE = 'eskimi'; -// const ENDPOINT = 'https://hb.eskimi.com/bids' const ENDPOINT = 'https://sspback.eskimi.com/bid-request' const DEFAULT_BID_TTL = 30; @@ -21,7 +20,7 @@ const VIDEO_ORTB_PARAMS = [ 'mimes', 'minduration', 'maxduration', - 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -142,7 +141,7 @@ function buildVideoImp(bidRequest, imp) { }); if (imp.video && videoParams?.context === 'outstream') { - imp.video.placement = imp.video.placement || 4; + imp.video.plcmt = imp.video.plcmt || 4; } return { ...imp }; diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 617ce49f171..bff74f0755b 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -1,7 +1,7 @@ import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue} from '../src/utils.js'; +import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue, isFn, logWarn} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {find} from '../src/polyfill.js'; @@ -170,6 +170,19 @@ function buildImpression(bidRequest) { }, } + let bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + + if (!isNaN(bidFloor)) { + imp.bidfloor = deepAccess(bidRequest, 'params.floor'); + imp.bidfloorcur = 'USD'; + const bidfloorcur = deepAccess(bidRequest, 'params.bidfloorcur') + if (bidfloorcur && bidfloorcur !== 'USD') { + delete imp.bidfloor; + delete imp.bidfloorcur; + logWarn('insticator: bidfloorcur supported by insticator is USD only. ignoring bidfloor and bidfloorcur params'); + } + } + if (deepAccess(bidRequest, 'mediaTypes.banner')) { imp.banner = buildBanner(bidRequest); } @@ -178,6 +191,49 @@ function buildImpression(bidRequest) { imp.video = buildVideo(bidRequest); } + if (isFn(bidRequest.getFloor)) { + let moduleBidFloor; + + const mediaType = deepAccess(bidRequest, 'mediaTypes.banner') ? 'banner' : deepAccess(bidRequest, 'mediaTypes.video') ? 'video' : undefined; + + let _mediaType = mediaType; + let _size = '*'; + + if (mediaType && ['banner', 'video'].includes(mediaType)) { + if (mediaType === 'banner') { + const { w: width, h: height } = imp[mediaType]; + if (width && height) { + _size = [width, height]; + } else { + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.format'); + if (sizes && sizes.length > 0) { + const {w: width, h: height} = sizes[0]; + _size = [width, height]; + } + } + } else if (mediaType === 'video') { + const { w: width, h: height } = imp[mediaType]; + _mediaType = mediaType; + _size = [width, height]; + } + } + try { + moduleBidFloor = bidRequest.getFloor({ + currency: 'USD', + mediaType: _mediaType, + size: _size + }); + } catch (err) { + // continue with no module floors + logWarn('priceFloors module call getFloor failed, error : ', err); + } + + if (moduleBidFloor) { + imp.bidfloor = moduleBidFloor.floor; + imp.bidfloorcur = moduleBidFloor.currency; + } + } + return imp; } diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index 52f3be7e4b4..3ce622dba10 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -1,4 +1,4 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import { logMessage, logError, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -9,8 +9,7 @@ const AD_URL = 'https://smartssp-us-east.iqzone.com/pbjs'; const SYNC_URL = 'https://cs.smartssp.iqzone.com'; function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { return false; } @@ -27,7 +26,7 @@ function isBidResponseValid(bid) { } function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; + const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; const schain = bid.schain || {}; const { placementId, endpointId } = params; const bidfloor = getBidFloor(bid); @@ -57,7 +56,7 @@ function getPlacementReqData(bid) { placement.mimes = mediaTypes[VIDEO].mimes; placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; @@ -71,14 +70,19 @@ function getPlacementReqData(bid) { placement.adFormat = NATIVE; } + if (transactionId) { + placement.ext = placement.ext || {}; + placement.ext.tid = transactionId; + } + + if (userIdAsEids && userIdAsEids.length) { + placement.eids = userIdAsEids; + } + return placement; } function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - try { const bidFloor = bid.getFloor({ currency: 'USD', @@ -86,8 +90,9 @@ function getBidFloor(bid) { size: '*', }); return bidFloor.floor; - } catch (_) { - return 0 + } catch (err) { + logError(err); + return 0; } } @@ -151,12 +156,28 @@ export const spec = { host, page, placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, + coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, tmax: bidderRequest.timeout }; + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + const len = validBidRequests.length; for (let i = 0; i < len; i++) { const bid = validBidRequests[i]; @@ -184,9 +205,10 @@ export const spec = { return response; }, - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { if (typeof gdprConsent.gdprApplies === 'boolean') { syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; @@ -194,10 +216,16 @@ export const spec = { syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; } } + if (uspConsent && uspConsent.consentString) { syncUrl += `&ccpa_consent=${uspConsent.consentString}`; } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncUrl += '&gpp=' + gppConsent.gppString; + syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); + } + const coppa = config.getConfig('coppa') ? 1 : 0; syncUrl += `&coppa=${coppa}`; diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 596e5b2695f..3ef40c8a921 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -90,8 +90,6 @@ export const onTimeout = function (timeoutDataArray) { timeoutDataArray.forEach(timeoutData => { const query = parseQueryStringParameters({ ad_unit_code: timeoutData.adUnitCode, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auction_id: timeoutData.auctionId, bid_id: timeoutData.bidId, timeout: timeoutData.timeout, page_url: pageUrl, @@ -103,13 +101,6 @@ export const onTimeout = function (timeoutDataArray) { }; function getPageUrlFromRequest(validBidRequest, bidderRequest) { - // pageUrl is considered only when testing to ensure that non-test requests always contain the correct URL - if (isTest(validBidRequest) && config.getConfig('pageUrl')) { - // TODO: it's not clear what the intent is here - but all adapters should always respect pageUrl. - // With prebid 7, using `refererInfo.page` will do that automatically. - return config.getConfig('pageUrl'); - } - return (bidderRequest.refererInfo && bidderRequest.refererInfo.page) ? bidderRequest.refererInfo.page : window.location.href; @@ -125,7 +116,26 @@ function getPageUrlFromRefererInfo() { function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { const imps = validBidRequests.map(buildOpenRtbImpObject); const timeout = bidderRequest.timeout; - const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) + const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest); + // Kobler, a contextual advertising provider, does not process any personal data itself, so it is not part of TCF/GVL. + // However, it supports using select third-party creatives in its platform, some of which require certain permissions + // in order to be shown. Kobler's bidder checks if necessary permissions are present to avoid bidding + // with ineligible creatives. + let purpose2Given; + let purpose3Given; + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.vendorData) { + const vendorData = bidderRequest.gdprConsent.vendorData + const purposeData = vendorData.purpose; + const restrictions = vendorData.publisher ? vendorData.publisher.restrictions : null; + const restrictionForPurpose2 = restrictions ? (restrictions[2] ? Object.values(restrictions[2])[0] : null) : null; + purpose2Given = restrictionForPurpose2 === 1 ? ( + purposeData && purposeData.consents && purposeData.consents[2] + ) : ( + restrictionForPurpose2 === 0 + ? false : (purposeData && purposeData.legitimateInterests && purposeData.legitimateInterests[2]) + ); + purpose3Given = purposeData && purposeData.consents && purposeData.consents[3]; + } const request = { id: bidderRequest.bidderRequestId, at: 1, @@ -138,7 +148,13 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { site: { page: pageUrl, }, - test: getTestAsNumber(validBidRequests[0]) + test: getTestAsNumber(validBidRequests[0]), + ext: { + kobler: { + tcf_purpose_2_given: purpose2Given, + tcf_purpose_3_given: purpose3Given + } + } }; return JSON.stringify(request); diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 786feeb8052..6925f5fd4a0 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -7,12 +7,12 @@ import { triggerPixel, logError } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports -import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { LiveConnect } from 'live-connect-js/prebid'; // eslint-disable-line prebid/validate-imports +import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../src/adapterManager.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { UID2_EIDS } from '../libraries/uid2Eids/uid2Eids.js'; import {UID1_EIDS} from '../libraries/uid1Eids/uid1Eids.js'; -import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; import { getRefererInfo } from '../src/refererDetection.js'; /** @@ -21,12 +21,12 @@ import { getRefererInfo } from '../src/refererDetection.js'; * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse */ -const DEFAULT_AJAX_TIMEOUT = 5000 -const EVENTS_TOPIC = 'pre_lips' +const DEFAULT_AJAX_TIMEOUT = 5000; +const EVENTS_TOPIC = 'pre_lips'; const MODULE_NAME = 'liveIntentId'; const LI_PROVIDER_DOMAIN = 'liveintent.com'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); -const defaultRequestedAttributes = {'nonId': true} +const defaultRequestedAttributes = {'nonId': true}; const calls = { ajaxGet: (url, onSuccess, onError, timeout) => { ajaxBuilder(timeout)( @@ -53,10 +53,10 @@ let liveConnect = null; */ export function reset() { if (window && window.liQ_instances) { - window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)) + window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)); window.liQ_instances = []; } - liveIntentIdSubmodule.setModuleMode(null) + liveIntentIdSubmodule.setModuleMode(null); eventFired = false; liveConnect = null; } @@ -70,7 +70,7 @@ export function setEventFiredFlag() { function parseLiveIntentCollectorConfig(collectConfig) { const config = {}; - collectConfig = collectConfig || {} + collectConfig = collectConfig || {}; collectConfig.appId && (config.appId = collectConfig.appId); collectConfig.fpiStorageStrategy && (config.storageStrategy = collectConfig.fpiStorageStrategy); collectConfig.fpiExpirationDays && (config.expirationDays = collectConfig.fpiExpirationDays); @@ -86,30 +86,39 @@ function parseLiveIntentCollectorConfig(collectConfig) { * @returns {Array} */ function parseRequestedAttributes(overrides) { + function renameAttribute(attribute) { + if (attribute === 'fpid') { + return 'idCookie'; + } else { + return attribute; + }; + } function createParameterArray(config) { - return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [k] : []); + return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [renameAttribute(k)] : []); } if (typeof overrides === 'object') { - return createParameterArray({...defaultRequestedAttributes, ...overrides}) + return createParameterArray({...defaultRequestedAttributes, ...overrides}); } else { return createParameterArray(defaultRequestedAttributes); } } function initializeLiveConnect(configParams) { - configParams = configParams || {}; if (liveConnect) { return liveConnect; } + configParams = configParams || {}; + const fpidConfig = configParams.fpid || {}; + const publisherId = configParams.publisherId || 'any'; const identityResolutionConfig = { publisherId: publisherId, requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides) }; if (configParams.url) { - identityResolutionConfig.url = configParams.url - } + identityResolutionConfig.url = configParams.url; + }; identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; @@ -119,7 +128,7 @@ function initializeLiveConnect(configParams) { liveConnectConfig.distributorId = configParams.distributorId; identityResolutionConfig.source = configParams.distributorId; } else { - identityResolutionConfig.source = configParams.partner || 'prebid' + identityResolutionConfig.source = configParams.partner || 'prebid'; } liveConnectConfig.wrapperName = 'prebid'; @@ -127,11 +136,16 @@ function initializeLiveConnect(configParams) { liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; liveConnectConfig.fireEventDelay = configParams.fireEventDelay; + + liveConnectConfig.idCookie = {}; + liveConnectConfig.idCookie.name = fpidConfig.name; + liveConnectConfig.idCookie.strategy = fpidConfig.strategy == 'html5' ? 'localStorage' : fpidConfig.strategy; + const usPrivacyString = uspDataHandler.getConsentData(); if (usPrivacyString) { liveConnectConfig.usPrivacyString = usPrivacyString; } - const gdprConsent = gdprDataHandler.getConsentData() + const gdprConsent = gdprDataHandler.getConsentData(); if (gdprConsent) { liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; liveConnectConfig.gdprConsent = gdprConsent.consentString; @@ -145,21 +159,21 @@ function initializeLiveConnect(configParams) { // The third param is the ajax and pixel object, the ajax and pixel use PBJS liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); if (configParams.emailHash) { - liveConnect.push({ hash: configParams.emailHash }) + liveConnect.push({ hash: configParams.emailHash }); } return liveConnect; } function tryFireEvent() { if (!eventFired && liveConnect) { - const eventDelay = liveConnect.config.fireEventDelay || 500 + const eventDelay = liveConnect.config.fireEventDelay || 500; setTimeout(() => { - const instances = window.liQ_instances - instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)) + const instances = window.liQ_instances; + instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)); if (!eventFired && liveConnect) { liveConnect.fire(); } - }, eventDelay) + }, eventDelay); } } @@ -173,10 +187,10 @@ export const liveIntentIdSubmodule = { name: MODULE_NAME, setModuleMode(mode) { - this.moduleMode = mode + this.moduleMode = mode; }, getInitializer() { - return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode) + return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode); }, /** @@ -194,46 +208,54 @@ export const liveIntentIdSubmodule = { const result = {}; // old versions stored lipbid in unifiedId. Ensure that we can still read the data. - const lipbid = value.nonId || value.unifiedId + const lipbid = value.nonId || value.unifiedId; if (lipbid) { - value.lipbid = lipbid - delete value.unifiedId - result.lipb = value + const lipb = { ...value, lipbid }; + delete lipb.unifiedId; + result.lipb = lipb; } // Lift usage of uid2 by exposing uid2 if we were asked to resolve it. // As adapters are applied in lexicographical order, we will always // be overwritten by the 'proper' uid2 module if it is present. if (value.uid2) { - result.uid2 = { 'id': value.uid2, ext: { provider: LI_PROVIDER_DOMAIN } } + result.uid2 = { 'id': value.uid2, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.bidswitch) { - result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } } + result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.medianet) { - result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } } + result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.magnite) { - result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } } + result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.index) { - result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } } + result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.openx) { - result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } } + result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.pubmatic) { - result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } } + result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.sovrn) { - result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } } + result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } }; + } + + if (value.idCookie) { + if (!coppaDataHandler.getCoppa()) { + result.lipb = { ...result.lipb, fpid: value.idCookie }; + result.fpid = { 'id': value.idCookie }; + } + delete result.lipb.idCookie; } if (value.thetradedesk) { @@ -380,6 +402,13 @@ export const liveIntentIdSubmodule = { return data.ext; } } + }, + 'fpid': { + source: 'fpid.liveintent.com', + atype: 1, + getValue: function(data) { + return data.id; + } } } }; diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 2797664e954..413147b4fa4 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { timestamp, logInfo, getWindowTop } from '../src/utils.js'; +import { timestamp, logInfo } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS, STATUS } from '../src/constants.js'; @@ -171,7 +171,7 @@ livewrappedAnalyticsAdapter.sendEvents = function() { timeouts: getTimeouts(sentRequests.gdpr, sentRequests.auctionIds), bidAdUnits: getbidAdUnits(), rf: getAdRenderFailed(sentRequests.auctionIds), - rcv: getAdblockerRecovered() + ext: initOptions.ext }; if (events.requests.length == 0 && @@ -185,12 +185,6 @@ livewrappedAnalyticsAdapter.sendEvents = function() { ajax(initOptions.endpoint || URL, undefined, JSON.stringify(events), {method: 'POST'}); }; -function getAdblockerRecovered() { - try { - return getWindowTop().I12C && getWindowTop().I12C.Morph === 1; - } catch (e) {} -} - function getSentRequests() { var sentRequests = []; var gdpr = []; diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index d14af07210e..a724a0446a4 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -76,7 +76,7 @@ export const spec = { width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, + creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, mediaType: adUnit.mediaType, diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index d8423253aaf..62bee5c2aeb 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -291,6 +291,7 @@ function setGeneralInfo(bidRequest) { this['gpid'] = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); this['pubId'] = params.pubId; this['ext'] = params.ext; + this['ortb2Imp'] = deepAccess(bidRequest, 'ortb2Imp'); if (params.pubClick) { this['click'] = params.pubClick; } diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index cf0334b7f29..5bc74ac6465 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -76,7 +76,7 @@ export const spec = { width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, + creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, mediaType: adUnit.mediaType, diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 7ff86c5c46f..a0e117a2e1b 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -673,7 +673,9 @@ function _createImpressionObject(bid, bidderRequest) { ext: { pmZoneId: _parseSlotParam('pmzoneid', bid.params.pmzoneid) }, - bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY + bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY, + displaymanager: 'Prebid.js', + displaymanagerver: '$prebid.version$' // prebid version }; _addPMPDealsInImpression(impObj, bid); diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index b176ab08aaf..72170706fc0 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -82,7 +82,7 @@ export const spec = { width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, + creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, mediaType: adUnit.mediaType, diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 0be3e6831e7..ea2b62c95c9 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -5,10 +5,16 @@ import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'smarthub'; -const ALIASES = [{code: 'markapp', skipPbsAliasing: true}]; +const ALIASES = [ + {code: 'markapp', skipPbsAliasing: true}, + {code: 'jdpmedia', skipPbsAliasing: true}, + {code: 'tredio', skipPbsAliasing: true}, +]; const BASE_URLS = { smarthub: 'https://prebid.smart-hub.io/pbjs', - markapp: 'https://markapp-prebid.smart-hub.io/pbjs' + markapp: 'https://markapp-prebid.smart-hub.io/pbjs', + jdpmedia: 'https://jdpmedia-prebid.smart-hub.io/pbjs', + tredio: 'https://tredio-prebid.smart-hub.io/pbjs' }; function getUrl(partnerName) { diff --git a/modules/stnBidAdapter.js b/modules/stnBidAdapter.js index 42b69ee7c2b..af4f4c45d38 100644 --- a/modules/stnBidAdapter.js +++ b/modules/stnBidAdapter.js @@ -75,7 +75,7 @@ export const spec = { width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, + creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, mediaType: adUnit.mediaType, diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 6e6d89dc921..74a614bc6b0 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -113,6 +113,9 @@ const converter = ortbConverter({ const bidResponse = buildBidResponse(bid, context); bidResponse.nurl = bid.nurl; bidResponse.ad = replaceAuctionPrice(bid.adm, bid.price); + if (bid.ext && bid.ext.dchain) { + deepSetValue(bidResponse, 'meta.dchain', bid.ext.dchain); + } return bidResponse } }); diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index a3c8d3e24dc..8f775f2580d 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -81,6 +81,18 @@ export const spec = { payload.schain = firstBidRequest.schain; } + let gpp = bidderRequest.gppConsent; + if (bidderRequest && gpp) { + let isValidConsentString = typeof gpp.gppString === 'string'; + let validateApplicableSections = + Array.isArray(gpp.applicableSections) && + gpp.applicableSections.every((section) => typeof (section) === 'number') + payload.gpp = { + consentString: isValidConsentString ? gpp.gppString : '', + applicableSectionIds: validateApplicableSections ? gpp.applicableSections : [], + }; + } + let gdpr = bidderRequest.gdprConsent; if (bidderRequest && gdpr) { let isCmp = typeof gdpr.gdprApplies === 'boolean'; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index c10ecde9c30..aa1601e95e3 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -107,7 +107,7 @@ userIdAsEids = [ segments: ['s1', 's2'] } }, - + { source: 'bidswitch.net', uids: [{ @@ -118,7 +118,7 @@ userIdAsEids = [ } }] }, - + { source: 'liveintent.indexexchange.com', uids: [{ @@ -161,7 +161,7 @@ userIdAsEids = [ provider: 'liveintent.com' } }] - }, + }, { source: 'media.net', @@ -185,6 +185,17 @@ userIdAsEids = [ }] }, + { + source: 'fpid.liveintent.com', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + provider: 'liveintent.com' + } + }] + }, + { source: 'merkleinc.com', uids: [{ diff --git a/modules/userId/index.js b/modules/userId/index.js index 90d377a816e..977af127534 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -239,6 +239,29 @@ function cookieSetter(submodule, storageMgr) { } } +function setValueInCookie(submodule, valueStr, expiresStr) { + const storage = submodule.config.storage; + const setCookie = cookieSetter(submodule); + + setCookie(null, valueStr, expiresStr); + setCookie('_cst', getConsentHash(), expiresStr); + if (typeof storage.refreshInSeconds === 'number') { + setCookie('_last', new Date().toUTCString(), expiresStr); + } +} + +function setValueInLocalStorage(submodule, valueStr, expiresStr) { + const storage = submodule.config.storage; + const mgr = submodule.storageMgr; + + mgr.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); + mgr.setDataInLocalStorage(`${storage.name}_cst`, getConsentHash()); + mgr.setDataInLocalStorage(storage.name, encodeURIComponent(valueStr)); + if (typeof storage.refreshInSeconds === 'number') { + mgr.setDataInLocalStorage(`${storage.name}_last`, new Date().toUTCString()); + } +} + /** * @param {SubmoduleContainer} submodule * @param {(Object|string)} value @@ -248,54 +271,62 @@ export function setStoredValue(submodule, value) { * @type {SubmoduleStorage} */ const storage = submodule.config.storage; - const mgr = submodule.storageMgr; try { const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString(); const valueStr = isPlainObject(value) ? JSON.stringify(value) : value; - if (storage.type === COOKIE) { - const setCookie = cookieSetter(submodule); - setCookie(null, valueStr, expiresStr); - setCookie('_cst', getConsentHash(), expiresStr); - if (typeof storage.refreshInSeconds === 'number') { - setCookie('_last', new Date().toUTCString(), expiresStr); - } - } else if (storage.type === LOCAL_STORAGE) { - mgr.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); - mgr.setDataInLocalStorage(`${storage.name}_cst`, getConsentHash()); - mgr.setDataInLocalStorage(storage.name, encodeURIComponent(valueStr)); - if (typeof storage.refreshInSeconds === 'number') { - mgr.setDataInLocalStorage(`${storage.name}_last`, new Date().toUTCString()); + + submodule.enabledStorageTypes.forEach(storageType => { + switch (storageType) { + case COOKIE: + setValueInCookie(submodule, valueStr, expiresStr); + break; + case LOCAL_STORAGE: + setValueInLocalStorage(submodule, valueStr, expiresStr); + break; } - } + }); } catch (error) { logError(error); } } +function deleteValueFromCookie(submodule) { + const setCookie = cookieSetter(submodule, coreStorage); + const expiry = (new Date(Date.now() - 1000 * 60 * 60 * 24)).toUTCString(); + + ['', '_last', '_cst'].forEach(suffix => { + try { + setCookie(suffix, '', expiry); + } catch (e) { + logError(e); + } + }) +} + +function deleteValueFromLocalStorage(submodule) { + ['', '_last', '_exp', '_cst'].forEach(suffix => { + try { + coreStorage.removeDataFromLocalStorage(submodule.config.storage.name + suffix); + } catch (e) { + logError(e); + } + }); +} + export function deleteStoredValue(submodule) { - let deleter, suffixes; - switch (submodule.config?.storage?.type) { - case COOKIE: - const setCookie = cookieSetter(submodule, coreStorage); - const expiry = (new Date(Date.now() - 1000 * 60 * 60 * 24)).toUTCString(); - deleter = (suffix) => setCookie(suffix, '', expiry) - suffixes = ['', '_last', '_cst']; - break; - case LOCAL_STORAGE: - deleter = (suffix) => coreStorage.removeDataFromLocalStorage(submodule.config.storage.name + suffix) - suffixes = ['', '_last', '_exp', '_cst']; - break; - } - if (deleter) { - suffixes.forEach(suffix => { - try { - deleter(suffix) - } catch (e) { - logError(e); - } - }); - } + populateEnabledStorageTypes(submodule); + + submodule.enabledStorageTypes.forEach(storageType => { + switch (storageType) { + case COOKIE: + deleteValueFromCookie(submodule); + break; + case LOCAL_STORAGE: + deleteValueFromLocalStorage(submodule); + break; + } + }); } function setPrebidServerEidPermissions(initializedSubmodules) { @@ -305,30 +336,46 @@ function setPrebidServerEidPermissions(initializedSubmodules) { } } +function getValueFromCookie(submodule, storedKey) { + return submodule.storageMgr.getCookie(storedKey) +} + +function getValueFromLocalStorage(submodule, storedKey) { + const mgr = submodule.storageMgr; + const storage = submodule.config.storage; + const storedValueExp = mgr.getDataFromLocalStorage(`${storage.name}_exp`); + + // empty string means no expiration set + if (storedValueExp === '') { + return mgr.getDataFromLocalStorage(storedKey); + } else if (storedValueExp && ((new Date(storedValueExp)).getTime() - Date.now() > 0)) { + return decodeURIComponent(mgr.getDataFromLocalStorage(storedKey)); + } +} + /** * @param {SubmoduleContainer} submodule * @param {String|undefined} key optional key of the value * @returns {string} */ function getStoredValue(submodule, key = undefined) { - const mgr = submodule.storageMgr; const storage = submodule.config.storage; const storedKey = key ? `${storage.name}_${key}` : storage.name; let storedValue; try { - if (storage.type === COOKIE) { - storedValue = mgr.getCookie(storedKey); - } else if (storage.type === LOCAL_STORAGE) { - const storedValueExp = mgr.getDataFromLocalStorage(`${storage.name}_exp`); - // empty string means no expiration set - if (storedValueExp === '') { - storedValue = mgr.getDataFromLocalStorage(storedKey); - } else if (storedValueExp) { - if ((new Date(storedValueExp)).getTime() - Date.now() > 0) { - storedValue = decodeURIComponent(mgr.getDataFromLocalStorage(storedKey)); - } + submodule.enabledStorageTypes.find(storageType => { + switch (storageType) { + case COOKIE: + storedValue = getValueFromCookie(submodule, storedKey); + break; + case LOCAL_STORAGE: + storedValue = getValueFromLocalStorage(submodule, storedKey); + break; } - } + + return !!storedValue; + }); + // support storing a string or a stringified object if (typeof storedValue === 'string' && storedValue.trim().charAt(0) === '{') { storedValue = JSON.parse(storedValue); @@ -776,8 +823,10 @@ function populateSubmoduleId(submodule, forceRefresh, allSubmodules) { } if (!storedId || refreshNeeded || forceRefresh || consentChanged(submodule)) { + const extendedConfig = Object.assign({ enabledStorageTypes: submodule.enabledStorageTypes }, submodule.config); + // No id previously saved, or a refresh is needed, or consent has changed. Request a new id from the submodule. - response = submodule.submodule.getId(submodule.config, gdprConsent, storedId); + response = submodule.submodule.getId(extendedConfig, gdprConsent, storedId); } else if (typeof submodule.submodule.extendId === 'function') { // If the id exists already, give submodule a chance to decide additional actions that need to be taken response = submodule.submodule.extendId(submodule.config, gdprConsent, storedId); @@ -834,6 +883,8 @@ function initSubmodules(dest, submodules, forceRefresh = false) { return uidMetrics().fork().measureTime('userId.init.modules', function () { if (!submodules.length) return []; // to simplify log messages from here on + submodules.forEach(submod => populateEnabledStorageTypes(submod)); + /** * filter out submodules that: * @@ -884,6 +935,16 @@ function updateInitializedSubmodules(dest, submodule) { } } +function getConfiguredStorageTypes(config) { + return config?.storage?.type?.trim().split(/\s*&\s*/) || []; +} + +function hasValidStorageTypes(config) { + const storageTypes = getConfiguredStorageTypes(config); + + return storageTypes.every(storageType => ALL_STORAGE_TYPES.has(storageType)); +} + /** * list of submodule configurations with valid 'storage' or 'value' obj definitions * storage config: contains values for storing/retrieving User ID data in browser storage @@ -905,7 +966,7 @@ function getValidSubmoduleConfigs(configRegistry) { if (config.storage && !isEmptyStr(config.storage.type) && !isEmptyStr(config.storage.name) && - ALL_STORAGE_TYPES.has(config.storage.type)) { + hasValidStorageTypes(config)) { carry.push(config); } else if (isPlainObject(config.value)) { carry.push(config); @@ -918,28 +979,53 @@ function getValidSubmoduleConfigs(configRegistry) { const ALL_STORAGE_TYPES = new Set([LOCAL_STORAGE, COOKIE]); -function canUseStorage(submodule) { - switch (submodule.config?.storage?.type) { - case LOCAL_STORAGE: - if (submodule.storageMgr.localStorageIsEnabled()) { - if (coreStorage.getDataFromLocalStorage(PBJS_USER_ID_OPTOUT_NAME)) { - logInfo(`${MODULE_NAME} - opt-out localStorage found, storage disabled`); - return false - } - return true; - } - break; - case COOKIE: - if (submodule.storageMgr.cookiesAreEnabled()) { - if (coreStorage.getCookie(PBJS_USER_ID_OPTOUT_NAME)) { - logInfo(`${MODULE_NAME} - opt-out cookie found, storage disabled`); - return false; - } - return true - } - break; +function canUseLocalStorage(submodule) { + if (!submodule.storageMgr.localStorageIsEnabled()) { + return false; + } + + if (coreStorage.getDataFromLocalStorage(PBJS_USER_ID_OPTOUT_NAME)) { + logInfo(`${MODULE_NAME} - opt-out localStorage found, storage disabled`); + return false + } + + return true; +} + +function canUseCookies(submodule) { + if (!submodule.storageMgr.cookiesAreEnabled()) { + return false; + } + + if (coreStorage.getCookie(PBJS_USER_ID_OPTOUT_NAME)) { + logInfo(`${MODULE_NAME} - opt-out cookie found, storage disabled`); + return false; + } + + return true +} + +function populateEnabledStorageTypes(submodule) { + if (submodule.enabledStorageTypes) { + return; } - return false; + + const storageTypes = getConfiguredStorageTypes(submodule.config); + + submodule.enabledStorageTypes = storageTypes.filter(type => { + switch (type) { + case LOCAL_STORAGE: + return canUseLocalStorage(submodule); + case COOKIE: + return canUseCookies(submodule); + } + + return false; + }); +} + +function canUseStorage(submodule) { + return !!submodule.enabledStorageTypes.length; } function updateEIDConfig(submodules) { diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index f3e2e12c143..918d03861ae 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -3,7 +3,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {parseDomain} from '../src/refererDetection.js'; -import {ajax} from '../src/ajax.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -15,7 +14,6 @@ import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'zeta_global_ssp'; const ENDPOINT_URL = 'https://ssp.disqus.com/bid/prebid'; -const TIMEOUT_URL = 'https://ssp.disqus.com/timeout/prebid'; const USER_SYNC_URL_IFRAME = 'https://ssp.disqus.com/sync?type=iframe'; const USER_SYNC_URL_IMAGE = 'https://ssp.disqus.com/sync?type=image'; const DEFAULT_CUR = 'USD'; @@ -268,25 +266,6 @@ export const spec = { url: USER_SYNC_URL_IMAGE + syncurl }]; } - }, - - onTimeout: function(timeoutData) { - if (timeoutData) { - const payload = timeoutData.map(d => ({ - bidder: d?.bidder, - shortname: d?.params?.map(p => p?.tags?.shortname).find(p => p), - sid: d?.params?.map(p => p?.sid).find(p => p), - country: d?.ortb2?.device?.geo?.country, - devicetype: d?.ortb2?.device?.devicetype - })); - ajax(TIMEOUT_URL, null, JSON.stringify(payload), { - method: 'POST', - options: { - withCredentials: false, - contentType: 'application/json' - } - }); - } } } diff --git a/package-lock.json b/package-lock.json index 198eb105ed5..7c28a82a9f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "8.50.0-pre", + "version": "8.51.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "8.48.0-pre", + "version": "8.49.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -23,7 +23,7 @@ "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^6.3.4" + "live-connect-js": "^6.7.3" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -19842,19 +19842,19 @@ "dev": true }, "node_modules/live-connect-common": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.3.tgz", - "integrity": "sha512-ZPycT04ROBUvPiksnLTunrKC3ROhBSeO99fQ+4qMIkgKwP2CvS44L7fK+0WFV4nAi+65KbzSng7JWcSlckfw8w==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.1.4.tgz", + "integrity": "sha512-NK5HH0b/6bQX6hZQttlDfqrpDiP+iYtYYGO47LfM9YVwT1OZITgYZUJ0oG4IVynwdpas/VGvXv5hN0UcVK97oQ==", "engines": { "node": ">=18" } }, "node_modules/live-connect-js": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.4.tgz", - "integrity": "sha512-lg2XeCaj/eEbK66QGGDEdz9IdT/K3ExZ83Qo6xGVLdP5XJ33xAUCk/gds34rRTmpIwUfAnboOpyj3UoYtS3QUQ==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.7.3.tgz", + "integrity": "sha512-K2/GGhyhJ7/bFJfjiNw41W5xLRER9Smc49a8A6PImCcgit/sp2UsYz/F+sQwoj8IkJ3PufHvBnIGBbeQ31VsBg==", "dependencies": { - "live-connect-common": "^v3.0.3", + "live-connect-common": "^v3.1.4", "tiny-hashes": "1.0.1" }, "engines": { @@ -44521,16 +44521,16 @@ "dev": true }, "live-connect-common": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.3.tgz", - "integrity": "sha512-ZPycT04ROBUvPiksnLTunrKC3ROhBSeO99fQ+4qMIkgKwP2CvS44L7fK+0WFV4nAi+65KbzSng7JWcSlckfw8w==" + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.1.4.tgz", + "integrity": "sha512-NK5HH0b/6bQX6hZQttlDfqrpDiP+iYtYYGO47LfM9YVwT1OZITgYZUJ0oG4IVynwdpas/VGvXv5hN0UcVK97oQ==" }, "live-connect-js": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.4.tgz", - "integrity": "sha512-lg2XeCaj/eEbK66QGGDEdz9IdT/K3ExZ83Qo6xGVLdP5XJ33xAUCk/gds34rRTmpIwUfAnboOpyj3UoYtS3QUQ==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.7.3.tgz", + "integrity": "sha512-K2/GGhyhJ7/bFJfjiNw41W5xLRER9Smc49a8A6PImCcgit/sp2UsYz/F+sQwoj8IkJ3PufHvBnIGBbeQ31VsBg==", "requires": { - "live-connect-common": "^v3.0.3", + "live-connect-common": "^v3.1.4", "tiny-hashes": "1.0.1" } }, diff --git a/package.json b/package.json index 087cab55d7c..c6d19acbe1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.50.0-pre", + "version": "8.51.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -135,7 +135,7 @@ "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^6.3.4" + "live-connect-js": "^6.7.3" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/src/auction.js b/src/auction.js index 26845936797..881dee9f2de 100644 --- a/src/auction.js +++ b/src/auction.js @@ -94,7 +94,7 @@ import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js'; import adapterManager from './adapterManager.js'; -import { EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, S2S, TARGETING_KEYS } from './constants.js'; +import { EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS } from './constants.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {adjustCpm} from './utils/cpm.js'; @@ -421,7 +421,11 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a * @param {function(String): void} reject a function that, when called, rejects `bid` with the given reason. */ export const addBidResponse = hook('sync', function(adUnitCode, bid, reject) { - this.dispatch.call(null, adUnitCode, bid); + if (!isValidPrice(bid)) { + reject(REJECTION_REASON.PRICE_TOO_HIGH) + } else { + this.dispatch.call(null, adUnitCode, bid); + } }, 'addBidResponse'); /** @@ -974,3 +978,17 @@ function groupByPlacement(bidsByPlacement, bid) { bidsByPlacement[bid.adUnitCode].bids.push(bid); return bidsByPlacement; } + +/** + * isValidPrice is price validation function + * which checks if price from bid response + * is not higher than top limit set in config + * @type {Function} + * @param bid + * @returns {boolean} + */ +function isValidPrice(bid) { + const maxBidValue = config.getConfig('maxBid'); + if (!maxBidValue || !bid.cpm) return true; + return maxBidValue >= Number(bid.cpm); +} diff --git a/src/config.js b/src/config.js index f4dd0de9612..21c34cf34d2 100644 --- a/src/config.js +++ b/src/config.js @@ -36,6 +36,7 @@ const DEFAULT_DISABLE_AJAX_TIMEOUT = false; const DEFAULT_BID_CACHE = false; const DEFAULT_DEVICE_ACCESS = true; const DEFAULT_MAX_NESTED_IFRAMES = 10; +const DEFAULT_MAXBID_VALUE = 5000 const DEFAULT_TIMEOUTBUFFER = 400; @@ -160,6 +161,9 @@ export function newConfig() { // default max nested iframes for referer detection maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, + + // default max bid + maxBid: DEFAULT_MAXBID_VALUE }; Object.defineProperties(newConfig, diff --git a/src/constants.js b/src/constants.js index b40b7ddb9b0..bb76083862b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -135,7 +135,8 @@ export const REJECTION_REASON = { FLOOR_NOT_MET: 'Bid does not meet price floor', CANNOT_CONVERT_CURRENCY: 'Unable to convert currency', DSA_REQUIRED: 'Bid does not provide required DSA transparency info', - DSA_MISMATCH: 'Bid indicates inappropriate DSA rendering method' + DSA_MISMATCH: 'Bid indicates inappropriate DSA rendering method', + PRICE_TOO_HIGH: 'Bid price exceeds maximum value' }; export const PREBID_NATIVE_DATA_KEYS_TO_ORTB = { diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 65c6256acdc..e5cdb66e75f 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -24,6 +24,9 @@ import {expect} from 'chai'; import {deepClone} from '../../src/utils.js'; import { IMAGE as ortbNativeRequest } from 'src/native.js'; import {PrebidServer} from '../../modules/prebidServerBidAdapter/index.js'; +import '../../modules/currency.js' +import { setConfig as setCurrencyConfig } from '../../modules/currency.js'; +import { REJECTION_REASON } from '../../src/constants.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -1467,6 +1470,16 @@ describe('auctionmanager.js', function () { config.getConfig.restore(); store.store.restore(); }); + + it('should reject bid for price higher than limit for the same currency', () => { + sinon.stub(auction, 'addBidRejected'); + config.setConfig({ + maxBid: 1 + }); + + auction.callBids(); + sinon.assert.calledWith(auction.addBidRejected, sinon.match({rejectionReason: REJECTION_REASON.PRICE_TOO_HIGH})); + }) }); describe('addBidRequests', function () { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index c288bfb4f12..4044e62280a 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -566,7 +566,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO); }); - it('should pass segments if available in config', function () { + it('should pass segments if available in config and merge from targeting', function () { const ortb2 = { user: { data: [{ @@ -580,10 +580,16 @@ describe('adnuntiusBidAdapter', function () { } }; + bidderRequests[0].params.targeting = { + segments: ['merge-this', 'and-this'] + }; + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') - expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); + expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS.replace('segment3', 'segment3,merge-this,and-this')); + + delete bidderRequests[0].params.targeting; }); it('should pass site data ext as key values to ad server', function () { @@ -592,20 +598,28 @@ describe('adnuntiusBidAdapter', function () { ext: { data: { '12345': 'true', - '45678': 'true' + '45678': 'true', + '9090': 'should-be-overwritten' } } } }; - + bidderRequests[0].params.targeting = { + kv: { + 'merge': ['this'], + '9090': ['take it over'] + } + }; const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') const data = JSON.parse(request[0].data); - expect(data.adUnits[0].kv).to.have.property('12345'); expect(data.adUnits[0].kv['12345']).to.equal('true'); - expect(data.adUnits[0].kv).to.have.property('45678'); expect(data.adUnits[0].kv['45678']).to.equal('true'); + expect(data.adUnits[0].kv['9090'][0]).to.equal('take it over'); + expect(data.adUnits[0].kv['merge'][0]).to.equal('this'); + + delete bidderRequests[0].params.targeting; }); it('should skip passing site data ext if missing', function () { diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index 3de348197b2..c9d29ff09dd 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -168,7 +168,7 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ }, params: { siteId: '1', - placementId: '13144370' + placementId: '13144370', }, src: 'client', transactionId: 'db739693-9b4a-4669-9945-8eab938783cc' @@ -193,7 +193,7 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ }, params: { siteId: '1', - placementId: '13144370' + placementId: '13144370', }, src: 'client', transactionId: 'db739693-9b4a-4669-9945-8eab938783cc' @@ -209,7 +209,50 @@ const VALID_BIDDER_REQUEST = { params: { placementId: '13144370', siteId: '23434', - publisherId: '7689670753' + publisherId: '7689670753', + }, + } + ], + refererInfo: { + page: 'test-page', + domain: 'test-domain', + ref: 'test-referer' + }, +} + +const VALID_GDPR_BIDDER_REQUEST = { + auctionId: '19c97f22-5bd1-4b16-a128-80f75fb0a8a0', + bidderCode: 'aidem', + bidderRequestId: '1bbb7854dfa0d8', + bids: [ + { + params: { + placementId: '13144370', + siteId: '23434', + publisherId: '7689670753', + }, + } + ], + refererInfo: { + page: 'test-page', + domain: 'test-domain', + ref: 'test-referer' + }, + gdprConsent: { + consentString: 'test-gdpr-string' + } +} + +const VALID_USP_BIDDER_REQUEST = { + auctionId: '19c97f22-5bd1-4b16-a128-80f75fb0a8a0', + bidderCode: 'aidem', + bidderRequestId: '1bbb7854dfa0d8', + bids: [ + { + params: { + placementId: '13144370', + siteId: '23434', + publisherId: '7689670753', }, } ], @@ -218,6 +261,7 @@ const VALID_BIDDER_REQUEST = { domain: 'test-domain', ref: 'test-referer' }, + uspConsent: '1YYY' } const SERVER_RESPONSE_BANNER = { @@ -601,47 +645,51 @@ describe('Aidem adapter', () => { }); it(`should set gdpr to true`, function () { - config.setConfig({ - consentManagement: { - gdpr: { - // any data here set gdpr to true - }, - } - }); - const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + // config.setConfig({ + // consentManagement: { + // gdpr: { + // consentData: { + // getTCData: { + // tcString: 'test-gdpr-string' + // } + // } + // }, + // } + // }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_GDPR_BIDDER_REQUEST); expect(data.regs.gdpr_applies).to.equal(true) }); it(`should set usp_consent string`, function () { - config.setConfig({ - consentManagement: { - usp: { - cmpApi: 'static', - consentData: { - getUSPData: { - uspString: '1YYY' - } - } - } - } - }); - const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + // config.setConfig({ + // consentManagement: { + // usp: { + // cmpApi: 'static', + // consentData: { + // getUSPData: { + // uspString: '1YYY' + // } + // } + // } + // } + // }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_USP_BIDDER_REQUEST); expect(data.regs.us_privacy).to.equal('1YYY') }); it(`should not set usp_consent string`, function () { - config.setConfig({ - consentManagement: { - usp: { - cmpApi: 'iab', - consentData: { - getUSPData: { - uspString: '1YYY' - } - } - } - } - }); + // config.setConfig({ + // consentManagement: { + // usp: { + // cmpApi: 'iab', + // consentData: { + // getUSPData: { + // uspString: '1YYY' + // } + // } + // } + // } + // }); const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); expect(data.regs.us_privacy).to.undefined }); diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index c0994985aae..2e766487951 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -245,14 +245,14 @@ describe('BeachfrontAdapter', function () { const mimes = ['video/webm']; const playbackmethod = 2; const maxduration = 30; - const placement = 4; + const plcmt = 4; const skip = 1; bidRequest.mediaTypes = { - video: { mimes, playbackmethod, maxduration, placement, skip } + video: { mimes, playbackmethod, maxduration, plcmt, skip } }; const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; - expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, plcmt, skip }); }); it('must override video params from the bidder object', function () { @@ -260,13 +260,13 @@ describe('BeachfrontAdapter', function () { const mimes = ['video/webm']; const playbackmethod = 2; const maxduration = 30; - const placement = 4; + const plcmt = 4; const skip = 1; - bidRequest.mediaTypes = { video: { placement: 3, skip: 0 } }; - bidRequest.params.video = { mimes, playbackmethod, maxduration, placement, skip }; + bidRequest.mediaTypes = { video: { plcmt: 3, skip: 0 } }; + bidRequest.params.video = { mimes, playbackmethod, maxduration, plcmt, skip }; const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; - expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, plcmt, skip }); }); it('must add US privacy data to the request', function () { diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js index 5fcc78f4322..92a08c8fbef 100644 --- a/test/spec/modules/browsiRtdProvider_spec.js +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -1,9 +1,9 @@ import * as browsiRTD from '../../../modules/browsiRtdProvider.js'; -import {makeSlot} from '../integration/faker/googletag.js'; import * as utils from '../../../src/utils' import * as events from '../../../src/events'; import * as sinon from 'sinon'; import {sendPageviewEvent} from '../../../modules/browsiRtdProvider.js'; +import * as mockGpt from 'test/spec/integration/faker/googletag.js'; describe('browsi Real time data sub module', function () { const conf = { @@ -55,7 +55,7 @@ describe('browsi Real time data sub module', function () { }); it('should match placement with ad unit', function () { - const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); + const slot = mockGpt.makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc']); // true const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc', '/456/def']); // true @@ -69,7 +69,7 @@ describe('browsi Real time data sub module', function () { }); it('should return correct macro values', function () { - const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); + const slot = mockGpt.makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); slot.setTargeting('test', ['test', 'value']); // slot getTargeting doesn't act like GPT so we can't expect real value @@ -90,7 +90,7 @@ describe('browsi Real time data sub module', function () { }); it('should return prediction from server', function () { - makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); + mockGpt.makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); const data = { p: {'hasPrediction': {ps: {0: 0.234}}}, kn: 'bv', @@ -266,4 +266,29 @@ describe('browsi Real time data sub module', function () { expect(eventsEmitSpy.callCount).to.equal(0); }) }) + + describe('set targeting - invalid params', function () { + it('should return false if key is undefined', function () { + expect(browsiRTD.setKeyValue()).to.equal(false); + }) + it('should return false if key is not string', function () { + expect(browsiRTD.setKeyValue(1)).to.equal(false); + }) + }) + describe('set targeting - valid params', function () { + let slot; + const splitKey = 'splitTest'; + before(() => { + mockGpt.reset(); + window.googletag.pubads().clearTargeting(); + slot = mockGpt.makeSlot({code: '/123/split', divId: 'split'}); + browsiRTD.setKeyValue(splitKey); + window.googletag.cmd.forEach(cmd => cmd()); + }) + it('should place numeric key value on all slots', function () { + const targetingValue = window.googletag.pubads().getTargeting(splitKey); + expect(targetingValue).to.be.an('array').that.is.not.empty; + expect(targetingValue[0]).to.be.a('string'); + }) + }) }); diff --git a/test/spec/modules/cadentApertureMXBidAdapter_spec.js b/test/spec/modules/cadentApertureMXBidAdapter_spec.js index 3ccb5405552..4f0f2cf8f20 100644 --- a/test/spec/modules/cadentApertureMXBidAdapter_spec.js +++ b/test/spec/modules/cadentApertureMXBidAdapter_spec.js @@ -451,10 +451,27 @@ describe('cadent_aperture_mx Adapter', function () { }); }); - it('should add gpid to request if present', () => { + it('should add gpid to request if present in ext.gpid', () => { + const gpid = '/12345/my-gpt-tag-0'; + let bid = utils.deepClone(bidderRequest.bids[0]); + bid.ortb2Imp = { ext: { gpid, data: { adserver: { adslot: gpid + '1' }, pbadslot: gpid + '2' } } }; + let requestWithGPID = spec.buildRequests([bid], bidderRequest); + requestWithGPID = JSON.parse(requestWithGPID.data); + expect(requestWithGPID.imp[0].ext.gpid).to.exist.and.equal(gpid); + }); + + it('should add gpid to request if present in ext.data.adserver.adslot', () => { + const gpid = '/12345/my-gpt-tag-0'; + let bid = utils.deepClone(bidderRequest.bids[0]); + bid.ortb2Imp = { ext: { data: { adserver: { adslot: gpid }, pbadslot: gpid + '1' } } }; + let requestWithGPID = spec.buildRequests([bid], bidderRequest); + requestWithGPID = JSON.parse(requestWithGPID.data); + expect(requestWithGPID.imp[0].ext.gpid).to.exist.and.equal(gpid); + }); + + it('should add gpid to request if present in ext.data.pbadslot', () => { const gpid = '/12345/my-gpt-tag-0'; let bid = utils.deepClone(bidderRequest.bids[0]); - bid.ortb2Imp = { ext: { data: { adserver: { adslot: gpid } } } }; bid.ortb2Imp = { ext: { data: { pbadslot: gpid } } }; let requestWithGPID = spec.buildRequests([bid], bidderRequest); requestWithGPID = JSON.parse(requestWithGPID.data); diff --git a/test/spec/modules/ceeIdSystem_spec.js b/test/spec/modules/ceeIdSystem_spec.js new file mode 100644 index 00000000000..049e596ad15 --- /dev/null +++ b/test/spec/modules/ceeIdSystem_spec.js @@ -0,0 +1,77 @@ +import { ceeIdSubmodule, storage, readId } from 'modules/ceeIdSystem.js'; +import sinon from 'sinon'; + +describe('ceeIdSystem', () => { + let sandbox; + let getCookieStub; + let getDataFromLocalStorageStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + getCookieStub = sandbox.stub(storage, 'getCookie'); + getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getId', () => { + it('should return an object with id when ceeIdToken is found', () => { + getDataFromLocalStorageStub.returns('testToken'); + getCookieStub.returns('testToken'); + + const result = ceeIdSubmodule.getId(); + + expect(result).to.deep.equal({ id: 'testToken' }); + }); + + it('should return undefined when ceeIdToken is not found', () => { + const result = ceeIdSubmodule.getId(); + + expect(result).to.be.undefined; + }); + }); + + describe('decode', () => { + it('should return an object with ceeId when value is a string', () => { + const result = ceeIdSubmodule.decode('testId'); + + expect(result).to.deep.equal({ ceeId: 'testId' }); + }); + + it('should return undefined when value is not a string', () => { + const result = ceeIdSubmodule.decode({}); + + expect(result).to.be.undefined; + }); + }); + + describe('readId', () => { + it('should return data from local storage when it exists', () => { + getDataFromLocalStorageStub.returns('local_storage_data'); + + const result = readId(); + + expect(result).to.equal('local_storage_data'); + }); + + it('should return data from cookie when local storage data does not exist', () => { + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns('cookie_data'); + + const result = readId(); + + expect(result).to.equal('cookie_data'); + }); + + it('should return null when neither local storage data nor cookie data exists', () => { + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns(null); + + const result = readId(); + + expect(result).to.be.null; + }); + }); +}); diff --git a/test/spec/modules/colombiaBidAdapter_spec.js b/test/spec/modules/colombiaBidAdapter_spec.js new file mode 100644 index 00000000000..b7256545c5e --- /dev/null +++ b/test/spec/modules/colombiaBidAdapter_spec.js @@ -0,0 +1,155 @@ +import { expect } from 'chai'; +import { spec } from 'modules/colombiaBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const HOST_NAME = document.location.protocol + '//' + window.location.host; +const ENDPOINT = 'https://ade.clmbtech.com/cde/prebid.htm'; + +describe('colombiaBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId not passed correctly', function () { + bid.params.placementId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code1', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, + { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code2', + 'sizes': [ + [300, 250] + ], + 'bidId': '382091349b149f"', + 'bidderRequestId': '"1f9c98192de2511"', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + let bidderRequest = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://example.com', + stack: ['http://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests); + it('sends bid request to our endpoint via POST', function () { + expect(request[0].method).to.equal('POST'); + }); + + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = [ + { + 'method': 'POST', + 'url': 'https://ade.clmbtech.com/cde/prebid.htm', + 'data': { + 'v': 'hb1', + 'p': '307466', + 'w': '300', + 'h': '250', + 'cb': 12892917383, + 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'uid': '23beaa6af6cdde', + 't': 'i', + } + } + ]; + + let serverResponse = [{ + 'ad': '