From a2229527200e92ea0e6c80db3c36bedb11e0ecf7 Mon Sep 17 00:00:00 2001 From: AdmixerTech <35560933+AdmixerTech@users.noreply.github.com> Date: Wed, 10 May 2023 19:50:01 +0300 Subject: [PATCH 01/38] Admixer Bid Adapter: add alias handlers (#9931) * Update README.md update * add alias handlers --- README.md | 3 + modules/admixerBidAdapter.js | 24 +++--- test/spec/modules/admixerBidAdapter_spec.js | 89 +++++++++++++++------ 3 files changed, 82 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index fdbb5482ebf..58007519b15 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,8 @@ pbjs.requestBids({ ## Install + + $ git clone https://github.com/prebid/Prebid.js.git $ cd Prebid.js $ npm ci @@ -358,3 +360,4 @@ Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to ### Governance Review our governance model [here](https://github.com/prebid/Prebid.js/tree/master/governance.md). +### END diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 8dbcfdafd32..1006fef631c 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,17 +1,23 @@ -import {logError} from '../src/utils.js'; +import {isStr, logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {find} from '../src/polyfill.js'; const BIDDER_CODE = 'admixer'; -const BIDDER_CODE_ADX = 'admixeradx'; -const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads', 'admixeradx']; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; -const ADX_ENDPOINT_URL = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; +const ALIASES = [ + {code: 'go2net', endpoint: 'https://ads.go2net.com.ua/prebid.1.2.aspx'}, + 'adblender', + {code: 'adsyield', endpoint: 'https://ads.adsyield.com/prebid.1.2.aspx'}, + {code: 'futureads', endpoint: 'https://ads.futureads.io/prebid.1.2.aspx'}, + {code: 'smn', endpoint: 'https://ads.smn.rs/prebid.1.2.aspx'}, + {code: 'admixeradx', endpoint: 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'}, +]; export const spec = { code: BIDDER_CODE, - aliases: ALIASES, + aliases: ALIASES.map(val => isStr(val) ? val : val.code), supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. @@ -72,10 +78,7 @@ export const spec = { return { method: 'POST', url: - endpointUrl || - (bidderRequest.bidderCode === BIDDER_CODE_ADX - ? ADX_ENDPOINT_URL - : ENDPOINT_URL), + endpointUrl || getEndpointUrl(bidderRequest.bidderCode), data: payload, }; }, @@ -106,6 +109,9 @@ export const spec = { return pixels; } }; +function getEndpointUrl(code) { + return find(ALIASES, (val) => val.code === code)?.endpoint || ENDPOINT_URL; +} function getBidFloor(bid) { try { const bidFloor = bid.getFloor({ diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 26caaf5b70f..8cf433460b7 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -100,33 +100,72 @@ describe('AdmixerAdapter', function () { }); }); - describe('buildRequestsAdmixerADX', function () { - let validRequest = [ - { - bidder: BIDDER_CODE_ADX, - params: { - zone: ZONE_ID, + describe('buildRequests URL check', function () { + const requestParamsFor = (bidder) => ({ + validRequest: [ + { + bidder: bidder, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', }, - adUnitCode: 'adunit-code', - sizes: [ - [300, 250], - [300, 600], - ], - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - }, - ]; - let bidderRequest = { - bidderCode: BIDDER_CODE_ADX, - refererInfo: { - page: 'https://example.com', - }, - }; + ], + bidderRequest: { + bidderCode: bidder, + refererInfo: { + page: 'https://example.com', + }, + } + }) - it('sends bid request to ADX ENDPOINT', function () { - const request = spec.buildRequests(validRequest, bidderRequest); - expect(request.url).to.equal(ENDPOINT_URL_ADX); + it('build request for admixer', function () { + const requestParams = requestParamsFor('admixer'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://inv-nets.admixer.net/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for go2net', function () { + const requestParams = requestParamsFor('go2net'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://ads.go2net.com.ua/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for adblender', function () { + const requestParams = requestParamsFor('adblender'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://inv-nets.admixer.net/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for adsyield', function () { + const requestParams = requestParamsFor('adsyield'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://ads.adsyield.com/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for futureads', function () { + const requestParams = requestParamsFor('futureads'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://ads.futureads.io/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for smn', function () { + const requestParams = requestParamsFor('smn'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://ads.smn.rs/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for admixeradx', function () { + const requestParams = requestParamsFor('admixeradx'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://inv-nets.admixer.net/adxprebid.1.2.aspx'); expect(request.method).to.equal('POST'); }); }); From 8c5ced34d22e6f35b825702629c32bd54d0b8701 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 10 May 2023 09:50:50 -0700 Subject: [PATCH 02/38] Remove Rubicon Analytics for Prebid 8 (#9932) --- modules/rubiconAnalyticsAdapter.js | 938 ------ .../modules/rubiconAnalyticsAdapter_spec.js | 2540 ----------------- test/spec/modules/rubiconAnalyticsSchema.json | 494 ---- 3 files changed, 3972 deletions(-) delete mode 100644 modules/rubiconAnalyticsAdapter.js delete mode 100644 test/spec/modules/rubiconAnalyticsAdapter_spec.js delete mode 100644 test/spec/modules/rubiconAnalyticsSchema.json diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js deleted file mode 100644 index 7bbc435cb27..00000000000 --- a/modules/rubiconAnalyticsAdapter.js +++ /dev/null @@ -1,938 +0,0 @@ -import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, isGptPubadsDefined, _each, deepSetValue, deepClone, logInfo } from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; - -const RUBICON_GVL_ID = 52; -export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'rubicon'}); -const COOKIE_NAME = 'rpaSession'; -const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins -const END_EXPIRE_TIME = 21600000; // 6 hours -const MODULE_NAME = 'Rubicon Analytics'; - -const pbsErrorMap = { - 1: 'timeout-error', - 2: 'input-error', - 3: 'connect-error', - 4: 'request-error', - 999: 'generic-error' -} - -let prebidGlobal = getGlobal(); -const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_RESPONSE, - BIDDER_DONE, - BID_TIMEOUT, - BID_WON, - SET_TARGETING, - BILLABLE_EVENT - }, - STATUS: { - GOOD, - NO_BID - }, - BID_STATUS: { - BID_REJECTED - } -} = CONSTANTS; - -let serverConfig; -config.getConfig('s2sConfig', ({ s2sConfig }) => { - serverConfig = s2sConfig; -}); - -export const SEND_TIMEOUT = 3000; -const DEFAULT_INTEGRATION = 'pbjs'; - -const cache = { - auctions: {}, - targeting: {}, - timeouts: {}, - gpt: {}, - billing: {} -}; - -const BID_REJECTED_IPF = 'rejected-ipf'; - -export let rubiConf; -export const resetRubiConf = () => { - rubiConf = { - pvid: generateUUID().slice(0, 8), - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - } - } -} -resetRubiConf(); - -// we are saving these as global to this module so that if a pub accidentally overwrites the entire -// rubicon object, then we do not lose other data -config.getConfig('rubicon', config => { - mergeDeep(rubiConf, config.rubicon); - if (deepAccess(config, 'rubicon.updatePageView') === true) { - rubiConf.pvid = generateUUID().slice(0, 8) - } -}); - -export function getHostNameFromReferer(referer) { - try { - rubiconAdapter.referrerHostname = parseUrl(referer, { noDecodeWholeURL: true }).hostname; - } catch (e) { - logError(`${MODULE_NAME}: Unable to parse hostname from supplied url: `, referer, e); - rubiconAdapter.referrerHostname = ''; - } - return rubiconAdapter.referrerHostname -}; - -function stringProperties(obj) { - return Object.keys(obj).reduce((newObj, prop) => { - let value = obj[prop]; - if (typeof value === 'number') { - value = value.toFixed(3); - } else if (typeof value !== 'string') { - value = String(value); - } - newObj[prop] = value || undefined; - return newObj; - }, {}); -} - -function sizeToDimensions(size) { - return { - width: size.w || size[0], - height: size.h || size[1] - }; -} - -function validMediaType(type) { - return ['banner', 'native', 'video'].indexOf(type) !== -1; -} - -function formatSource(src) { - if (typeof src === 'undefined') { - src = 'client'; - } else if (src === 's2s') { - src = 'server'; - } - return src.toLowerCase(); -} - -function getBillingPayload(event) { - let billingEvent = deepClone(event); - // Pass along type if is string and not empty else general - billingEvent.type = (typeof event.type === 'string' && event.type) || 'general'; - billingEvent.accountId = accountId; - // mark as sent - deepSetValue(cache.billing, `${event.vendor}.${event.billingId}`, true); - return billingEvent; -} - -function sendBillingEvent(event) { - let message = getBasicEventDetails(undefined, 'soloBilling'); - message.billableEvents = [getBillingPayload(event)]; - ajax( - rubiconAdapter.getUrl(), - null, - JSON.stringify(message), - { - contentType: 'application/json' - } - ); -} - -function getBasicEventDetails(auctionId, trigger) { - let auctionCache = cache.auctions[auctionId]; - let referrer = pageReferer || (auctionCache && auctionCache.referrer); - let message = { - timestamps: { - prebidLoaded: rubiconAdapter.MODULE_INITIALIZED_TIME, - auctionEnded: auctionCache ? auctionCache.endTs : undefined, - eventTime: Date.now() - }, - trigger, - integration: rubiConf.int_type || DEFAULT_INTEGRATION, - version: '$prebid.version$', - referrerUri: referrer, - referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer), - channel: 'web', - }; - if (rubiConf.wrapperName) { - message.wrapper = { - name: rubiConf.wrapperName, - family: rubiConf.wrapperFamily, - rule: rubiConf.rule_name - } - } - return message; -} - -function sendMessage(auctionId, bidWonId, trigger) { - function formatBid(bid) { - return pick(bid, [ - 'bidder', - 'bidderDetail', - 'bidId', bidId => deepAccess(bid, 'bidResponse.pbsBidId') || deepAccess(bid, 'bidResponse.seatBidId') || bidId, - 'status', - 'error', - 'source', (source, bid) => { - if (source) { - return source; - } - return serverConfig && Array.isArray(serverConfig.bidders) && serverConfig.bidders.some(s2sBidder => s2sBidder.toLowerCase() === bid.bidder) !== -1 - ? 'server' : 'client' - }, - 'clientLatencyMillis', - 'serverLatencyMillis', - 'params', - 'bidResponse', bidResponse => bidResponse ? pick(bidResponse, [ - 'bidPriceUSD', - 'dealId', - 'dimensions', - 'mediaType', - 'floorValue', - 'floorRuleValue', - 'floorRule', - 'adomains' - ]) : undefined - ]); - } - function formatBidWon(bid) { - return Object.assign(formatBid(bid), pick(bid.adUnit, [ - 'adUnitCode', - 'transactionId', - 'videoAdFormat', () => bid.videoAdFormat, - 'mediaTypes' - ]), { - adserverTargeting: !isEmpty(cache.targeting[bid.adUnit.adUnitCode]) ? stringProperties(cache.targeting[bid.adUnit.adUnitCode]) : undefined, - bidwonStatus: 'success', // hard-coded for now - accountId, - siteId: bid.siteId, - zoneId: bid.zoneId, - samplingFactor - }); - } - let message = getBasicEventDetails(auctionId, trigger); - let auctionCache = cache.auctions[auctionId]; - if (auctionCache && !auctionCache.sent) { - let adUnitMap = Object.keys(auctionCache.bids).reduce((adUnits, bidId) => { - let bid = auctionCache.bids[bidId]; - let adUnit = adUnits[bid.adUnit.adUnitCode]; - if (!adUnit) { - adUnit = adUnits[bid.adUnit.adUnitCode] = pick(bid.adUnit, [ - 'adUnitCode', - 'transactionId', - 'mediaTypes', - 'dimensions', - 'adserverTargeting', () => !isEmpty(cache.targeting[bid.adUnit.adUnitCode]) ? stringProperties(cache.targeting[bid.adUnit.adUnitCode]) : undefined, - 'gam', gam => !isEmpty(gam) ? gam : undefined, - 'pbAdSlot', - 'gpid', - 'pattern' - ]); - adUnit.bids = []; - adUnit.status = 'no-bid'; // default it to be no bid - } - - // Add site and zone id if not there and if we found a rubicon bidder - if ((!adUnit.siteId || !adUnit.zoneId) && rubiconAliases.indexOf(bid.bidder) !== -1) { - if (deepAccess(bid, 'params.accountId') == accountId) { - adUnit.accountId = parseInt(accountId); - adUnit.siteId = parseInt(deepAccess(bid, 'params.siteId')); - adUnit.zoneId = parseInt(deepAccess(bid, 'params.zoneId')); - } - } - - if (bid.videoAdFormat && !adUnit.videoAdFormat) { - adUnit.videoAdFormat = bid.videoAdFormat; - } - - // determine adUnit.status from its bid statuses. Use priority below to determine, higher index is better - let statusPriority = ['error', 'no-bid', 'success']; - if (statusPriority.indexOf(bid.status) > statusPriority.indexOf(adUnit.status)) { - adUnit.status = bid.status; - } - - adUnit.bids.push(formatBid(bid)); - - return adUnits; - }, {}); - - // We need to mark each cached bid response with its appropriate rubicon site-zone id - // This allows the bidWon events to have these params even in the case of a delayed render - Object.keys(auctionCache.bids).forEach(function (bidId) { - let adCode = auctionCache.bids[bidId].adUnit.adUnitCode; - Object.assign(auctionCache.bids[bidId], pick(adUnitMap[adCode], ['accountId', 'siteId', 'zoneId'])); - }); - - let auction = { - clientTimeoutMillis: auctionCache.timeout, - auctionStart: auctionCache.timestamp, - auctionEnd: auctionCache.endTs, - bidderOrder: auctionCache.bidderOrder, - samplingFactor, - accountId, - adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]), - requestId: auctionId - }; - - // pick our of top level floor data we want to send! - if (auctionCache.floorData) { - if (auctionCache.floorData.location === 'noData') { - auction.floors = pick(auctionCache.floorData, [ - 'location', - 'fetchStatus', - 'floorProvider as provider' - ]); - } else { - auction.floors = pick(auctionCache.floorData, [ - 'location', - 'modelVersion as modelName', - 'modelWeight', - 'modelTimestamp', - 'skipped', - 'enforcement', () => deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), - 'dealsEnforced', () => deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), - 'skipRate', - 'fetchStatus', - 'floorMin', - 'floorProvider as provider' - ]); - } - } - - // gather gdpr info - if (auctionCache.gdprConsent) { - auction.gdpr = pick(auctionCache.gdprConsent, [ - 'gdprApplies as applies', - 'consentString', - 'apiVersion as version' - ]); - } - - // gather session info - if (auctionCache.session) { - message.session = pick(auctionCache.session, [ - 'id', - 'pvid', - 'start', - 'expires' - ]); - if (!isEmpty(auctionCache.session.fpkvs)) { - message.fpkvs = Object.keys(auctionCache.session.fpkvs).map(key => { - return { key, value: auctionCache.session.fpkvs[key] }; - }); - } - } - - if (serverConfig) { - auction.serverTimeoutMillis = serverConfig.timeout; - } - - if (auctionCache.userIds.length) { - auction.user = { ids: auctionCache.userIds }; - } - - message.auctions = [auction]; - - let bidsWon = Object.keys(auctionCache.bidsWon).reduce((memo, adUnitCode) => { - let bidId = auctionCache.bidsWon[adUnitCode]; - if (bidId) { - memo.push(formatBidWon(auctionCache.bids[bidId])); - } - return memo; - }, []); - - if (bidsWon.length > 0) { - message.bidsWon = bidsWon; - } - - auctionCache.sent = true; - } else if (bidWonId && auctionCache && auctionCache.bids[bidWonId]) { - message.bidsWon = [ - formatBidWon(auctionCache.bids[bidWonId]) - ]; - } - - // if we have not sent any billingEvents send them - const pendingBillingEvents = getPendingBillingEvents(auctionCache); - if (pendingBillingEvents && pendingBillingEvents.length) { - message.billableEvents = pendingBillingEvents; - } - - ajax( - this.getUrl(), - null, - JSON.stringify(message), - { - contentType: 'application/json' - } - ); -} - -function getPendingBillingEvents(auctionCache) { - if (auctionCache && auctionCache.billing && auctionCache.billing.length) { - return auctionCache.billing.reduce((accum, billingEvent) => { - if (deepAccess(cache.billing, `${billingEvent.vendor}.${billingEvent.billingId}`) === false) { - accum.push(getBillingPayload(billingEvent)); - } - return accum; - }, []); - } -} - -function adUnitIsOnlyInstream(adUnit) { - return adUnit.mediaTypes && Object.keys(adUnit.mediaTypes).length === 1 && deepAccess(adUnit, 'mediaTypes.video.context') === 'instream'; -} - -function getBidPrice(bid) { - // get the cpm from bidResponse - let cpm; - let currency; - if (bid.status === BID_REJECTED && deepAccess(bid, 'floorData.cpmAfterAdjustments')) { - // if bid was rejected and bid.floorData.cpmAfterAdjustments use it - cpm = bid.floorData.cpmAfterAdjustments; - currency = bid.floorData.floorCurrency; - } else if (typeof bid.currency === 'string' && bid.currency.toUpperCase() === 'USD') { - // bid is in USD use it - return Number(bid.cpm); - } else { - // else grab cpm - cpm = bid.cpm; - currency = bid.currency; - } - // if after this it is still going and is USD then return it. - if (currency === 'USD') { - return Number(cpm); - } - // otherwise we convert and return - try { - return Number(prebidGlobal.convertCurrency(cpm, currency, 'USD')); - } catch (err) { - logWarn(`${MODULE_NAME}: Could not determine the bidPriceUSD of the bid `, bid); - } -} - -export function parseBidResponse(bid, previousBidResponse) { - // The current bidResponse for this matching requestId/bidRequestId - let responsePrice = getBidPrice(bid) - // we need to compare it with the previous one (if there was one) - if (previousBidResponse && previousBidResponse.bidPriceUSD > responsePrice) { - return previousBidResponse; - } - return pick(bid, [ - 'bidPriceUSD', () => responsePrice, - 'dealId', - 'status', - 'mediaType', - 'dimensions', () => { - const width = bid.width || bid.playerWidth; - const height = bid.height || bid.playerHeight; - return (width && height) ? { width, height } : undefined; - }, - // Handling use case where pbs sends back 0 or '0' bidIds - 'pbsBidId', pbsBidId => pbsBidId == 0 ? generateUUID() : pbsBidId, - 'seatBidId', seatBidId => seatBidId == 0 ? generateUUID() : seatBidId, - 'floorValue', () => deepAccess(bid, 'floorData.floorValue'), - 'floorRuleValue', () => deepAccess(bid, 'floorData.floorRuleValue'), - 'floorRule', () => debugTurnedOn() ? deepAccess(bid, 'floorData.floorRule') : undefined, - 'adomains', () => { - const adomains = deepAccess(bid, 'meta.advertiserDomains'); - const validAdomains = Array.isArray(adomains) && adomains.filter(domain => typeof domain === 'string'); - return validAdomains && validAdomains.length > 0 ? validAdomains.slice(0, 10) : undefined - } - ]); -} - -/* - Filters and converts URL Params into an object and returns only KVs that match the 'utm_KEY' format -*/ -function getUtmParams() { - let search; - - try { - search = parseQS(getWindowLocation().search); - } catch (e) { - search = {}; - } - - return Object.keys(search).reduce((accum, param) => { - if (param.match(/utm_/)) { - accum[param.replace(/utm_/, '')] = search[param]; - } - return accum; - }, {}); -} - -function getFpkvs() { - rubiConf.fpkvs = Object.assign((rubiConf.fpkvs || {}), getUtmParams()); - - // convert all values to strings - Object.keys(rubiConf.fpkvs).forEach(key => { - rubiConf.fpkvs[key] = rubiConf.fpkvs[key] + ''; - }); - - return rubiConf.fpkvs; -} - -let samplingFactor = 1; -let accountId; -// List of known rubicon aliases -// This gets updated on auction init to account for any custom aliases present -let rubiconAliases = ['rubicon']; - -/* - Checks the alias registry for any entries of the rubicon bid adapter. - adds to the rubiconAliases list if found -*/ -function setRubiconAliases(aliasRegistry) { - Object.keys(aliasRegistry).forEach(function (alias) { - if (aliasRegistry[alias] === 'rubicon') { - rubiconAliases.push(alias); - } - }); -} - -function getRpaCookie() { - let encodedCookie = storage.getDataFromLocalStorage(COOKIE_NAME); - if (encodedCookie) { - try { - return JSON.parse(window.atob(encodedCookie)); - } catch (e) { - logError(`${MODULE_NAME}: Unable to decode ${COOKIE_NAME} value: `, e); - } - } - return {}; -} - -function setRpaCookie(decodedCookie) { - try { - storage.setDataInLocalStorage(COOKIE_NAME, window.btoa(JSON.stringify(decodedCookie))); - } catch (e) { - logError(`${MODULE_NAME}: Unable to encode ${COOKIE_NAME} value: `, e); - } -} - -function updateRpaCookie() { - const currentTime = Date.now(); - let decodedRpaCookie = getRpaCookie(); - if ( - !Object.keys(decodedRpaCookie).length || - (currentTime - decodedRpaCookie.lastSeen) > LAST_SEEN_EXPIRE_TIME || - decodedRpaCookie.expires < currentTime - ) { - decodedRpaCookie = { - id: generateUUID(), - start: currentTime, - expires: currentTime + END_EXPIRE_TIME, // six hours later, - } - } - // possible that decodedRpaCookie is undefined, and if it is, we probably are blocked by storage or some other exception - if (Object.keys(decodedRpaCookie).length) { - decodedRpaCookie.lastSeen = currentTime; - decodedRpaCookie.fpkvs = { ...decodedRpaCookie.fpkvs, ...getFpkvs() }; - decodedRpaCookie.pvid = rubiConf.pvid; - setRpaCookie(decodedRpaCookie) - } - return decodedRpaCookie; -} - -function subscribeToGamSlots() { - window.googletag.pubads().addEventListener('slotRenderEnded', event => { - const isMatchingAdSlot = isAdUnitCodeMatchingSlot(event.slot); - // loop through auctions and adUnits and mark the info - // only mark first auction which finds a match - let hasMatch = false; - Object.keys(cache.auctions).find(auctionId => { - (Object.keys(cache.auctions[auctionId].bids) || []).forEach(bidId => { - let bid = cache.auctions[auctionId].bids[bidId]; - // if this slot matches this bids adUnit, add the adUnit info - // only mark it if it already has not been marked - if (!bid.adUnit.gamRendered && isMatchingAdSlot(bid.adUnit.adUnitCode)) { - // mark this adUnit as having been rendered by gam - cache.auctions[auctionId].gamHasRendered[bid.adUnit.adUnitCode] = true; - - // this current auction has an adunit that matched the slot, so mark it as matched so next auciton is skipped - hasMatch = true; - - bid.adUnit.gam = pick(event, [ - // these come in as `null` from Gpt, which when stringified does not get removed - // so set explicitly to undefined when not a number - 'advertiserId', advertiserId => isNumber(advertiserId) ? advertiserId : undefined, - 'creativeId', creativeId => isNumber(event.sourceAgnosticCreativeId) ? event.sourceAgnosticCreativeId : isNumber(creativeId) ? creativeId : undefined, - 'lineItemId', lineItemId => isNumber(event.sourceAgnosticLineItemId) ? event.sourceAgnosticLineItemId : isNumber(lineItemId) ? lineItemId : undefined, - 'adSlot', () => event.slot.getAdUnitPath(), - 'isSlotEmpty', () => event.isEmpty || undefined - ]); - - // this lets us know next iteration not to check this bids adunit - bid.adUnit.gamRendered = true; - } - }); - // Now if all adUnits have gam rendered, send the payload - if (rubiConf.waitForGamSlots && !cache.auctions[auctionId].sent && Object.keys(cache.auctions[auctionId].gamHasRendered).every(adUnitCode => cache.auctions[auctionId].gamHasRendered[adUnitCode])) { - clearTimeout(cache.timeouts[auctionId]); - delete cache.timeouts[auctionId]; - if (rubiConf.analyticsEventDelay > 0) { - setTimeout(() => sendMessage.call(rubiconAdapter, auctionId, undefined, 'delayedGam'), rubiConf.analyticsEventDelay) - } else { - sendMessage.call(rubiconAdapter, auctionId, undefined, 'gam') - } - } - return hasMatch; - }); - }); -} - -let pageReferer; - -const isBillingEventValid = event => { - // vendor is whitelisted - const isWhitelistedVendor = rubiConf.dmBilling.vendors.includes(event.vendor); - // event is not duplicated - const isNotDuplicate = typeof deepAccess(cache.billing, `${event.vendor}.${event.billingId}`) !== 'boolean'; - // billingId is defined and a string - return typeof event.billingId === 'string' && isWhitelistedVendor && isNotDuplicate; -} - -const sendOrAddEventToQueue = event => { - // if any auction is not sent yet, then add it to the auction queue - const pendingAuction = Object.keys(cache.auctions).find(auctionId => !cache.auctions[auctionId].sent); - - if (rubiConf.dmBilling.waitForAuction && pendingAuction) { - cache.auctions[pendingAuction].billing = cache.auctions[pendingAuction].billing || []; - cache.auctions[pendingAuction].billing.push(event); - } else { - // send it - sendBillingEvent(event); - } -} - -let baseAdapter = adapter({ analyticsType: 'endpoint' }); -let rubiconAdapter = Object.assign({}, baseAdapter, { - MODULE_INITIALIZED_TIME: Date.now(), - referrerHostname: '', - enableAnalytics(config = {}) { - let error = false; - samplingFactor = 1; - - if (typeof config.options === 'object') { - if (config.options.accountId) { - accountId = Number(config.options.accountId); - } - if (config.options.endpoint) { - this.getUrl = () => config.options.endpoint; - } else { - logError(`${MODULE_NAME}: required endpoint missing`); - error = true; - } - if (typeof config.options.sampling !== 'undefined') { - samplingFactor = 1 / parseFloat(config.options.sampling); - } - if (typeof config.options.samplingFactor !== 'undefined') { - if (typeof config.options.sampling !== 'undefined') { - logWarn(`${MODULE_NAME}: Both options.samplingFactor and options.sampling enabled defaulting to samplingFactor`); - } - samplingFactor = parseFloat(config.options.samplingFactor); - config.options.sampling = 1 / samplingFactor; - } - } - - let validSamplingFactors = [1, 10, 20, 40, 100]; - if (validSamplingFactors.indexOf(samplingFactor) === -1) { - error = true; - logError(`${MODULE_NAME}: invalid samplingFactor ${samplingFactor} - must be one of ${validSamplingFactors.join(', ')}`); - } else if (!accountId) { - error = true; - logError(`${MODULE_NAME}: required accountId missing for rubicon analytics`); - } - - if (!error) { - baseAdapter.enableAnalytics.call(this, config); - } - }, - disableAnalytics() { - this.getUrl = baseAdapter.getUrl; - accountId = undefined; - rubiConf = {}; - cache.gpt.registered = false; - cache.billing = {}; - baseAdapter.disableAnalytics.apply(this, arguments); - }, - track({ eventType, args }) { - switch (eventType) { - case AUCTION_INIT: - // set the rubicon aliases - setRubiconAliases(adapterManager.aliasRegistry); - let cacheEntry = pick(args, [ - 'timestamp', - 'timeout' - ]); - cacheEntry.bids = {}; - cacheEntry.bidsWon = {}; - cacheEntry.gamHasRendered = {}; - // TODO: is 'page' the right value here? - cacheEntry.referrer = pageReferer = deepAccess(args, 'bidderRequests.0.refererInfo.page'); - cacheEntry.bidderOrder = []; - const floorData = deepAccess(args, 'bidderRequests.0.bids.0.floorData'); - if (floorData) { - cacheEntry.floorData = { ...floorData }; - } - cacheEntry.gdprConsent = deepAccess(args, 'bidderRequests.0.gdprConsent'); - cacheEntry.session = storage.localStorageIsEnabled() && updateRpaCookie(); - cacheEntry.userIds = Object.keys(deepAccess(args, 'bidderRequests.0.bids.0.userId', {})).map(id => { - return { provider: id, hasId: true } - }); - cache.auctions[args.auctionId] = cacheEntry; - // register to listen to gpt events if not done yet - if (!cache.gpt.registered && isGptPubadsDefined()) { - subscribeToGamSlots(); - cache.gpt.registered = true; - } else if (!cache.gpt.registered) { - cache.gpt.registered = true; - window.googletag = window.googletag || {}; - window.googletag.cmd = window.googletag.cmd || []; - window.googletag.cmd.push(function () { - subscribeToGamSlots(); - }); - } - break; - case BID_REQUESTED: - cache.auctions[args.auctionId].bidderOrder.push(args.bidderCode); - Object.assign(cache.auctions[args.auctionId].bids, args.bids.reduce((memo, bid) => { - // mark adUnits we expect bidWon events for - cache.auctions[args.auctionId].bidsWon[bid.adUnitCode] = false; - - if (rubiConf.waitForGamSlots && !adUnitIsOnlyInstream(bid)) { - cache.auctions[args.auctionId].gamHasRendered[bid.adUnitCode] = false; - } - - memo[bid.bidId] = pick(bid, [ - 'bidder', bidder => bidder.toLowerCase(), - 'bidId', - 'status', () => 'no-bid', // default a bid to no-bid until response is recieved or bid is timed out - 'source', () => formatSource(bid.src), - 'params', (params, bid) => { - switch (bid.bidder) { - // specify bidder params we want here - case 'rubicon': - return pick(params, [ - 'accountId', - 'siteId', - 'zoneId' - ]); - } - }, - 'videoAdFormat', (_, cachedBid) => { - if (cachedBid.bidder === 'rubicon') { - return ({ - 201: 'pre-roll', - 202: 'interstitial', - 203: 'outstream', - 204: 'mid-roll', - 205: 'post-roll', - 207: 'vertical' - })[deepAccess(bid, 'params.video.size_id')]; - } else { - let startdelay = parseInt(deepAccess(bid, 'params.video.startdelay'), 10); - if (!isNaN(startdelay)) { - if (startdelay > 0) { - return 'mid-roll'; - } - return ({ - '0': 'pre-roll', - '-1': 'mid-roll', - '-2': 'post-roll' - })[startdelay] - } - } - }, - 'adUnit', () => pick(bid, [ - 'adUnitCode', - 'transactionId', - 'sizes as dimensions', sizes => sizes.map(sizeToDimensions), - 'mediaTypes', (types) => { - if (bid.mediaType && validMediaType(bid.mediaType)) { - return [bid.mediaType]; - } - if (Array.isArray(types)) { - return types.filter(validMediaType); - } - if (typeof types === 'object') { - if (!bid.sizes) { - bid.dimensions = []; - _each(types, (type) => - bid.dimensions = bid.dimensions.concat( - type.sizes.map(sizeToDimensions) - ) - ); - } - return Object.keys(types).filter(validMediaType); - } - return ['banner']; - }, - 'gam', () => { - if (deepAccess(bid, 'ortb2Imp.ext.data.adserver.name') === 'gam') { - return { adSlot: bid.ortb2Imp.ext.data.adserver.adslot } - } - }, - 'pbAdSlot', () => deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), - 'pattern', () => deepAccess(bid, 'ortb2Imp.ext.data.aupname'), - 'gpid', () => deepAccess(bid, 'ortb2Imp.ext.gpid') - ]) - ]); - return memo; - }, {})); - break; - case BID_RESPONSE: - let auctionEntry = cache.auctions[args.auctionId]; - - if (!auctionEntry.bids[args.requestId] && args.originalRequestId) { - auctionEntry.bids[args.requestId] = { ...auctionEntry.bids[args.originalRequestId] }; - auctionEntry.bids[args.requestId].bidId = args.requestId; - auctionEntry.bids[args.requestId].bidderDetail = args.targetingBidder; - } - - let bid = auctionEntry.bids[args.requestId]; - // If floor resolved gptSlot but we have not yet, then update the adUnit to have the adSlot name - if (!deepAccess(bid, 'adUnit.gam.adSlot') && deepAccess(args, 'floorData.matchedFields.gptSlot')) { - deepSetValue(bid, 'adUnit.gam.adSlot', args.floorData.matchedFields.gptSlot); - } - // if we have not set enforcements yet set it - if (!deepAccess(auctionEntry, 'floorData.enforcements') && deepAccess(args, 'floorData.enforcements')) { - auctionEntry.floorData.enforcements = { ...args.floorData.enforcements }; - } - if (!bid) { - logError(`${MODULE_NAME}: Could not find associated bid request for bid response with requestId: `, args.requestId); - break; - } - bid.source = formatSource(bid.source || args.source); - switch (args.getStatusCode()) { - case GOOD: - bid.status = 'success'; - delete bid.error; // it's possible for this to be set by a previous timeout - break; - case NO_BID: - bid.status = args.status === BID_REJECTED ? BID_REJECTED_IPF : 'no-bid'; - delete bid.error; - break; - default: - bid.status = 'error'; - bid.error = { - code: 'request-error' - }; - } - bid.clientLatencyMillis = bid.timeToRespond || Date.now() - cache.auctions[args.auctionId].timestamp; - bid.bidResponse = parseBidResponse(args, bid.bidResponse); - break; - case BIDDER_DONE: - const serverError = deepAccess(args, 'serverErrors.0'); - const serverResponseTimeMs = args.serverResponseTimeMs; - args.bids.forEach(bid => { - let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId || bid.requestId]; - if (typeof bid.serverResponseTimeMs !== 'undefined') { - cachedBid.serverLatencyMillis = bid.serverResponseTimeMs; - } else if (serverResponseTimeMs && bid.source === 's2s') { - cachedBid.serverLatencyMillis = serverResponseTimeMs; - } - // if PBS said we had an error, and this bid has not been processed by BID_RESPONSE YET - if (serverError && (!cachedBid.status || ['no-bid', 'error'].indexOf(cachedBid.status) !== -1)) { - cachedBid.status = 'error'; - cachedBid.error = { - code: pbsErrorMap[serverError.code] || pbsErrorMap[999], - description: serverError.message - } - } - if (!cachedBid.status) { - cachedBid.status = 'no-bid'; - } - if (!cachedBid.clientLatencyMillis) { - cachedBid.clientLatencyMillis = Date.now() - cache.auctions[bid.auctionId].timestamp; - } - }); - break; - case SET_TARGETING: - Object.assign(cache.targeting, args); - break; - case BID_WON: - let auctionCache = cache.auctions[args.auctionId]; - auctionCache.bidsWon[args.adUnitCode] = args.requestId; - - // check if this BID_WON missed the boat, if so send by itself - if (auctionCache.sent === true) { - sendMessage.call(this, args.auctionId, args.requestId, 'soloBidWon'); - } else if (!rubiConf.waitForGamSlots && Object.keys(auctionCache.bidsWon).reduce((memo, adUnitCode) => { - // only send if we've received bidWon events for all adUnits in auction - memo = memo && auctionCache.bidsWon[adUnitCode]; - return memo; - }, true)) { - clearTimeout(cache.timeouts[args.auctionId]); - delete cache.timeouts[args.auctionId]; - - sendMessage.call(this, args.auctionId, undefined, 'allBidWons'); - } - break; - case AUCTION_END: - // see how long it takes for the payload to come fire - let auctionData = cache.auctions[args.auctionId]; - // if for some reason the auction did not do its normal thing, this could be undefied so bail - if (!auctionData) { - break; - } - auctionData.endTs = Date.now(); - - const isOnlyInstreamAuction = args.adUnits && args.adUnits.every(adUnit => adUnitIsOnlyInstream(adUnit)); - // If only instream, do not wait around, just send payload - if (isOnlyInstreamAuction) { - sendMessage.call(this, args.auctionId, undefined, 'instreamAuction'); - } else { - // start timer to send batched payload just in case we don't hear any BID_WON events - cache.timeouts[args.auctionId] = setTimeout(() => { - sendMessage.call(this, args.auctionId, undefined, 'auctionEnd'); - }, rubiConf.analyticsBatchTimeout || SEND_TIMEOUT); - } - break; - case BID_TIMEOUT: - args.forEach(badBid => { - let auctionCache = cache.auctions[badBid.auctionId]; - let bid = auctionCache.bids[badBid.bidId || badBid.requestId]; - // might be set already by bidder-done, so do not overwrite - if (bid.status !== 'error') { - bid.status = 'error'; - bid.error = { - code: 'timeout-error', - description: 'prebid.js timeout' // will help us diff if timeout was set by PBS or PBJS - }; - } - }); - break; - case BILLABLE_EVENT: - if (rubiConf.dmBilling.enabled && isBillingEventValid(args)) { - // add to the map indicating it has not been sent yet - deepSetValue(cache.billing, `${args.vendor}.${args.billingId}`, false); - sendOrAddEventToQueue(args); - } else { - logInfo(`${MODULE_NAME}: Billing event ignored`, args); - } - } - } -}); - -adapterManager.registerAnalyticsAdapter({ - adapter: rubiconAdapter, - code: 'rubicon', - gvlid: RUBICON_GVL_ID -}); - -export default rubiconAdapter; diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js deleted file mode 100644 index 05b6cc6b6f4..00000000000 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ /dev/null @@ -1,2540 +0,0 @@ -import rubiconAnalyticsAdapter, { - SEND_TIMEOUT, - parseBidResponse, - getHostNameFromReferer, - storage, - rubiConf, - resetRubiConf -} from 'modules/rubiconAnalyticsAdapter.js'; -import CONSTANTS from 'src/constants.json'; -import { config } from 'src/config.js'; -import { server } from 'test/mocks/xhr.js'; -import * as mockGpt from '../integration/faker/googletag.js'; -import { - setConfig, - addBidResponseHook, -} from 'modules/currency.js'; - -let Ajv = require('ajv'); -let schema = require('./rubiconAnalyticsSchema.json'); -let ajv = new Ajv({ - allErrors: true -}); - -let validator = ajv.compile(schema); - -function validate(message) { - validator(message); - expect(validator.errors).to.deep.equal(null); -} - -let events = require('src/events.js'); -let utils = require('src/utils.js'); - -const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_RESPONSE, - BIDDER_DONE, - BID_WON, - BID_TIMEOUT, - SET_TARGETING, - BILLABLE_EVENT - } -} = CONSTANTS; - -const BID = { - 'bidder': 'rubicon', - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'statusMessage': 'Bid available', - 'bidId': '2ecff0db240757', - 'adId': 'fake_ad_id', - 'source': 'client', - 'requestId': '2ecff0db240757', - 'currency': 'USD', - 'creativeId': '3571560', - 'cpm': 1.22752, - 'ttl': 300, - 'netRevenue': false, - 'ad': '', - 'rubiconTargeting': { - 'rpfl_elemid': '/19968336/header-bid-tag-0', - 'rpfl_14062': '2_tier0100' - }, - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'responseTimestamp': 1519149629415, - 'requestTimestamp': 1519149628471, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'timeToRespond': 944, - 'pbLg': '1.00', - 'pbMg': '1.20', - 'pbHg': '1.22', - 'pbAg': '1.20', - 'pbDg': '1.22', - 'pbCg': '', - 'size': '640x480', - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '2ecff0db240757', - 'hb_pb': 1.20, - 'hb_size': '640x480', - 'hb_source': 'client' - }, - getStatusCode() { - return 1; - } -}; - -const BID2 = Object.assign({}, BID, { - adUnitCode: '/19968336/header-bid-tag1', - bidId: '3bd4ebb1c900e2', - adId: 'fake_ad_id', - requestId: '3bd4ebb1c900e2', - width: 728, - height: 90, - mediaType: 'banner', - cpm: 1.52, - source: 'server', - seatBidId: 'aaaa-bbbb-cccc-dddd', - rubiconTargeting: { - 'rpfl_elemid': '/19968336/header-bid-tag1', - 'rpfl_14062': '2_tier0100' - }, - adserverTargeting: { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - } -}); - -const BID3 = Object.assign({}, BID, { - adUnitCode: '/19968336/siderail-tag1', - bidId: '5fg6hyy4r879f0', - adId: 'fake_ad_id', - requestId: '5fg6hyy4r879f0', - width: 300, - height: 250, - mediaType: 'banner', - cpm: 2.01, - source: 'server', - seatBidId: 'aaaa-bbbb-cccc-dddd', - rubiconTargeting: { - 'rpfl_elemid': '/19968336/siderail-tag1', - 'rpfl_14062': '15_tier0200' - }, - adserverTargeting: { - 'hb_bidder': 'rubicon', - 'hb_adid': '5fg6hyy4r879f0', - 'hb_pb': '2.00', - 'hb_size': '300x250', - 'hb_source': 'server' - } -}); - -const BID4 = Object.assign({}, BID, { - adUnitCode: '/19968336/header-bid-tag1', - bidId: '3bd4ebb1c900e2', - adId: 'fake_ad_id', - requestId: '3bd4ebb1c900e2', - width: 728, - height: 90, - mediaType: 'banner', - cpm: 1.52, - source: 'server', - pbsBidId: 'zzzz-yyyy-xxxx-wwww', - seatBidId: 'aaaa-bbbb-cccc-dddd', - rubiconTargeting: { - 'rpfl_elemid': '/19968336/header-bid-tag1', - 'rpfl_14062': '2_tier0100' - }, - adserverTargeting: { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - } -}); - -const floorMinRequest = { - 'bidder': 'rubicon', - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918', - 'userId': '12346', - 'keywords': ['a', 'b', 'c'], - 'inventory': { 'rating': '4-star', 'prodtype': 'tech' }, - 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, - 'position': 'atf' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'adUnitCode': '/19968336/siderail-tag1', - 'transactionId': 'c435626g-9e3f-401a-bee1-d56aec29a1d4', - 'sizes': [[300, 250]], - 'bidId': '5fg6hyy4r879f0', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' -}; - -const MOCK = { - SET_TARGETING: { - [BID.adUnitCode]: BID.adserverTargeting, - [BID2.adUnitCode]: BID2.adserverTargeting, - [BID3.adUnitCode]: BID3.adserverTargeting - }, - AUCTION_INIT: { - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'timestamp': 1519767010567, - 'auctionStatus': 'inProgress', - 'adUnits': [{ - 'code': '/19968336/header-bid-tag1', - 'sizes': [[640, 480]], - 'bids': [{ - 'bidder': 'rubicon', - 'params': { - 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 - } - }], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' - } - ], - 'adUnitCodes': ['/19968336/header-bid-tag1'], - 'bidderRequests': [{ - 'bidderCode': 'rubicon', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'bidderRequestId': '1be65d7958826a', - 'bids': [{ - 'bidder': 'rubicon', - 'params': { - 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] - } - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 'client', - 'bidRequestsCount': 1 - } - ], - 'timeout': 3000, - 'refererInfo': { - 'page': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - } - } - ], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 3000, - 'config': { - 'accountId': 1001, 'endpoint': '//localhost:9999/event' - } - }, - BID_REQUESTED: { - 'bidderCode': 'rubicon', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'bidderRequestId': '1be65d7958826a', - 'bids': [ - { - 'bidder': 'rubicon', - 'params': { - 'accountId': '1001', - 'siteId': '70608', - 'zoneId': '335918', - 'userId': '12346', - 'keywords': ['a', 'b', 'c'], - 'inventory': 'test', - 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, - 'position': 'btf', - 'video': { - 'language': 'en', - 'playerHeight': 480, - 'playerWidth': 640, - 'size_id': 203, - 'skip': 1, - 'skipdelay': 15, - 'aeParams': { - 'p_aso.video.ext.skip': '1', - 'p_aso.video.ext.skipdelay': '15' - } - } - }, - 'mediaTypes': { - 'video': { - 'context': 'instream', - 'playerSize': [640, 480] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 'client' - }, - { - 'bidder': 'rubicon', - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918', - 'userId': '12346', - 'keywords': ['a', 'b', 'c'], - 'inventory': { 'rating': '4-star', 'prodtype': 'tech' }, - 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, - 'position': 'atf' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[1000, 300], [970, 250], [728, 90]] - } - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', - 'sizes': [[1000, 300], [970, 250], [728, 90]], - 'bidId': '3bd4ebb1c900e2', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 's2s' - } - ], - 'auctionStart': 1519149536560, - 'timeout': 5000, - 'start': 1519149562216, - 'refererInfo': { - 'page': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - } - }, - BID_RESPONSE: [ - BID, - BID2, - BID3 - ], - AUCTION_END: { - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' - }, - BID_WON: [ - Object.assign({}, BID, { - 'status': 'rendered' - }), - Object.assign({}, BID2, { - 'status': 'rendered' - }), - Object.assign({}, BID3, { - 'status': 'rendered' - }) - ], - BIDDER_DONE: { - 'bidderCode': 'rubicon', - 'serverResponseTimeMs': 42, - 'bids': [ - BID, - Object.assign({}, BID2, { - 'serverResponseTimeMs': 42, - }), - Object.assign({}, BID3, { - 'serverResponseTimeMs': 55, - }) - ] - }, - BID_TIMEOUT: [ - { - 'bidId': '2ecff0db240757', - 'bidder': 'rubicon', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' - } - ] -}; - -const STUBBED_UUID = '12345678-1234-1234-1234-123456789abc'; - -const ANALYTICS_MESSAGE = { - 'channel': 'web', - 'integration': 'pbjs', - 'version': '$prebid.version$', - 'referrerUri': 'http://www.test.com/page.html', - 'session': { - 'expires': 1519788613781, - 'id': STUBBED_UUID, - 'start': 1519767013781 - }, - 'timestamps': { - 'auctionEnded': 1519767013781, - 'eventTime': 1519767013781, - 'prebidLoaded': rubiconAnalyticsAdapter.MODULE_INITIALIZED_TIME - }, - 'trigger': 'allBidWons', - 'referrerHostname': 'www.test.com', - 'auctions': [ - { - - 'auctionEnd': 1519767013781, - 'auctionStart': 1519767010567, - 'bidderOrder': ['rubicon'], - 'requestId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'clientTimeoutMillis': 3000, - 'serverTimeoutMillis': 1000, - 'accountId': 1001, - 'samplingFactor': 1, - 'adUnits': [ - { - 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'videoAdFormat': 'outstream', - 'mediaTypes': [ - 'video' - ], - 'dimensions': [ - { - 'width': 640, - 'height': 480 - } - ], - 'status': 'success', - 'accountId': 1001, - 'siteId': 70608, - 'zoneId': 335918, - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '2ecff0db240757', - 'hb_pb': '1.200', - 'hb_size': '640x480', - 'hb_source': 'client' - }, - 'bids': [ - { - 'bidder': 'rubicon', - 'bidId': '2ecff0db240757', - 'status': 'success', - 'source': 'client', - 'clientLatencyMillis': 3214, - 'params': { - 'accountId': '1001', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'bidResponse': { - 'bidPriceUSD': 1.22752, - 'dimensions': { - 'width': 640, - 'height': 480 - }, - 'mediaType': 'video' - } - } - ] - }, - { - 'adUnitCode': '/19968336/header-bid-tag1', - 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', - 'mediaTypes': [ - 'banner' - ], - 'dimensions': [ - { - 'width': 1000, - 'height': 300 - }, - { - 'width': 970, - 'height': 250 - }, - { - 'width': 728, - 'height': 90 - } - ], - 'status': 'success', - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - }, - 'bids': [ - { - 'bidder': 'rubicon', - 'bidId': 'aaaa-bbbb-cccc-dddd', - 'status': 'success', - 'source': 'server', - 'clientLatencyMillis': 3214, - 'serverLatencyMillis': 42, - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'bidResponse': { - 'bidPriceUSD': 1.52, - 'dimensions': { - 'width': 728, - 'height': 90 - }, - 'mediaType': 'banner' - } - } - ] - } - ] - } - ], - 'bidsWon': [ - { - 'bidder': 'rubicon', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'bidId': '2ecff0db240757', - 'status': 'success', - 'source': 'client', - 'clientLatencyMillis': 3214, - 'samplingFactor': 1, - 'accountId': 1001, - 'siteId': 70608, - 'zoneId': 335918, - 'params': { - 'accountId': '1001', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'videoAdFormat': 'outstream', - 'mediaTypes': [ - 'video' - ], - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '2ecff0db240757', - 'hb_pb': '1.200', - 'hb_size': '640x480', - 'hb_source': 'client' - }, - 'bidResponse': { - 'bidPriceUSD': 1.22752, - 'dimensions': { - 'width': 640, - 'height': 480 - }, - 'mediaType': 'video' - }, - 'bidwonStatus': 'success' - }, - { - 'bidder': 'rubicon', - 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', - 'adUnitCode': '/19968336/header-bid-tag1', - 'bidId': 'aaaa-bbbb-cccc-dddd', - 'status': 'success', - 'source': 'server', - 'clientLatencyMillis': 3214, - 'serverLatencyMillis': 42, - 'samplingFactor': 1, - 'accountId': 1001, - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'mediaTypes': [ - 'banner' - ], - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - }, - 'bidResponse': { - 'bidPriceUSD': 1.52, - 'dimensions': { - 'width': 728, - 'height': 90 - }, - 'mediaType': 'banner' - }, - 'bidwonStatus': 'success' - } - ], - 'wrapper': { - 'name': '10000_fakewrapper_test' - } -}; - -function performStandardAuction(gptEvents, auctionId = MOCK.AUCTION_INIT.auctionId) { - events.emit(AUCTION_INIT, { ...MOCK.AUCTION_INIT, auctionId }); - events.emit(BID_REQUESTED, { ...MOCK.BID_REQUESTED, auctionId }); - events.emit(BID_RESPONSE, { ...MOCK.BID_RESPONSE[0], auctionId }); - events.emit(BID_RESPONSE, { ...MOCK.BID_RESPONSE[1], auctionId }); - events.emit(BIDDER_DONE, { ...MOCK.BIDDER_DONE, auctionId }); - events.emit(AUCTION_END, { ...MOCK.AUCTION_END, auctionId }); - - if (gptEvents && gptEvents.length) { - gptEvents.forEach(gptEvent => mockGpt.emitEvent(gptEvent.eventName, gptEvent.params)); - } - - events.emit(SET_TARGETING, { ...MOCK.SET_TARGETING, auctionId }); - events.emit(BID_WON, { ...MOCK.BID_WON[0], auctionId }); - events.emit(BID_WON, { ...MOCK.BID_WON[1], auctionId }); -} - -describe('rubicon analytics adapter', function () { - let sandbox; - let clock; - let getDataFromLocalStorageStub, setDataInLocalStorageStub, localStorageIsEnabledStub; - beforeEach(function () { - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); - localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); - mockGpt.disable(); - sandbox = sinon.sandbox.create(); - - localStorageIsEnabledStub.returns(true); - - sandbox.stub(events, 'getEvents').returns([]); - - sandbox.stub(utils, 'generateUUID').returns(STUBBED_UUID); - - clock = sandbox.useFakeTimers(1519767013781); - - rubiconAnalyticsAdapter.referrerHostname = ''; - - config.setConfig({ - s2sConfig: { - timeout: 1000, - accountId: 10000, - }, - rubicon: { - wrapperName: '10000_fakewrapper_test' - } - }) - }); - - afterEach(function () { - sandbox.restore(); - config.resetConfig(); - mockGpt.enable(); - getDataFromLocalStorageStub.restore(); - setDataInLocalStorageStub.restore(); - localStorageIsEnabledStub.restore(); - }); - - it('should require accountId', function () { - sandbox.stub(utils, 'logError'); - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event' - } - }); - - expect(utils.logError.called).to.equal(true); - }); - - it('should require endpoint', function () { - sandbox.stub(utils, 'logError'); - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - accountId: 1001 - } - }); - - expect(utils.logError.called).to.equal(true); - }); - - describe('config subscribe', function () { - it('should update the pvid if user asks', function () { - expect(utils.generateUUID.called).to.equal(false); - config.setConfig({ rubicon: { updatePageView: true } }); - expect(utils.generateUUID.called).to.equal(true); - }); - it('should merge in and preserve older set configs', function () { - resetRubiConf(); - config.setConfig({ - rubicon: { - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - source: 'fb' - } - } - }); - expect(rubiConf).to.deep.equal({ - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - }, - pvid: '12345678', - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - source: 'fb' - }, - }); - - // update it with stuff - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' - } - } - }); - expect(rubiConf).to.deep.equal({ - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - }, - pvid: '12345678', - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - source: 'fb', - link: 'email' - }, - }); - - // overwriting specific edge keys should update them - config.setConfig({ - rubicon: { - fpkvs: { - link: 'iMessage', - source: 'twitter' - } - } - }); - expect(rubiConf).to.deep.equal({ - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - }, - pvid: '12345678', - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - link: 'iMessage', - source: 'twitter' - }, - }); - }); - }); - - describe('sampling', function () { - beforeEach(function () { - sandbox.stub(Math, 'random').returns(0.08); - sandbox.stub(utils, 'logError'); - }); - - afterEach(function () { - rubiconAnalyticsAdapter.disableAnalytics(); - }); - - describe('with options.samplingFactor', function () { - it('should sample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - samplingFactor: 10 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - }); - - it('should unsample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - samplingFactor: 20 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - }); - - it('should throw errors for invalid samplingFactor', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - samplingFactor: 30 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - expect(utils.logError.called).to.equal(true); - }); - }); - describe('with options.sampling', function () { - it('should sample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - sampling: 0.1 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - }); - - it('should unsample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - sampling: 0.05 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - }); - - it('should throw errors for invalid samplingFactor', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - sampling: 1 / 30 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - expect(utils.logError.called).to.equal(true); - }); - }); - }); - - describe('when handling events', function () { - beforeEach(function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - }); - - afterEach(function () { - rubiconAnalyticsAdapter.disableAnalytics(); - }); - - it('should build a batched message from prebid events', function () { - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - expect(request.url).to.equal('//localhost:9999/event'); - - let message = JSON.parse(request.requestBody); - validate(message); - - expect(message).to.deep.equal(ANALYTICS_MESSAGE); - }); - - it('should pass along bidderOrder correctly', function () { - const appnexusBid = utils.deepClone(MOCK.BID_REQUESTED); - appnexusBid.bidderCode = 'appnexus'; - const pubmaticBid = utils.deepClone(MOCK.BID_REQUESTED); - pubmaticBid.bidderCode = 'pubmatic'; - const indexBid = utils.deepClone(MOCK.BID_REQUESTED); - indexBid.bidderCode = 'ix'; - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, pubmaticBid); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_REQUESTED, indexBid); - events.emit(BID_REQUESTED, appnexusBid); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - clock.tick(SEND_TIMEOUT + 1000); - - let message = JSON.parse(server.requests[0].requestBody); - expect(message.auctions[0].bidderOrder).to.deep.equal([ - 'pubmatic', - 'rubicon', - 'ix', - 'appnexus' - ]); - }); - - it('should pass along user ids', function () { - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.bidderRequests[0].bids[0].userId = { - criteoId: 'sadfe4334', - lotamePanoramaId: 'asdf3gf4eg', - pubcid: 'dsfa4545-svgdfs5', - sharedId: { id1: 'asdf', id2: 'sadf4344' } - }; - - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - let message = JSON.parse(request.requestBody); - validate(message); - - expect(message.auctions[0].user).to.deep.equal({ - ids: [ - { provider: 'criteoId', 'hasId': true }, - { provider: 'lotamePanoramaId', 'hasId': true }, - { provider: 'pubcid', 'hasId': true }, - { provider: 'sharedId', 'hasId': true }, - ] - }); - }); - - it('should handle bidResponse dimensions correctly', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // mock bid response with playerWidth and playerHeight (NO width and height) - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - delete bidResponse1.width; - delete bidResponse1.height; - bidResponse1.playerWidth = 640; - bidResponse1.playerHeight = 480; - - // mock bid response with no width height or playerwidth playerheight - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - delete bidResponse2.width; - delete bidResponse2.height; - delete bidResponse2.playerWidth; - delete bidResponse2.playerHeight; - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.dimensions).to.deep.equal({ - width: 640, - height: 480 - }); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.dimensions).to.equal(undefined); - }); - - it('should pass along adomians correctly', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // 1 adomains - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - bidResponse1.meta = { - advertiserDomains: ['magnite.com'] - } - - // two adomains - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse2.meta = { - advertiserDomains: ['prebid.org', 'magnite.com'] - } - - // make sure we only pass max 10 adomains - bidResponse2.meta.advertiserDomains = [...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains] - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['magnite.com']); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com']); - }); - - it('should NOT pass along adomians correctly when edge cases', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // empty => nothing - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - bidResponse1.meta = { - advertiserDomains: [] - } - - // not array => nothing - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse2.meta = { - advertiserDomains: 'prebid.org' - } - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.be.undefined; - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; - }); - - it('should NOT pass along adomians with other edge cases', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // should filter out non string values and pass valid ones - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - bidResponse1.meta = { - advertiserDomains: [123, 'prebid.org', false, true, [], 'magnite.com', {}] - } - - // array of arrays (as seen when passed by kargo bid adapter) - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse2.meta = { - advertiserDomains: [['prebid.org']] - } - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com']); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; - }); - - it('should not pass empty adServerTargeting values', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - - const mockTargeting = utils.deepClone(MOCK.SET_TARGETING); - mockTargeting['/19968336/header-bid-tag-0'].hb_test = ''; - mockTargeting['/19968336/header-bid-tag1'].hb_test = 'NOT_EMPTY'; - events.emit(SET_TARGETING, mockTargeting); - - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].adserverTargeting.hb_test).to.be.undefined; - expect(message.auctions[0].adUnits[1].adserverTargeting.hb_test).to.equal('NOT_EMPTY'); - }); - - function performFloorAuction(provider) { - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.bidderRequests[0].bids[0].floorData = { - skipped: false, - modelVersion: 'someModelName', - modelWeight: 10, - modelTimestamp: 1606772895, - location: 'setConfig', - skipRate: 15, - fetchStatus: 'error', - floorProvider: provider - }; - let flooredResponse = { - ...BID, - floorData: { - floorValue: 4, - floorRule: '12345/sports|video', - floorCurrency: 'USD', - cpmAfterAdjustments: 2.1, - enforcements: { - enforceJS: true, - enforcePBS: false, - floorDeals: false, - bidAdjustment: true - }, - matchedFields: { - gptSlot: '12345/sports', - mediaType: 'video' - } - }, - status: 'bidRejected', - cpm: 0, - getStatusCode() { - return 2; - } - }; - - let notFlooredResponse = { - ...BID2, - floorData: { - floorValue: 1, - floorRule: '12345/news|banner', - floorCurrency: 'USD', - cpmAfterAdjustments: 1.55, - enforcements: { - enforceJS: true, - enforcePBS: false, - floorDeals: false, - bidAdjustment: true - }, - matchedFields: { - gptSlot: '12345/news', - mediaType: 'banner' - } - } - }; - - let floorMinResponse = { - ...BID3, - floorData: { - floorValue: 1.5, - floorRuleValue: 1, - floorRule: '12345/entertainment|banner', - floorCurrency: 'USD', - cpmAfterAdjustments: 2.00, - enforcements: { - enforceJS: true, - enforcePBS: false, - floorDeals: false, - bidAdjustment: true - }, - matchedFields: { - gptSlot: '12345/entertainment', - mediaType: 'banner' - } - } - }; - - let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids.push(floorMinRequest) - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequest); - events.emit(BID_RESPONSE, flooredResponse); - events.emit(BID_RESPONSE, notFlooredResponse); - events.emit(BID_RESPONSE, floorMinResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[1]); - events.emit(BID_WON, MOCK.BID_WON[2]); - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - return message; - } - - it('should capture price floor information correctly', function () { - let message = performFloorAuction('rubicon'); - - // verify our floor stuff is passed - // top level floor info - expect(message.auctions[0].floors).to.deep.equal({ - location: 'setConfig', - modelName: 'someModelName', - modelWeight: 10, - modelTimestamp: 1606772895, - skipped: false, - enforcement: true, - dealsEnforced: false, - skipRate: 15, - fetchStatus: 'error', - provider: 'rubicon' - }); - // first adUnit's adSlot - expect(message.auctions[0].adUnits[0].gam.adSlot).to.equal('12345/sports'); - // since no other bids, we set adUnit status to no-bid - expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); - // first adUnits bid is rejected - expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected-ipf'); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); - // if bid rejected should take cpmAfterAdjustments val - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[1].gam.adSlot).to.equal('12345/news'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[1].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[2].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); - }); - - it('should still send floor info if provider is not rubicon', function () { - let message = performFloorAuction('randomProvider'); - - // verify our floor stuff is passed - // top level floor info - expect(message.auctions[0].floors).to.deep.equal({ - location: 'setConfig', - modelName: 'someModelName', - modelWeight: 10, - modelTimestamp: 1606772895, - skipped: false, - enforcement: true, - dealsEnforced: false, - skipRate: 15, - fetchStatus: 'error', - provider: 'randomProvider' - }); - // first adUnit's adSlot - expect(message.auctions[0].adUnits[0].gam.adSlot).to.equal('12345/sports'); - // since no other bids, we set adUnit status to no-bid - expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); - // first adUnits bid is rejected - expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected-ipf'); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); - // if bid rejected should take cpmAfterAdjustments val - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[1].gam.adSlot).to.equal('12345/news'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[1].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[2].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); - }); - - describe('with session handling', function () { - const expectedPvid = STUBBED_UUID.slice(0, 8); - beforeEach(function () { - config.setConfig({ rubicon: { updatePageView: true } }); - }); - - it('should not log any session data if local storage is not enabled', function () { - localStorageIsEnabledStub.returns(false); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - delete expectedMessage.session; - delete expectedMessage.fpkvs; - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - expect(request.url).to.equal('//localhost:9999/event'); - - let message = JSON.parse(request.requestBody); - validate(message); - - expect(message).to.deep.equal(expectedMessage); - }); - - it('should should pass along custom rubicon kv and pvid when defined', function () { - config.setConfig({ - rubicon: { - fpkvs: { - source: 'fb', - link: 'email' - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); - expectedMessage.fpkvs = [ - { key: 'source', value: 'fb' }, - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - }); - - it('should convert kvs to strings before sending', function () { - config.setConfig({ - rubicon: { - fpkvs: { - number: 24, - boolean: false, - string: 'hello', - array: ['one', 2, 'three'], - object: { one: 'two' } - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); - expectedMessage.fpkvs = [ - { key: 'number', value: '24' }, - { key: 'boolean', value: 'false' }, - { key: 'string', value: 'hello' }, - { key: 'array', value: 'one,2,three' }, - { key: 'object', value: '[object Object]' } - ] - expect(message).to.deep.equal(expectedMessage); - }); - - it('should use the query utm param rubicon kv value and pass updated kv and pvid when defined', function () { - sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=other', 'pbjs_debug': 'true' }); - - config.setConfig({ - rubicon: { - fpkvs: { - source: 'fb', - link: 'email' - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); - expectedMessage.fpkvs = [ - { key: 'source', value: 'other' }, - { key: 'link', value: 'email' } - ] - - message.fpkvs.sort((left, right) => left.key < right.key); - expectedMessage.fpkvs.sort((left, right) => left.key < right.key); - - expect(message).to.deep.equal(expectedMessage); - }); - - it('should pick up existing localStorage and use its values', function () { - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519766113781, // 15 mins before "now" - expires: 1519787713781, // six hours later - lastSeen: 1519766113781, - fpkvs: { source: 'tw' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session = { - id: '987654', - start: 1519766113781, - expires: 1519787713781, - pvid: expectedPvid - } - expectedMessage.fpkvs = [ - { key: 'source', value: 'tw' }, - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: '987654', // should have stayed same - start: 1519766113781, // should have stayed same - expires: 1519787713781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { source: 'tw', link: 'email' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - - it('should overwrite matching localstorge value and use its remaining values', function () { - sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=fb&utm_click=dog' }); - - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519766113781, // 15 mins before "now" - expires: 1519787713781, // six hours later - lastSeen: 1519766113781, - fpkvs: { source: 'tw', link: 'email' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session = { - id: '987654', - start: 1519766113781, - expires: 1519787713781, - pvid: expectedPvid - } - expectedMessage.fpkvs = [ - { key: 'source', value: 'fb' }, - { key: 'link', value: 'email' }, - { key: 'click', value: 'dog' } - ] - - message.fpkvs.sort((left, right) => left.key < right.key); - expectedMessage.fpkvs.sort((left, right) => left.key < right.key); - - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: '987654', // should have stayed same - start: 1519766113781, // should have stayed same - expires: 1519787713781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { source: 'fb', link: 'email', click: 'dog' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - - it('should throw out session if lastSeen > 30 mins ago and create new one', function () { - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519764313781, // 45 mins before "now" - expires: 1519785913781, // six hours later - lastSeen: 1519764313781, // 45 mins before "now" - fpkvs: { source: 'tw' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid - expectedMessage.session.pvid = expectedPvid; - - // the saved fpkvs should have been thrown out since session expired - expectedMessage.fpkvs = [ - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: STUBBED_UUID, // should have stayed same - start: 1519767013781, // should have stayed same - expires: 1519788613781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { link: 'email' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - - it('should throw out session if past expires time and create new one', function () { - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519745353781, // 6 hours before "expires" - expires: 1519766953781, // little more than six hours ago - lastSeen: 1519767008781, // 5 seconds ago - fpkvs: { source: 'tw' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid - expectedMessage.session.pvid = expectedPvid; - - // the saved fpkvs should have been thrown out since session expired - expectedMessage.fpkvs = [ - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: STUBBED_UUID, // should have stayed same - start: 1519767013781, // should have stayed same - expires: 1519788613781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { link: 'email' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - }); - describe('with googletag enabled', function () { - let gptSlot0, gptSlot1; - let gptSlotRenderEnded0, gptSlotRenderEnded1; - beforeEach(function () { - mockGpt.enable(); - gptSlot0 = mockGpt.makeSlot({ code: '/19968336/header-bid-tag-0' }); - gptSlotRenderEnded0 = { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot0, - isEmpty: false, - advertiserId: 1111, - sourceAgnosticCreativeId: 2222, - sourceAgnosticLineItemId: 3333 - } - }; - - gptSlot1 = mockGpt.makeSlot({ code: '/19968336/header-bid-tag1' }); - gptSlotRenderEnded1 = { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 4444, - sourceAgnosticCreativeId: 5555, - sourceAgnosticLineItemId: 6666 - } - }; - }); - - afterEach(function () { - mockGpt.disable(); - }); - - it('should add necessary gam information if gpt is enabled and slotRender event emmited', function () { - performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should handle empty gam renders', function () { - performStandardAuction([gptSlotRenderEnded0, { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: true - } - }]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - isSlotEmpty: true, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should still add gam ids if falsy', function () { - performStandardAuction([gptSlotRenderEnded0, { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 0, - sourceAgnosticCreativeId: 0, - sourceAgnosticLineItemId: 0 - } - }]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 0, - creativeId: 0, - lineItemId: 0, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should pick backup Ids if no sourceAgnostic available first', function () { - performStandardAuction([gptSlotRenderEnded0, { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 0, - lineItemId: 1234, - creativeId: 5678 - } - }]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 0, - creativeId: 5678, - lineItemId: 1234, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should correctly set adUnit for associated slots', function () { - performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should only mark the first gam data not all matches', function () { - config.setConfig({ - rubicon: { - waitForGamSlots: true - } - }); - performStandardAuction(); - performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1], '32d332de-123a-32dg-2345-cefef3423324'); - - // tick the clock and both should fire - clock.tick(3000); - - expect(server.requests.length).to.equal(2); - - // first one should have GAM data - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - - // trigger should be gam since all adunits had associated gam render - expect(message.trigger).to.be.equal('gam'); - expect(message.auctions[0].adUnits[0].gam).to.deep.equal({ - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }); - expect(message.auctions[0].adUnits[1].gam).to.deep.equal({ - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }); - - // second one should NOT have gam data - request = server.requests[1]; - message = JSON.parse(request.requestBody); - validate(message); - - // trigger should be auctionEnd - expect(message.trigger).to.be.equal('auctionEnd'); - expect(message.auctions[0].adUnits[0].gam).to.be.undefined; - expect(message.auctions[0].adUnits[1].gam).to.be.undefined; - }); - - it('should send request when waitForGamSlots is present but no bidWons are sent', function () { - config.setConfig({ - rubicon: { - waitForGamSlots: true, - } - }); - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - // should send if just slotRenderEnded is emmitted for both - mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); - mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - delete expectedMessage.bidsWon; // should not be any of these - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expectedMessage.trigger = 'gam'; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should delay the event call depending on analyticsEventDelay config', function () { - config.setConfig({ - rubicon: { - waitForGamSlots: true, - analyticsEventDelay: 2000 - } - }); - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - // should send if just slotRenderEnded is emmitted for both - mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); - mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); - - // Should not be sent until delay - expect(server.requests.length).to.equal(0); - - // tick the clock and it should fire - clock.tick(2000); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - let expectedGam0 = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - let expectedGam1 = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expect(expectedGam0).to.deep.equal(message.auctions[0].adUnits[0].gam); - expect(expectedGam1).to.deep.equal(message.auctions[0].adUnits[1].gam); - }); - }); - - it('should correctly overwrite bidId if seatBidId is on the bidResponse', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let seatBidResponse = utils.deepClone(BID2); - seatBidResponse.seatBidId = 'abc-123-do-re-me'; - - const setTargeting = { - [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting - }; - - const bidWon = Object.assign({}, seatBidResponse, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, seatBidResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('abc-123-do-re-me'); - expect(message.bidsWon[0].bidId).to.equal('abc-123-do-re-me'); - }); - - it('should correctly overwrite bidId if pbsBidId is on the bidResponse', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let seatBidResponse = utils.deepClone(BID4); - - const setTargeting = { - [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting - }; - - const bidWon = Object.assign({}, seatBidResponse, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, seatBidResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); - expect(message.bidsWon[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); - }); - - it('should correctly generate new bidId if it is 0', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let seatBidResponse = utils.deepClone(BID4); - seatBidResponse.pbsBidId = '0'; - - const setTargeting = { - [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting - }; - - const bidWon = Object.assign({}, seatBidResponse, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, seatBidResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal(STUBBED_UUID); - expect(message.bidsWon[0].bidId).to.equal(STUBBED_UUID); - }); - - it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let duplicateResponse1 = utils.deepClone(BID2); - duplicateResponse1.cpm = 1.0; - duplicateResponse1.adserverTargeting.hb_pb = '1.0'; - duplicateResponse1.adserverTargeting.hb_adid = '1111'; - let duplicateResponse2 = utils.deepClone(BID2); - duplicateResponse2.cpm = 5.5; - duplicateResponse2.adserverTargeting.hb_pb = '5.5'; - duplicateResponse2.adserverTargeting.hb_adid = '5555'; - let duplicateResponse3 = utils.deepClone(BID2); - duplicateResponse3.cpm = 0.1; - duplicateResponse3.adserverTargeting.hb_pb = '0.1'; - duplicateResponse3.adserverTargeting.hb_adid = '3333'; - - const setTargeting = { - [duplicateResponse2.adUnitCode]: duplicateResponse2.adserverTargeting - }; - - const bidWon = Object.assign({}, duplicateResponse2, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, duplicateResponse1); - events.emit(BID_RESPONSE, duplicateResponse2); - events.emit(BID_RESPONSE, duplicateResponse3); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(5.5); - expect(message.auctions[0].adUnits[0].adserverTargeting.hb_pb).to.equal('5.5'); - expect(message.auctions[0].adUnits[0].adserverTargeting.hb_adid).to.equal('5555'); - expect(message.bidsWon.length).to.equal(1); - expect(message.bidsWon[0].bidResponse.bidPriceUSD).to.equal(5.5); - expect(message.bidsWon[0].adserverTargeting.hb_pb).to.equal('5.5'); - expect(message.bidsWon[0].adserverTargeting.hb_adid).to.equal('5555'); - }); - - it('should send batched message without BID_WON if necessary and further BID_WON events individually', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - - clock.tick(SEND_TIMEOUT + 1000); - - events.emit(BID_WON, MOCK.BID_WON[1]); - - expect(server.requests.length).to.equal(2); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.bidsWon.length).to.equal(1); - expect(message.auctions).to.deep.equal(ANALYTICS_MESSAGE.auctions); - expect(message.bidsWon[0]).to.deep.equal(ANALYTICS_MESSAGE.bidsWon[0]); - - message = JSON.parse(server.requests[1].requestBody); - validate(message); - expect(message.bidsWon.length).to.equal(1); - expect(message).to.not.have.property('auctions'); - expect(message.bidsWon[0]).to.deep.equal(ANALYTICS_MESSAGE.bidsWon[1]); - }); - - it('should properly mark bids as timed out', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); - events.emit(AUCTION_END, MOCK.AUCTION_END); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - let timedOutBid = message.auctions[0].adUnits[0].bids[0]; - expect(timedOutBid.status).to.equal('error'); - expect(timedOutBid.error.code).to.equal('timeout-error'); - expect(timedOutBid.error.description).to.equal('prebid.js timeout'); - expect(timedOutBid).to.not.have.property('bidResponse'); - }); - - it('should pass aupName as pattern', function () { - let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids[0].ortb2Imp = { - ext: { - data: { - aupname: '1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile' - } - } - }; - bidRequest.bids[1].ortb2Imp = { - ext: { - data: { - aupname: '1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile' - } - } - }; - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, bidRequest); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].pattern).to.equal('1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile'); - expect(message.auctions[0].adUnits[1].pattern).to.equal('1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile'); - }); - - it('should pass gpid if defined', function () { - let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids[0].ortb2Imp = { - ext: { - gpid: '1234/mycoolsite/lowerbox' - } - }; - bidRequest.bids[1].ortb2Imp = { - ext: { - gpid: '1234/mycoolsite/leaderboard' - } - }; - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, bidRequest); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].gpid).to.equal('1234/mycoolsite/lowerbox'); - expect(message.auctions[0].adUnits[1].gpid).to.equal('1234/mycoolsite/leaderboard'); - }); - - it('should pass bidderDetail for multibid auctions', function () { - let bidResponse = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse.targetingBidder = 'rubi2'; - bidResponse.originalRequestId = bidResponse.requestId; - bidResponse.requestId = '1a2b3c4d5e6f7g8h9'; - - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, bidResponse); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - - expect(message.auctions[0].adUnits[1].bids[1].bidder).to.equal('rubicon'); - expect(message.auctions[0].adUnits[1].bids[1].bidderDetail).to.equal('rubi2'); - }); - - it('should successfully convert bid price to USD in parseBidResponse', function () { - // Set the rates - setConfig({ - adServerCurrency: 'JPY', - rates: { - USD: { - JPY: 100 - } - } - }); - - // set our bid response to JPY - const bidCopy = utils.deepClone(BID2); - bidCopy.currency = 'JPY'; - bidCopy.cpm = 100; - - // Now add the bidResponse hook which hooks on the currenct conversion function onto the bid response - let innerBid; - addBidResponseHook(function (adCodeId, bid) { - innerBid = bid; - }, 'elementId', bidCopy); - - // Use the rubi analytics parseBidResponse Function to get the resulting cpm from the bid response! - const bidResponseObj = parseBidResponse(innerBid); - expect(bidResponseObj).to.have.property('bidPriceUSD'); - expect(bidResponseObj.bidPriceUSD).to.equal(1.0); - }); - }); - - describe('config with integration type', () => { - it('should use the integration type provided in the config instead of the default', () => { - config.setConfig({ - rubicon: { - int_type: 'testType' - } - }) - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.integration).to.equal('testType'); - - rubiconAnalyticsAdapter.disableAnalytics(); - }); - }); - - describe('billing events integration', () => { - beforeEach(function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - // default dmBilling - config.setConfig({ - rubicon: { - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - } - } - }) - }); - afterEach(function () { - rubiconAnalyticsAdapter.disableAnalytics(); - }); - const basicBillingAuction = (billingEvents = []) => { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // emit billing events - billingEvents.forEach(ev => events.emit(BILLABLE_EVENT, ev)); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - } - it('should ignore billing events when not enabled', () => { - basicBillingAuction([{ - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.billableEvents).to.be.undefined; - }); - it('should ignore billing events when enabled but vendor is not whitelisted', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true - } - } - }); - basicBillingAuction([{ - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.billableEvents).to.be.undefined; - }); - it('should ignore billing events if billingId is not defined or billingId is not a string', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - basicBillingAuction([ - { - vendor: 'vendorName', - type: 'auction', - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: true - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: 1233434 - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: null - } - ]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.billableEvents).to.be.undefined; - }); - it('should pass along billing event in same payload', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - basicBillingAuction([{ - vendor: 'vendorName', - type: 'pageView', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message).to.haveOwnProperty('auctions'); - expect(message.billableEvents).to.deep.equal([{ - accountId: 1001, - vendor: 'vendorName', - type: 'pageView', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - }); - it('should pass along multiple billing events but filter out duplicates', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - basicBillingAuction([ - { - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }, - { - vendor: 'vendorName', - type: 'impression', - billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f' - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - } - ]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message).to.haveOwnProperty('auctions'); - expect(message.billableEvents).to.deep.equal([ - { - accountId: 1001, - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }, - { - accountId: 1001, - vendor: 'vendorName', - type: 'impression', - billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f' - } - ]); - }); - it('should pass along event right away if no pending auction', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - - events.emit(BILLABLE_EVENT, { - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message).to.not.haveOwnProperty('auctions'); - expect(message.billableEvents).to.deep.equal([ - { - accountId: 1001, - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - } - ]); - }); - it('should pass along event right away if pending auction but not waiting', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'], - waitForAuction: false - } - } - }); - // should fire right away, and then auction later - basicBillingAuction([{ - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(2); - const billingRequest = server.requests[0]; - const billingMessage = JSON.parse(billingRequest.requestBody); - expect(billingMessage).to.not.haveOwnProperty('auctions'); - expect(billingMessage.billableEvents).to.deep.equal([ - { - accountId: 1001, - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - } - ]); - // auction event after - const auctionRequest = server.requests[1]; - const auctionMessage = JSON.parse(auctionRequest.requestBody); - // should not double pass events! - expect(auctionMessage).to.not.haveOwnProperty('billableEvents'); - }); - }); - - describe('wrapper details passed in', () => { - it('should correctly pass in the wrapper details if provided', () => { - config.setConfig({ - rubicon: { - wrapperName: '1001_wrapperName_exp.4', - wrapperFamily: '1001_wrapperName', - rule_name: 'na-mobile' - } - }); - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.wrapper).to.deep.equal({ - name: '1001_wrapperName_exp.4', - family: '1001_wrapperName', - rule: 'na-mobile' - }); - - rubiconAnalyticsAdapter.disableAnalytics(); - }); - }); - - it('getHostNameFromReferer correctly grabs hostname from an input URL', function () { - let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); - inputUrl = 'https://www.prebid.com/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.com'); - inputUrl = 'https://prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); - inputUrl = 'http://xn--p8j9a0d9c9a.xn--q9jyb4c/'; - expect(typeof getHostNameFromReferer(inputUrl)).to.equal('string'); - - // not non-UTF char's in query / path which break if noDecodeWholeURL not set - inputUrl = 'https://prebid.org/search_results/%95x%8Em%92%CA/?category=000'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); - }); -}); diff --git a/test/spec/modules/rubiconAnalyticsSchema.json b/test/spec/modules/rubiconAnalyticsSchema.json deleted file mode 100644 index 2d0dca42d23..00000000000 --- a/test/spec/modules/rubiconAnalyticsSchema.json +++ /dev/null @@ -1,494 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Prebid Auctions", - "description": "A batched data object describing the lifecycle of an auction or multiple auction across a single page view.", - "type": "object", - "required": [ - "integration", - "version" - ], - "anyOf": [ - { - "required": [ - "auctions" - ] - }, - { - "required": [ - "bidsWon" - ] - }, - { - "required": [ - "billableEvents" - ] - } - ], - "properties": { - "integration": { - "type": "string", - "description": "Integration type that generated this event.", - "default": "pbjs" - }, - "version": { - "type": "string", - "description": "Version of Prebid.js responsible for the auctions contained within." - }, - "fpkvs": { - "type": "array", - "description": "List of any dynamic key value pairs set by publisher.", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } - }, - "session": { - "type": "object", - "description": "The session information for a given event", - "required": [ - "id", - "start", - "expires" - ], - "properties": { - "id": { - "type": "string", - "description": "UUID of session." - }, - "start": { - "type": "integer", - "description": "Unix timestamp of time of creation for this session in milliseconds." - }, - "expires": { - "type": "integer", - "description": "Unix timestamp of the maximum allowed time in milliseconds of the session." - }, - "pvid": { - "type": "string", - "description": "id to track page view." - } - } - }, - "auctions": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "adUnits", - "samplingFactor" - ], - "properties": { - "clientTimeoutMillis": { - "type": "integer", - "description": "Timeout given in client for given auction in milliseconds (if applicable)." - }, - "serverTimeoutMillis": { - "type": "integer", - "description": "Timeout configured for server adapter request in milliseconds (if applicable)." - }, - "accountId": { - "type": "number", - "description": "The account id for prebid server (if applicable)." - }, - "samplingFactor": { - "$ref": "#/definitions/samplingFactor" - }, - "adUnits": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "description": "An array of adUnits involved in this auction.", - "required": [ - "status", - "adUnitCode", - "transactionId", - "mediaTypes", - "dimensions", - "bids" - ], - "properties": { - "status": { - "type": "string", - "description": "The status of the adUnit" - }, - "adUnitCode": { - "type": "string", - "description": "The adUnit.code identifier" - }, - "transactionId": { - "type": "string", - "description": "The UUID generated id to represent this adunit in this auction." - }, - "adSlot": { - "type": "string" - }, - "mediaTypes": { - "$ref": "#/definitions/mediaTypes" - }, - "videoAdFormat": { - "$ref": "#/definitions/videoAdFormat" - }, - "dimensions": { - "type": "array", - "description": "All valid sizes included in this auction (note: may be sizeConfig filtered).", - "minItems": 1, - "items": { - "$ref": "#/definitions/dimensions" - } - }, - "adserverTargeting": { - "$ref": "#/definitions/adserverTargeting" - }, - "bids": { - "type": "array", - "description": "An array that contains a combination of the bids from the adUnit combined with their responses.", - "minItems": 1, - "items": { - "$ref": "#/definitions/bid" - } - }, - "accountId": { - "type": "number", - "description": "The Rubicon AccountId associated with this adUnit - Removed if null" - }, - "siteId": { - "type": "number", - "description": "The Rubicon siteId associated with this adUnit - Removed if null" - }, - "zoneId": { - "type": "number", - "description": "The Rubicon zoneId associated with this adUnit - Removed if null" - }, - "gam": { - "$ref": "#/definitions/gam" - } - } - } - } - } - } - }, - "bidsWon": { - "type": "array", - "minItems": 1, - "items": { - "allOf": [ - { - "$ref": "#/definitions/bid" - }, - { - "required": [ - "transactionId", - "accountId", - "samplingFactor", - "mediaTypes", - "adUnitCode", - "bidwonStatus" - ], - "properties": { - "transactionId": { - "type": "string" - }, - "accountId": { - "type": "number" - }, - "samplingFactor": { - "$ref": "#/definitions/samplingFactor" - }, - "adUnitCode": { - "type": "string" - }, - "videoAdFormat": { - "$ref": "#/definitions/videoAdFormat" - }, - "mediaTypes": { - "$ref": "#/definitions/mediaTypes" - }, - "adserverTargeting": { - "$ref": "#/definitions/adserverTargeting" - }, - "bidwonStatus": { - "description": "Whether the bid was successfully rendered or not", - "type": "string", - "enum": [ - "success", - "error" - ] - }, - "siteId": { - "type": "number", - "description": "The Rubicon siteId associated with this adUnit - Removed if null" - }, - "zoneId": { - "type": "number", - "description": "The Rubicon zoneId associated with this adUnit - Removed if null" - } - } - } - ] - } - }, - "billableEvents":{ - "type":"array", - "minItems":1, - "items":{ - "type":"object", - "required":[ - "accountId", - "vendor", - "type", - "billingId" - ], - "properties":{ - "vendor":{ - "type":"string", - "description":"The name of the vendor who emitted the billable event" - }, - "type":{ - "type":"string", - "description":"The type of billable event", - "enum":[ - "impression", - "pageLoad", - "auction", - "request", - "general" - ] - }, - "billingId":{ - "type":"string", - "description":"A UUID which is responsible more mapping this event to" - }, - "accountId": { - "type": "number", - "description": "The account id for the rubicon publisher" - } - } - } - } - }, - "definitions": { - "gam": { - "type": "object", - "description": "The gam information for a given ad unit", - "required": [ - "adSlot" - ], - "properties": { - "adSlot": { - "type": "string" - }, - "advertiserId": { - "type": "integer" - }, - "creativeId": { - "type": "integer" - }, - "LineItemId": { - "type": "integer" - }, - "isSlotEmpty": { - "type": "boolean", - "enum": [ - true - ] - } - } - }, - "adserverTargeting": { - "type": "object", - "description": "The adserverTargeting key/value pairs", - "patternProperties": { - ".+": { - "type": "string" - } - } - }, - "samplingFactor": { - "type": "integer", - "description": "An integer value representing the factor to multiply event count by to receive unsampled count.", - "enum": [ - 1, - 10, - 20, - 40, - 100 - ] - }, - "videoAdFormat": { - "type": "string", - "description": "This value only provided for video specifies the ad format", - "enum": [ - "pre-roll", - "interstitial", - "outstream", - "mid-roll", - "post-roll", - "vertical" - ] - }, - "mediaTypes": { - "type": "array", - "uniqueItems": true, - "minItems": 1, - "items": { - "type": "string", - "enum": [ - "native", - "video", - "banner" - ] - } - }, - "dimensions": { - "type": "object", - "description": "Size object representing the dimensions of creative in pixels.", - "required": [ - "width", - "height" - ], - "properties": { - "width": { - "type": "integer", - "minimum": 1 - }, - "height": { - "type": "integer", - "minimum": 1 - } - } - }, - "bid": { - "type": "object", - "required": [ - "bidder", - "bidId", - "status", - "source" - ], - "properties": { - "bidder": { - "type": "string" - }, - "bidId": { - "type": "string", - "description": "UUID representing this individual bid request in this auction." - }, - "params": { - "description": "A copy of the bid.params from the adUnit.bids", - "anyOf": [ - { - "type": "object" - }, - { - "$ref": "#/definitions/params/rubicon" - } - ] - }, - "status": { - "type": "string", - "enum": [ - "success", - "no-bid", - "error", - "rejected-gdpr", - "rejected-ipf" - ] - }, - "error": { - "type": "object", - "additionalProperties": false, - "required": [ - "code" - ], - "properties": { - "code": { - "type": "string", - "enum": [ - "request-error", - "connect-error", - "timeout-error" - ] - }, - "description": { - "type": "string" - } - } - }, - "source": { - "type": "string", - "enum": [ - "client", - "server" - ] - }, - "clientLatencyMillis": { - "type": "integer", - "description": "Latency from auction start to bid response recieved in milliseconds." - }, - "serverLatencyMillis": { - "type": "integer", - "description": "Latency returned by prebid server (response_time_ms)." - }, - "bidResponse": { - "type": "object", - "required": [ - "mediaType", - "bidPriceUSD" - ], - "properties": { - "dimensions": { - "$ref": "#/definitions/dimensions" - }, - "mediaType": { - "type": "string", - "enum": [ - "native", - "video", - "banner" - ] - }, - "bidPriceUSD": { - "type": "number", - "description": "The bid value denoted in USD" - }, - "dealId": { - "type": "integer", - "description": "The id associated with any potential deals" - } - } - } - } - }, - "params": { - "rubicon": { - "type": "object", - "properties": { - "accountId": { - "type": "number" - }, - "siteId": { - "type": "number" - }, - "zoneId": { - "type": "number" - } - } - } - } - } -} From bc6aa84ace332b582e9f125b4c8fa014e503e7a8 Mon Sep 17 00:00:00 2001 From: shahinrahbariasl <56240400+shahinrahbariasl@users.noreply.github.com> Date: Wed, 10 May 2023 13:29:57 -0400 Subject: [PATCH 03/38] IX Bid Adapter : fix missing IX First Party Data (#9920) * fix: update ix fpd logic to append kvs to exisitng page url [PB-1719] * fix: update ix fpd logic to append kvs to exisitng page url [PB-1719] * fix: update ix fpd logic to append kvs to exisitng page url [PB-1719] * fix: update ix fpd logic to append kvs to exisitng page url [PB-1719] * fix: update ix fpd logic to append kvs to exisitng page url [PB-1719] --------- Co-authored-by: shahin.rahbariasl --- modules/ixBidAdapter.js | 112 ++++++++++++++----------- test/spec/modules/ixBidAdapter_spec.js | 67 ++++++++++++++- 2 files changed, 131 insertions(+), 48 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 06f96edca30..652574402a8 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -635,7 +635,8 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r = applyRegulations(r, bidderRequest); let payload = {}; - createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload); + siteID = validBidRequests[0].params.siteId; + payload.s = siteID; const transactionIds = Object.keys(impressions); let isFpdAdded = false; @@ -649,15 +650,13 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const fpd = deepAccess(bidderRequest, 'ortb2') || {}; const site = { ...(fpd.site || fpd.context) }; + + // update page URL with IX FPD KVs if they exist + site.page = getIxFirstPartyDataPageUrl(bidderRequest); + const user = { ...fpd.user }; if (!isEmpty(fpd) && !isFpdAdded) { r = addFPD(bidderRequest, r, fpd, site, user); - - const clonedRObject = deepClone(r); - - clonedRObject.site = mergeDeep({}, clonedRObject.site, site); - clonedRObject.user = mergeDeep({}, clonedRObject.user, user); - r.site = mergeDeep({}, r.site, site); r.user = mergeDeep({}, r.user, user); isFpdAdded = true; @@ -856,46 +855,6 @@ function applyRegulations(r, bidderRequest) { return r } -/** - * createPayload creates the payload to be sent with the request. - * - * @param {array} validBidRequests A list of valid bid request config objects. - * @param {object} bidderRequest An object containing other info like gdprConsent. - * @param {object} r Reuqest object. - * @param {string} baseUrl Base exchagne URL. - * @param {array} requests List of request obejcts. - * @param {object} payload Request payload object. - */ -function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload) { - // Use the siteId in the first bid request as the main siteId. - siteID = validBidRequests[0].params.siteId; - payload.s = siteID; - - // Parse additional runtime configs. - const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix'; - const otherIxConfig = config.getConfig(bidderCode); - - if (otherIxConfig) { - // Append firstPartyData to r.site.page if firstPartyData exists. - if (typeof otherIxConfig.firstPartyData === 'object') { - const firstPartyData = otherIxConfig.firstPartyData; - let firstPartyString = '?'; - for (const key in firstPartyData) { - if (firstPartyData.hasOwnProperty(key)) { - firstPartyString += `${encodeURIComponent(key)}=${encodeURIComponent(firstPartyData[key])}&`; - } - } - firstPartyString = firstPartyString.slice(0, -1); - - if ('page' in r.site) { - r.site.page += firstPartyString; - } else { - r.site.page = firstPartyString; - } - } - } -} - /** * addImpressions adds impressions to request object * @@ -977,6 +936,65 @@ function addImpressions(impressions, transactionIds, r, adUnitIndex) { return r; } +/** +This function retrieves the page URL and appends first party data query parameters +to it without adding duplicate query parameters. Returns original referer URL if no IX FPD exists. +@param {Object} bidderRequest - The bidder request object containing information about the bid and the page. +@returns {string} - The modified page URL with first party data query parameters appended. +*/ +function getIxFirstPartyDataPageUrl (bidderRequest) { + // Parse additional runtime configs. + const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix'; + const otherIxConfig = config.getConfig(bidderCode); + + let pageUrl = ''; + if (deepAccess(bidderRequest, 'ortb2.site.page')) { + pageUrl = bidderRequest.ortb2.site.page; + } else { + pageUrl = deepAccess(bidderRequest, 'refererInfo.page'); + } + + if (otherIxConfig) { + // Append firstPartyData to r.site.page if firstPartyData exists. + if (typeof otherIxConfig.firstPartyData === 'object') { + const firstPartyData = otherIxConfig.firstPartyData; + return appendIXQueryParams(bidderRequest, pageUrl, firstPartyData); + } + } + + return pageUrl +} + +/** +This function appends the provided query parameters to the given URL without adding duplicate query parameters. +@param {Object} bidderRequest - The bidder request object containing information about the bid and the page to be used as fallback in case url is not valid. +@param {string} url - The base URL to which query parameters will be appended. +@param {Object} params - An object containing key-value pairs of query parameters to append. +@returns {string} - The modified URL with the provided query parameters appended. +*/ +function appendIXQueryParams(bidderRequest, url, params) { + let urlObj; + try { + urlObj = new URL(url); + } catch (error) { + logWarn(`IX Bid Adapter: Invalid URL set in ortb2.site.page: ${url}. Using referer URL instead.`); + urlObj = new URL(deepAccess(bidderRequest, 'refererInfo.page')); + } + + const searchParams = new URLSearchParams(urlObj.search); + + // Loop through the provided query parameters and append them + for (const [key, value] of Object.entries(params)) { + if (!searchParams.has(key)) { + searchParams.append(key, value); + } + } + + // Construct the final URL with the updated query parameters + urlObj.search = searchParams.toString(); + return urlObj.toString(); +} + /** * addFPD adds ortb2 first party data to request object. * diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 149c4c44c21..874f5048ce0 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -725,6 +725,11 @@ describe('IndexexchangeAdapter', function () { refererInfo: { page: 'https://www.prebid.org', canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + }, + ortb2: { + site: { + page: 'https://www.prebid.org' + } } }; @@ -2135,7 +2140,7 @@ describe('IndexexchangeAdapter', function () { const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = extractPayload(requestWithFirstPartyData).site.page; - const expectedPageUrl = DEFAULT_OPTION.refererInfo.page + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; + const expectedPageUrl = DEFAULT_OPTION.ortb2.site.page + '/?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; expect(pageUrl).to.equal(expectedPageUrl); }); @@ -2233,6 +2238,66 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('getIxFirstPartyDataPageUrl', () => { + beforeEach(() => { + config.setConfig({ ix: { firstPartyData: { key1: 'value1', key2: 'value2' } } }); + }); + + afterEach(() => { + config.resetConfig(); + }); + + it('should return the modified URL with first party data query parameters appended', () => { + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org/?key1=value1&key2=value2'); + }); + + it('should return the modified URL with first party data query parameters appended but not duplicated', () => { + const bidderRequest = deepClone(DEFAULT_OPTION); + bidderRequest.ortb2.site.page = 'https://www.prebid.org/?key1=value1' + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org/?key1=value1&key2=value2'); + }); + + it('should not overwrite existing query parameters with first party data', () => { + config.setConfig({ ix: { firstPartyData: { key1: 'value1', key2: 'value2' } } }); + const bidderRequest = deepClone(DEFAULT_OPTION); + bidderRequest.ortb2.site.page = 'https://www.prebid.org/?key1=existingValue1' + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, bidderRequest)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org/?key1=existingValue1&key2=value2'); + }); + + it('should return the original URL if no first party data is available', () => { + config.setConfig({ ix: {} }); + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org'); + }); + + it('should return the original URL referer page url if ortb2 does not exist', () => { + config.setConfig({ ix: {} }); + const bidderRequest = deepClone(DEFAULT_OPTION); + delete bidderRequest.ortb2; + bidderRequest.refererInfo.page = 'https://example.com'; + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, bidderRequest)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://example.com'); + }); + + it('should use referer URL if the provided ortb2.site.page URL is not valid', () => { + config.setConfig({ ix: { firstPartyData: { key1: 'value1', key2: 'value2' } } }); + const bidderRequest = deepClone(DEFAULT_OPTION); + bidderRequest.ortb2.site.page = 'www.invalid-url*&?.com'; + bidderRequest.refererInfo.page = 'https://www.prebid.org'; + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, bidderRequest)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org/?key1=value1&key2=value2'); + }); + }); + describe('request should contain both banner and video requests', function () { const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]], {}); it('should have banner request', () => { From a9e357aa5645815cde7318779609713ff1b7a9c8 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 10 May 2023 16:41:24 -0700 Subject: [PATCH 04/38] Prebid 8: stop using transactionId as source.tid on ortb2 payloads (#9916) * Update bizzclickBidAdapter.js * Update rubiconBidAdapter.js * Update gothamadsBidAdapter.js * Update luponmediaBidAdapter.js * Update ozoneBidAdapter.js * Update pubmaticBidAdapter.js * Update improvedigitalBidAdapter.js * Update newspassidBidAdapter.js * Update deltaprojectsBidAdapter.js * Update bizzclickBidAdapter.js * Update deltaprojectsBidAdapter.js * Update gothamadsBidAdapter.js * Update luponmediaBidAdapter.js * Update newspassidBidAdapter.js * Update ozoneBidAdapter.js * Update pubmaticBidAdapter.js * Update rubiconBidAdapter.js * Update improvedigitalBidAdapter_spec.js * Update luponmediaBidAdapter_spec.js * Update rubiconBidAdapter_spec.js * Update improvedigitalBidAdapter.js * Update pubmaticBidAdapter.js * Update improvedigitalBidAdapter.js * Update pubmaticBidAdapter.js * Update pubmaticBidAdapter.js * Update pubmaticBidAdapter_spec.js * Update adtrueBidAdapter.js * Update adtrueBidAdapter_spec.js * Update adtrueBidAdapter.js * Update adtrueBidAdapter_spec.js * Update adtrueBidAdapter_spec.js --- modules/adtrueBidAdapter.js | 2 +- modules/bizzclickBidAdapter.js | 2 +- modules/deltaprojectsBidAdapter.js | 2 +- modules/gothamadsBidAdapter.js | 2 +- modules/improvedigitalBidAdapter.js | 2 +- modules/luponmediaBidAdapter.js | 2 +- modules/newspassidBidAdapter.js | 2 +- modules/ozoneBidAdapter.js | 2 +- modules/pubmaticBidAdapter.js | 4 +--- modules/rubiconBidAdapter.js | 2 +- test/spec/modules/adtrueBidAdapter_spec.js | 3 --- test/spec/modules/improvedigitalBidAdapter_spec.js | 2 +- test/spec/modules/luponmediaBidAdapter_spec.js | 2 +- test/spec/modules/pubmaticBidAdapter_spec.js | 1 - test/spec/modules/rubiconBidAdapter_spec.js | 8 ++++---- 15 files changed, 16 insertions(+), 22 deletions(-) diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index 2ec5cd59e1d..9bff2c85c57 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -507,7 +507,7 @@ export const spec = { if (typeof config.getConfig('device') === 'object') { payload.device = Object.assign(payload.device, config.getConfig('device')); } - deepSetValue(payload, 'source.tid', conf.transactionId); + // test bids if (window.location.href.indexOf('adtrueTest=true') !== -1) { payload.test = 1; diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 4ef2b6dd9f8..8219a2a41cf 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -92,7 +92,7 @@ export const spec = { host: location.host }, source: { - tid: bidRequest.transactionId, + tid: bidRequest.auctionId, ext: { schain: {} } diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js index e40ec58461c..92b8d29d209 100644 --- a/modules/deltaprojectsBidAdapter.js +++ b/modules/deltaprojectsBidAdapter.js @@ -90,7 +90,7 @@ function buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs // build source const source = { - tid: validBidRequest.transactionId, + tid: validBidRequest.auctionId, fd: 1, } diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js index 6b549f347bb..4e3d9e190c4 100644 --- a/modules/gothamadsBidAdapter.js +++ b/modules/gothamadsBidAdapter.js @@ -104,7 +104,7 @@ export const spec = { host: location.host }, source: { - tid: bidRequest.transactionId + tid: bidRequest.auctionId }, regs: { coppa: config.getConfig('coppa') === true ? 1 : 0, diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 437fcf7d5bb..24f3b2328b7 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -151,7 +151,7 @@ export const CONVERTER = ortbConverter({ id: getUniqueIdentifierStr(), source: { // TODO: once https://github.com/prebid/Prebid.js/issues/8573 is resolved, this should be handled by the base ortbConverter logic - tid: context.bidRequests[0].transactionId, + tid: context.bidRequests[0].auctionId, }, ext: { improvedigital: { diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 059a07b9999..0a4887c007d 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -290,7 +290,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { id: bidRequest.transactionId, test: config.getConfig('debug') ? 1 : 0, source: { - tid: bidRequest.transactionId + tid: bidRequest.auctionId }, tmax: bidderRequest.timeout, imp: currentImps.concat([{ diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js index 409cb55e6a0..ec28a625599 100644 --- a/modules/newspassidBidAdapter.js +++ b/modules/newspassidBidAdapter.js @@ -283,7 +283,7 @@ export const spec = { npRequestSingle.auctionId = imp.ext['newspassid'].transactionId; // not sure if this should be here? npRequestSingle.imp = [imp]; npRequestSingle.ext = extObj; - deepSetValue(npRequestSingle, 'source.tid', imp.ext['newspassid'].transactionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + deepSetValue(npRequestSingle, 'source.tid', bidderRequest.auctionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). deepSetValue(npRequestSingle, 'user.ext.eids', userExtEids); logInfo('buildRequests RequestSingle (for non-single) = ', npRequestSingle); return { diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 3e59c2c8def..8882859b532 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -381,7 +381,7 @@ export const spec = { ozoneRequestSingle.auctionId = imp.ext[whitelabelBidder].transactionId; // not sure if this should be here? ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.ext = extObj; - deepSetValue(ozoneRequestSingle, 'source.tid', imp.ext[whitelabelBidder].transactionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + deepSetValue(ozoneRequestSingle, 'source.tid', bidderRequest.auctionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). deepSetValue(ozoneRequestSingle, 'user.ext.eids', userExtEids); logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); return { diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index edfee7a0b6d..b013cf82215 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1151,9 +1151,7 @@ export const spec = { // update device.language to ISO-639-1-alpha-2 (2 character language) payload.device.language = payload.device.language && payload.device.language.split('-')[0]; - // passing transactionId in source.tid - deepSetValue(payload, 'source.tid', conf.transactionId); - + // passing nothing in source.tid -- Pubmatic can correct in a future PR // test bids if (window.location.href.indexOf('pubmaticTest=true') !== -1) { payload.test = 1; diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 36e077aeab8..ebb3b1cea2e 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -464,7 +464,7 @@ export const spec = { 'rp_floor': (params.floor = parseFloat(params.floor)) >= 0.01 ? params.floor : undefined, 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, - 'x_source.tid': bidRequest.transactionId, + 'x_source.tid': bidRequest.auctionId, 'x_imp.ext.tid': bidRequest.transactionId, 'l_pb_bid_id': bidRequest.bidId, 'p_screen_res': _getScreenResolution(), diff --git a/test/spec/modules/adtrueBidAdapter_spec.js b/test/spec/modules/adtrueBidAdapter_spec.js index b499d077a3c..1daf8b645c7 100644 --- a/test/spec/modules/adtrueBidAdapter_spec.js +++ b/test/spec/modules/adtrueBidAdapter_spec.js @@ -281,7 +281,6 @@ describe('AdTrueBidAdapter', function () { expect(data.site.domain).to.be.a('string'); // domain should be set expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId - expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.reserve); // reverse expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId @@ -304,7 +303,6 @@ describe('AdTrueBidAdapter', function () { expect(data.site.domain).to.be.a('string'); // domain should be set expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId - expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId @@ -324,7 +322,6 @@ describe('AdTrueBidAdapter', function () { expect(data.site.domain).to.be.a('string'); // domain should be set expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId - expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 1c3bd3197d0..b06669f2429 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -236,7 +236,7 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.tmax).not.to.exist; expect(payload.regs).to.not.exist; expect(payload.schain).to.not.exist; - sinon.assert.match(payload.source, {tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb'}) + sinon.assert.match(payload.source, {tid: '192721e36a0239'}) expect(payload.device).to.be.an('object'); expect(payload.user).to.not.exist; sinon.assert.match(payload.imp, [ diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index c8d4c18c407..8486faa5049 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -150,7 +150,7 @@ describe('luponmediaBidAdapter', function () { let dynRes = JSON.parse(requests.data); expect(requests.url).to.equal(ENDPOINT_URL); expect(requests.method).to.equal('POST'); - expect(requests.data).to.equal('{"id":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","test":0,"source":{"tid":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"novi.ba","sid":"199424","hp":1}]}}},"tmax":1500,"imp":[{"id":"268a30af10dd6f","secure":1,"ext":{"luponmedia":{"siteId":303522,"keyId":"4o2c4"}},"banner":{"format":[{"w":300,"h":250}]}}],"ext":{"prebid":{"targeting":{"includewinners":true,"includebidderkeys":false}}},"user":{"id":"' + dynRes.user.id + '","buyeruid":"8d8b16cb-1383-4a0f-b4bb-0be28464d974"},"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}'); + expect(requests.data).to.equal('{"id":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","test":0,"source":{"tid":"7376c117-b7aa-49f5-a661-488543deeefd","ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"novi.ba","sid":"199424","hp":1}]}}},"tmax":1500,"imp":[{"id":"268a30af10dd6f","secure":1,"ext":{"luponmedia":{"siteId":303522,"keyId":"4o2c4"}},"banner":{"format":[{"w":300,"h":250}]}}],"ext":{"prebid":{"targeting":{"includewinners":true,"includebidderkeys":false}}},"user":{"id":"' + dynRes.user.id + '","buyeruid":"8d8b16cb-1383-4a0f-b4bb-0be28464d974"},"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}'); }); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 3198fe406e7..861f7323701 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1136,7 +1136,6 @@ describe('PubMatic adapter', function () { expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId - expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index e3ebf15619d..9a98aeb1e91 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -428,7 +428,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -618,7 +618,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', @@ -970,7 +970,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2286,7 +2286,7 @@ describe('the rubicon adapter', function () { 'p_pos': 'atf', 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', From 36ccf08accacd0ade05b571d04877d5a39502f4a Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Thu, 11 May 2023 14:04:25 +0200 Subject: [PATCH 05/38] Update nexx360BidAdapter.js (#9938) --- modules/nexx360BidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 9a7541fdd96..8a10cb311f9 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -21,7 +21,8 @@ const ALIASES = [ { code: 'revenuemaker' }, { code: 'first-id', gvlid: 1178 }, { code: 'adwebone' }, - { code: 'league-m', gvlid: 965 } + { code: 'league-m', gvlid: 965 }, + { code: 'prjads' }, ]; export const storage = getStorageManager({ From b5a955679734470285301bc3f5d370895864923e Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Thu, 11 May 2023 15:45:04 +0300 Subject: [PATCH 06/38] KueezRtb Bid Adapter: added support for all user id systems. (#9926) --- modules/kueezRtbBidAdapter.js | 63 ++++++++++---------- test/spec/modules/kueezRtbBidAdapter_spec.js | 5 +- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index ef53ed9baf4..02d98064ca2 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -2,7 +2,7 @@ import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../sr import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import { config } from '../src/config.js'; +import {config} from '../src/config.js'; const GVLID = 1165; const DEFAULT_SUB_DOMAIN = 'exchange'; @@ -11,18 +11,6 @@ const BIDDER_VERSION = '1.0.0'; const CURRENCY = 'USD'; const TTL_SECONDS = 60 * 5; const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -export const SUPPORTED_ID_SYSTEMS = { - 'britepoolid': 1, - 'criteoId': 1, - 'id5id': 1, - 'idl_env': 1, - 'lipb': 1, - 'netId': 1, - 'parrableId': 1, - 'pubcid': 1, - 'tdid': 1, - 'pubProvidedId': 1 -}; const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { @@ -161,25 +149,23 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { function appendUserIdsToRequestPayload(payloadRef, userIds) { let key; _each(userIds, (userId, idSystemProviderName) => { - if (SUPPORTED_ID_SYSTEMS[idSystemProviderName]) { - key = `uid.${idSystemProviderName}`; - - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - case 'lipb': - payloadRef[key] = userId.lipbid; - break; - case 'parrableId': - payloadRef[key] = userId.eid; - break; - case 'id5id': - payloadRef[key] = userId.uid; - break; - default: - payloadRef[key] = userId; - } + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; } }); } @@ -207,7 +193,18 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const {creativeId, ad, price, exp, width, height, currency, metaData, advertiserDomains, mediaType = BANNER} = result; + const { + creativeId, + ad, + price, + exp, + width, + height, + currency, + metaData, + advertiserDomains, + mediaType = BANNER + } = result; if (!ad || !price) { return; } diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index fc7219d2ee7..84454c8dffa 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -1,7 +1,6 @@ import {expect} from 'chai'; import { spec as adapter, - SUPPORTED_ID_SYSTEMS, createDomain, hashCode, extractPID, @@ -18,6 +17,8 @@ import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + const SUB_DOMAIN = 'exchange'; const BID = { @@ -498,7 +499,7 @@ describe('KueezRtbBidAdapter', function () { }); describe('user id system', function () { - Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { const id = Date.now().toString(); const bid = utils.deepClone(BID); From a5ae1f60fe5e3af20ef1cb0aa2aeb65ec1b5e053 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 11 May 2023 05:45:54 -0700 Subject: [PATCH 07/38] Do not call onBidViewable, onBidWon, etc for S2S bids (#9919) --- src/adapterManager.js | 8 +- test/spec/unit/core/adapterManager_spec.js | 126 +++++++++------------ 2 files changed, 56 insertions(+), 78 deletions(-) diff --git a/src/adapterManager.js b/src/adapterManager.js index e1b1b1f9611..ab0da3045bb 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -585,9 +585,11 @@ function invokeBidderMethod(bidder, method, spec, fn, ...params) { } function tryCallBidderMethod(bidder, method, param) { - const target = getBidderMethod(bidder, method); - if (target != null) { - invokeBidderMethod(bidder, method, ...target, param); + if (param?.src !== CONSTANTS.S2S.SRC) { + const target = getBidderMethod(bidder, method); + if (target != null) { + invokeBidderMethod(bidder, method, ...target, param); + } } } diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 9a2acd17998..031f5988616 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -353,103 +353,79 @@ describe('adapterManager tests', function () { }); }); // end callTimedOutBidders - describe('onBidWon', function () { - var criteoSpec = { onBidWon: sinon.stub() } - var criteoAdapter = { - bidder: 'criteo', - getSpec: function() { return criteoSpec; } - } + describe('bidder spec methods', () => { + let adUnits, bids, criteoSpec; before(function () { config.setConfig({s2sConfig: { enabled: false }}); }); - beforeEach(function () { - adapterManager.bidderRegistry['criteo'] = criteoAdapter; - }); - - afterEach(function () { - delete adapterManager.bidderRegistry['criteo']; - }); - - it('should call spec\'s onBidWon callback when a bid is won', function () { - const bids = [ + beforeEach(() => { + criteoSpec = {} + adapterManager.bidderRegistry['criteo'] = { + bidder: 'criteo', + getSpec: function() { return criteoSpec; }, + } + bids = [ {bidder: 'criteo', params: {placementId: 'id'}}, ]; - const adUnits = [{ + adUnits = [{ code: 'adUnit-code', sizes: [[728, 90]], bids }]; - - adapterManager.callBidWonBidder(bids[0].bidder, bids[0], adUnits); - sinon.assert.called(criteoSpec.onBidWon); - }); - }); // end onBidWon - - describe('onSetTargeting', function () { - var criteoSpec = { onSetTargeting: sinon.stub() } - var criteoAdapter = { - bidder: 'criteo', - getSpec: function() { return criteoSpec; } - } - before(function () { - config.setConfig({s2sConfig: { enabled: false }}); - }); - - beforeEach(function () { - adapterManager.bidderRegistry['criteo'] = criteoAdapter; }); afterEach(function () { delete adapterManager.bidderRegistry['criteo']; }); - it('should call spec\'s onSetTargeting callback when setTargeting is called', function () { - const bids = [ - {bidder: 'criteo', params: {placementId: 'id'}}, - ]; - const adUnits = [{ - code: 'adUnit-code', - sizes: [[728, 90]], - bids - }]; - adapterManager.callSetTargetingBidder(bids[0].bidder, bids[0], adUnits); - sinon.assert.called(criteoSpec.onSetTargeting); - }); - }); // end onSetTargeting + describe('onBidWon', function () { + beforeEach(() => { + criteoSpec.onBidWon = sinon.stub() + }); + it('should call spec\'s onBidWon callback when a bid is won', function () { + adapterManager.callBidWonBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.called(criteoSpec.onBidWon); + }); - describe('onBidViewable', function () { - var criteoSpec = { onBidViewable: sinon.stub() } - var criteoAdapter = { - bidder: 'criteo', - getSpec: function() { return criteoSpec; } - } - before(function () { - config.setConfig({s2sConfig: { enabled: false }}); + it('should NOT call onBidWon when the bid is S2S', () => { + bids[0].src = CONSTANTS.S2S.SRC + adapterManager.callBidWonBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.notCalled(criteoSpec.onBidWon); + }) }); - beforeEach(function () { - adapterManager.bidderRegistry['criteo'] = criteoAdapter; - }); + describe('onSetTargeting', function () { + beforeEach(() => { + criteoSpec.onSetTargeting = sinon.stub() + }) - afterEach(function () { - delete adapterManager.bidderRegistry['criteo']; - }); + it('should call spec\'s onSetTargeting callback when setTargeting is called', function () { + adapterManager.callSetTargetingBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.called(criteoSpec.onSetTargeting); + }); - it('should call spec\'s onBidViewable callback when callBidViewableBidder is called', function () { - const bids = [ - {bidder: 'criteo', params: {placementId: 'id'}}, - ]; - const adUnits = [{ - code: 'adUnit-code', - sizes: [[728, 90]], - bids - }]; - adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); - sinon.assert.called(criteoSpec.onBidViewable); + it('should NOT call onSetTargeting when bid is S2S', () => { + bids[0].src = CONSTANTS.S2S.SRC; + adapterManager.callSetTargetingBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.notCalled(criteoSpec.onSetTargeting); + }) + }); // end onSetTargeting + describe('onBidViewable', function () { + beforeEach(() => { + criteoSpec.onBidViewable = sinon.stub(); + }) + it('should call spec\'s onBidViewable callback when callBidViewableBidder is called', function () { + adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); + sinon.assert.called(criteoSpec.onBidViewable); + }); + it('should NOT call onBidViewable when bid is S2S', () => { + bids[0].src = CONSTANTS.S2S.SRC; + adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); + sinon.assert.notCalled(criteoSpec.onBidViewable); + }) }); - }); // end onBidViewable - + }) describe('onBidderError', function () { const bidder = 'appnexus'; const appnexusSpec = { onBidderError: sinon.stub() }; From facd0b7e421a51b43e650088a8535aa1e686c17c Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Thu, 11 May 2023 15:56:24 +0300 Subject: [PATCH 08/38] MinuteMediaPlus Bid Adapter: added support for all user id systems. (#9927) --- modules/minutemediaplusBidAdapter.js | 62 +++++++++---------- .../modules/minutemediaplusBidAdapter_spec.js | 5 +- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js index bd7874c405b..7e76e1b6a17 100644 --- a/modules/minutemediaplusBidAdapter.js +++ b/modules/minutemediaplusBidAdapter.js @@ -11,18 +11,7 @@ const BIDDER_VERSION = '1.0.0'; const CURRENCY = 'USD'; const TTL_SECONDS = 60 * 5; const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -export const SUPPORTED_ID_SYSTEMS = { - 'britepoolid': 1, - 'criteoId': 1, - 'id5id': 1, - 'idl_env': 1, - 'lipb': 1, - 'netId': 1, - 'parrableId': 1, - 'pubcid': 1, - 'tdid': 1, - 'pubProvidedId': 1 -}; + const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { @@ -161,25 +150,23 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { function appendUserIdsToRequestPayload(payloadRef, userIds) { let key; _each(userIds, (userId, idSystemProviderName) => { - if (SUPPORTED_ID_SYSTEMS[idSystemProviderName]) { - key = `uid.${idSystemProviderName}`; - - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - case 'lipb': - payloadRef[key] = userId.lipbid; - break; - case 'parrableId': - payloadRef[key] = userId.eid; - break; - case 'id5id': - payloadRef[key] = userId.uid; - break; - default: - payloadRef[key] = userId; - } + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; } }); } @@ -207,7 +194,18 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const {creativeId, ad, price, exp, width, height, currency, metaData, advertiserDomains, mediaType = BANNER} = result; + const { + creativeId, + ad, + price, + exp, + width, + height, + currency, + metaData, + advertiserDomains, + mediaType = BANNER + } = result; if (!ad || !price) { return; } diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js index e19323c794f..359a97465e4 100644 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -1,7 +1,6 @@ import {expect} from 'chai'; import { spec as adapter, - SUPPORTED_ID_SYSTEMS, createDomain, hashCode, extractPID, @@ -18,6 +17,8 @@ import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + const SUB_DOMAIN = 'exchange'; const BID = { @@ -498,7 +499,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); describe('user id system', function () { - Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { const id = Date.now().toString(); const bid = utils.deepClone(BID); From 2abfd46f438b5aad1c26eb504f5f39c3e9dc2b0b Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 11 May 2023 14:16:04 +0000 Subject: [PATCH 09/38] Prebid 7.49.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f004bd63bd..c39abb905eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.49.0-pre", + "version": "7.49.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index f4da91ca580..5e9daa81a0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.49.0-pre", + "version": "7.49.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 8fe7115021fd348d0f3b090da48c40c095078800 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 11 May 2023 14:16:04 +0000 Subject: [PATCH 10/38] Increment version to 7.50.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c39abb905eb..a6e6020a443 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.49.0", + "version": "7.50.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 5e9daa81a0e..c666ea19bda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.49.0", + "version": "7.50.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 5db8560bad37e1544dfe96abf60916f02bbf1b9b Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Fri, 12 May 2023 14:19:08 -0300 Subject: [PATCH 11/38] includes a type property to all events (#9771) --- libraries/video/shared/helpers.js | 14 ++++++++++++ modules/videoModule/adQueue.js | 4 ++-- modules/videoModule/index.js | 8 +++---- .../videoModule/shared/helpers_spec.js | 22 +++++++++++++++++++ 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 test/spec/modules/videoModule/shared/helpers_spec.js diff --git a/libraries/video/shared/helpers.js b/libraries/video/shared/helpers.js index 1b87f3bf1f3..e61fde6a331 100644 --- a/libraries/video/shared/helpers.js +++ b/libraries/video/shared/helpers.js @@ -1,5 +1,19 @@ import { videoKey } from '../constants/constants.js' export function getExternalVideoEventName(eventName) { + if (!eventName) { + return ''; + } return videoKey + eventName.replace(/^./, eventName[0].toUpperCase()); } + +export function getExternalVideoEventPayload(eventName, payload) { + if (!payload) { + payload = {}; + } + + if (!payload.type) { + payload.type = eventName; + } + return payload; +} diff --git a/modules/videoModule/adQueue.js b/modules/videoModule/adQueue.js index a6da3752f6e..54cad4befc0 100644 --- a/modules/videoModule/adQueue.js +++ b/modules/videoModule/adQueue.js @@ -1,5 +1,5 @@ import { AD_BREAK_END, AUCTION_AD_LOAD_ATTEMPT, AUCTION_AD_LOAD_QUEUED, SETUP_COMPLETE } from '../../libraries/video/constants/events.js' -import { getExternalVideoEventName } from '../../libraries/video/shared/helpers.js' +import { getExternalVideoEventName, getExternalVideoEventPayload } from '../../libraries/video/shared/helpers.js' export function AdQueueCoordinator(videoCore, pbEvents) { const storage = {}; @@ -58,6 +58,6 @@ export function AdQueueCoordinator(videoCore, pbEvents) { function triggerEvent(eventName, adTagUrl, options) { const payload = Object.assign({ adTagUrl }, options); - pbEvents.emit(getExternalVideoEventName(eventName), payload); + pbEvents.emit(getExternalVideoEventName(eventName), getExternalVideoEventPayload(eventName, payload)); } } diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js index 53f8a217f37..6d1d70db81d 100644 --- a/modules/videoModule/index.js +++ b/modules/videoModule/index.js @@ -20,7 +20,7 @@ import { videoCoreFactory } from './coreVideo.js'; import { gamSubmoduleFactory } from './gamAdServerSubmodule.js'; import { videoImpressionVerifierFactory } from './videoImpressionVerifier.js'; import { AdQueueCoordinator } from './adQueue.js'; -import { getExternalVideoEventName } from '../../libraries/video/shared/helpers.js' +import { getExternalVideoEventName, getExternalVideoEventPayload } from '../../libraries/video/shared/helpers.js' const allVideoEvents = Object.keys(videoEvents).map(eventKey => videoEvents[eventKey]); events.addEvents(allVideoEvents.concat([AUCTION_AD_LOAD_ATTEMPT, AUCTION_AD_LOAD_QUEUED, AUCTION_AD_LOAD_ABORT, BID_IMPRESSION, BID_ERROR]).map(getExternalVideoEventName)); @@ -54,7 +54,7 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent adQueueCoordinator.registerProvider(divId); videoCore.initProvider(divId); videoCore.onEvents(videoEvents, (type, payload) => { - pbEvents.emit(getExternalVideoEventName(type), payload); + pbEvents.emit(getExternalVideoEventName(type), getExternalVideoEventPayload(type, payload)); }, divId); const adServerConfig = provider.adServer; @@ -199,7 +199,7 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent const highestCpmBids = pbGlobal.getHighestCpmBids(adUnitCode); if (!highestCpmBids.length) { - pbEvents.emit(getExternalVideoEventName(AUCTION_AD_LOAD_ABORT), options); + pbEvents.emit(getExternalVideoEventName(AUCTION_AD_LOAD_ABORT), getExternalVideoEventPayload(AUCTION_AD_LOAD_ABORT, options)); return; } @@ -223,7 +223,7 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent } pbGlobal.markWinningBidAsUsed(bid); - pbEvents.emit(getExternalVideoEventName(eventName), { bid, adEvent: adEventPayload }); + pbEvents.emit(getExternalVideoEventName(eventName), getExternalVideoEventPayload(eventName, { bid, adEvent: adEventPayload })); } function getBid(adPayload) { diff --git a/test/spec/modules/videoModule/shared/helpers_spec.js b/test/spec/modules/videoModule/shared/helpers_spec.js new file mode 100644 index 00000000000..e59988172e6 --- /dev/null +++ b/test/spec/modules/videoModule/shared/helpers_spec.js @@ -0,0 +1,22 @@ +import { getExternalVideoEventName, getExternalVideoEventPayload } from 'libraries/video/shared/helpers.js'; +import { expect } from 'chai'; + +describe('Helpers', function () { + describe('getExternalVideoEventName', function () { + it('should append video prefix and stay camelcase', function () { + expect(getExternalVideoEventName('eventName')).to.equal('videoEventName'); + expect(getExternalVideoEventName(null)).to.equal(''); + }); + }); + + describe('getExternalVideoEventPayload', function () { + it('should include type in payload when absent', function () { + const testType = 'testType'; + const payloadWithType = { datum: 'datum', type: 'existingType' }; + expect(getExternalVideoEventPayload(testType, payloadWithType).type).to.equal('existingType'); + + const payloadWithoutType = { datum: 'datum' }; + expect(getExternalVideoEventPayload(testType, payloadWithoutType).type).to.equal(testType); + }); + }); +}); From 6d0e9919b97fe7b2a4f433730d3444ff6b0cccff Mon Sep 17 00:00:00 2001 From: Marios Kosmas <110163065+mariosk5@users.noreply.github.com> Date: Fri, 12 May 2023 22:41:22 +0300 Subject: [PATCH 12/38] added projectagora as an alias for appnexus (#9944) --- modules/appnexusBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index cf3763be9c8..8347a4812c3 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -110,6 +110,7 @@ export const spec = { { code: 'oftmedia', gvlid: 32 }, { code: 'adasta', gvlid: 32 }, { code: 'beintoo', gvlid: 618 }, + { code: 'projectagora', gvlid: 1032 }, ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 81fcd91c09867748f87c7c4745cfd1eda0031fbc Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Fri, 12 May 2023 21:55:39 +0200 Subject: [PATCH 13/38] TheMediaGrid: added video.plcmt support (#9941) --- modules/gridBidAdapter.js | 3 +++ test/spec/modules/gridBidAdapter_spec.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index a043483d9b0..130eac9512d 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -984,6 +984,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { playbackmethod: bidRequest.mediaTypes.video.playbackmethod, startdelay: bidRequest.mediaTypes.video.startdelay }; + if ('plcmt' in bidRequest.mediaTypes.video) { + video.plcmt = bidRequest.mediaTypes.video.plcmt; + } const paramsVideo = bidRequest.params.video; if (paramsVideo !== undefined) { video.skip = video.skip || paramsVideo.skip || 0; diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 2ef604dd097..775e1a98a9c 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -423,6 +423,7 @@ describe('TheMediaGrid Adapter', function () { api: [1, 2], skip: 1, placement: 1, + plcmt: 2, minduration: 0, playbackmethod: 1, startdelay: 0 @@ -486,6 +487,7 @@ describe('TheMediaGrid Adapter', function () { 'api': [1, 2], 'skip': 1, 'placement': 1, + 'plcmt': 2, 'playbackmethod': 1, 'startdelay': 0 } @@ -539,6 +541,7 @@ describe('TheMediaGrid Adapter', function () { ], 'minduration': 0, 'placement': 1, + 'plcmt': 2, 'playbackmethod': 1, 'playersizes': [ '400x600' From 6b3ad928bca121c9db8de7ec21a9760d07d917a2 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 12 May 2023 13:14:06 -0700 Subject: [PATCH 14/38] MASS module: fix tests (#9939) --- test/spec/modules/mass_spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/spec/modules/mass_spec.js b/test/spec/modules/mass_spec.js index 3b5d89b0a8c..e7fc66991ca 100644 --- a/test/spec/modules/mass_spec.js +++ b/test/spec/modules/mass_spec.js @@ -130,6 +130,8 @@ describe('MASS Module', function() { }); it('should have a default renderer', function() { + // SOT needs at least one script to be on the page; ugly solution, but good enough since mass is to be removed in v8 + document.body.appendChild(document.createElement('script')); const render = useDefaultRender('https://example.com/render.js', 'abc'); render({}); From 1a69318a988ec7a1e1893e97bb1a2d830c65e7b3 Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Fri, 12 May 2023 22:26:32 +0200 Subject: [PATCH 15/38] be sure that bidResponse contains width and height (#9929) Co-authored-by: Michele Nasti --- modules/rubiconBidAdapter.js | 6 +- test/spec/modules/rubiconBidAdapter_spec.js | 68 +++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 36e077aeab8..62ec61b3dcf 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -207,11 +207,13 @@ export const converter = ortbConverter({ const bidResponse = buildBidResponse(bid, context); bidResponse.meta.mediaType = deepAccess(bid, 'ext.prebid.type'); const {bidRequest} = context; + + bidResponse.width = bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth') || bidResponse.playerWidth; + bidResponse.height = bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight') || bidResponse.playerHeight; + if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = outstreamRenderer(bidResponse); } - bidResponse.width = bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth'); - bidResponse.height = bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight'); if (deepAccess(bid, 'ext.bidder.rp.advid')) { deepSetValue(bidResponse, 'meta.advertiserId', bid.ext.bidder.rp.advid); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index e3ebf15619d..4f1c0dd606a 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -3430,6 +3430,74 @@ describe('the rubicon adapter', function () { // cleanup adUnit.parentNode.removeChild(adUnit); }); + + it('should render ad with Magnite renderer without video object', function () { + delete bidderRequest.bids[0].params.video; + bidderRequest.bids[0].params.bidonmultiformat = true; + bidderRequest.bids[0].mediaTypes.video.placement = 3; + bidderRequest.bids[0].mediaTypes.video.playerSize = [640, 480]; + + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } + }, + nurl: 'https://test.com/vast.xml' + }], + group: 0, + seat: 'rubicon' + }], + }; + + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + sinon.spy(window.MagniteApex, 'renderAd'); + + let bids = spec.interpretResponse({body: response}, {data: request}); + const bid = bids[0]; + bid.adUnitCode = 'outstream_video1_placement'; + const adUnit = document.createElement('div'); + adUnit.id = bid.adUnitCode; + document.body.appendChild(adUnit); + + bid.renderer.render(bid); + + const renderCall = window.MagniteApex.renderAd.getCall(0); + expect(renderCall.args[0]).to.deep.equal({ + closeButton: true, + collapse: true, + height: 480, + label: undefined, + placement: { + align: 'left', + attachTo: adUnit, + position: 'append', + }, + vastUrl: 'https://test.com/vast.xml', + width: 640 + }); + // cleanup + adUnit.parentNode.removeChild(adUnit); + }); }); } From 887c7d4b263439e37eb27f783aee429bac38f648 Mon Sep 17 00:00:00 2001 From: SebRobert Date: Mon, 15 May 2023 14:40:21 +0200 Subject: [PATCH 16/38] Beop Bid Adapter : fix gdpr applies (#9945) * send 'gdpr applies' info to our server * Add gdpr_applies in tests --- modules/beopBidAdapter.js | 1 + test/spec/modules/beopBidAdapter_spec.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index 4ee5aaec5be..2a0a4c5fd4c 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -64,6 +64,7 @@ export const spec = { dbg: false, slts: slots, is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'), + gdpr_applies: gdpr ? gdpr.gdprApplies : false, tc_string: (gdpr && gdpr.gdprApplies) ? gdpr.consentString : null, }; diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index ad096fd6526..9d3c00b368b 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -99,6 +99,8 @@ describe('BeOp Bid Adapter tests', () => { expect(url).to.equal(ENDPOINT); expect(payload.pid).to.exist; expect(payload.pid).to.equal('5a8af500c9e77c00017e4cad'); + expect(payload.gdpr_applies).to.exist; + expect(payload.gdpr_applies).to.equals(false); expect(payload.slts[0].name).to.exist; expect(payload.slts[0].name).to.equal('bellow-article'); expect(payload.slts[0].flr).to.equal(10); @@ -121,6 +123,8 @@ describe('BeOp Bid Adapter tests', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); + expect(payload.gdpr_applies).to.exist; + expect(payload.gdpr_applies).to.equals(true); expect(payload.tc_string).to.exist; expect(payload.tc_string).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='); expect(payload.url).to.exist; From a2411cca576736c135304db65635c88090501152 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Mon, 15 May 2023 06:51:39 -0700 Subject: [PATCH 17/38] Magnite Analytics: Utilize perfMetrics (#9925) --- modules/magniteAnalyticsAdapter.js | 29 +++++++++-- .../modules/magniteAnalyticsAdapter_spec.js | 52 ++++++------------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 7cede6af38d..88a6ab50b5e 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -702,6 +702,25 @@ magniteAdapter.onDataDeletionRequest = function () { magniteAdapter.MODULE_INITIALIZED_TIME = Date.now(); magniteAdapter.referrerHostname = ''; +const getLatencies = (args, auctionStart) => { + try { + const metrics = args.metrics.getMetrics(); + const src = args.src || args.source; + return { + total: parseInt(metrics[`adapter.${src}.total`]), + // If it is array, get slowest + net: parseInt(Array.isArray(metrics[`adapter.${src}.net`]) ? metrics[`adapter.${src}.net`][metrics[`adapter.${src}.net`].length - 1] : metrics[`adapter.${src}.net`]) + } + } catch (error) { + // default to old way if not able to get better ones + const latency = Date.now() - auctionStart; + return { + total: latency, + net: latency + } + } +} + let browser; magniteAdapter.track = ({ eventType, args }) => { switch (eventType) { @@ -872,7 +891,9 @@ magniteAdapter.track = ({ eventType, args }) => { code: 'request-error' }; } - bid.clientLatencyMillis = args.timeToRespond || Date.now() - cache.auctions[args.auctionId].auction.auctionStart; + const latencies = getLatencies(args, auctionEntry.auctionStart); + bid.clientLatencyMillis = latencies.total; + bid.httpLatencyMillis = latencies.net; bid.bidResponse = parseBidResponse(args, bid.bidResponse); // if pbs gave us back a bidId, we need to use it and update our bidId to PBA @@ -904,8 +925,10 @@ magniteAdapter.track = ({ eventType, args }) => { } // set client latency if not done yet - if (!cachedBid.clientLatencyMillis) { - cachedBid.clientLatencyMillis = Date.now() - cache.auctions[args.auctionId].auction.auctionStart; + if (!cachedBid.clientLatencyMillis || !cachedBid.httpLatencyMillis) { + const latencies = getLatencies(bid, deepAccess(cache, `auctions.${args.auctionId}.auction.auctionStart`)); + cachedBid.clientLatencyMillis = cachedBid.clientLatencyMillis || latencies.total; + cachedBid.httpLatencyMillis = cachedBid.httpLatencyMillis || latencies.net; } }); break; diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index bf9c3050bf6..9183a883f63 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -9,10 +9,6 @@ import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; import * as mockGpt from '../integration/faker/googletag.js'; -import { - setConfig, - addBidResponseHook, -} from 'modules/currency.js'; import { getGlobal } from '../../../src/prebidGlobal.js'; import { deepAccess } from '../../../src/utils.js'; @@ -35,6 +31,16 @@ const { const STUBBED_UUID = '12345678-1234-1234-1234-123456789abc'; +const metrics = { + getMetrics: () => { + return { + 'adapter.client.total': 271, + 'adapter.client.net': 240, + 'adapter.s2s.total': 371, + 'adapter.s2s.net': 340 + } + } +} // Mock Event Data const MOCK = { AUCTION_INIT: { @@ -160,6 +166,7 @@ const MOCK = { 'size': '300x250', 'status': 'rendered', getStatusCode: () => 1, + metrics }, SEAT_NON_BID: { auctionId: '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', @@ -186,6 +193,7 @@ const MOCK = { 'bidId': '23fcd8cf4bf0d7', 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', 'src': 'client', + metrics } ] }, @@ -262,6 +270,7 @@ const ANALYTICS_MESSAGE = { 'source': 'client', 'status': 'success', 'clientLatencyMillis': 271, + 'httpLatencyMillis': 240, 'bidResponse': { 'bidPriceUSD': 3.4, 'mediaType': 'banner', @@ -298,6 +307,7 @@ const ANALYTICS_MESSAGE = { 'source': 'client', 'status': 'success', 'clientLatencyMillis': 271, + 'httpLatencyMillis': 240, 'bidResponse': { 'bidPriceUSD': 3.4, 'mediaType': 'banner', @@ -1353,9 +1363,6 @@ describe('magnite analytics adapter', function () { // adunit should be marked as error expectedMessage.auctions[0].adUnits[0].status = 'error'; - // timed out in 1000 ms - expectedMessage.auctions[0].adUnits[0].bids[0].clientLatencyMillis = 1000; - expectedMessage.auctions[0].auctionStart = auctionStart; expect(message).to.deep.equal(expectedMessage); @@ -1439,34 +1446,6 @@ describe('magnite analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); - it('should pass bidderDetail for multibid auctions', function () { - // Set the rates - setConfig({ - adServerCurrency: 'JPY', - rates: { - USD: { - JPY: 100 - } - } - }); - - // set our bid response to JPY - let bidResponse = utils.deepClone(MOCK.BID_RESPONSE); - bidResponse.currency = 'JPY'; - bidResponse.cpm = 100; - - // Now add the bidResponse hook which hooks on the currenct conversion function onto the bid response - let innerBid; - addBidResponseHook(function (adCodeId, bid) { - innerBid = bid; - }, 'elementId', bidResponse); - - // Use the rubi analytics parseBidResponse Function to get the resulting cpm from the bid response! - const bidResponseObj = parseBidResponse(innerBid); - expect(bidResponseObj).to.have.property('bidPriceUSD'); - expect(bidResponseObj.bidPriceUSD).to.equal(1.0); - }); - it('should use the integration type provided in the config instead of the default', () => { config.setConfig({ rubicon: { @@ -2096,7 +2075,8 @@ describe('magnite analytics adapter', function () { oldBidId: 'fakeId', unknownBid: true, bidId: 'fakeId', - clientLatencyMillis: 271 + clientLatencyMillis: 271, + httpLatencyMillis: 240 } ); }); From ba6a97fe81fc9ff44144e7ac8d3b5edae0a58b72 Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Mon, 15 May 2023 08:41:51 -0600 Subject: [PATCH 18/38] Rubicon Bid Adapter : access x_source.tid from ortb2 object (#9940) * Access x_source.tid from ortb2 object * Update linting --------- Co-authored-by: Chris Huie --- modules/rubiconBidAdapter.js | 2 +- test/spec/modules/rubiconBidAdapter_spec.js | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 62ec61b3dcf..041dd5bcc99 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -466,7 +466,7 @@ export const spec = { 'rp_floor': (params.floor = parseFloat(params.floor)) >= 0.01 ? params.floor : undefined, 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, - 'x_source.tid': bidRequest.transactionId, + 'x_source.tid': deepAccess(bidderRequest, 'ortb2.source.tid'), 'x_imp.ext.tid': bidRequest.transactionId, 'l_pb_bid_id': bidRequest.bidId, 'p_screen_res': _getScreenResolution(), diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 4f1c0dd606a..048c68f44e7 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -89,6 +89,7 @@ describe('the rubicon adapter', function () { bidderCode: 'rubicon', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', bidderRequestId: '178e34bad3658f', + ortb2: { source: { tid: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' } }, bids: [ { bidder: 'rubicon', @@ -346,6 +347,7 @@ describe('the rubicon adapter', function () { transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' } ], + ortb2: { source: { tid: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' } }, start: 1472239426002, auctionStart: 1472239426000, timeout: 5000 @@ -619,7 +621,6 @@ describe('the rubicon adapter', function () { 'rand': '0.1', 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', - 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2276,6 +2277,8 @@ describe('the rubicon adapter', function () { }); describe('createSlotParams', function () { + const localBidderRequest = Object.assign({}, bidderRequest); + localBidderRequest.ortb2 = {source: {tid: 'faked707-a418-42ec-b8a7-b70a6c6fab0b'}}; it('should return a valid slot params object', function () { let expectedQuery = { 'account_id': '14062', @@ -2286,7 +2289,8 @@ describe('the rubicon adapter', function () { 'p_pos': 'atf', 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'faked707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2299,7 +2303,7 @@ describe('the rubicon adapter', function () { 'rf': 'localhost' }; - const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); + const slotParams = spec.createSlotParams(bidderRequest.bids[0], localBidderRequest); // test that all values above are both present and correct Object.keys(expectedQuery).forEach(key => { From 7ca9878f345e5bfb2af2e1e279106c594579e9b1 Mon Sep 17 00:00:00 2001 From: Pgb-Criteo <92986445+Pgb-Criteo@users.noreply.github.com> Date: Mon, 15 May 2023 16:55:12 +0200 Subject: [PATCH 19/38] Criteo: Add mapping for bid.meta.networkName (#9930) --- modules/criteoBidAdapter.js | 3 +++ test/spec/modules/criteoBidAdapter_spec.js | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 8f24dd6c1aa..e6f1d0d8aa1 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -230,6 +230,9 @@ export const spec = { if (slot.adomain) { bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [slot.adomain].flat() }); } + if (slot.ext?.meta?.networkName) { + bid.meta = Object.assign({}, bid.meta, {networkName: slot.ext.meta.networkName}) + } if (slot.native) { if (bidRequest.params.nativeCallback) { bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index a1d738c3657..c940f1fa2f5 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1565,6 +1565,11 @@ describe('The Criteo bidding adapter', function () { height: 90, deal: 'myDealCode', adomain: ['criteo.com'], + ext: { + meta: { + networkName: 'Criteo' + } + } }], }, }; @@ -1587,6 +1592,7 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].height).to.equal(90); expect(bids[0].dealId).to.equal('myDealCode'); expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); + expect(bids[0].meta.networkName).to.equal('Criteo'); }); it('should properly parse a bid response with a zoneId', function () { From 2f078e05c46bd267e2bb0ddd556e956f4e810cb7 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Mon, 15 May 2023 12:08:07 -0300 Subject: [PATCH 20/38] warns when plcmt is missing (#9937) --- modules/videoModule/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js index 6d1d70db81d..b9cba60594d 100644 --- a/modules/videoModule/index.js +++ b/modules/videoModule/index.js @@ -1,7 +1,7 @@ import { config } from '../../src/config.js'; import { find } from '../../src/polyfill.js'; import * as events from '../../src/events.js'; -import { mergeDeep } from '../../src/utils.js'; +import { mergeDeep, logWarn } from '../../src/utils.js'; import { getGlobal } from '../../src/prebidGlobal.js'; import CONSTANTS from '../../src/constants.json'; import { @@ -145,6 +145,10 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent video.context = ortbVideo.placement === PLACEMENT.INSTREAM ? 'instream' : 'outstream'; } + if (!video.plcmt) { + logWarn('Video.plcmt has not been set. Failure to set a value may result in loss of bids'); + } + const width = ortbVideo.w; const height = ortbVideo.h; if (!video.playerSize && width && height) { From a773c4fdca1abd72b6499c796483b4cf33d5036e Mon Sep 17 00:00:00 2001 From: prebid-sp <131664583+prebid-sp@users.noreply.github.com> Date: Mon, 15 May 2023 20:43:57 +0530 Subject: [PATCH 21/38] Silverpush Bid Adapter : initial release (#9844) * added silverpush bid adapter * fixed linting and added new test cases * added check for video feature test cases * publisher id validation fix * removed test url and ip * pub id var add * typo fix * rerun --------- Co-authored-by: Amit Jangra --- modules/silverpushBidAdapter.js | 326 +++++++++++++++ modules/silverpushBidAdapter.md | 107 +++++ .../spec/modules/silverpushBidAdapter_spec.js | 394 ++++++++++++++++++ 3 files changed, 827 insertions(+) create mode 100644 modules/silverpushBidAdapter.js create mode 100644 modules/silverpushBidAdapter.md create mode 100644 test/spec/modules/silverpushBidAdapter_spec.js diff --git a/modules/silverpushBidAdapter.js b/modules/silverpushBidAdapter.js new file mode 100644 index 00000000000..df81e144380 --- /dev/null +++ b/modules/silverpushBidAdapter.js @@ -0,0 +1,326 @@ +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { mergeDeep } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { Renderer } from '../src/Renderer.js'; +import { ajax } from '../src/ajax.js'; + +const BIDDER_CODE = 'silverpush'; +const bidderConfig = 'sp_pb_ortb'; +const bidderVersion = '1.0.0'; +const DEFAULT_CURRENCY = 'USD'; + +export const REQUEST_URL = 'https://apac.chocolateplatform.com/bidder/?identifier=prebidchoc'; +export const SP_OUTSTREAM_PLAYER_URL = 'https://xaido.sgp1.cdn.digitaloceanspaces.com/prebid/spoutstream.min.js'; + +const VIDEO_ORTB_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity' +]; + +export const VIDEO_ORTB_REQUIRED = ['api', 'mimes', 'placement', 'protocols', 'minduration', 'maxduration', 'startdelay']; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidWon, + getRequest: function(endpoint) { + ajax(endpoint, null, undefined, {method: 'GET'}); + }, + getOS: function(ua) { + if (ua.indexOf('Windows') != -1) { return 'Windows'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'iOS'; } else if (ua.indexOf('Mac OS X') != -1) { return 'macOS'; } else if (ua.match(/Android/)) { return 'Android'; } else if (ua.indexOf('Linux') != -1) { return 'Linux'; } else { return 'Unknown'; } + } +}; + +registerBidder(spec); + +export const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: 300 + }, + imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + + if (bidRequest.mediaTypes[VIDEO]) { + imp = buildVideoImp(bidRequest, imp); + } else if (bidRequest.mediaTypes[BANNER]) { + imp = buildBannerImp(bidRequest, imp); + } + + const bidFloor = getBidFloor(bidRequest); + + utils.deepSetValue(imp, 'bidfloor', bidFloor); + + if (bidRequest.params.deals && bidRequest.params.deals.length > 0) { + utils.deepSetValue(imp, 'pmp', { deals: bidRequest.params.deals }); + } + + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + at: 1, + ext: { + bc: `${bidderConfig}_${bidderVersion}` + } + }) + + let userAgent = navigator.userAgent; + utils.deepSetValue(req, 'device.os', spec.getOS(userAgent)); + utils.deepSetValue(req, 'device.devicetype', _isMobile() ? 1 : _isConnectedTV() ? 3 : 2); + + const bid = context.bidRequests[0]; + if (bid.params.publisherId) { + utils.deepSetValue(req, 'site.publisher.id', bid.params.publisherId); + } + + return req; + }, + + bidResponse(buildBidResponse, bid, context) { + let bidResponse = buildBidResponse(bid, context); + + if (bid.ext) { + bidResponse.meta.networkId = bid.ext.dsp_id; + bidResponse.meta.advertiserId = bid.ext.buyer_id; + bidResponse.meta.brandId = bid.ext.brand_id; + } + + if (context.ortbResponse.ext && context.ortbResponse.ext.paf) { + bidResponse.meta.paf = Object.assign({}, context.ortbResponse.ext.paf); + bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); + } + + bidResponse = buildVideoVastResponse(bidResponse) + bidResponse = buildVideoOutstreamResponse(bidResponse, context) + + return bidResponse; + }, + response(buildResponse, bidResponses, ortbResponse, context) { + const response = buildResponse(bidResponses, ortbResponse, context); + + let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return Object.assign({ + bidId, + auctionSignals: {} + }, cfg); + }); + return { + bids: response.bids, + fledgeAuctionConfigs, + } + } else { + return response.bids + } + } +}); + +function isBidRequestValid(bidRequest) { + return (isPublisherIdValid(bidRequest) && (isValidBannerRequest(bidRequest) || isValidVideoRequest(bidRequest))); +} + +function isPublisherIdValid(bidRequest) { + let pubId = utils.deepAccess(bidRequest, 'params.publisherId'); + return (pubId != null && utils.isStr(pubId) && pubId != ''); +} + +function isValidBannerRequest(bidRequest) { + const bannerSizes = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}.sizes`); + + return utils.isArray(bannerSizes) && bannerSizes.length > 0 && bannerSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); +} + +function isValidVideoRequest(bidRequest) { + const videoSizes = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}.playerSize`); + const PARAM_EXISTS = VIDEO_ORTB_REQUIRED.every(param => utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}.${param}`) != null); + + return PARAM_EXISTS && utils.isArray(videoSizes) && videoSizes.length > 0 && videoSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); +} + +function buildRequests(validBids, bidderRequest) { + let videoBids = validBids.filter(bid => isVideoBid(bid)); + let bannerBids = validBids.filter(bid => isBannerBid(bid)); + let requests = []; + + bannerBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, BANNER)); + }); + + videoBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); + }); + + return requests; +} + +function buildVideoImp(bidRequest, imp) { + if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; + } + + const videoMediaType = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`); + const videoSizes = (videoMediaType && videoMediaType.playerSize) || []; + + if (videoSizes && videoSizes.length > 0) { + utils.deepSetValue(imp, 'video.w', videoSizes[0][0]); + utils.deepSetValue(imp, 'video.h', videoSizes[0][1]); + } + + const videoAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`, {}); + const videoBidderParams = utils.deepAccess(bidRequest, `params.${VIDEO}`, {}); + + const videoParams = { ...videoAdUnitParams, ...videoBidderParams }; + + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + utils.deepSetValue(imp, `video.${param}`, videoParams[param]); + } + }); + + return { ...imp }; +} + +function buildBannerImp(bidRequest, imp) { + const bannerSizes = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}.sizes`, []); + + if (bannerSizes && bannerSizes.length > 0) { + utils.deepSetValue(imp, 'banner.w', bannerSizes[0][0]); + utils.deepSetValue(imp, 'banner.h', bannerSizes[0][1]); + } + + return {...imp}; +} + +function createRequest(bidRequests, bidderRequest, mediaType) { + return { + method: 'POST', + url: REQUEST_URL, + data: CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }) + } +} + +function buildVideoVastResponse(bidResponse) { + if (bidResponse.mediaType == VIDEO && bidResponse.vastXml) { + bidResponse.vastUrl = bidResponse.vastXml; + } + + return { ...bidResponse } +} + +function buildVideoOutstreamResponse(bidResponse, context) { + if (context.bidRequest.mediaTypes[VIDEO]?.context === 'outstream') { + bidResponse.rendererUrl = SP_OUTSTREAM_PLAYER_URL; + bidResponse.adUnitCode = context.bidRequest.adUnitCode; + + bidResponse.renderer = Renderer.install({ + id: bidResponse.requestId, + adUnitCode: context.bidRequest.adUnitCode, + url: bidResponse.rendererUrl + }); + + bidResponse.renderer.setRender(_renderer(bidResponse)); + + bidResponse.renderer.render(bidResponse); + } + + return {...bidResponse}; +} + +function getBidFloor(bid) { + const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; + + if (typeof bid.getFloor !== 'function') { + return utils.deepAccess(bid, 'params.bidFloor', 0.05); + } + + const bidFloor = bid.getFloor({ + currency: currency, + mediaType: '*', + size: '*', + }); + return bidFloor.floor; +} + +function _renderer(bid) { + bid.renderer.push(() => { + if (typeof window.SPOutStreamPlayer === 'function') { + const spoplayer = new window.SPOutStreamPlayer(bid); + + spoplayer.on('ready', () => { + spoplayer.startAd(); + }); + + try { + let vastUrlbt = 'data:text/xml;charset=utf-8;base64,' + btoa(bid.vastUrl.replace(/\\"/g, '"')); + spoplayer.load(vastUrlbt).then(function() { + window.spoplayer = spoplayer; + }).catch(function(reason) { + setTimeout(function() { throw reason; }, 0); + }); + } catch (err) { + utils.logMessage(err); + } + } else { + utils.logMessage(`Silverpush outstream player is not defined`); + } + }); +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function interpretResponse(resp, req) { + if (!resp.body) { + resp.body = { nbr: 0 }; + } + + return CONVERTER.fromORTB({ request: req.data, response: resp.body }); +} + +function onBidWon(bid) { + if (bid == null) { return; } + if (bid['burl'] == null) { return; } + + let burlMac = bid['burl']; + burlMac = burlMac.replace('$' + '{AUCTION_PRICE}', bid['cpm']); + burlMac = burlMac.replace('$' + '{AUCTION_ID}', bid['auctionId']); + burlMac = burlMac.replace('$' + '{AUCTION_IMP_ID}', bid['requestId']); + burlMac = burlMac.replace('$' + '{AUCTION_AD_ID}', bid['adId']); + burlMac = burlMac.replace('$' + '{AUCTION_SEAT_ID}', bid['seatBidId']); + + spec.getRequest(burlMac); +} + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} diff --git a/modules/silverpushBidAdapter.md b/modules/silverpushBidAdapter.md new file mode 100644 index 00000000000..d0af8ba8da8 --- /dev/null +++ b/modules/silverpushBidAdapter.md @@ -0,0 +1,107 @@ +# Overview + +``` +Module Name: Silverpush OpenRTB Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@silverpush.co +``` + +# Description + +Prebid.JS adapter that connects to the Chocolate Ad Exchange. + +*NOTE*: The Silverpush Bidder Adapter requires setup and approval before use. Please reach out to prebid@silverpush.co representative for more details. + +# Bid Parameters + +## Banner/Video + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +| -------------- | ----------- | ------------------------------------------ | ------------- | ------------ | +| `publisherId` | required | Publisher id provided by silverpush | "123456" | String | +| `bidFloor` | optional | Minimum price in USD. bidFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50.
| 1.50 | Number | + + + + +# mediaTypes Parameters + +## mediaTypes.banner + +The following banner parameters are supported here so publishers may fully declare their banner inventory: + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- | +| sizes | required | Avalaible sizes supported for banner ad unit | [ [300, 250], [300, 600] ] | [[Integer, Integer], [Integer, Integer]] | + +## mediaTypes.video + + +The following video parameters are supported here so publishers may fully declare their video inventory: + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- | +| context | required | instream or outstream |"outstream" | string | +| playerSize | required | Avalaible sizes supported for video ad unit. | [300, 250] | [Integer, Integer] | +| mimes | required | List of content MIME types supported by the player. | ["video/mp4"]| [String]| +| protocols | required | Supported video bid response protocol values. | [2,3,5,6] | [integers]| +| api | required | Supported API framework values. | [2] | [integers] | +| maxduration | required | Maximum video ad duration in seconds. | 30 | Integer | +| minduration | required | Minimum video ad duration in seconds. | 6 | Integer | +| startdelay | required | Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. | 0 | Integer | +| placement | required | Placement type for the impression. | 1 | Integer | +| minbitrate | optional | Minimum bit rate in Kbps. | 300 | Integer | +| maxbitrate | optional | Maximum bit rate in Kbps. | 9600 | Integer | +| playbackmethod | optional | Playback methods that may be in use. Only one method is typically used in practice. | [2]| [Integers] | +| linearity | optional | OpenRTB2 linearity. in-strea,overlay... | 1 | Integer | +| skip | optional | Indicates if the player will allow the video to be skipped, where 0 = no, 1 = yes . | 1 | Integer | +| skipafter | optional | Number of seconds a video must play before skipping is enabled; only applicable if the ad is skippable. | 5 | Integer | +| delivery | optional | OpenRTB2 delivery. Supported delivery methods (e.g., streaming, progressive). If none specified, assume all are supported. | 1 | [Integer] | + + +# Example +```javascript + var adUnits = [{ + code: 'div-1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [300,600] ] + } + }, + bids: [{ + bidder: 'silverpush', + params: { + publisherId: "123456", + bidFloor: 1.2 + } + }] + },{ + code: 'video-1', + mediaTypes: { + video: { + api: [1, 2, 4, 6], + mimes: ['video/mp4'], + context: 'instream', // or 'outstream' + playerSize: [ 640, 480 ], + protocols: [4,5,6,7], + placement: 1, + minduration: 0, + maxduration: 60, + startdelay: 0 + } + }, + bids: [ + { + bidder: 'silverpush', + params: { + publisherId: "123456", + bidfloor: 2.5 + } + } + ] + } +]; +``` diff --git a/test/spec/modules/silverpushBidAdapter_spec.js b/test/spec/modules/silverpushBidAdapter_spec.js new file mode 100644 index 00000000000..de31135eabe --- /dev/null +++ b/test/spec/modules/silverpushBidAdapter_spec.js @@ -0,0 +1,394 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { REQUEST_URL, SP_OUTSTREAM_PLAYER_URL, CONVERTER, spec } from '../../../modules/silverpushBidAdapter.js'; + +const bannerBid = { + bidder: 'silverpush', + params: { + publisherId: '012345', + bidFloor: 1.5 + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 50], + ], + }, + }, + adUnitCode: 'div-gpt-ad-928572628472-0', + bidId: 'dl38fjf9d', + bidderRequestId: 'brid00000000', + auctionId: 'aucid0000000', +}; + +const videoBid = { + bidder: 'silverpush', + params: { + publisherId: '012345', + bidFloor: 0.1 + }, + mediaTypes: { + video: { + api: [1, 2, 4, 6], + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + playerSize: [[1024, 768]], + protocols: [3, 4, 7, 8, 10], + placement: 1, + minduration: 0, + maxduration: 60, + startdelay: 0 + }, + }, + adUnitCode: 'div-gpt-ad-928572628472-1', + bidId: '281141d3541362', + bidderRequestId: 'brid00000000', + auctionId: 'aucid0000000', +}; + +const bidderRequest = { + auctionId: 'aucid0000000', + bidderRequestId: 'brid00000000', + timeout: 200, + refererInfo: { + page: 'https://hello-world-page.com/', + domain: 'hello-world-page.com', + ref: 'http://example-domain.com/foo', + } +}; + +const bannerReponse = { + 'id': 'brid00000000', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'ARUYoUZx', + 'impid': 'dl38fjf9d', + 'price': 1.64, + 'adid': 'aaaaadddddddd', + 'burl': 'http://0.0.0.0:8181/burl', + 'adm': '
', + 'adomain': [ + 'https://www.exampleabc.com' + ], + 'iurl': 'https://example.example.com/2.png', + 'cid': 'aaaaadddddddd', + 'crid': 'aaaaadddddddd', + 'h': 250, + 'w': 300 + } + ] + } + ], + 'bidid': 'ARUYoUZx', + 'cur': 'USD' +} + +const videoResponse = { + 'id': 'brid00000000', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'soCWeklh', + 'impid': '281141d3541362', + 'price': 1.09, + 'adid': 'outstream_video', + 'burl': 'http://0.0.0.0:8181/burl', + 'adm': '\n', + 'adomain': [ + 'https://www.exampleabc.com' + ], + 'cid': '229369649', + 'crid': 'aaaaadddddddd', + 'h': 768, + 'w': 1024 + } + ] + } + ], + 'bidid': 'soCWeklh', + 'cur': 'USD' +} + +describe('Silverpush Adapter', function () { + describe('isBidRequestValid()', () => { + it('should return false when publisherId is not defined', () => { + const bid = utils.deepClone(bannerBid); + delete bid.params.publisherId; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when publisherId is empty string', () => { + const bid = utils.deepClone(bannerBid); + bid.params.publisherId = ''; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when publisherId is a number', () => { + const bid = utils.deepClone(bannerBid); + bid.params.publisherId = 12345; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when there is no banner in mediaTypes', () => { + const bid = utils.deepClone(bannerBid); + delete bid.mediaTypes.banner; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when sizes for banner are not specified', () => { + const bid = utils.deepClone(bannerBid); + delete bid.mediaTypes.banner.sizes; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when there is no video in mediaTypes', () => { + const bid = utils.deepClone(videoBid); + delete bid.mediaTypes.video; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should reutrn false if player size is not set', () => { + const bid = utils.deepClone(videoBid); + delete bid.mediaTypes.video.playerSize; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bannerBid)).to.equal(true); + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + }); + }); + + describe('buildRequests()', () => { + it('should build correct request for banner bid with both w, h', () => { + const bid = utils.deepClone(bannerBid); + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].banner.w).to.equal(300); + expect(requestData.imp[0].banner.h).to.equal(250); + }); + + it('should return default bidfloor when bidFloor is not defined', () => { + const bid = utils.deepClone(bannerBid); + delete bid.params.bidFloor; + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].bidfloor).to.equal(0.05); + }); + + it('should contain deals in request if deal is specified in params', () => { + const bid = utils.deepClone(bannerBid); + bid.params.deals = [{ id: 'test' }]; + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].pmp.deals).to.equal(bid.params.deals); + }); + + it('should return bidfloor when bidFloor is defined', () => { + const bid = utils.deepClone(bannerBid); + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].bidfloor).to.equal(bannerBid.params.bidFloor); + }); + + it('should build correct request for video bid with playerSize', () => { + const bid = utils.deepClone(videoBid); + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].video.w).to.equal(1024); + expect(requestData.imp[0].video.h).to.equal(768); + }); + + it('should use bidder video params if they are set', () => { + const videoBidWithParams = utils.deepClone(videoBid); + const bidderVideoParams = { + api: [1, 2], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [3, 4], + protocols: [5, 6], + placement: 1, + minduration: 0, + maxduration: 60, + w: 1024, + h: 768, + startdelay: 0 + }; + + videoBidWithParams.params.video = bidderVideoParams; + + const requests = spec.buildRequests([videoBidWithParams], bidderRequest); + const request = requests[0].data; + + expect(request.imp[0]).to.deep.include({ + video: { + ...bidderVideoParams, + w: videoBidWithParams.mediaTypes.video.playerSize[0][0], + h: videoBidWithParams.mediaTypes.video.playerSize[0][1], + }, + }); + }); + }); + + describe('getOS()', () => { + it('shold return correct os name for Windows', () => { + let userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('Windows'); + }); + + it('shold return correct os name for Mac OS', () => { + let userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('macOS'); + }); + + it('shold return correct os name for Android', () => { + let userAgent = 'Mozilla/5.0 (Linux; Android 10; SM-G996U Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('Android'); + }); + + it('shold return correct os name for ios', () => { + let userAgent = 'Mozilla/5.0 (iPhone14,3; U; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/19A346 Safari/602.1'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('iOS'); + }); + + it('shold return correct os name for Linux', () => { + let userAgent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('Linux'); + }); + }); + + describe('interpretResponse()', () => { + it('should return nbr to 0 when response not received', () => { + const requests = spec.buildRequests([bannerBid], bidderRequest); + const bids = spec.interpretResponse({ body: null }, requests[0]); + + expect(bids[0]).to.equal(undefined); + }); + + it('should correctly interpret valid banner response', () => { + const response = utils.deepClone(bannerReponse); + const requests = spec.buildRequests([bannerBid], bidderRequest); + const bids = spec.interpretResponse({ body: response }, requests[0]); + + expect(bids[0].ad).to.equal('
'); + expect(bids[0].burl).to.equal('http://0.0.0.0:8181/burl'); + expect(bids[0].cpm).to.equal(1.64); + expect(bids[0].creativeId).to.equal('aaaaadddddddd'); + expect(bids[0].creative_id).to.equal('aaaaadddddddd'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].height).to.equal(250); + expect(bids[0].mediaType).to.equal('banner'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://www.exampleabc.com'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].requestId).to.equal('dl38fjf9d'); + expect(bids[0].seatBidId).to.equal('ARUYoUZx'); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].width).to.equal(300); + }); + + if (FEATURES.VIDEO) { + it('should correctly interpret valid instream video response', () => { + const response = utils.deepClone(videoResponse); + videoBid.mediaTypes.video.context = 'outstream'; + const requests = spec.buildRequests([videoBid], bidderRequest); + const bids = spec.interpretResponse({ body: response }, requests[0]); + + expect(bids[0].vastXml).to.equal('\n'); + expect(bids[0].burl).to.equal('http://0.0.0.0:8181/burl'); + expect(bids[0].cpm).to.equal(1.09); + expect(bids[0].creativeId).to.equal('aaaaadddddddd'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://www.exampleabc.com'); + expect(bids[0].requestId).to.equal('281141d3541362'); + expect(bids[0].seatBidId).to.equal('soCWeklh'); + expect(bids[0].width).to.equal(1024); + expect(bids[0].height).to.equal(768); + }); + + it('should correctly interpret valid outstream video response', () => { + const response = utils.deepClone(videoResponse); + videoBid.mediaTypes.video.context = 'outstream'; + + const requests = spec.buildRequests([videoBid], bidderRequest); + const bids = spec.interpretResponse({ body: response }, requests[0]); + + expect(bids[0].vastXml).to.equal('\n'); + expect(bids[0].rendererUrl).to.equal(SP_OUTSTREAM_PLAYER_URL); + expect(bids[0].renderer.url).to.equal(SP_OUTSTREAM_PLAYER_URL); + expect(bids[0].burl).to.equal('http://0.0.0.0:8181/burl'); + expect(bids[0].cpm).to.equal(1.09); + expect(bids[0].creativeId).to.equal('aaaaadddddddd'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://www.exampleabc.com'); + expect(bids[0].requestId).to.equal('281141d3541362'); + expect(bids[0].seatBidId).to.equal('soCWeklh'); + expect(bids[0].width).to.equal(1024); + expect(bids[0].height).to.equal(768); + }); + } + }); + + describe('onBidWon', function() { + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'getRequest') + }) + + afterEach(() => { + ajaxStub.restore() + }) + + it('Should not trigger pixel if bid does not contain burl', function() { + const result = spec.onBidWon({}); + + expect(ajaxStub.calledOnce).to.equal(false); + }) + + it('Should trigger pixel with correct macros if bid burl is present', function() { + const result = spec.onBidWon({ + cpm: 1.5, + auctionId: 'auc123', + requestId: 'req123', + adId: 'ad1234', + seatBidId: 'sea123', + burl: 'http://won.foo.bar/trk?ap=${AUCTION_PRICE}&aid=${AUCTION_ID}&imp=${AUCTION_IMP_ID}&adid=${AUCTION_AD_ID}&sid=${AUCTION_SEAT_ID}' + }); + + expect(ajaxStub.calledOnceWith('http://won.foo.bar/trk?ap=1.5&aid=auc123&imp=req123&adid=ad1234&sid=sea123')).to.equal(true); + }) + }) +}); From 38cf6341ffab2b9aa3fb9f171930c1b22d627957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Mon, 15 May 2023 19:32:36 +0200 Subject: [PATCH 22/38] Criteo Bid Adapter: add eids to bid request sent to bidder (#9949) --- modules/criteoBidAdapter.js | 10 +++++- test/spec/modules/criteoBidAdapter_spec.js | 36 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index e6f1d0d8aa1..edb718ec953 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -410,6 +410,7 @@ function checkNativeSendId(bidRequest) { function buildCdbRequest(context, bidRequests, bidderRequest) { let networkId; let schain; + let userIdAsEids; const request = { publisher: { url: context.url, @@ -421,6 +422,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { gpp_sid: bidderRequest.ortb2?.regs?.gpp_sid }, slots: bidRequests.map(bidRequest => { + if (!userIdAsEids) { + userIdAsEids = bidRequest.userIdAsEids; + } networkId = bidRequest.params.networkId || networkId; schain = bidRequest.schain || schain; const slot = { @@ -438,7 +442,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); } if (bidRequest.nativeOrtbRequest?.assets) { - slot.ext = Object.assign({}, slot.ext, {assets: bidRequest.nativeOrtbRequest.assets}); + slot.ext = Object.assign({}, slot.ext, { assets: bidRequest.nativeOrtbRequest.assets }); } if (bidRequest.params.publisherSubId) { slot.publishersubid = bidRequest.params.publisherSubId; @@ -519,6 +523,10 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { request.user.ext = request.user.ext || {}; request.user.ext.sua = bidderRequest.ortb2?.device?.sua || {}; } + if (userIdAsEids) { + request.user.ext = request.user.ext || {}; + request.user.ext.eids = [...userIdAsEids]; + } return request; } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index c940f1fa2f5..d88e70a6cdc 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -710,6 +710,42 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].native).to.equal(true); }); + it('should properly forward eids', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + userIdAsEids: [ + { + source: 'criteo.com', + uids: [{ + id: 'abc', + atype: 1 + }] + } + ], + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.user.ext.eids).to.deep.equal([ + { + source: 'criteo.com', + uids: [{ + id: 'abc', + atype: 1 + }] + } + ]); + }); + it('should properly detect and forward native flag', function () { const bidRequests = [ { From 3de4b6720d2f6500801026e04e66a62dadbcccf7 Mon Sep 17 00:00:00 2001 From: Antonio Gargaro <38767071+AntonioGargaro@users.noreply.github.com> Date: Mon, 15 May 2023 20:32:28 +0200 Subject: [PATCH 23/38] fix(permutiveRtd): remove ozone map and set site ext (#9628) --- modules/permutiveRtdProvider.js | 47 ++++--------------- .../spec/modules/permutiveRtdProvider_spec.js | 20 -------- 2 files changed, 9 insertions(+), 58 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index ab827f2b6a5..697d7721205 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -201,6 +201,12 @@ function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transfo logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}"`, customCohortsData) } + // Set site extensions + if (segmentIDs.length > 0) { + deepSetValue(ortbConfig, `ortb2.site.ext.permutive.${PERMUTIVE_STANDARD_KEYWORD}`, segmentIDs) + logger.logInfo(`Extending ortb2.site.ext.permutive with "${PERMUTIVE_STANDARD_KEYWORD}"`, segmentIDs) + } + logger.logInfo(`Updated ortb2 config`, { bidder, config: ortbConfig }) return ortbConfig } @@ -230,12 +236,11 @@ function setSegments (reqBidsConfigObj, moduleConfig, segmentData) { } const acEnabled = isAcEnabled(moduleConfig, bidder) const customFn = getCustomBidderFn(moduleConfig, bidder) - const defaultFn = getDefaultBidderFn(bidder) if (customFn) { - customFn(bid, segmentData, acEnabled, utils, defaultFn) - } else if (defaultFn) { - defaultFn(bid, segmentData, acEnabled) + // For backwards compatibility we pass an identity function to any custom bidder function set by a publisher + const bidIdentity = (bid) => bid + customFn(bid, segmentData, acEnabled, utils, bidIdentity) } }) }) @@ -263,40 +268,6 @@ function getCustomBidderFn (moduleConfig, bidder) { } } -/** - * Returns a function that receives a `bid` object, a `data` object and a `acEnabled` boolean - * and which will set the right segment targeting keys for `bid` based on `data` and `acEnabled` - * @param {string} bidder - Bidder name - * @return {Object} Bidder function - */ -function getDefaultBidderFn (bidder) { - const isPStandardTargetingEnabled = (data, acEnabled) => { - return (acEnabled && data.ac && data.ac.length) || (data.ssp && data.ssp.cohorts && data.ssp.cohorts.length) - } - const pStandardTargeting = (data, acEnabled) => { - const ac = (acEnabled) ? (data.ac ?? []) : [] - const ssp = data?.ssp?.cohorts ?? [] - return [...new Set([...ac, ...ssp])] - } - const bidderMap = { - ozone: function (bid, data, acEnabled) { - if (isPStandardTargetingEnabled(data, acEnabled)) { - const segments = pStandardTargeting(data, acEnabled) - deepSetValue(bid, 'params.customData.0.targeting.p_standard', segments) - } - - return bid - } - } - - // On no default bidder just return the same bid as passed in - function bidIdentity(bid) { - return bid - } - - return bidderMap[bidder] || bidIdentity -} - /** * Check whether ac is enabled for bidder * @param {Object} moduleConfig - Module configuration diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 1f39d1a2cda..942ec2eaa46 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -560,26 +560,6 @@ describe('permutiveRtdProvider', function () { }) }) - describe('Default segment targeting', function () { - it('sets segment targeting for Ozone', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() - - readAndSetCohorts({ adUnits }, config) - - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - }) - }) - describe('Existing key-value targeting', function () { it('doesn\'t overwrite existing key-values for Xandr', function () { const adUnits = getAdUnits() From 1e25e2942193bc090f14a7c80d6b1976d1de200e Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Mon, 15 May 2023 12:20:26 -0700 Subject: [PATCH 24/38] Revert "Rubicon Bid Adapter : access x_source.tid from ortb2 object (#9940)" (#9953) This reverts commit ba6a97fe81fc9ff44144e7ac8d3b5edae0a58b72. --- modules/rubiconBidAdapter.js | 2 +- test/spec/modules/rubiconBidAdapter_spec.js | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 041dd5bcc99..62ec61b3dcf 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -466,7 +466,7 @@ export const spec = { 'rp_floor': (params.floor = parseFloat(params.floor)) >= 0.01 ? params.floor : undefined, 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, - 'x_source.tid': deepAccess(bidderRequest, 'ortb2.source.tid'), + 'x_source.tid': bidRequest.transactionId, 'x_imp.ext.tid': bidRequest.transactionId, 'l_pb_bid_id': bidRequest.bidId, 'p_screen_res': _getScreenResolution(), diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 048c68f44e7..4f1c0dd606a 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -89,7 +89,6 @@ describe('the rubicon adapter', function () { bidderCode: 'rubicon', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', bidderRequestId: '178e34bad3658f', - ortb2: { source: { tid: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' } }, bids: [ { bidder: 'rubicon', @@ -347,7 +346,6 @@ describe('the rubicon adapter', function () { transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' } ], - ortb2: { source: { tid: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' } }, start: 1472239426002, auctionStart: 1472239426000, timeout: 5000 @@ -621,6 +619,7 @@ describe('the rubicon adapter', function () { 'rand': '0.1', 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2277,8 +2276,6 @@ describe('the rubicon adapter', function () { }); describe('createSlotParams', function () { - const localBidderRequest = Object.assign({}, bidderRequest); - localBidderRequest.ortb2 = {source: {tid: 'faked707-a418-42ec-b8a7-b70a6c6fab0b'}}; it('should return a valid slot params object', function () { let expectedQuery = { 'account_id': '14062', @@ -2289,8 +2286,7 @@ describe('the rubicon adapter', function () { 'p_pos': 'atf', 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, - 'x_source.tid': 'faked707-a418-42ec-b8a7-b70a6c6fab0b', - 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2303,7 +2299,7 @@ describe('the rubicon adapter', function () { 'rf': 'localhost' }; - const slotParams = spec.createSlotParams(bidderRequest.bids[0], localBidderRequest); + const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); // test that all values above are both present and correct Object.keys(expectedQuery).forEach(key => { From 667bbda82a49fcf83e81cfb8c22e07fa6b9c4cc0 Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Tue, 16 May 2023 17:13:58 +0200 Subject: [PATCH 25/38] RubiconBidAdapter: sync parseSize algorithm for isBidRequestValid and ortb conversion (#9957) * sync parseSize algorithm for isBidRequestValid and ortb conversion * use same sizes of parseSizes in video outstream * general test refactor to be less flacky --------- Co-authored-by: Michele Nasti --- modules/rubiconBidAdapter.js | 6 +- test/spec/modules/rubiconBidAdapter_spec.js | 142 +++++++++++--------- 2 files changed, 79 insertions(+), 69 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 62ec61b3dcf..5c30b413780 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -208,8 +208,10 @@ export const converter = ortbConverter({ bidResponse.meta.mediaType = deepAccess(bid, 'ext.prebid.type'); const {bidRequest} = context; - bidResponse.width = bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth') || bidResponse.playerWidth; - bidResponse.height = bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight') || bidResponse.playerHeight; + let [parseSizeWidth, parseSizeHeight] = bidRequest.mediaTypes.video?.context === 'outstream' ? parseSizes(bidRequest, VIDEO) : [undefined, undefined]; + + bidResponse.width = bid.w || parseSizeWidth || bidResponse.playerWidth; + bidResponse.height = bid.h || parseSizeHeight || bidResponse.playerHeight; if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = outstreamRenderer(bidResponse); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 4f1c0dd606a..0c8477a0fcb 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -192,6 +192,7 @@ describe('the rubicon adapter', function () { * @param {boolean} [gdprApplies] */ function createGdprBidderRequest(gdprApplies) { + const bidderRequest = getBidderRequest(); if (typeof gdprApplies === 'boolean') { bidderRequest.gdprConsent = { 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', @@ -202,15 +203,16 @@ describe('the rubicon adapter', function () { 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' }; } + return bidderRequest; } - function createUspBidderRequest() { + function addUspToBidderRequest(bidderRequest) { bidderRequest.uspConsent = '1NYN'; } function createVideoBidderRequest() { - createGdprBidderRequest(true); - createUspBidderRequest(); + const bidderRequest = createGdprBidderRequest(true); + addUspToBidderRequest(bidderRequest); let bid = bidderRequest.bids[0]; bid.mediaTypes = { @@ -260,9 +262,10 @@ describe('the rubicon adapter', function () { criteoId: '1111', }; bid.userIdAsEids = createEidsArray(bid.userId); + return bidderRequest; } - function createVideoBidderRequestNoVideo() { + function removeVideoParamFromBidderRequest(bidderRequest) { let bid = bidderRequest.bids[0]; bid.mediaTypes = { video: { @@ -273,7 +276,9 @@ describe('the rubicon adapter', function () { } function createVideoBidderRequestOutstream() { + const bidderRequest = createGdprBidderRequest(false); let bid = bidderRequest.bids[0]; + delete bid.sizes; bid.mediaTypes = { video: { context: 'outstream', @@ -291,17 +296,20 @@ describe('the rubicon adapter', function () { protocols: [1, 2, 3, 4, 5, 6] }, }; - bid.params.accountId = 14062; - bid.params.siteId = 70608; - bid.params.zoneId = 335918; - bid.params.video = { - 'language': 'en', - 'skip': 1, - 'skipafter': 15, - 'playerHeight': 320, - 'playerWidth': 640, - 'size_id': 203 - }; + bid.params = { + accountId: 14062, + siteId: 70608, + zoneId: 335918, + video: { + 'language': 'en', + 'skip': 1, + 'skipafter': 15, + 'playerHeight': 320, + 'playerWidth': 640, + 'size_id': 203 + } + } + return bidderRequest; } beforeEach(function () { @@ -744,7 +752,7 @@ describe('the rubicon adapter', function () { describe('GDPR consent config', function () { it('should send "gdpr" and "gdpr_consent", when gdprConsent defines consentString and gdprApplies', function () { - createGdprBidderRequest(true); + const bidderRequest = createGdprBidderRequest(true); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); @@ -753,7 +761,7 @@ describe('the rubicon adapter', function () { }); it('should send only "gdpr_consent", when gdprConsent defines only consentString', function () { - createGdprBidderRequest(); + const bidderRequest = createGdprBidderRequest(); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); @@ -770,12 +778,12 @@ describe('the rubicon adapter', function () { }); it('should set "gdpr" value as 1 or 0, using "gdprApplies" value of either true/false', function () { - createGdprBidderRequest(true); + let bidderRequest = createGdprBidderRequest(true); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); expect(data['gdpr']).to.equal('1'); - createGdprBidderRequest(false); + bidderRequest = createGdprBidderRequest(false); [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); data = parseQuery(request.data); expect(data['gdpr']).to.equal('0'); @@ -784,7 +792,7 @@ describe('the rubicon adapter', function () { describe('USP Consent', function () { it('should send us_privacy if bidderRequest has a value for uspConsent', function () { - createUspBidderRequest(); + addUspToBidderRequest(bidderRequest); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); @@ -1542,7 +1550,7 @@ describe('the rubicon adapter', function () { if (FEATURES.VIDEO) { describe('for video requests', function () { it('should make a well-formed video request', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 @@ -1628,7 +1636,7 @@ describe('the rubicon adapter', function () { }); it('should add ortb values to video requests', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 @@ -1661,7 +1669,7 @@ describe('the rubicon adapter', function () { }); it('should correctly set bidfloor on imp when getfloor in scope', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); // default getFloor response is empty object so should not break and not send hard_floor bidderRequest.bids[0].getFloor = () => getFloorResponse; sinon.spy(bidderRequest.bids[0], 'getFloor'); @@ -1706,7 +1714,7 @@ describe('the rubicon adapter', function () { }); it('should continue with auction if getFloor throws error', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); // default getFloor response is empty object so should not break and not send hard_floor bidderRequest.bids[0].getFloor = () => { throw new Error('An exception!'); @@ -1727,7 +1735,7 @@ describe('the rubicon adapter', function () { }); it('should add alias name to PBS Request', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); adapterManager.aliasRegistry['superRubicon'] = 'rubicon'; bidderRequest.bidderCode = 'superRubicon'; bidderRequest.bids[0].bidder = 'superRubicon'; @@ -1743,7 +1751,7 @@ describe('the rubicon adapter', function () { }); it('should add floors flag correctly to PBS Request', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); // should not pass if undefined @@ -1759,7 +1767,7 @@ describe('the rubicon adapter', function () { }); it('should add multibid configuration to PBS Request', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); const multibid = [{ bidder: 'bidderA', @@ -1786,7 +1794,7 @@ describe('the rubicon adapter', function () { }); it('should pass client analytics to PBS endpoint if all modules included', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); $$PREBID_GLOBAL$$.installedModules = []; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let payload = request.data; @@ -1796,7 +1804,7 @@ describe('the rubicon adapter', function () { }); it('should pass client analytics to PBS endpoint if rubicon analytics adapter is included', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter', 'rubiconAnalyticsAdapter']; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let payload = request.data; @@ -1806,7 +1814,7 @@ describe('the rubicon adapter', function () { }); it('should not pass client analytics to PBS endpoint if rubicon analytics adapter is not included', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter']; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let payload = request.data; @@ -1815,7 +1823,7 @@ describe('the rubicon adapter', function () { }); it('should send video exp param correctly when set', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); config.setConfig({s2sConfig: {defaultTtl: 600}}); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let post = request.data; @@ -1826,7 +1834,7 @@ describe('the rubicon adapter', function () { }); it('should not send video exp at all if not set in s2sConfig config', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let post = request.data; @@ -1837,7 +1845,7 @@ describe('the rubicon adapter', function () { }); it('should send tmax as the bidderRequest timeout value', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); bidderRequest.timeout = 3333; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let post = request.data; @@ -1845,7 +1853,7 @@ describe('the rubicon adapter', function () { }); it('should send correct bidfloor to PBS', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); bidderRequest.bids[0].params.floor = 0.1; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); @@ -1873,7 +1881,7 @@ describe('the rubicon adapter', function () { }); it('should send request with proper ad position', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); let positionBidderRequest = utils.deepClone(bidderRequest); positionBidderRequest.bids[0].mediaTypes.video.pos = 1; let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); @@ -1943,7 +1951,7 @@ describe('the rubicon adapter', function () { }); it('should enforce the new required mediaTypes.video params', function () { - createVideoBidderRequest(); + let bidderRequest = createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 @@ -1952,48 +1960,48 @@ describe('the rubicon adapter', function () { expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); // change mimes to a non array, no good - createVideoBidderRequest(); + bidderRequest = createVideoBidderRequest(); bidderRequest.bids[0].mediaTypes.video.mimes = 'video/mp4'; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); // delete mimes, no good - createVideoBidderRequest(); + bidderRequest = createVideoBidderRequest(); delete bidderRequest.bids[0].mediaTypes.video.mimes; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); // change protocols to an int not array of ints, no good - createVideoBidderRequest(); + bidderRequest = createVideoBidderRequest(); bidderRequest.bids[0].mediaTypes.video.protocols = 1; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); // delete protocols, no good - createVideoBidderRequest(); + bidderRequest = createVideoBidderRequest(); delete bidderRequest.bids[0].mediaTypes.video.protocols; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); // change linearity to an string, no good - createVideoBidderRequest(); + bidderRequest = createVideoBidderRequest(); bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); // delete linearity, no good - createVideoBidderRequest(); + bidderRequest = createVideoBidderRequest(); delete bidderRequest.bids[0].mediaTypes.video.linearity; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); // change api to an string, no good - createVideoBidderRequest(); + bidderRequest = createVideoBidderRequest(); bidderRequest.bids[0].mediaTypes.video.api = 'string'; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); // delete api, no good - createVideoBidderRequest(); + bidderRequest = createVideoBidderRequest(); delete bidderRequest.bids[0].mediaTypes.video.api; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); }); it('bid request is valid when video context is outstream', function () { - createVideoBidderRequestOutstream(); + const bidderRequest = createVideoBidderRequestOutstream(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); @@ -2035,7 +2043,7 @@ describe('the rubicon adapter', function () { }); it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { - createVideoBidderRequestNoVideo(); + removeVideoParamFromBidderRequest(bidderRequest); let bid = bidderRequest.bids[0]; bid.mediaTypes.banner = { @@ -2054,7 +2062,7 @@ describe('the rubicon adapter', function () { }); it('should include coppa flag in video bid request', () => { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 @@ -2071,7 +2079,7 @@ describe('the rubicon adapter', function () { }); it('should include first party data', () => { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); const site = { ext: { @@ -2123,7 +2131,7 @@ describe('the rubicon adapter', function () { }); it('should include pbadslot in bid request', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); bidderRequest.bids[0].ortb2Imp = { ext: { data: { @@ -2141,7 +2149,7 @@ describe('the rubicon adapter', function () { }); it('should NOT include storedrequests in pbs payload', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); bidderRequest.bids[0].ortb2 = { ext: { prebid: { @@ -2168,7 +2176,7 @@ describe('the rubicon adapter', function () { }); it('should include GAM ad unit in bid request', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); bidderRequest.bids[0].ortb2Imp = { ext: { data: { @@ -2190,7 +2198,7 @@ describe('the rubicon adapter', function () { }); it('should use the integration type provided in the config instead of the default', () => { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); config.setConfig({rubicon: {int_type: 'testType'}}); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); @@ -2198,7 +2206,7 @@ describe('the rubicon adapter', function () { it('should pass the user.id provided in the config', function () { config.setConfig({user: {id: '123'}}); - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 @@ -2321,13 +2329,13 @@ describe('the rubicon adapter', function () { describe('classifiedAsVideo', function () { it('should return true if mediaTypes is video', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); const bidClassifiedAsVideo = classifiedAsVideo(bidderRequest.bids[0]); expect(bidClassifiedAsVideo).is.equal(true); }); it('should return false if trying to use legacy mediaType with video', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); delete bidderRequest.bids[0].mediaTypes; bidderRequest.bids[0].mediaType = 'video'; const legacyVideoTypeBidRequest = classifiedAsVideo(bidderRequest.bids[0]); @@ -2345,13 +2353,13 @@ describe('the rubicon adapter', function () { }); it('Should return false if both banner and video mediaTypes are set and params.video is not an object', function () { - createVideoBidderRequestNoVideo(); + removeVideoParamFromBidderRequest(bidderRequest); let bid = bidderRequest.bids[0]; bid.mediaTypes.banner = {flag: true}; expect(classifiedAsVideo(bid)).to.equal(false); }); it('Should return true if both banner and video mediaTypes are set and params.video is an object', function () { - createVideoBidderRequestNoVideo(); + removeVideoParamFromBidderRequest(bidderRequest); let bid = bidderRequest.bids[0]; bid.mediaTypes.banner = {flag: true}; bid.params.video = {}; @@ -2359,7 +2367,7 @@ describe('the rubicon adapter', function () { }); it('Should return true and create a params.video object if one is not already present', function () { - createVideoBidderRequestNoVideo(); + removeVideoParamFromBidderRequest(bidderRequest); let bid = bidderRequest.bids[0] expect(classifiedAsVideo(bid)).to.equal(true); expect(bid.params.video).to.not.be.undefined; @@ -3213,11 +3221,8 @@ describe('the rubicon adapter', function () { if (FEATURES.VIDEO) { describe('for video', function () { - beforeEach(function () { - createVideoBidderRequest(); - }); - it('should register a successful bid', function () { + const bidderRequest = createVideoBidderRequest(); let response = { cur: 'USD', seatbid: [{ @@ -3288,7 +3293,6 @@ describe('the rubicon adapter', function () { describe('for outstream video', function () { const sandbox = sinon.createSandbox(); beforeEach(function () { - createVideoBidderRequestOutstream(); config.setConfig({rubicon: { rendererConfig: { align: 'left', @@ -3309,6 +3313,7 @@ describe('the rubicon adapter', function () { }); it('should register a successful bid', function () { + const bidderRequest = createVideoBidderRequestOutstream(); let response = { cur: 'USD', seatbid: [{ @@ -3369,6 +3374,7 @@ describe('the rubicon adapter', function () { }); it('should render ad with Magnite renderer', function () { + const bidderRequest = createVideoBidderRequestOutstream(); let response = { cur: 'USD', seatbid: [{ @@ -3432,6 +3438,7 @@ describe('the rubicon adapter', function () { }); it('should render ad with Magnite renderer without video object', function () { + const bidderRequest = createVideoBidderRequestOutstream(); delete bidderRequest.bids[0].params.video; bidderRequest.bids[0].params.bidonmultiformat = true; bidderRequest.bids[0].mediaTypes.video.placement = 3; @@ -3749,7 +3756,7 @@ describe('the rubicon adapter', function () { }); it('should copy the schain JSON to to bid.source.ext.schain', () => { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); const schain = getSupplyChainConfig(); bidderRequest.bids[0].schain = schain; const request = spec.buildRequests(bidderRequest.bids, bidderRequest); @@ -3785,12 +3792,13 @@ describe('the rubicon adapter', function () { }); // banner - let [bannerRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const bannerBidderRequest = createGdprBidderRequest(false); + let [bannerRequest] = spec.buildRequests(bannerBidderRequest.bids, bannerBidderRequest); expect(bannerRequest.url).to.equal('https://fastlane-qa.rubiconproject.com/a/api/fastlane.json'); // video and returnVast - createVideoBidderRequest(); - let [videoRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const videoBidderRequest = createVideoBidderRequest(); + let [videoRequest] = spec.buildRequests(videoBidderRequest.bids, videoBidderRequest); let post = videoRequest.data; expect(videoRequest.url).to.equal('https://prebid-server-qa.rubiconproject.com/openrtb2/auction'); expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); From 99c9869adee4e8de87f0279542df911ce97bcf98 Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Tue, 16 May 2023 12:27:30 -0600 Subject: [PATCH 26/38] Access x_source.tid from ortb2 object (#9960) * Access x_source.tid from ortb2 object * Fix lint errors --- modules/rubiconBidAdapter.js | 2 +- test/spec/modules/rubiconBidAdapter_spec.js | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 5c30b413780..1e407d0bc0b 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -468,7 +468,7 @@ export const spec = { 'rp_floor': (params.floor = parseFloat(params.floor)) >= 0.01 ? params.floor : undefined, 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, - 'x_source.tid': bidRequest.transactionId, + 'x_source.tid': deepAccess(bidderRequest, 'ortb2.source.tid'), 'x_imp.ext.tid': bidRequest.transactionId, 'l_pb_bid_id': bidRequest.bidId, 'p_screen_res': _getScreenResolution(), diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 0c8477a0fcb..a0600879a31 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -89,6 +89,11 @@ describe('the rubicon adapter', function () { bidderCode: 'rubicon', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', bidderRequestId: '178e34bad3658f', + ortb2: { + source: { + tid: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }, bids: [ { bidder: 'rubicon', @@ -354,6 +359,11 @@ describe('the rubicon adapter', function () { transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' } ], + ortb2: { + source: { + tid: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }, start: 1472239426002, auctionStart: 1472239426000, timeout: 5000 @@ -627,7 +637,6 @@ describe('the rubicon adapter', function () { 'rand': '0.1', 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', - 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2284,6 +2293,8 @@ describe('the rubicon adapter', function () { }); describe('createSlotParams', function () { + const localBidderRequest = Object.assign({}, bidderRequest); + localBidderRequest.ortb2 = {source: {tid: 'faked707-a418-42ec-b8a7-b70a6c6fab0b'}}; it('should return a valid slot params object', function () { let expectedQuery = { 'account_id': '14062', @@ -2294,7 +2305,8 @@ describe('the rubicon adapter', function () { 'p_pos': 'atf', 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'faked707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2307,7 +2319,7 @@ describe('the rubicon adapter', function () { 'rf': 'localhost' }; - const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); + const slotParams = spec.createSlotParams(bidderRequest.bids[0], localBidderRequest); // test that all values above are both present and correct Object.keys(expectedQuery).forEach(key => { From 92daa81f277598cbed486cf8be01ce796aa80c8f Mon Sep 17 00:00:00 2001 From: radubarbos Date: Wed, 17 May 2023 08:37:55 +0300 Subject: [PATCH 27/38] Yahoo SSP adapter support for extra site publisher info. (#9921) * Yahoo SSP adapter support for extra site publisher info. * Yahoo SSP adapter support for extra site publisher info - docs. --------- Co-authored-by: dumitrubarbos --- modules/yahoosspBidAdapter.js | 6 ++ modules/yahoosspBidAdapter.md | 37 ++++++++++++ test/spec/modules/yahoosspBidAdapter_spec.js | 61 +++++++++++++++++++- 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/modules/yahoosspBidAdapter.js b/modules/yahoosspBidAdapter.js index 40677f90064..249a088ed1c 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahoosspBidAdapter.js @@ -380,6 +380,7 @@ function appendFirstPartyData(outBoundBidRequest, bid) { const ortb2Object = bid.ortb2; const siteObject = deepAccess(ortb2Object, 'site') || undefined; const siteContentObject = deepAccess(siteObject, 'content') || undefined; + const sitePublisherObject = deepAccess(siteObject, 'publisher') || undefined; const siteContentDataArray = deepAccess(siteObject, 'content.data') || undefined; const appContentObject = deepAccess(ortb2Object, 'app.content') || undefined; const appContentDataArray = deepAccess(ortb2Object, 'app.content.data') || undefined; @@ -394,6 +395,11 @@ function appendFirstPartyData(outBoundBidRequest, bid) { outBoundBidRequest.site = validateAppendObject('object', allowedSiteObjectKeys, siteObject, outBoundBidRequest.site); }; + if (sitePublisherObject && isPlainObject(sitePublisherObject)) { + const allowedPublisherObjectKeys = ['ext']; + outBoundBidRequest.site.publisher = validateAppendObject('object', allowedPublisherObjectKeys, sitePublisherObject, outBoundBidRequest.site.publisher); + } + if (siteContentObject && isPlainObject(siteContentObject)) { const allowedContentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language']; const allowedContentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; diff --git a/modules/yahoosspBidAdapter.md b/modules/yahoosspBidAdapter.md index 7fb7a307192..e0d1b365acd 100644 --- a/modules/yahoosspBidAdapter.md +++ b/modules/yahoosspBidAdapter.md @@ -435,6 +435,43 @@ pbjs.setConfig({ } }); ``` + +Notes: The first party site info is filtered and only the following specific keys are allowed in the bidRequests: + +| Field | Type | +|-----------------------------|--------| +| site.name | String | +| site.domain | String | +| site.page | String | +| site.ref | String | +| site.keywords | String | +| site.search | String | +| site.cat | Array | +| site.sectioncat | Array | +| site.pagecat | Array | +| site.ext | Object | +| site.publisher.ext | Object | +| site.content.id | String | +| site.content.title | String | +| site.content.series | String | +| site.content.season | String | +| site.content.genre | String | +| site.content.contentrating | String | +| site.content.language | String | +| site.content.episode | Number | +| site.content.prodq | Number | +| site.content.context | Number | +| site.content.livestream | Number | +| site.content.len | Number | +| site.content.cat | Array | +| site.content.ext | Object | +| site.content.data | Array | +| site.content.data[].id | String | +| site.content.data[].name | String | +| site.content.data[].segment | Array | +| site.content.data[].ext | Object | + + ### Passing First Party "user" data: ```javascript pbjs.setConfig({ diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahoosspBidAdapter_spec.js index a326b22ecb4..f558089b7b0 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahoosspBidAdapter_spec.js @@ -481,6 +481,23 @@ describe('YahooSSP Bid Adapter:', () => { }); }); + const VALID_PUBLISHER_OBJECTS = ['ext']; + VALID_PUBLISHER_OBJECTS.forEach(param => { + it(`should determine that the ortb2.site.publisher Object key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + publisher: { + [param]: {a: '123', b: '456'} + } + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.publisher[param]).to.be.a('object'); + expect(data.site.publisher[param]).to.be.equal(ortb2.site.publisher[param]); + }); + }); + const VALID_CONTENT_ARRAYS = ['cat']; VALID_CONTENT_ARRAYS.forEach(param => { it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { @@ -512,7 +529,6 @@ describe('YahooSSP Bid Adapter:', () => { const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site.content[param]).to.be.a('object'); expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); - config.setConfig({ortb2: {}}); }); }); }); @@ -959,6 +975,49 @@ describe('YahooSSP Bid Adapter:', () => { expect(data.site.id).to.equal('1234567'); }); + it('should use site publisher ortb2 config in default integration mode', () => { + const ortb2 = { + site: { + publisher: { + ext: { + publisherblob: 'pblob', + bucket: 'bucket' + } + } + } + } + let { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.site.publisher).to.deep.equal({ + ext: { + publisherblob: 'pblob', + bucket: 'bucket' + } + }); + }); + + it('should use site publisher ortb2 config when using "pubId" integration mode', () => { + const ortb2 = { + site: { + publisher: { + ext: { + publisherblob: 'pblob', + bucket: 'bucket' + } + } + } + } + let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true, ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.site.publisher).to.deep.equal({ + id: DEFAULT_PUBID, + ext: { + publisherblob: 'pblob', + bucket: 'bucket' + } + }); + }); + it('should use placementId value as imp.tagid in the outbound bid-request when using "pubId" integration mode', () => { let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true}); validBidRequests[0].params.placementId = 'header-300x250'; From c46e38c8f324c3788fbce88ee48018352cd5d702 Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Wed, 17 May 2023 13:10:12 -0700 Subject: [PATCH 28/38] Prebid 8: set pubmatic source.tid to auctionId (#9954) * set source.tid to auctionId * removed changes to test page * added support for setting ext.wrapper.transactionId as well --- modules/pubmaticBidAdapter.js | 10 ++++++++-- test/spec/modules/pubmaticBidAdapter_spec.js | 19 +++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index b013cf82215..2201945e48a 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1112,7 +1112,11 @@ export const spec = { payload.ext.wrapper.wiid = conf.wiid || bidderRequest.auctionId; // eslint-disable-next-line no-undef payload.ext.wrapper.wv = $$REPO_AND_VERSION$$; - payload.ext.wrapper.transactionId = conf.transactionId; + + if (bidderRequest) { + payload.ext.wrapper.transactionId = bidderRequest.auctionId; + } + payload.ext.wrapper.wp = 'pbjs'; const allowAlternateBidder = bidderRequest ? bidderSettings.get(bidderRequest.bidderCode, 'allowAlternateBidderCodes') : undefined; if (allowAlternateBidder !== undefined) { @@ -1151,7 +1155,9 @@ export const spec = { // update device.language to ISO-639-1-alpha-2 (2 character language) payload.device.language = payload.device.language && payload.device.language.split('-')[0]; - // passing nothing in source.tid -- Pubmatic can correct in a future PR + // passing auctionId in source.tid + if (bidderRequest) deepSetValue(payload, 'source.tid', bidderRequest.auctionId); + // test bids if (window.location.href.indexOf('pubmaticTest=true') !== -1) { payload.test = 1; diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 861f7323701..eb0c89bcc45 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1135,7 +1135,8 @@ describe('PubMatic adapter', function () { expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal('new-auction-id'); // Prebid TransactionId + expect(data.source.tid).to.equal('new-auction-id'); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -1387,7 +1388,7 @@ describe('PubMatic adapter', function () { expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal('new-auction-id'); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -1598,7 +1599,7 @@ describe('PubMatic adapter', function () { expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal('new-auction-id'); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal('new-auction-id'); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -1616,7 +1617,8 @@ describe('PubMatic adapter', function () { gdprConsent: { consentString: 'kjfdniwjnifwenrif3', gdprApplies: true - } + }, + auctionId: 'new-auction-id' }; let request = spec.buildRequests(bidRequests, bidRequest); let data = JSON.parse(request.data); @@ -1634,7 +1636,7 @@ describe('PubMatic adapter', function () { expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal('new-auction-id'); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -1649,7 +1651,8 @@ describe('PubMatic adapter', function () { it('Request params check with USP/CCPA Consent', function () { let bidRequest = { - uspConsent: '1NYN' + uspConsent: '1NYN', + auctionId: 'new-auction-id' }; let request = spec.buildRequests(bidRequests, bidRequest); let data = JSON.parse(request.data); @@ -1666,7 +1669,7 @@ describe('PubMatic adapter', function () { expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal('new-auction-id'); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -2696,7 +2699,7 @@ describe('PubMatic adapter', function () { expect(data.user.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude expect(data.user.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal('new-auction-id'); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(multipleMediaRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(multipleMediaRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID From eeab092d7642cde5bf70f5620155452631ffa56f Mon Sep 17 00:00:00 2001 From: Pgb-Criteo <92986445+Pgb-Criteo@users.noreply.github.com> Date: Wed, 17 May 2023 23:22:51 +0200 Subject: [PATCH 29/38] Criteo Bid Adapter: add support for imp.rwdd (#9964) --- modules/criteoBidAdapter.js | 5 ++ test/spec/modules/criteoBidAdapter_spec.js | 67 ++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index edb718ec953..63c4482ab84 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -438,6 +438,11 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (deepAccess(bidRequest, 'ortb2Imp.ext')) { slot.ext = bidRequest.ortb2Imp.ext; } + + if (deepAccess(bidRequest, 'ortb2Imp.rwdd')) { + slot.rwdd = bidRequest.ortb2Imp.rwdd; + } + if (bidRequest.params.ext) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index d88e70a6cdc..17527eb42c7 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1579,6 +1579,73 @@ describe('The Criteo bidding adapter', function () { } }); }); + + it('should properly build a request when imp.rwdd is present', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + ext: { + bidfloor: 0.75 + } + }, + ortb2Imp: { + rwdd: 1, + ext: { + data: { + someContextAttribute: 'abc' + } + } + } + }, + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].rwdd).to.be.not.null; + expect(request.data.slots[0].rwdd).to.equal(1); + }); + + it('should properly build a request when imp.rwdd is false', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + ext: { + bidfloor: 0.75 + } + }, + ortb2Imp: { + rwdd: 0, + ext: { + data: { + someContextAttribute: 'abc' + } + } + } + }, + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].rwdd).to.be.undefined; + }); }); describe('interpretResponse', function () { From 3cdbd1de50ae02012f3714fd9558365b3418aa36 Mon Sep 17 00:00:00 2001 From: denys-berzoy-confiant <50574764+denys-berzoy-confiant@users.noreply.github.com> Date: Thu, 18 May 2023 00:25:19 +0300 Subject: [PATCH 30/38] confiant Rtd Provider : add message type check (#9950) * confiantRtdProvider: add message type check - added check for message type and propertyId * rm unused variable * fix test --- modules/confiantRtdProvider.js | 27 ++++++++++--------- test/spec/modules/confiantRtdProvider_spec.js | 5 +++- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/modules/confiantRtdProvider.js b/modules/confiantRtdProvider.js index 2c13ad87d75..6b1066a20f1 100644 --- a/modules/confiantRtdProvider.js +++ b/modules/confiantRtdProvider.js @@ -44,7 +44,7 @@ function setupPage(config) { if (config?.params?.shouldEmitBillableEvent) { if (window.frames['cnftComm']) { - subscribeToConfiantCommFrame(window); + subscribeToConfiantCommFrame(window, propertyId); } else { setUpMutationObserver(); } @@ -58,8 +58,8 @@ function setupPage(config) { * Subscribe to window's message events to report Billable events * @param {Window} targetWindow window instance to subscribe to */ -function subscribeToConfiantCommFrame(targetWindow) { - targetWindow.addEventListener('message', reportBillableEvents); +function subscribeToConfiantCommFrame(targetWindow, propertyId) { + targetWindow.addEventListener('message', getEventHandlerFunction(propertyId)); } let mutationObserver; @@ -86,14 +86,18 @@ function setUpMutationObserver() { /** * Emit billable event when Confiant integration reports that it has monitored an impression */ -function reportBillableEvents (e) { - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { - auctionId: e.data.auctionId, - billingId: generateUUID(), - transactionId: e.data.transactionId, - type: 'impression', - vendor: 'confiant' - }); +function getEventHandlerFunction(propertyId) { + return function reportBillableEvent(e) { + if (e.data.type.indexOf('cnft:reportBillableEvent:' + propertyId) > -1) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + auctionId: e.data.auctionId, + billingId: generateUUID(), + transactionId: e.data.transactionId, + type: 'impression', + vendor: 'confiant' + }); + } + } } /** @@ -123,6 +127,5 @@ export default { setupPage, subscribeToConfiantCommFrame, setUpMutationObserver, - reportBillableEvents, registerConfiantSubmodule }; diff --git a/test/spec/modules/confiantRtdProvider_spec.js b/test/spec/modules/confiantRtdProvider_spec.js index 987aca2e020..8f9fcd0ba98 100644 --- a/test/spec/modules/confiantRtdProvider_spec.js +++ b/test/spec/modules/confiantRtdProvider_spec.js @@ -46,6 +46,7 @@ describe('Confiant RTD module', function () { let listenerCallback; const mockWindow = { addEventListener: (a, cb) => (listenerCallback = cb) }; let billableEventsCounter = 0; + const propertyId = 'fff'; events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (e) => { if (e.vendor === 'confiant') { @@ -57,15 +58,17 @@ describe('Confiant RTD module', function () { } }); - subscribeToConfiantCommFrame(mockWindow); + subscribeToConfiantCommFrame(mockWindow, propertyId); listenerCallback({ data: { + type: 'cnft:reportBillableEvent:' + propertyId, auctionId: 'auctionId', transactionId: 'transactionId' } }); listenerCallback({ data: { + type: 'cnft:reportBillableEvent:' + propertyId, auctionId: 'auctionId', transactionId: 'transactionId' } From a205c6c893b3bd469377c1bb655a9ca2090901d2 Mon Sep 17 00:00:00 2001 From: anthonyjl92 Date: Wed, 17 May 2023 17:33:32 -0400 Subject: [PATCH 31/38] Add mobile client hint 33x adapter (#9958) Co-authored-by: Carlos Felix --- modules/33acrossBidAdapter.js | 2 +- test/spec/modules/33acrossBidAdapter_spec.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index e9901794ff9..606933e7b63 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -765,7 +765,7 @@ function _buildDeviceORTB(device = {}) { } if (device.sua) { - deviceProps.sua = pick(device.sua, [ 'browsers', 'platform', 'model' ]); + deviceProps.sua = pick(device.sua, [ 'browsers', 'platform', 'model', 'mobile' ]); } return deviceProps; diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 3b3c05660df..3702a34fbb8 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -53,7 +53,8 @@ describe('33acrossBidAdapter:', function () { brand: 'macOS', version: ['11', '6', '8'] }, - model: '' + model: '', + mobile: 0 } }, id: 'r1', @@ -337,7 +338,7 @@ describe('33acrossBidAdapter:', function () { version: ['11', '6', '8'] }, model: '', - mobile: false + mobile: 0 } } } @@ -369,7 +370,7 @@ describe('33acrossBidAdapter:', function () { version: ['11', '6', '8'] }, model: '', - mobile: false + mobile: 0 } } }, From 4b62ff513ca5a70771783c5518c2f585438e77fa Mon Sep 17 00:00:00 2001 From: Aaron Price <67345931+AaronColbyPrice@users.noreply.github.com> Date: Wed, 17 May 2023 15:59:35 -0700 Subject: [PATCH 32/38] eps_aliasing - adding in aliases for epsilon (formerly conversant) for bidder and analytics (#9961) Co-authored-by: aarprice@publicisgroupe.net --- modules/conversantAnalyticsAdapter.js | 12 +++++++----- modules/conversantBidAdapter.js | 2 +- test/spec/modules/conversantBidAdapter_spec.js | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/conversantAnalyticsAdapter.js b/modules/conversantAnalyticsAdapter.js index e934f1bb153..ce4597eecfe 100644 --- a/modules/conversantAnalyticsAdapter.js +++ b/modules/conversantAnalyticsAdapter.js @@ -18,6 +18,7 @@ const DOMAIN = 'https://web.hb.ad.cpe.dotomi.com/'; const ANALYTICS_URL = DOMAIN + 'cvx/event/prebidanalytics'; const ERROR_URL = DOMAIN + 'cvx/event/prebidanalyticerrors'; const ANALYTICS_CODE = 'conversant'; +const ANALYTICS_ALIASES = [ANALYTICS_CODE, 'epsilon', 'cnvr']; export const CNVR_CONSTANTS = { LOG_PREFIX: 'Conversant analytics adapter: ', @@ -688,11 +689,12 @@ conversantAnalytics.disableAnalytics = function () { conversantAnalyticsEnabled = false; conversantAnalytics.originDisableAnalytics(); }; - -adapterManager.registerAnalyticsAdapter({ - adapter: conversantAnalytics, - code: ANALYTICS_CODE, - gvlid: GVLID +ANALYTICS_ALIASES.forEach(alias => { + adapterManager.registerAnalyticsAdapter({ + adapter: conversantAnalytics, + code: alias, + gvlid: GVLID + }); }); export default conversantAnalytics; diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index e17a9fe4021..b5a1424dcba 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -12,7 +12,7 @@ const URL = 'https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25'; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['cnvr'], // short code + aliases: ['cnvr', 'epsilon'], // short code supportedMediaTypes: [BANNER, VIDEO], /** diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index bda5d8daca7..bf02a2893f0 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -221,8 +221,9 @@ describe('Conversant adapter tests', function() { it('Verify basic properties', function() { expect(spec.code).to.equal('conversant'); - expect(spec.aliases).to.be.an('array').with.lengthOf(1); + expect(spec.aliases).to.be.an('array').with.lengthOf(2); expect(spec.aliases[0]).to.equal('cnvr'); + expect(spec.aliases[1]).to.equal('epsilon'); expect(spec.supportedMediaTypes).to.be.an('array').with.lengthOf(2); expect(spec.supportedMediaTypes[1]).to.equal('video'); }); From 4d998deee44fd9af56618816e928b32ec4a5a878 Mon Sep 17 00:00:00 2001 From: Kevin Park <109548315+kevpark02@users.noreply.github.com> Date: Wed, 17 May 2023 16:15:31 -0700 Subject: [PATCH 33/38] Underdog Media Bid Adapter: Update ttl & referer information (#9826) * Update ttl and referer info in underdogmediaBidAdapter * Restore hello world default * Fix the unit test for referer info * undo change to package lock * undo change to package lock * revert change to package-lock.json --------- Co-authored-by: Jake --- modules/underdogmediaBidAdapter.js | 4 ++-- test/spec/modules/underdogmediaBidAdapter_spec.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index bdc5c04279f..e8494119b62 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -74,7 +74,7 @@ export const spec = { pbTimeout: config.getConfig('bidderTimeout'), pbjsVersion: prebidVersion, placements: [], - ref: deepAccess(bidderRequest, 'refererInfo.ref') ? bidderRequest.refererInfo.ref : undefined, + ref: deepAccess(bidderRequest, 'refererInfo.page') ? bidderRequest.refererInfo.page : undefined, usp: {}, userIds: { '33acrossId': deepAccess(validBidRequests[0], 'userId.33acrossId.envelope') ? validBidRequests[0].userId['33acrossId'].envelope : undefined, @@ -193,7 +193,7 @@ export const spec = { creativeId: mid.mid, currency: 'USD', netRevenue: false, - ttl: mid.ttl || 60, + ttl: mid.ttl || 300, meta: { advertiserDomains: mid.advertiser_domains || [] } diff --git a/test/spec/modules/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index 4201ecc2329..040d85f9179 100644 --- a/test/spec/modules/underdogmediaBidAdapter_spec.js +++ b/test/spec/modules/underdogmediaBidAdapter_spec.js @@ -735,7 +735,7 @@ describe('UnderdogMedia adapter', function () { }, }, refererInfo: { - ref: 'www.example.com' + page: 'www.example.com' } } From 425cc5601ddfbab561a1991a37d84fc619bc95ec Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Thu, 18 May 2023 01:24:49 +0200 Subject: [PATCH 34/38] LiveIntent UserId module: add support for distributorId configuration parameter (#9963) * Allow to configure distributorId * Add test for did being present in track events * Compute source only once --------- Co-authored-by: Viktor Dreiling Co-authored-by: Viktor Dreiling --- modules/liveIntentIdSystem.js | 12 +++-- .../modules/liveIntentIdMinimalSystem_spec.js | 28 ++++++++++++ test/spec/modules/liveIntentIdSystem_spec.js | 45 +++++++++++++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 3ecd061085c..3157a4c155e 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -92,21 +92,25 @@ function initializeLiveConnect(configParams) { const publisherId = configParams.publisherId || 'any'; const identityResolutionConfig = { - source: 'prebid', publisherId: publisherId, requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides) }; if (configParams.url) { identityResolutionConfig.url = configParams.url } - if (configParams.partner) { - identityResolutionConfig.source = configParams.partner - } if (configParams.ajaxTimeout) { identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout; } const liveConnectConfig = parseLiveIntentCollectorConfig(configParams.liCollectConfig); + + if (!liveConnectConfig.appId && configParams.distributorId) { + liveConnectConfig.distributorId = configParams.distributorId; + identityResolutionConfig.source = configParams.distributorId; + } else { + identityResolutionConfig.source = configParams.partner || 'prebid' + } + liveConnectConfig.wrapperName = 'prebid'; liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index 6a5afa58da2..5640a6d36cb 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -73,6 +73,34 @@ describe('LiveIntentMinimalId', function() { expect(callBackSpy.calledOnce).to.be.true; }); + it('should call the Identity Exchange endpoint with the privided distributorId', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&resolve=nonId'); + request.respond( + 204, + responseHeader + ); + expect(callBackSpy.calledOnceWith({})).to.be.true; + }); + + it('should call the Identity Exchange endpoint without the privided distributorId when appId is provided', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?resolve=nonId'); + request.respond( + 204, + responseHeader + ); + expect(callBackSpy.calledOnceWith({})).to.be.true; + }); + it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index afbd1566438..859db423eac 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -100,6 +100,23 @@ describe('LiveIntentId', function() { }, 200); }); + it('should fire an event with the provided distributorId', function (done) { + liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111' } }); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); + done(); + }, 200); + }); + + it('should fire an event without the provided distributorId when appId is provided', function (done) { + liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + expect(server.requests[0].url).to.not.match(/.*did=*/); + done(); + }, 200); + }); + it('should initialize LiveConnect and emit an event with a privacy string when decode', function(done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ @@ -162,6 +179,34 @@ describe('LiveIntentId', function() { expect(callBackSpy.calledOnceWith({})).to.be.true; }); + it('should call the Identity Exchange endpoint with the privided distributorId', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&resolve=nonId'); + request.respond( + 204, + responseHeader + ); + expect(callBackSpy.calledOnceWith({})).to.be.true; + }); + + it('should call the Identity Exchange endpoint without the privided distributorId when appId is provided', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?resolve=nonId'); + request.respond( + 204, + responseHeader + ); + expect(callBackSpy.calledOnceWith({})).to.be.true; + }); + it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); From ed726c788cb1ba4dcdff6407dabc73d1210de4ec Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Thu, 18 May 2023 08:04:04 -0300 Subject: [PATCH 35/38] Docs Integration Examples : add Events UI page for Video Module (#9934) * adds UI for events * improves visuals * removes duplicate event * adds title * fixes scoping var bug * adds videojs page --- .../videoModule/eventsUI/eventsUI.css | 205 +++++++++++ .../videoModule/eventsUI/eventsUI.js | 181 ++++++++++ .../videoModule/jwplayer/eventsUI.html | 295 ++++++++++++++++ .../videoModule/videojs/eventsUI.html | 319 ++++++++++++++++++ 4 files changed, 1000 insertions(+) create mode 100644 integrationExamples/videoModule/eventsUI/eventsUI.css create mode 100644 integrationExamples/videoModule/eventsUI/eventsUI.js create mode 100644 integrationExamples/videoModule/jwplayer/eventsUI.html create mode 100644 integrationExamples/videoModule/videojs/eventsUI.html diff --git a/integrationExamples/videoModule/eventsUI/eventsUI.css b/integrationExamples/videoModule/eventsUI/eventsUI.css new file mode 100644 index 00000000000..ccfe17ca4c6 --- /dev/null +++ b/integrationExamples/videoModule/eventsUI/eventsUI.css @@ -0,0 +1,205 @@ +html { + width: 100%; +} +body { + background: #ccc; + margin: 20px 5px; + font: 42px/60px Helvetica, Arial, sans-serif; + min-width: 320px; + max-width: 1920px; +} +#player:first-child { + width: 100%; +} + +#player { + margin: auto; + background: rgba(0, 0, 20, 0.8); +} +#eventsLog.group-player-disabled .group-player, +#eventsLog.group-media-disabled .group-media, +#eventsLog.group-ads-disabled .group-ads, +#eventsLog.group-auction-disabled .group-auction, +#eventsLog.group-adRequest-disabled .group-adRequest, +#eventsLog.group-adBreak-disabled .group-adBreak, +#eventsLog.group-related-disabled .group-related, +#eventsLog.group-ping-disabled .group-ping, +#eventsLog.group-unknown-disabled .group-unknown, +#eventsLog.group-quickPeek-disabled .group-quickPeek, +#eventsLog.group-provider-disabled .group-provider, +#eventsLog.group-video-disabled .group-video { + display: none; +} +.input-field { + padding: 0 0.25em; + margin: 0 0 0 4px; + border: 0; + background-color: #232323; + color: #F8F8F8; +} +.input-field:invalid { + text-decoration: underline; + text-decoration-color: red; +} +#eventsLog .sequence > .pre.filter-not-matched { + display: none; + opacity: 0.2; +} +a.button { + -webkit-appearance: button; + cursor: pointer; + margin: 2px; + background: #ccc; + border-color: rgb(216, 216, 216) rgb(209, 209, 209) rgb(186, 186, 186); + border-style: solid; + border-width: 1px; + padding: 1px 7px 2px; + color: inherit; + text-decoration: inherit; + user-select: none; +} +.right { + float: right; +} +.disabled { + opacity: 0.5; +} +.nav.disabled { + user-select: none; + cursor: default; +} +.block { + margin: 5px; + background-color: #eee; +} +div.mode-player { + background: #acc; +} +div.mode-ads { + background: #fea; +} +div.sequence { + display: block; +} +.group-player { + background: #acc; +} +.group-media { + background: #fa6; +} +.group-ads { + background: #fcc; +} +.group-auction { + background: #228; + color: #eee; +} +pre { + margin: 0; +} +div.toggle-block pre { + margin: 1px 3px; +} +.events-block { + min-height: 1440px; +} +.events-block .pre, +.toggle-block pre { + display: inline-block; + padding: 0 5px; + margin: 1px 3px 1px 20px; + border-radius: 5px; + font-family: monospace; + white-space: pre; +} +.pre.group-ads, +.pre.group-media, +.pre.group-player, +.pre.group-auction, +.pre.event-ready { + display: inline-block; + padding: 0 5px; + margin: 1px 3px 1px 20px; + border-radius: 5px; + font-family: monospace; + white-space: pre; +} + +.list-events.events-block .pre.group-player, +.list-events.events-block .pre.group-media, +.list-events.events-block .pre.group-ads, +.list-events.events-block .pre.group-auction { + display: block; + margin: 0; + padding: 1px 10px; + border-radius: 0; +} + +.pre.event-playlistItem { + margin: 1px 3px 1px 10px; +} +.pre.event-adBreakStart, +.pre.event-adBreakEnd { + margin: 1px 3px 1px 20px; +} +.pre.event-adBreakStart, +.pre.event-ready, +.pre.event-adImpression, +.pre.event-adError, +.pre.event-adWarning { + font-weight: 800; +} +.pre pre { + display: inline; + margin: 0 0 0 20px; +} +.toggle { + cursor: pointer; + user-select: none; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; +} +button { + overflow: visible; + padding: 1px 7px 2px; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; + margin: 2px; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} + +@media only screen and (min-device-width : 320px) and (max-device-width : 768px) { + body { + font-size: 2vw; + line-height: 3vw; + } +} diff --git a/integrationExamples/videoModule/eventsUI/eventsUI.js b/integrationExamples/videoModule/eventsUI/eventsUI.js new file mode 100644 index 00000000000..618a4774765 --- /dev/null +++ b/integrationExamples/videoModule/eventsUI/eventsUI.js @@ -0,0 +1,181 @@ +"use strict"; + +let sequenceCount = 0; +const eventLogGroups = {}; +const Uint8Array = window.Uint8Array; +const TimeRanges = window.TimeRanges; + +function stringify(value, replacer, space) { + try { + return truncate(JSON.stringify(value, replacer || stringifyReplacer(value), space), 100000); + } catch (error) { + return `[${error}]`; + } +} + +function truncate(str, length) { + return (str && str.length) > length ? (str.substr(0, length) + + '\n... Event truncated due to length (see console for complete output)') : str; +} + +function stringifyReplacer(parentValue) { + const references = []; + const safeResults = []; + let complexity = 0; + return function stringifyKeyValue(key, value) { + if (typeof value === 'object') { + if (value === null || value instanceof Date || value instanceof RegExp) { + return value; + } + if (!!Uint8Array && value instanceof Uint8Array) { + // Stub values of Arrays with more than 1000 items + let str = ('' + value); + str = (str.length > 40 ? (str.substr(0, 40) + '...(see console)') : str); + return `Uint8Array(${value.length}) [${str}]`; + } + if (!!TimeRanges && value instanceof TimeRanges) { + const ranges = []; + for (let i = 0; i < value.length; i++) { + ranges[i] = `start(${i}) = ${value.start(i)} end(${i}) = ${value.end(i)}`; + } + return `TimeRanges(${value.length}) [${ranges}]`; + } + if (value === parentValue && complexity > 0) { + return ''; + } + const referenceIndex = references.indexOf(value); + if (referenceIndex !== -1) { + // Duplicate reference found + const safe = safeResults[referenceIndex]; + if (safe) { + return safe; + } + try { + // Test for circular references + JSON.stringify(value); + } catch (error) { + return (safeResults[referenceIndex] = '<' + value + '...(see console)>'); + } + safeResults[referenceIndex] = value; + } + if (complexity++ > 10000) { + return ''; + } + references.push(value); + return value; + } + if (typeof value === 'function') { + return `${value}`; + } + return value; + }; +} + +function createEventSequenceElement(eventGroup) { + const element = document.createElement('div'); + element.classList.add('sequence', `mode-${eventGroup}`); + element.setAttribute('data-sequence', `${sequenceCount++}`); + return element; +} + +function appendSequenceElement(container, element) { + container.appendChild(element); +} + +function textContentGrouped(inEvent, group) { + if (group) { + return `${inEvent} (${group[inEvent]})`; + } + return inEvent; +} + +function appendEvent(container, currentEventType, currentEventGroup, data) { + const div = document.createElement('div'); + div.classList.add('group-' + currentEventGroup, 'event-' + currentEventType, 'pre'); + div.textContent = textContentGrouped(currentEventType); + div.setAttribute('title', `${currentEventGroup} event "${currentEventType}"`); + div.setAttribute('tabindex', '0'); + const theData = Object.assign({}, data); + div.onclick = div.onkeyup = function(e) { + if (e && e.keyCode && e.keyCode !== 13) { + return; + } + + console.log(theData); + div.textContent = ((div.expanded = !div.expanded)) ? + textContentExpanded(currentEventType, [theData]) : textContentGrouped(currentEventType); + if (e) { + e.preventDefault(); + } + return [theData]; + }; + container.appendChild(div); + return div; +} + +function textContentExpanded(inEvent, allData) { + return `${inEvent} (${allData.map((item, i) => + (allData.length > 1 ? `[${i}] = ` : '') + stringify(item, null, 4)).join('\n')})`; +} + +function incrementEvent(group, currentEventType, currentEventGroup, div, datum) { + group[currentEventType]++; + div.textContent = textContentGrouped(currentEventType, group); + const logPreviousEvents = div.onclick; + const scopedDatum = Object.assign({}, datum); + div.onclick = div.onkeyup = function(e) { + if (e && e.keyCode && e.keyCode !== 13) { + return; + } + + const allData = logPreviousEvents(); + allData.push(scopedDatum); + console.log(scopedDatum); + div.textContent = (div.expanded) ? textContentExpanded(currentEventType, allData) : textContentGrouped(currentEventType, group); + if (e) { + e.preventDefault(); + } + return allData; + }; +} + +function getGenericEventHandler() { + const logContainer = document.querySelector('#eventsLog'); + let currentEventGroup = ''; + let currentEventType = ''; + let lastEvent = ''; + let lastGroup; + const genericEventHandler = function(e, type, eventGroup) { + currentEventGroup = eventGroup; + currentEventType = type; + + let group = eventLogGroups[eventGroup]; + if (!group || group !== lastGroup) { + const beforeReadyElement = createEventSequenceElement(currentEventGroup); + appendSequenceElement(logContainer, beforeReadyElement); + group = eventLogGroups[currentEventGroup] = { + eventGroup: currentEventGroup, + event: currentEventType, + container: logContainer, + eventElement: beforeReadyElement + }; + lastGroup = lastGroup || group; + } + if (lastEvent === currentEventType && !(/^(?:meta|hlsBufferAppend)/).test(currentEventType)) { + incrementEvent(group, currentEventType, currentEventGroup, group.pre, e); + } else { + const eventElement = createEventSequenceElement(currentEventGroup); + group[currentEventType] = 1; + group.eventElement = eventElement; + group.lastEventGroup = currentEventGroup; + group.pre = appendEvent(eventElement, currentEventType, currentEventGroup, e); + appendSequenceElement(group.container, eventElement); + } + lastEvent = currentEventType; + lastGroup = group; + }; + + return genericEventHandler; +} + +window.getGenericEventHandler = getGenericEventHandler; diff --git a/integrationExamples/videoModule/jwplayer/eventsUI.html b/integrationExamples/videoModule/jwplayer/eventsUI.html new file mode 100644 index 00000000000..69ace5f6c2b --- /dev/null +++ b/integrationExamples/videoModule/jwplayer/eventsUI.html @@ -0,0 +1,295 @@ + + + + + + + + + JW Player Event UI + + + + + + + +
+
+
+ +
+ + + + diff --git a/integrationExamples/videoModule/videojs/eventsUI.html b/integrationExamples/videoModule/videojs/eventsUI.html new file mode 100644 index 00000000000..04e0ca9eaf8 --- /dev/null +++ b/integrationExamples/videoModule/videojs/eventsUI.html @@ -0,0 +1,319 @@ + + + + + + + + + + + + + + + + Video.JS Event UI + + + + + + + +
+ + +
+ +
+ + + + From 7224457772a579dfdb993db6cb3680dab5f966ba Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 18 May 2023 13:07:19 +0000 Subject: [PATCH 36/38] Prebid 7.50.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6e6020a443..07e20fb864f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.50.0-pre", + "version": "7.50.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c666ea19bda..add7185d761 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.50.0-pre", + "version": "7.50.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 343605d5cdb140c288fcf64c18419eeda573cad0 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 18 May 2023 13:07:19 +0000 Subject: [PATCH 37/38] Increment version to 7.51.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 07e20fb864f..af1d2cfde5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.50.0", + "version": "7.51.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index add7185d761..855ecd86437 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.50.0", + "version": "7.51.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 18f9774c7a9d989594ef6e6ef78231d0c80c0b3f Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Thu, 18 May 2023 06:14:07 -0700 Subject: [PATCH 38/38] GumGum Bid Adapter : Id5 integration suppress link type for ttd (#9924) * ADJS-1271-send-envelope-param-for-lexicon * Read the id5 id linktype field and send it to the server. * Remove unit test for Id5 * Add unit test for id5. * minor changes to possibly fix edge error --------- Co-authored-by: John Bauzon Co-authored-by: John Ivan Bauzon --- modules/gumgumBidAdapter.js | 5 +++++ test/spec/modules/gumgumBidAdapter_spec.js | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 8a6c4efa7fc..fdd3030bb7a 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -312,6 +312,11 @@ function buildRequests(validBidRequests, bidderRequest) { data.lt = lt; data.to = to; + // ADJS-1286 Read id5 id linktype field + if (userId && userId.id5id && userId.id5id.uid && userId.id5id.ext) { + data.id5Id = userId.id5id.uid || null + data.id5IdLinkType = userId.id5id.ext.linkType || null + } // ADTS-169 add adUnitCode to requests if (adUnitCode) data.aun = adUnitCode; diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 71f356a83ae..0e64ec67b27 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -111,6 +111,14 @@ describe('gumgumAdapter', function () { sizes: sizesArray } }, + userId: { + id5id: { + uid: 'uid-string', + ext: { + linkType: 2 + } + } + }, adUnitCode: 'adunit-code', sizes: sizesArray, bidId: '30b31c1838de1e', @@ -158,6 +166,12 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.aun).to.equal(bidRequests[0].adUnitCode); }); + it('should set id5Id and id5IdLinkType if the uid and linkType are available', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.id5Id).to.equal(bidRequests[0].userId.id5id.uid); + expect(bidRequest.data.id5IdLinkType).to.equal(bidRequests[0].userId.id5id.ext.linkType); + }); it('should set pubId param if found', function () { const request = { ...bidRequests[0], params: pubIdParam };