diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md index 45f4e6c7dc5..016e3e71cfc 100644 --- a/RELEASE_SCHEDULE.md +++ b/RELEASE_SCHEDULE.md @@ -9,7 +9,7 @@ ## Release Schedule -We aim to push a new release of Prebid.js every week on Tuesday. +We aim to push a new release of Prebid.js each week barring any unforseen circumstances or in observance of holidays. While the releases will be available immediately for those using direct Git access, it will be about a week before the Prebid Org [Download Page](https://docs.prebid.org/download.html) will be updated. diff --git a/gulpfile.js b/gulpfile.js index f3d44243ef8..17c421f4dc1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -295,8 +295,7 @@ function bundle(dev, moduleArr) { [coreFile].concat(moduleFiles).map(name => path.basename(name)).forEach((file) => { (depGraph[file] || []).forEach((dep) => dependencies.add(helpers.getBuiltPath(dev, dep))); }); - - const entries = [coreFile].concat(Array.from(dependencies), moduleFiles); + const entries = _.uniq([coreFile].concat(Array.from(dependencies), moduleFiles)); var outputFileName = argv.bundleName ? argv.bundleName : 'prebid.js'; diff --git a/karma.conf.maker.js b/karma.conf.maker.js index e05d5b08afd..7b8ca13b20b 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -15,8 +15,7 @@ function newWebpackConfig(codeCoverage, disableFeatures) { mode: 'development', devtool: 'inline-source-map', }); - - delete webpackConfig.entry; + ['entry', 'optimization'].forEach(prop => delete webpackConfig[prop]); webpackConfig.module.rules .flatMap((r) => r.use) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 62e0ab29f8a..491808f2602 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -112,7 +112,8 @@ export const spec = { {code: 'qortex'}, {code: 'adpluto'}, {code: 'headbidder'}, - {code: 'digiad'} + {code: 'digiad'}, + {code: 'monetix'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 3f87476def7..6c268b2d382 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -2,6 +2,7 @@ import {getValue, formatQS, logError, deepAccess, isArray, getBidIdParameter} fr import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -33,11 +34,13 @@ export const OPENRTB = { let SYNC_URL = ''; const BIDDER_CODE = 'admatic'; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const spec = { code: BIDDER_CODE, + gvlid: 1281, aliases: [ - {code: 'pixad'} + {code: 'pixad', gvlid: 1281} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -190,38 +193,45 @@ export const spec = { interpretResponse: (response, request) => { const body = response.body; const bidResponses = []; + if (body && body?.data && isArray(body.data)) { body.data.forEach(bid => { - const resbid = { - requestId: bid.id, - cpm: bid.price, - width: bid.width, - height: bid.height, - currency: body.cur || 'TRY', - netRevenue: true, - creativeId: bid.creative_id, - meta: { - model: bid.mime_type, - advertiserDomains: bid && bid.adomain ? bid.adomain : [] - }, - bidder: bid.bidder, - mediaType: bid.type, - ttl: 60 - }; - - if (resbid.mediaType === 'video' && isUrl(bid.party_tag)) { - resbid.vastUrl = bid.party_tag; - resbid.vastImpUrl = bid.iurl; - } else if (resbid.mediaType === 'video') { - resbid.vastXml = bid.party_tag; - resbid.vastImpUrl = bid.iurl; - } else if (resbid.mediaType === 'banner') { - resbid.ad = bid.party_tag; - } else if (resbid.mediaType === 'native') { - resbid.native = interpretNativeAd(bid.party_tag) - }; + const bidRequest = getAssociatedBidRequest(request.data.imp, bid); + if (bidRequest) { + const resbid = { + requestId: bid.id, + cpm: bid.price, + width: bid.width, + height: bid.height, + currency: body.cur || 'TRY', + netRevenue: true, + creativeId: bid.creative_id, + meta: { + model: bid.mime_type, + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + }, + bidder: bid.bidder, + mediaType: bid.type, + ttl: 60 + }; + + if (resbid.mediaType === 'video' && isUrl(bid.party_tag)) { + resbid.vastUrl = bid.party_tag; + } else if (resbid.mediaType === 'video') { + resbid.vastXml = bid.party_tag; + } else if (resbid.mediaType === 'banner') { + resbid.ad = bid.party_tag; + } else if (resbid.mediaType === 'native') { + resbid.native = interpretNativeAd(bid.party_tag) + }; + + const context = deepAccess(bidRequest, 'mediatype.context'); + if (resbid.mediaType === 'video' && context === 'outstream') { + resbid.renderer = createOutstreamVideoRenderer(bid); + } - bidResponses.push(resbid); + bidResponses.push(resbid); + } }); } return bidResponses; @@ -272,6 +282,40 @@ function isUrl(str) { } }; +function outstreamRender (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse + }); + }); +} + +function createOutstreamVideoRenderer(bid) { + const renderer = Renderer.install({ + id: bid.bidId, + url: RENDERER_URL, + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + logError('Prebid Error calling setRender on renderer' + err); + } + + return renderer; +} + +function getAssociatedBidRequest(bidRequests, bid) { + for (const request of bidRequests) { + if (request.id === bid.id) { + return request; + } + } + return undefined; +} + function enrichSlotWithFloors(slot, bidRequest) { try { const slotFloors = {}; diff --git a/modules/anyclipBidAdapter.js b/modules/anyclipBidAdapter.js new file mode 100644 index 00000000000..cb9103899a4 --- /dev/null +++ b/modules/anyclipBidAdapter.js @@ -0,0 +1,213 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {deepAccess, isArray, isFn, logError, logInfo} from '../src/utils.js'; +import {config} from '../src/config.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + */ + +const BIDDER_CODE = 'anyclip'; +const ENDPOINT_URL = 'https://prebid.anyclip.com'; +const DEFAULT_CURRENCY = 'USD'; +const NET_REVENUE = false; + +/* + * Get the bid floor value from the bidRequest object, either using the getFloor function or by accessing the 'params.floor' property. + * If the bid floor cannot be determined, return 0 as a fallback value. + */ +function getBidFloor(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return deepAccess(bidRequest, 'params.floor', 0); + } + + try { + const bidFloor = bidRequest.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +/** @type {BidderSpec} */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * @param {object} bid + * @return {boolean} + */ + isBidRequestValid: (bid = {}) => { + const bidder = deepAccess(bid, 'bidder'); + const params = deepAccess(bid, 'params', {}); + const mediaTypes = deepAccess(bid, 'mediaTypes', {}); + const banner = deepAccess(mediaTypes, BANNER, {}); + + const isValidBidder = (bidder === BIDDER_CODE); + const isValidSize = (Boolean(banner.sizes) && isArray(mediaTypes[BANNER].sizes) && mediaTypes[BANNER].sizes.length > 0); + const hasSizes = mediaTypes[BANNER] ? isValidSize : false; + const hasRequiredBidParams = Boolean(params.publisherId && params.supplyTagId); + + const isValid = isValidBidder && hasSizes && hasRequiredBidParams; + if (!isValid) { + logError(`Invalid bid request: isValidBidder: ${isValidBidder}, hasSizes: ${hasSizes}, hasRequiredBidParams: ${hasRequiredBidParams}`); + } + return isValid; + }, + + /** + * @param {BidRequest[]} validBidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ + buildRequests: (validBidRequests, bidderRequest) => { + const bidRequest = validBidRequests[0]; + + let refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + + const timeout = bidderRequest.timeout; + const timeoutAdjustment = timeout - ((20 / 100) * timeout); // timeout adjustment - 20% + + if (isPubTagAvailable()) { + // Options + const options = { + publisherId: bidRequest.params.publisherId, + supplyTagId: bidRequest.params.supplyTagId, + url: refererInfo.page, + domain: refererInfo.domain, + prebidTimeout: timeoutAdjustment, + gpid: bidRequest.adUnitCode, + ext: { + transactionId: bidRequest.transactionId + }, + sizes: bidRequest.sizes.map((size) => { + return {width: size[0], height: size[1]} + }) + } + // Floor + const floor = parseFloat(getBidFloor(bidRequest)); + if (!isNaN(floor)) { + options.ext.floor = floor; + } + // Supply Chain (Schain) + if (bidRequest?.schain) { + options.schain = bidRequest.schain + } + // GDPR & Consent (EU) + if (bidderRequest?.gdprConsent) { + options.gdpr = (bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + options.consent = bidderRequest.gdprConsent.consentString; + } + // GPP + if (bidderRequest?.gppConsent?.gppString) { + options.gpp = { + gppVersion: bidderRequest.gppConsent.gppVersion, + sectionList: bidderRequest.gppConsent.sectionList, + applicableSections: bidderRequest.gppConsent.applicableSections, + gppString: bidderRequest.gppConsent.gppString + } + } + // CCPA (US Privacy) + if (bidderRequest?.uspConsent) { + options.usPrivacy = bidderRequest.uspConsent; + } + // COPPA + if (config.getConfig('coppa') === true) { + options.coppa = 1; + } + // Eids + if (bidRequest?.userIdAsEids) { + const eids = bidRequest.userIdAsEids; + if (eids && eids.length) { + options.eids = eids; + } + } + + // Request bids + const requestBidsPromise = window._anyclip.pubTag.requestBids(options); + if (requestBidsPromise !== undefined) { + requestBidsPromise + .then(() => { + logInfo('PubTag requestBids done'); + }) + .catch((err) => { + logError('PubTag requestBids error', err); + }); + } + + // Request + const payload = { + tmax: timeoutAdjustment + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: payload, + bidRequest + } + } + }, + + /** + * @param {*} serverResponse + * @param {ServerRequest} bidRequest + * @return {Bid[]} + */ + interpretResponse: (serverResponse, { bidRequest }) => { + const bids = []; + + if (bidRequest && isPubTagAvailable()) { + const bidResponse = window._anyclip.pubTag.getBids(bidRequest.transactionId); + if (bidResponse) { + const { adServer } = bidResponse; + if (adServer) { + bids.push({ + requestId: bidRequest.bidId, + creativeId: adServer.bid.creativeId, + cpm: bidResponse.cpm, + width: adServer.bid.width, + height: adServer.bid.height, + currency: adServer.bid.currency || DEFAULT_CURRENCY, + netRevenue: NET_REVENUE, + ttl: adServer.bid.ttl, + ad: adServer.bid.ad, + meta: adServer.bid.meta + }); + } + } + } + + return bids; + }, + + /** + * @param {Bid} bid + */ + onBidWon: (bid) => { + if (isPubTagAvailable()) { + window._anyclip.pubTag.bidWon(bid); + } + } +} + +/** + * @return {boolean} + */ +const isPubTagAvailable = () => { + return !!(window._anyclip && window._anyclip.pubTag); +} + +registerBidder(spec); diff --git a/modules/anyclipBidAdapter.md b/modules/anyclipBidAdapter.md new file mode 100644 index 00000000000..ed67c9f6722 --- /dev/null +++ b/modules/anyclipBidAdapter.md @@ -0,0 +1,52 @@ +# Overview + +``` +Module Name: AnyClip Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@anyclip.com +``` + +# Description + +Connects to AnyClip Marketplace for bids. + +For more information about [AnyClip](https://anyclip.com), please contact [support@anyclip.com](support@anyclip.com). + +AnyClip bid adapter supports Banner currently*. + +Use `anyclip` as bidder. + +# Bid Parameters + +| Key | Required | Example | Description | +|---------------|----------|--------------------------|---------------------------------------| +| `publisherId` | Yes | `'12345'` | The publisher ID provided by AnyClip | +| `supplyTagId` | Yes | `'-mptNo0BycUG4oCDgGrU'` | The supply tag ID provided by AnyClip | +| `floor` | No | `0.5` | Floor price | + + +# Sample Ad Unit: For Publishers +## Sample Banner only Ad Unit +```js +var adUnits = [{ + code: 'adunit1', // ad slot HTML element ID + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bids: [{ + bidder: 'anyclip', + params: { + publisherId: '12345', // required, string + supplyTagId: '-mptNo0BycUG4oCDgGrU', // required, string + floor: 0.5 // optional, floor + } + }] +}] +``` + + diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index a6dc05a101f..590ad264515 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -48,7 +48,7 @@ const URL = 'https://ib.adnxs.com/ut/v3/prebid'; const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset']; -const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api', 'startdelay']; +const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api', 'startdelay', 'placement', 'plcmt']; const USER_PARAMS = ['age', 'externalUid', 'external_uid', 'segments', 'gender', 'dnt', 'language']; const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; @@ -72,7 +72,12 @@ const VIDEO_MAPPING = { 'mid_roll': 2, 'post_roll': 3, 'outstream': 4, - 'in-banner': 5 + 'in-banner': 5, + 'in-feed': 6, + 'interstitial': 7, + 'accompanying_content_pre_roll': 8, + 'accompanying_content_mid_roll': 9, + 'accompanying_content_post_roll': 10 } }; const NATIVE_MAPPING = { @@ -916,15 +921,15 @@ function bidToTag(bid) { tag['video_frameworks'] = apiTmp; } break; - case 'startdelay': + case 'plcmt': case 'placement': - const contextKey = 'context'; - if (typeof tag.video[contextKey] !== 'number') { + if (typeof tag.video.context !== 'number') { + const plcmt = videoMediaType['plcmt']; const placement = videoMediaType['placement']; const startdelay = videoMediaType['startdelay']; - const context = getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); - tag.video[contextKey] = VIDEO_MAPPING[contextKey][context]; + const contextVal = getContextFromPlcmt(plcmt, startdelay) || getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); + tag.video.context = VIDEO_MAPPING.context[contextVal]; } break; } @@ -983,8 +988,12 @@ function getContextFromPlacement(ortbPlacement) { if (ortbPlacement === 2) { return 'in-banner'; - } else if (ortbPlacement > 2) { + } else if (ortbPlacement === 3) { return 'outstream'; + } else if (ortbPlacement === 4) { + return 'in-feed'; + } else if (ortbPlacement === 5) { + return 'intersitial'; } } @@ -1002,6 +1011,29 @@ function getContextFromStartDelay(ortbStartDelay) { } } +function getContextFromPlcmt(ortbPlcmt, ortbStartDelay) { + if (!ortbPlcmt) { + return; + } + + if (ortbPlcmt === 2) { + if (!ortbStartDelay) { + return; + } + if (ortbStartDelay === 0) { + return 'accompanying_content_pre_roll'; + } else if (ortbStartDelay === -1) { + return 'accompanying_content_mid_roll'; + } else if (ortbStartDelay === -2) { + return 'accompanying_content_post_roll'; + } + } else if (ortbPlcmt === 3) { + return 'interstitial'; + } else if (ortbPlcmt === 4) { + return 'outstream'; + } +} + function hasUserInfo(bid) { return !!bid.params.user; } diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index 704cffefb39..a4a6c78566e 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -1,32 +1,20 @@ -import { - _each, - deepAccess, - deepSetValue, - getDNT, - inIframe, - isArray, - isFn, - logWarn, - parseSizesInput -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { Renderer } from '../src/Renderer.js'; -import { parseDomain } from '../src/refererDetection.js'; +import {deepAccess, deepSetValue} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'aso'; const DEFAULT_SERVER_URL = 'https://srv.aso1.net'; const DEFAULT_SERVER_PATH = '/prebid/bidder'; -const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -const VERSION = '$prebid.version$_1.1'; +const DEFAULT_CURRENCY = 'USD'; +const VERSION = '$prebid.version$_2.0'; const TTL = 300; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], aliases: [ {code: 'bcmint'}, {code: 'bidgency'} @@ -36,91 +24,30 @@ export const spec = { return !!bid.params && !!bid.params.zone; }, - buildRequests: (validBidRequests, bidderRequest) => { - let serverRequests = []; + buildRequests: (bidRequests, bidderRequest) => { + let requests = []; - _each(validBidRequests, bidRequest => { - const payload = createBasePayload(bidRequest, bidderRequest); - - const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); - const videoParams = deepAccess(bidRequest, 'mediaTypes.video'); - - let imp; - - if (bannerParams && videoParams) { - logWarn('Please note, multiple mediaTypes are not supported. The only banner will be used.') - } - - if (bannerParams) { - imp = createBannerImp(bidRequest, bannerParams) - } else if (videoParams) { - imp = createVideoImp(bidRequest, videoParams) - } - - if (imp) { - payload.imp.push(imp); - } else { - return; - } - - serverRequests.push({ + bidRequests.forEach(bid => { + const data = converter.toORTB({bidRequests: [bid], bidderRequest}); + requests.push({ method: 'POST', - url: getEndpoint(bidRequest), - data: payload, + url: getEndpoint(bid), + data, options: { withCredentials: true, crossOrigin: true }, - bidRequest: bidRequest - }); + bidderRequest + }) }); - - return serverRequests; + return requests; }, - interpretResponse: (serverResponse, {bidRequest}) => { - const response = serverResponse && serverResponse.body; - - if (!response) { - return []; - } - - const serverBids = response.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); - const serverBid = serverBids[0]; - - let bids = []; - - const bid = { - requestId: serverBid.impid, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - ttl: TTL, - creativeId: serverBid.crid, - netRevenue: true, - currency: response.cur, - mediaType: bidRequest.mediaType, - meta: { - mediaType: bidRequest.mediaType, - advertiserDomains: serverBid.adomain ? serverBid.adomain : [] - } - }; - - if (bid.mediaType === BANNER) { - bid.ad = serverBid.adm; - } else if (bid.mediaType === VIDEO) { - bid.vastXml = serverBid.adm; - if (deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream') { - bid.adResponse = { - content: bid.vastXml, - }; - bid.renderer = createRenderer(bidRequest, OUTSTREAM_RENDERER_URL); - } + interpretResponse: (response, request) => { + if (response.body) { + return converter.fromORTB({response: response.body, request: request.data}).bids; } - - bids.push(bid); - - return bids; + return []; }, getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { @@ -141,16 +68,25 @@ export const spec = { query = tryAppendQueryString(query, 'us_privacy', uspConsent); } - _each(serverResponses, resp => { + if (query.slice(-1) === '&') { + query = query.slice(0, -1); + } + + serverResponses.forEach(resp => { const userSyncs = deepAccess(resp, 'body.ext.user_syncs'); if (!userSyncs) { return; } - _each(userSyncs, us => { + userSyncs.forEach(us => { + let url = us.url; + if (query) { + url = url + (url.indexOf('?') === -1 ? '?' : '&') + query; + } + urls.push({ type: us.type, - url: us.url + (query ? '?' + query : '') + url: url }); }); }); @@ -160,123 +96,50 @@ export const spec = { } }; -function outstreamRender(bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [bid.width, bid.height], - targetId: bid.adUnitCode, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }); - }); -} - -function createRenderer(bid, url) { - const renderer = Renderer.install({ - id: bid.bidId, - url: url, - loaded: false, - config: deepAccess(bid, 'renderer.options'), - adUnitCode: bid.adUnitCode - }); - renderer.setRender(outstreamRender); - return renderer; -} - -function getUrlsInfo(bidderRequest) { - const {page, domain, ref} = bidderRequest.refererInfo; - return { - // TODO: do the fallbacks make sense here? - page: page || bidderRequest.refererInfo?.topmostLocation, - referrer: ref || '', - domain: domain || parseDomain(bidderRequest?.refererInfo?.topmostLocation) - } -} - -function getSize(paramSizes) { - const parsedSizes = parseSizesInput(paramSizes); - const sizes = parsedSizes.map(size => { - const [width, height] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return {w, h}; - }); - - return sizes[0] || null; -} - -function getBidFloor(bidRequest, size) { - if (!isFn(bidRequest.getFloor)) { - return null; - } - - const bidFloor = bidRequest.getFloor({ - mediaType: bidRequest.mediaType, - size: size ? [size.w, size.h] : '*' - }); - - if (!isNaN(bidFloor.floor)) { - return bidFloor; - } - - return null; -} - -function createBaseImp(bidRequest, size) { - const imp = { - id: bidRequest.bidId, - tagid: bidRequest.adUnitCode, - secure: 1 - }; - - const bidFloor = getBidFloor(bidRequest, size); - if (bidFloor !== null) { - imp.bidfloor = bidFloor.floor; - imp.bidfloorcur = bidFloor.currency; - } +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: TTL + }, - return imp; -} + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); -function createBannerImp(bidRequest, bannerParams) { - bidRequest.mediaType = BANNER; + imp.tagid = bidRequest.adUnitCode; + imp.secure = Number(window.location.protocol === 'https:'); + return imp; + }, - const size = getSize(bannerParams.sizes); - const imp = createBaseImp(bidRequest, size); + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); - imp.banner = { - w: size.w, - h: size.h, - topframe: inIframe() ? 0 : 1 - } + if (bidderRequest.gdprConsent) { + const consentsIds = getConsentsIds(bidderRequest.gdprConsent); + if (consentsIds) { + deepSetValue(request, 'user.ext.consents', consentsIds); + } + } - return imp; -} + if (!request.cur) { + request.cur = [DEFAULT_CURRENCY]; + } -function createVideoImp(bidRequest, videoParams) { - bidRequest.mediaType = VIDEO; - const size = getSize(videoParams.playerSize); - const imp = createBaseImp(bidRequest, size); + return request; + }, - imp.video = { - mimes: videoParams.mimes, - minduration: videoParams.minduration, - startdelay: videoParams.startdelay, - linearity: videoParams.linearity, - maxduration: videoParams.maxduration, - skip: videoParams.skip, - protocols: videoParams.protocols, - skipmin: videoParams.skipmin, - api: videoParams.api - } + bidResponse(buildBidResponse, bid, context) { + context.mediaType = deepAccess(bid, 'ext.prebid.type'); + return buildBidResponse(bid, context); + }, - if (size) { - imp.video.w = size.w; - imp.video.h = size.h; + overrides: { + request: { + // We don't need extra data + gdprAddtlConsent(setAddtlConsent, ortbRequest, bidderRequest) { + } + } } - - return imp; -} +}); function getEndpoint(bidRequest) { const serverUrl = bidRequest.params.server || DEFAULT_SERVER_URL; @@ -287,7 +150,7 @@ function getConsentsIds(gdprConsent) { const consents = deepAccess(gdprConsent, 'vendorData.purpose.consents', []); let consentsIds = []; - Object.keys(consents).forEach(function (key) { + Object.keys(consents).forEach(key => { if (consents[key] === true) { consentsIds.push(key); } @@ -296,61 +159,4 @@ function getConsentsIds(gdprConsent) { return consentsIds.join(','); } -function createBasePayload(bidRequest, bidderRequest) { - const urlsInfo = getUrlsInfo(bidderRequest); - - const payload = { - id: bidRequest.bidId, - at: 1, - tmax: bidderRequest.timeout, - site: { - id: urlsInfo.domain, - domain: urlsInfo.domain, - page: urlsInfo.page, - ref: urlsInfo.referrer - }, - device: { - dnt: getDNT() ? 1 : 0, - h: window.innerHeight, - w: window.innerWidth, - }, - imp: [], - ext: {}, - user: {} - }; - - if (bidRequest.params.attr) { - deepSetValue(payload, 'site.ext.attr', bidRequest.params.attr); - } - - if (bidderRequest.gdprConsent) { - deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - const consentsIds = getConsentsIds(bidderRequest.gdprConsent); - if (consentsIds) { - deepSetValue(payload, 'user.ext.consents', consentsIds); - } - deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); - } - - if (bidderRequest.uspConsent) { - deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (config.getConfig('coppa')) { - deepSetValue(payload, 'regs.coppa', 1); - } - - const eids = deepAccess(bidRequest, 'userIdAsEids'); - if (eids && eids.length) { - deepSetValue(payload, 'user.ext.eids', eids); - } - - const schainData = deepAccess(bidRequest, 'schain.nodes'); - if (isArray(schainData) && schainData.length > 0) { - deepSetValue(payload, 'source.ext.schain', bidRequest.schain); - } - - return payload; -} - registerBidder(spec); diff --git a/modules/asoBidAdapter.md b/modules/asoBidAdapter.md index f187389c5b5..ebf5cfd4614 100644 --- a/modules/asoBidAdapter.md +++ b/modules/asoBidAdapter.md @@ -17,7 +17,6 @@ For more information, please visit [Adserver.Online](https://adserver.online). | Name | Scope | Description | Example | Type | |-----------|----------|-------------------------|------------------------|------------| | `zone` | required | Zone ID | `73815` | `Integer` | -| `attr` | optional | Custom targeting params | `{foo: ["a", "b"]}` | `Object` | | `server` | optional | Custom bidder endpoint | `https://endpoint.url` | `String` | # Test parameters for banner @@ -49,6 +48,9 @@ var videoAdUnit = [ code: 'video1', mediaTypes: { video: { + mimes: [ + "video/mp4" + ], playerSize: [[640, 480]], context: 'instream' // or 'outstream' } @@ -69,7 +71,6 @@ The Adserver.Online Bid Adapter expects Prebid Cache (for video) to be enabled. ``` pbjs.setConfig({ - usePrebidCache: true, cache: { url: 'https://prebid.adnxs.com/pbc/v1/cache' } diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index 775f8fc3da2..3afaacaeb81 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -54,8 +54,9 @@ Object.assign(BidInterceptor.prototype, { return { no: ruleNo, match: this.matcher(ruleDef.when, ruleNo), - replace: this.replacer(ruleDef.then || {}, ruleNo), + replace: this.replacer(ruleDef.then, ruleNo), options: Object.assign({}, this.DEFAULT_RULE_OPTIONS, ruleDef.options), + paapi: this.paapiReplacer(ruleDef.paapi || [], ruleNo) } }, /** @@ -114,6 +115,10 @@ Object.assign(BidInterceptor.prototype, { * @return {ReplacerFn} */ replacer(replDef, ruleNo) { + if (replDef === null) { + return () => null + } + replDef = replDef || {}; let replFn; if (typeof replDef === 'function') { replFn = ({args}) => replDef(...args); @@ -145,6 +150,17 @@ Object.assign(BidInterceptor.prototype, { return response; } }, + + paapiReplacer(paapiDef, ruleNo) { + if (Array.isArray(paapiDef)) { + return () => paapiDef; + } else if (typeof paapiDef === 'function') { + return paapiDef + } else { + this.logger.logError(`Invalid 'paapi' definition for debug bid interceptor (in rule #${ruleNo})`); + } + }, + responseDefaults(bid) { return { requestId: bid.bidId, @@ -198,11 +214,12 @@ Object.assign(BidInterceptor.prototype, { * @param {{}[]} bids? * @param {BidRequest} bidRequest * @param {function(*)} addBid called once for each mock response + * @param addPaapiConfig called once for each mock PAAPI config * @param {function()} done called once after all mock responses have been run through `addBid` * @returns {{bids: {}[], bidRequest: {}} remaining bids that did not match any rule (this applies also to * bidRequest.bids) */ - intercept({bids, bidRequest, addBid, done}) { + intercept({bids, bidRequest, addBid, addPaapiConfig, done}) { if (bids == null) { bids = bidRequest.bids; } @@ -211,10 +228,12 @@ Object.assign(BidInterceptor.prototype, { const callDone = delayExecution(done, matches.length); matches.forEach((match) => { const mockResponse = match.rule.replace(match.bid, bidRequest); + const mockPaapi = match.rule.paapi(match.bid, bidRequest); const delay = match.rule.options.delay; - this.logger.logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse) + this.logger.logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response, PAAPI configs:`, match.bid, mockResponse, mockPaapi) this.setTimeout(() => { - addBid(mockResponse, match.bid); + mockResponse && addBid(mockResponse, match.bid); + mockPaapi.forEach(cfg => addPaapiConfig(cfg, match.bid, bidRequest)); callDone(); }, delay) }); diff --git a/modules/debugging/debugging.js b/modules/debugging/debugging.js index 8a4ad7a9545..2fd1731dc4e 100644 --- a/modules/debugging/debugging.js +++ b/modules/debugging/debugging.js @@ -99,7 +99,13 @@ function registerBidInterceptor(getHookFn, interceptor) { export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, cbs) { const done = delayExecution(cbs.onCompletion, 2); - ({bids, bidRequest} = interceptBids({bids, bidRequest, addBid: cbs.onBid, done})); + ({bids, bidRequest} = interceptBids({ + bids, + bidRequest, + addBid: cbs.onBid, + addPaapiConfig: (config, bidRequest) => cbs.onPaapi({bidId: bidRequest.bidId, config}), + done + })); if (bids.length === 0) { done(); } else { diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index 1ca13eb4927..73df01bf205 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -5,7 +5,8 @@ export function makePbsInterceptor({createBid}) { return function pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, { onResponse, onError, - onBid + onBid, + onFledge, }) { let responseArgs; const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1) @@ -20,7 +21,19 @@ export function makePbsInterceptor({createBid}) { }) } bidRequests = bidRequests - .map((req) => interceptBids({bidRequest: req, addBid, done}).bidRequest) + .map((req) => interceptBids({ + bidRequest: req, + addBid, + addPaapiConfig(config, bidRequest, bidderRequest) { + onFledge({ + adUnitCode: bidRequest.adUnitCode, + ortb2: bidderRequest.ortb2, + ortb2Imp: bidRequest.ortb2Imp, + config + }) + }, + done + }).bidRequest) .filter((req) => req.bids.length > 0) if (bidRequests.length > 0) { diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index 7c24cd6a8f6..0a64ed88ca5 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -98,6 +98,20 @@ export const spec = { deepSetValue(openRtbBidRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } + // GPP Consent + if (bidderRequest?.gppConsent?.gppString) { + deepSetValue(openRtbBidRequest, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(openRtbBidRequest, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } else if (bidderRequest?.ortb2?.regs?.gpp) { + deepSetValue(openRtbBidRequest, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(openRtbBidRequest, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } + + // coppa compliance + if (bidderRequest?.ortb2?.regs?.coppa) { + deepSetValue(openRtbBidRequest, 'regs.coppa', 1); + } + injectEids(openRtbBidRequest, validBidRequests); return { diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 5f1b46ff9eb..2ce6cda16d1 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -4,6 +4,7 @@ import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'e_volution'; +const GVLID = 957; const AD_URL = 'https://service.e-volution.ai/?c=o&m=multi'; const URL_SYNC = 'https://service.e-volution.ai/?c=o&m=sync'; const NO_SYNC = true; @@ -57,6 +58,7 @@ function getUserId(eids, id, source, uidExt) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], noSync: NO_SYNC, diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index 7e841234e24..bda4494faaf 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -7,6 +7,11 @@ import {getGptSlotForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; +// import parent module to keep backwards-compat for NPM consumers after paapi was split from fledgeForGpt +// there's a special case in webpack.conf.js to avoid duplicating build output on non-npm builds +// TODO: remove this in prebid 9 +// eslint-disable-next-line prebid/validate-imports +import './paapi.js'; const MODULE = 'fledgeForGpt'; let getPAAPIConfig; diff --git a/modules/gammaBidAdapter.js b/modules/gammaBidAdapter.js index 40abdd81930..dadfe2ab14b 100644 --- a/modules/gammaBidAdapter.js +++ b/modules/gammaBidAdapter.js @@ -5,9 +5,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid */ -const ENDPOINT = 'https://hb.gammaplatform.com'; -const ENDPOINT_USERSYNC = 'https://cm-supply-web.gammaplatform.com'; const BIDDER_CODE = 'gamma'; +const ENDPOINTS = { + SGP: 'https://hb.gammaplatform.com', + JPN: 'https://hb-jp.gammaplatform.com', + US_WEST: 'https://hb-us.gammaplatform.com', + EU: 'https://hb-eu.gammaplatform.com' +} export const spec = { code: BIDDER_CODE, @@ -33,8 +37,10 @@ export const spec = { buildRequests: function(bidRequests, bidderRequest) { const serverRequests = []; const bidderRequestReferer = bidderRequest?.refererInfo?.page || ''; + let ENDPOINT; for (var i = 0, len = bidRequests.length; i < len; i++) { const gaxObjParams = bidRequests[i]; + ENDPOINT = getAdUrlByRegion(gaxObjParams); serverRequests.push({ method: 'GET', url: ENDPOINT + '/adx/request?wid=' + gaxObjParams.params.siteId + '&zid=' + gaxObjParams.params.zoneId + '&hb=pbjs&bidid=' + gaxObjParams.bidId + '&urf=' + encodeURIComponent(bidderRequestReferer) @@ -60,16 +66,45 @@ export const spec = { } return bids; - }, + } +} - getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: ENDPOINT_USERSYNC + '/adx/usersync' - }]; +/** + * Get endpoint url by region + * @param bid + * @return aUrl + */ +function getAdUrlByRegion(bid) { + let ENDPOINT; + + if (bid.params.region && ENDPOINTS[bid.params.region]) { + ENDPOINT = ENDPOINTS[bid.params.region]; + } else { + try { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const region = timezone.split('/')[0]; + + switch (region) { + case 'Europe': + ENDPOINT = ENDPOINTS['EU']; + break; + case 'Australia': + ENDPOINT = ENDPOINTS['JPN']; + break; + case 'Asia': + ENDPOINT = ENDPOINTS['SGP']; + break; + case 'America': + ENDPOINT = ENDPOINTS['US_WEST']; + break; + default: ENDPOINT = ENDPOINTS['SGP']; + } + } catch (err) { + ENDPOINT = ENDPOINTS['SGP']; } } + + return ENDPOINT; } /** diff --git a/modules/gammaBidAdapter.md b/modules/gammaBidAdapter.md index 2902be78492..bcb26d0b86e 100644 --- a/modules/gammaBidAdapter.md +++ b/modules/gammaBidAdapter.md @@ -12,6 +12,14 @@ Connects to Gamma exchange for bids. Gamma bid adapter supports Banner, Video. +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :------------------------ | :------------------- | +| `zoneId` | required | Zone ID | "1398219417" | +| `siteId` | required | Website ID | "1398219351" | +| `region` | optional (for prebid.js) | Prefix of the region to which prebid must send requests. Possible values: "SGP", "JPN", "US_WEST", "EU" | "SGP" | + # Test Parameters: For Banner ``` var adUnits = [{ @@ -22,8 +30,9 @@ var adUnits = [{ bids: [{ bidder: 'gamma', params: { - siteId: '1465446377', - zoneId: '1515999290' + siteId: '1398219351', + zoneId: '1398219417', + region: 'SGP' } }] @@ -39,8 +48,9 @@ var adUnits = [{ bids: [{ bidder: 'gamma', params: { - siteId: '1465446377', - zoneId: '1493280341' + siteId: '1398219351', + zoneId: '1614755846', + region: 'SGP' } }] @@ -59,8 +69,9 @@ In order to receive bids please map localhost to (any) test domain. bids: [{ bidder: 'gamma', params: { - siteId: '1465446377', - zoneId: '1515999290' + siteId: '1398219351', + zoneId: '1398219417', + region: 'SGP' } }] }]; diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 7f8627ec5f7..60851d2b4dd 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -318,7 +318,8 @@ function buildRequests(validBidRequests, bidderRequest) { const gpid = deepAccess(ortb2Imp, 'ext.data.pbadslot') || deepAccess(ortb2Imp, 'ext.data.adserver.adslot'); let sizes = [1, 1]; let data = {}; - + data.displaymanager = 'Prebid.js - gumgum'; + data.displaymanagerver = '$prebid.version$'; const date = new Date(); const lt = date.getTime(); const to = date.getTimezoneOffset(); diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index b72601e5ebb..e86d7022987 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -95,13 +95,16 @@ function buildRequests(validBidRequests, bidderRequest) { ] }, imp: impressions, - user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent), + user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent) }); - if (firstBidRequest.ortb2 != null) { - krakenParams.site = { - cat: firstBidRequest.ortb2.site.cat + // Add full ortb2 object as backup + if (firstBidRequest.ortb2) { + const siteCat = firstBidRequest.ortb2.site?.cat; + if (siteCat != null) { + krakenParams.site = { cat: siteCat }; } + krakenParams.ext = { ortb2: firstBidRequest.ortb2 }; } // Add schain @@ -478,6 +481,11 @@ function getImpression(bid) { } } + // Add full ortb2Imp object as backup + if (bid.ortb2Imp) { + imp.ext = { ortb2Imp: bid.ortb2Imp }; + } + if (bid.mediaTypes) { const { banner, video, native } = bid.mediaTypes; diff --git a/modules/lassoBidAdapter.js b/modules/lassoBidAdapter.js index e1f9636e4f1..6215af03a97 100644 --- a/modules/lassoBidAdapter.js +++ b/modules/lassoBidAdapter.js @@ -37,7 +37,6 @@ export const spec = { url: encodeURIComponent(window.location.href), bidderRequestId: bidRequest.bidderRequestId, adUnitCode: bidRequest.adUnitCode, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: bidRequest.auctionId, bidId: bidRequest.bidId, transactionId: bidRequest.ortb2Imp?.ext?.tid, @@ -48,11 +47,20 @@ export const spec = { params: JSON.stringify(bidRequest.params), crumbs: JSON.stringify(bidRequest.crumbs), prebidVersion: '$prebid.version$', - version: 3, + version: 4, coppa: config.getConfig('coppa') == true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined } + if ( + bidderRequest && + bidderRequest.gppConsent && + bidderRequest.gppConsent.gppString + ) { + payload.gpp = bidderRequest.gppConsent.gppString; + payload.gppSid = bidderRequest.gppConsent.applicableSections; + } + return { method: 'GET', url: getBidRequestUrl(aimXR, bidRequest.params), @@ -74,6 +82,7 @@ export const spec = { const bidResponse = { requestId: response.bidid, + bidId: response.bidid, cpm: response.bid.price, currency: response.cur, width: response.bid.w, diff --git a/modules/mediaimpactBidAdapter.js b/modules/mediaimpactBidAdapter.js index 9a86632f052..4ce11201507 100644 --- a/modules/mediaimpactBidAdapter.js +++ b/modules/mediaimpactBidAdapter.js @@ -27,9 +27,14 @@ export const spec = { }; validBidRequests.forEach(function(validBidRequest) { + let sizes = validBidRequest.sizes; + if (typeof validBidRequest.params.sizes !== 'undefined') { + sizes = validBidRequest.params.sizes; + } + let bidRequestObject = { adUnitCode: validBidRequest.adUnitCode, - sizes: validBidRequest.sizes, + sizes: sizes, bidId: validBidRequest.bidId, referer: referer }; @@ -47,7 +52,7 @@ export const spec = { bidRequests.push(bidRequestObject); - beaconParams.sizes.push(spec.joinSizesToString(validBidRequest.sizes)); + beaconParams.sizes.push(spec.joinSizesToString(sizes)); beaconParams.referer = encodeURIComponent(referer); }); diff --git a/modules/mediaimpactBidAdapter.md b/modules/mediaimpactBidAdapter.md index 2fc6697fffb..fb1a3ea27b2 100644 --- a/modules/mediaimpactBidAdapter.md +++ b/modules/mediaimpactBidAdapter.md @@ -35,7 +35,8 @@ About us : https://mediaimpact.com.ua { bidder: "mediaimpact", params: { - partnerId: 6698 + partnerId: 6698, + sizes: [[300, 600]], } } ] diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 18f2b461142..de91b508125 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -33,6 +33,8 @@ const DEFAULT_CURRENCY = 'USD'; const VIDEO_PARAMS_DEFAULT = { api: undefined, + context: undefined, + delivery: undefined, linearity: undefined, maxduration: undefined, mimes: [ @@ -43,8 +45,14 @@ const VIDEO_PARAMS_DEFAULT = { minduration: undefined, placement: undefined, + plcmt: undefined, + playbackend: undefined, playbackmethod: undefined, + pos: undefined, protocols: undefined, + skip: undefined, + skipafter: undefined, + skipmin: undefined, startdelay: undefined, }; diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index bef9a43749f..e6c8f8b098e 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -45,15 +45,19 @@ function buildRequests(bidReqs, bidderRequest) { const minSize = _getMinSize(processedSizes); const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + const gpidData = _extractGpidData(bid); const imp = { id: bid.bidId, banner: { format: processedSizes, ext: { - viewability: viewabilityAmountRounded + viewability: viewabilityAmountRounded, } }, + ext: { + ...gpidData + }, tagid: String(bid.adUnitCode) }; @@ -241,6 +245,15 @@ function _getViewability(element, topWin, {w, h} = {}) { return getWindowTop().document.visibilityState === 'visible' ? percentInView(element, topWin, {w, h}) : 0; } +function _extractGpidData(bid) { + return { + gpid: bid?.ortb2Imp?.ext?.gpid, + adserverName: bid?.ortb2Imp?.ext?.data?.adserver?.name, + adslot: bid?.ortb2Imp?.ext?.data?.adserver?.adslot, + pbadslot: bid?.ortb2Imp?.ext?.data?.pbadslot, + } +} + function _isIframe() { try { return getWindowSelf() !== getWindowTop(); diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index 547447039da..39bd50f61a9 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -1,249 +1,487 @@ -import {deepAccess, flatten, isArray, isNumber, parseSizesInput} from '../src/utils.js'; +import { + logWarn, + logInfo, + isArray, + isFn, + deepAccess, + isEmpty, + contains, + triggerPixel, + isInteger, + getBidIdParameter, + getDNT +} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import {find} from '../src/polyfill.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; -const ENDPOINT = 'https://ghb.spotim.market/v2/auction'; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openweb'; -const DISPLAY = 'display'; -const syncsCache = {}; +const ADAPTER_VERSION = '6.0.0'; +const TTL = 360; +const DEFAULT_CURRENCY = 'USD'; +const SELLER_ENDPOINT = 'https://hb.openwebmp.com/'; +const MODES = { + PRODUCTION: 'hb-multi', + TEST: 'hb-multi-test' +} +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} export const spec = { code: BIDDER_CODE, gvlid: 280, - supportedMediaTypes: [VIDEO, BANNER, ADPOD], - isBidRequestValid: function (bid) { - return isNumber(deepAccess(bid, 'params.aid')); - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; + version: ADAPTER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function (bidRequest) { + if (!bidRequest.params) { + logWarn('no params have been set to OpenWeb adapter'); + return false; + } - function addSyncs(bid) { - const uris = bid.cookieURLs; - const types = bid.cookieURLSTypes || []; + if (!bidRequest.params.org) { + logWarn('org is a mandatory param for OpenWeb adapter'); + return false; + } - if (Array.isArray(uris)) { - uris.forEach((uri, i) => { - const type = types[i] || 'image'; + return true; + }, + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; - if ((!syncOptions.pixelEnabled && type === 'image') || - (!syncOptions.iframeEnabled && type === 'iframe') || - syncsCache[uri]) { - return; - } + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; - syncsCache[uri] = true; - syncs.push({ - type: type, - url: uri - }) - }) - } + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: getEndpoint(testMode), + data: combinedRequestsObject } + }, + interpretResponse: function ({body}) { + const bidResponses = []; - if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { - isArray(serverResponses) && serverResponses.forEach((response) => { - if (response.body) { - if (isArray(response.body)) { - response.body.forEach(b => { - addSyncs(b); - }) - } else { - addSyncs(response.body) + if (body && body.bids && body.bids.length) { + body.bids.forEach(adUnit => { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || DEFAULT_CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.requestId, + netRevenue: adUnit.netRevenue || true, + nurl: adUnit.nurl, + mediaType: adUnit.mediaType, + meta: { + mediaType: adUnit.mediaType } + }; + + if (adUnit.mediaType === VIDEO) { + bidResponse.vastXml = adUnit.vastXml; + } else if (adUnit.mediaType === BANNER) { + bidResponse.ad = adUnit.ad; } - }) - } - return syncs; - }, - /** - * Make a server request from the list of BidRequests - * @param bidRequests - * @param adapterRequest - */ - buildRequests: function (bidRequests, adapterRequest) { - const { tag, bids } = bidToTag(bidRequests, adapterRequest); - return [{ - data: Object.assign({}, tag, { BidRequests: bids }), - adapterRequest, - method: 'POST', - url: ENDPOINT - }]; - }, - /** - * Unpack the response from the server into a list of bids - * @param serverResponse - * @param bidderRequest - * @return {Bid[]} An array of bids which were nested inside the server - */ - interpretResponse: function (serverResponse, { adapterRequest }) { - serverResponse = serverResponse.body; - let bids = []; + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } - if (!isArray(serverResponse)) { - return parseRTBResponse(serverResponse, adapterRequest); + bidResponses.push(bidResponse); + }); } - serverResponse.forEach(serverBidResponse => { - bids = flatten(bids, parseRTBResponse(serverBidResponse, adapterRequest)); - }); - - return bids; + return bidResponses; }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + syncs.push({ + type: 'iframe', + url: response.body.params.userSyncURL + }); + } + if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + const pixels = response.body.params.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }) + syncs.push(...pixels) + } + } + return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } - transformBidParams(params) { - return convertTypes({ - 'aid': 'number', - }, params); + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } } }; -function parseRTBResponse(serverResponse, adapterRequest) { - const isEmptyResponse = !serverResponse || !isArray(serverResponse.bids); - const bids = []; +registerBidder(spec); - if (isEmptyResponse) { - return bids; +/** + * Get floor price + * @param bid {bid} + * @returns {Number} + */ +function getFloor(bid, mediaType, currency) { + if (!isFn(bid.getFloor)) { + return 0; } + let floorResult = bid.getFloor({ + currency: currency, + mediaType: mediaType, + size: '*' + }); + return floorResult.currency === currency && floorResult.floor ? floorResult.floor : 0; +} - serverResponse.bids.forEach(serverBid => { - const request = find(adapterRequest.bids, (bidRequest) => { - return bidRequest.bidId === serverBid.requestId; - }); +/** + * Get the the ad sizes array from the bid + * @param bid {bid} + * @returns {Array} + */ +function getSizesArray(bid, mediaType) { + let sizesArray = [] - if (serverBid.cpm !== 0 && request !== undefined) { - const bid = createBid(serverBid, request); + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizesArray = bid.sizes; + } - bids.push(bid); - } + return sizesArray; +} + +/** + * Get schain string value + * @param schainObject {Object} + * @returns {string} + */ +function getSupplyChain(schainObject) { + if (isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; }); + return scStr; +} - return bids; +/** + * Get encoded node value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !isEmpty(val) ? encodeURIComponent(val) : ''; } -function bidToTag(bidRequests, adapterRequest) { - // start publisher env - const tag = { - // TODO: is 'page' the right value here? - Domain: deepAccess(adapterRequest, 'refererInfo.page') - }; - if (config.getConfig('coppa') === true) { - tag.Coppa = 1; +/** + * Get preferred user-sync method based on publisher configuration + * @param bidderCode {string} + * @returns {string} + */ +function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return SUPPORTED_SYNC_METHODS.IFRAME; } - if (deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { - tag.GDPR = 1; - tag.GDPRConsent = deepAccess(adapterRequest, 'gdprConsent.consentString'); + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; } - if (deepAccess(adapterRequest, 'uspConsent')) { - tag.USP = deepAccess(adapterRequest, 'uspConsent'); +} + +/** + * Check if sync rule is supported + * @param syncRule {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; } - if (deepAccess(bidRequests[0], 'schain')) { - tag.Schain = deepAccess(bidRequests[0], 'schain'); + const isInclude = syncRule.filter === 'include'; + const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && contains(bidders, bidderCode); +} + +/** + * Get the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : SELLER_ENDPOINT + MODES.PRODUCTION; +} + +/** + * get device type + * @param uad {ua} + * @returns {string} + */ +function getDeviceType(ua) { + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i + .test(ua.toLowerCase())) { + return '5'; } - if (deepAccess(bidRequests[0], 'userId')) { - tag.UserIds = deepAccess(bidRequests[0], 'userId'); + if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i + .test(ua.toLowerCase())) { + return '4'; } - if (deepAccess(bidRequests[0], 'userIdAsEids')) { - tag.UserEids = deepAccess(bidRequests[0], 'userIdAsEids'); + if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i + .test(ua.toLowerCase())) { + return '3'; } - // end publisher env - const bids = []; + return '1'; +} + +function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; - for (let i = 0, length = bidRequests.length; i < length; i++) { - const bid = prepareBidRequests(bidRequests[i]); - bids.push(bid); + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); } - return { tag, bids }; + return bidsArray; } /** - * Parse mediaType - * @param bidReq {object} - * @returns {object} + * Generate bid specific parameters + * @param {bid} bid + * @param {bidderRequest} bidderRequest + * @returns {Object} bid specific params object */ -function prepareBidRequests(bidReq) { - const mediaType = deepAccess(bidReq, 'mediaTypes.video') ? VIDEO : DISPLAY; - const sizes = mediaType === VIDEO ? deepAccess(bidReq, 'mediaTypes.video.playerSize') : deepAccess(bidReq, 'mediaTypes.banner.sizes'); - const bidReqParams = { - 'CallbackId': bidReq.bidId, - 'Aid': bidReq.params.aid, - 'AdType': mediaType, - 'Sizes': parseSizesInput(sizes).join(',') +function generateBidParameters(bid, bidderRequest) { + const {params} = bid; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); + const currency = params.currency || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; + + // fix floor price in case of NAN + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + currency: currency, + floorPrice: Math.max(getFloor(bid, mediaType, currency), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + loop: getBidIdParameter('bidderRequestsCount', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: bid.ortb2Imp?.ext?.tid || '', + coppa: 0, }; - bidReqParams.PlacementId = bidReq.adUnitCode; - if (bidReq.params.iframe) { - bidReqParams.AdmType = 'iframe'; + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); + if (mimes) { + bidObject.mimes = mimes; + } + const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); + if (api) { + bidObject.api = api; + } + + const sua = deepAccess(bid, `ortb2.device.sua`); + if (sua) { + bidObject.sua = sua; + } + + const coppa = deepAccess(bid, `ortb2.regs.coppa`); + if (coppa) { + bidObject.coppa = 1; + } + if (mediaType === VIDEO) { - const context = deepAccess(bidReq, 'mediaTypes.video.context'); - if (context === ADPOD) { - bidReqParams.Adpod = deepAccess(bidReq, 'mediaTypes.video'); + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + // verify playbackMethod is of type integer array, or integer only. + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + + const protocols = deepAccess(bid, `mediaTypes.video.protocols`); + if (protocols) { + bidObject.protocols = protocols; + } + + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); + if (plcmt) { + bidObject.plcmt = plcmt; } } - return bidReqParams; + + return bidObject; } -/** - * Prepare all parameters for request - * @param bidderRequest {object} - * @returns {object} - */ -function getMediaType(bidderRequest) { - return deepAccess(bidderRequest, 'mediaTypes.video') ? VIDEO : BANNER; +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; } /** - * Configure new bid by response - * @param bidResponse {object} - * @param bidRequest {Object} - * @returns {object} + * Generate params that are common between all bids + * @param {single bid object} generalObject + * @param {bidderRequest} bidderRequest + * @returns {object} the common params object */ -function createBid(bidResponse, bidRequest) { - const mediaType = getMediaType(bidRequest) - const context = deepAccess(bidRequest, 'mediaTypes.video.context'); - const bid = { - requestId: bidResponse.requestId, - creativeId: bidResponse.cmpId, - height: bidResponse.height, - currency: bidResponse.cur, - width: bidResponse.width, - cpm: bidResponse.cpm, - netRevenue: true, - mediaType, - ttl: 300, - meta: { - advertiserDomains: bidResponse.adomain || [] +function generateGeneralParams(generalObject, bidderRequest) { + const domain = deepAccess(bidderRequest, 'refererInfo.domain') || window.location.hostname; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const {bidderCode, timeout} = bidderRequest; + const generalBidParams = generalObject.params; + + // these params are snake_case instead of camelCase to allow backwards compatability on the server. + // in the future, these will be converted to camelCase to match our convention. + const generalParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: ADAPTER_VERSION, + publisher_id: generalBidParams.org, + publisher_name: domain, + site_domain: domain, + dnt: getDNT() ? 1 : 0, + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + is_wrapper: !!generalBidParams.isWrapper, + session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), + tmax: timeout + } + + const userIdsParam = getBidIdParameter('userId', generalObject); + if (userIdsParam) { + generalParams.userIds = JSON.stringify(userIdsParam); + } + + const ortb2Metadata = bidderRequest.ortb2 || {}; + if (ortb2Metadata.site) { + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); + } + if (ortb2Metadata.user) { + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); + } + + if (syncEnabled) { + const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + generalParams.cs_method = allowedSyncMethod; } - }; + } - if (mediaType === BANNER) { - return Object.assign(bid, { - ad: bidResponse.ad, - adUrl: bidResponse.adUrl, - }); + if (bidderRequest.auctionStart) { + generalParams.auction_start = bidderRequest.auctionStart; } - if (context === ADPOD) { - Object.assign(bid, { - meta: { - primaryCatId: bidResponse.primaryCatId, - }, - video: { - context: ADPOD, - durationSeconds: bidResponse.durationSeconds - } - }); + + if (bidderRequest.uspConsent) { + generalParams.us_privacy = bidderRequest.uspConsent; } - Object.assign(bid, { - vastUrl: bidResponse.vastUrl - }); + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } - return bid; -} + if (bidderRequest.gppConsent) { + generalParams.gpp = bidderRequest.gppConsent.gppString; + generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + generalParams.gpp = bidderRequest.ortb2.regs.gpp; + generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } -registerBidder(spec); + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; + } + + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); + generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); + } + + return generalParams; +} diff --git a/modules/openwebBidAdapter.md b/modules/openwebBidAdapter.md index dc8bfa6c59e..36c1f0ca6c5 100644 --- a/modules/openwebBidAdapter.md +++ b/modules/openwebBidAdapter.md @@ -1,27 +1,53 @@ # Overview -**Module Name**: OpenWeb Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: monetization@openweb.com +Module Name: OpenWeb Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: monetization@openweb.com + # Description -OpenWeb.com official prebid adapter. Available in both client and server side versions. -OpenWeb header bidding adapter provides solution for accessing both Video and Display demand. +Module that connects to OpenWeb's demand sources. + +The OpenWeb adapter requires setup and approval from OpenWeb. Please reach out to monetization@openweb.com to create an OpenWeb account. + +The adapter supports Video and Display demand. + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `org` | required | String | OpenWeb publisher Id provided by your OpenWeb representative | "1234567890abcdef12345678" +| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 +| `placementId` | optional | String | A unique placement identifier | "12345678" +| `testMode` | optional | Boolean | This activates the test mode | false +| `currency` | optional | String | 3 letters currency | "EUR" + # Test Parameters -``` - var adUnits = [ - // Banner adUnit - { - code: 'div-test-div', - sizes: [[300, 250]], +```javascript +var adUnits = [ + { + code: 'dfp-video-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + } + }, bids: [{ bidder: 'openweb', params: { - aid: 529814 + org: '1234567890abcdef12345678', // Required + floorPrice: 2.00, // Optional + placementId: '12345678', // Optional + testMode: false, // Optional, } }] } - ]; -``` + ]; +``` \ No newline at end of file diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 68431bcc383..f28feaa534d 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1010,6 +1010,10 @@ export function prepareMetaObject(br, bid, seat) { br.meta.secondaryCatIds = bid.cat; br.meta.primaryCatId = bid.cat[0]; } + + if (bid.ext && bid.ext.dsa && Object.keys(bid.ext.dsa).length) { + br.meta.dsa = bid.ext.dsa; + } } export const spec = { @@ -1217,6 +1221,11 @@ export const spec = { deepSetValue(payload, 'regs.coppa', 1); } + // dsa + if (bidderRequest?.ortb2?.regs?.ext?.dsa) { + deepSetValue(payload, 'regs.ext.dsa', bidderRequest.ortb2.regs.ext.dsa); + } + _handleEids(payload, validBidRequests); // First Party Data @@ -1396,6 +1405,7 @@ export const spec = { } catch (error) { logError(error); } + return bidResponses; }, diff --git a/modules/pxyzBidAdapter.js b/modules/pxyzBidAdapter.js index 8b9dbea339b..12bd04c744d 100644 --- a/modules/pxyzBidAdapter.js +++ b/modules/pxyzBidAdapter.js @@ -129,10 +129,13 @@ export const spec = { return bids; }, - getUserSyncs: function (syncOptions) { + getUserSyncs: function () { return [{ type: 'image', url: '//ib.adnxs.com/getuidnb?https://ads.playground.xyz/usersync?partner=appnexus&uid=$UID' + }, { + type: 'iframe', + url: '//rtb.gumgum.com/getuid/15801?r=https%3A%2F%2Fads.playground.xyz%2Fusersync%3Fpartner%3Dgumgum%26uid%3D' }]; } } diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index c4f6e7d545d..c03065cd5a5 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -201,9 +201,6 @@ export const converter = ortbConverter({ const imp = buildImp(bidRequest, context); imp.id = bidRequest.adUnitCode; delete imp.banner; - if (config.getConfig('s2sConfig.defaultTtl')) { - imp.exp = config.getConfig('s2sConfig.defaultTtl'); - }; bidRequest.params.position === 'atf' && imp.video && (imp.video.pos = 1); bidRequest.params.position === 'btf' && imp.video && (imp.video.pos = 3); delete imp.ext?.prebid?.storedrequest; @@ -237,7 +234,7 @@ export const converter = ortbConverter({ }, context: { netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true - ttl: 300, + ttl: 360, }, processors: pbsExtensions }); @@ -685,7 +682,7 @@ export const spec = { creativeId: ad.creative_id || `${ad.network || ''}-${ad.advertiser || ''}`, cpm: ad.cpm || 0, dealId: ad.deal, - ttl: 300, // 5 minutes + ttl: 360, // 6 minutes netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true rubicon: { advertiserId: ad.advertiser, networkId: ad.network diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 9146bba6514..7edaaa36957 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -132,18 +132,16 @@ export const spec = { * @returns positive integer value of startdelay */ getStartDelayForVideoBidRequest: function(videoMediaType, videoParams) { - if (videoParams !== undefined && videoParams.startDelay) { + if (videoParams?.startDelay) { return videoParams.startDelay; - } else if (videoMediaType !== undefined) { - if (videoMediaType.startdelay == 0) { - return 1; - } else if (videoMediaType.startdelay == -1) { + } else if (videoMediaType?.startdelay) { + if (videoMediaType.startdelay > 0 || videoMediaType.startdelay == -1) { return 2; } else if (videoMediaType.startdelay == -2) { return 3; } } - return 2;// Default value for all exotic cases set to bid.params.video.startDelay midroll hence 2. + return 1; // SADR-5619 }, /** diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index b6d5a1711b0..6920983e50d 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -82,6 +82,7 @@ export const spec = { location = winTop.location; logMessage(e); }; + let placements = []; let request = { 'deviceWidth': winTop.screen.width, @@ -91,7 +92,9 @@ export const spec = { 'host': location.host, 'page': location.pathname, 'coppa': config.getConfig('coppa') === true ? 1 : 0, - 'placements': placements + 'placements': placements, + 'eeid': validBidRequests[0]?.userIdAsEids, + 'ifa': bidderRequest?.ortb2?.device?.ifa, }; request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) if (bidderRequest) { @@ -111,8 +114,10 @@ export const spec = { for (let i = 0; i < len; i++) { let bid = validBidRequests[i]; + if (i === 0) adUrl = getAdUrlByRegion(bid); - let traff = bid.params.traffic || BANNER + + let traff = bid.params.traffic || BANNER; placements.push({ placementId: bid.params.sourceid, bidId: bid.bidId, diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index 102d217a658..f3702069d10 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -196,7 +196,7 @@ if (FEATURES.UID2_CSTG) { _logWarn('CSTG opts.serverPublicKey must be a string'); return false; } - const serverPublicKeyPrefix = /^UID2-X-[A-Z]-.+/; + const serverPublicKeyPrefix = /^(UID2|EUID)-X-[A-Z]-.+/; if (!serverPublicKeyPrefix.test(opts.serverPublicKey)) { _logWarn( `CSTG opts.serverPublicKey must match the regular expression ${serverPublicKeyPrefix}` diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 1bb0488bf5d..fe99adcec5f 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -77,6 +77,7 @@ export const spec = { let serverRequests = []; const eids = getEids(bidRequests[0]) || []; const topicsData = getTopics(bidderRequest); + const cdep = getCdep(bidderRequest); if (bannerBidRequests.length > 0) { let serverRequest = { pbav: '$prebid.version$', @@ -105,6 +106,9 @@ export const spec = { if (gpc) { serverRequest.gpc = gpc; } + if (cdep) { + serverRequest.cdep = cdep; + } if (canAccessTopWindow()) { serverRequest.pr = (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || ''; @@ -438,6 +442,11 @@ function getGPCSignal(bidderRequest) { return gpc; } +function getCdep(bidderRequest) { + const cdep = deepAccess(bidderRequest, 'ortb2.device.ext.cdep') || null; + return cdep; +} + function getTopics(bidderRequest) { const userData = deepAccess(bidderRequest, 'ortb2.user.data') || []; const topicsData = userData.filter((dataObj) => { diff --git a/package-lock.json b/package-lock.json index 19d7e70ff37..b65a43a197e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "8.42.0-pre", + "version": "8.44.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "8.42.0-pre", + "version": "8.43.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -8326,12 +8326,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -8339,7 +8339,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -9462,9 +9462,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -9481,9 +9481,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -13097,16 +13097,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -23942,9 +23942,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -35599,12 +35599,12 @@ } }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -35612,7 +35612,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -36454,9 +36454,9 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "continuable-cache": { "version": "0.3.1", @@ -36470,9 +36470,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -39248,16 +39248,16 @@ } }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -47594,9 +47594,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", diff --git a/package.json b/package.json index 9859d39606a..b7a1e1bafab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.42.0-pre", + "version": "8.44.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 3d55f2c06af..337ae47f338 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -89,8 +89,8 @@ import {ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD} from '../activities/activ /** * @typedef {object} BidderAuctionResponse An object encapsulating an adapter response for current Auction * - * @property {Array} bids Contextual bids returned by this adapter, if any - * @property {object|null} fledgeAuctionConfigs Optional FLEDGE response, as a map of impid -> auction_config + * @property {Array} bids? Contextual bids returned by this adapter, if any + * @property {Array<{bidId: String, config: {}}>} paapiAuctionConfigs? Array of paapi auction configs, each scoped to a particular bidId */ /** @@ -293,15 +293,13 @@ export function newBidder(spec) { onTimelyResponse(spec.code); responses.push(resp) }, - onFledgeAuctionConfigs: (fledgeAuctionConfigs) => { - fledgeAuctionConfigs.forEach((fledgeAuctionConfig) => { - const bidRequest = bidRequestMap[fledgeAuctionConfig.bidId]; - if (bidRequest) { - addComponentAuction(bidRequest, fledgeAuctionConfig.config); - } else { - logWarn('Received fledge auction configuration for an unknown bidId', fledgeAuctionConfig); - } - }); + onPaapi: (paapiConfig) => { + const bidRequest = bidRequestMap[paapiConfig.bidId]; + if (bidRequest) { + addComponentAuction(bidRequest, paapiConfig.config); + } else { + logWarn('Received fledge auction configuration for an unknown bidId', paapiConfig); + } }, // If the server responds with an error, there's not much we can do beside logging. onError: (errorMessage, error) => { @@ -363,6 +361,18 @@ export function newBidder(spec) { } } +// Transition from 'fledge' to 'paapi' +// TODO: remove this in prebid 9 +const PAAPI_RESPONSE_PROPS = ['paapiAuctionConfigs', 'fledgeAuctionConfigs']; +const RESPONSE_PROPS = ['bids'].concat(PAAPI_RESPONSE_PROPS); +function getPaapiConfigs(adapterResponse) { + const [paapi, fledge] = PAAPI_RESPONSE_PROPS.map(prop => adapterResponse[prop]); + if (paapi != null && fledge != null) { + throw new Error(`Adapter response should use ${PAAPI_RESPONSE_PROPS[0]} over ${PAAPI_RESPONSE_PROPS[1]}, not both`); + } + return paapi ?? fledge; +} + /** * Run a set of bid requests - that entails converting them to HTTP requests, sending * them over the network, and parsing the responses. @@ -378,7 +388,7 @@ export function newBidder(spec) { * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse * @param onCompletion {function()} invoked once when all bid requests have been processed */ -export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onFledgeAuctionConfigs, onError, onBid, onCompletion}) { +export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onPaapi, onError, onBid, onCompletion}) { const metrics = adapterMetrics(bidderRequest); onCompletion = metrics.startTiming('total').stopBefore(onCompletion); @@ -424,15 +434,21 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe return; } - let bids; - // Extract additional data from a structured {BidderAuctionResponse} response - if (response && isArray(response.fledgeAuctionConfigs)) { - onFledgeAuctionConfigs(response.fledgeAuctionConfigs); + // adapters can reply with: + // a single bid + // an array of bids + // a BidderAuctionResponse object ({bids: [*], paapiAuctionConfigs: [*]}) + + let bids, paapiConfigs; + if (response && !Object.keys(response).some(key => !RESPONSE_PROPS.includes(key))) { bids = response.bids; + paapiConfigs = getPaapiConfigs(response); } else { bids = response; } - + if (isArray(paapiConfigs)) { + paapiConfigs.forEach(onPaapi); + } if (bids) { if (isArray(bids)) { bids.forEach(addBid); diff --git a/test/fake-server/fixtures/basic-outstream/request.json b/test/fake-server/fixtures/basic-outstream/request.json index e9f3302ab4c..6d522058cff 100644 --- a/test/fake-server/fixtures/basic-outstream/request.json +++ b/test/fake-server/fixtures/basic-outstream/request.json @@ -19,6 +19,7 @@ "prebid": true, "disable_psa": true, "video": { + "context": 4, "skippable": true, "playback_method": 2 }, @@ -39,6 +40,7 @@ "prebid": true, "disable_psa": true, "video": { + "context": 4, "skippable": true, "playback_method": 2 }, diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index b4d84634962..8730f2c1a0d 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -130,7 +130,7 @@ describe('admaticBidAdapter', () => { ], 'mediatype': {}, 'type': 'banner', - 'id': '2205da7a81846b', + 'id': 1, 'floors': { 'banner': { '300x250': { 'currency': 'USD', 'floor': 1 }, @@ -434,7 +434,7 @@ describe('admaticBidAdapter', () => { 'h': 90 } ], - 'id': '2205da7a81846b', + 'id': 1, 'mediatype': {}, 'type': 'banner', 'floors': { @@ -802,7 +802,10 @@ describe('admaticBidAdapter', () => { 'price': 0.01, 'type': 'banner', 'bidder': 'admatic', - 'mime_type': 'iframe', + 'mime_type': { + 'name': 'backfill', + 'force': false + }, 'adomain': ['admatic.com.tr'], 'party_tag': '
', 'iurl': 'https://www.admatic.com.tr' @@ -814,7 +817,10 @@ describe('admaticBidAdapter', () => { 'height': 250, 'price': 0.01, 'type': 'video', - 'mime_type': 'iframe', + 'mime_type': { + 'name': 'backfill', + 'force': false + }, 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], 'party_tag': '', @@ -822,25 +828,15 @@ describe('admaticBidAdapter', () => { }, { 'id': 3, - 'creative_id': '3741', - 'width': 300, - 'height': 250, - 'price': 0.01, - 'type': 'video', - 'mime_type': 'iframe', - 'bidder': 'admatic', - 'adomain': ['admatic.com.tr'], - 'party_tag': 'https://www.admatic.com.tr', - 'iurl': 'https://www.admatic.com.tr' - }, - { - 'id': 4, 'creative_id': '3742', 'width': 1, 'height': 1, 'price': 0.01, 'type': 'native', - 'mime_type': 'iframe', + 'mime_type': { + 'name': 'backfill', + 'force': false + }, 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], 'party_tag': '{"native":{"ver":"1.1","assets":[{"id":1,"title":{"text":"title"}},{"id":4,"data":{"value":"body"}},{"id":5,"data":{"value":"sponsored"}},{"id":6,"data":{"value":"cta"}},{"id":2,"img":{"url":"https://www.admatic.com.tr","w":1200,"h":628}},{"id":3,"img":{"url":"https://www.admatic.com.tr","w":640,"h":480}}],"link":{"url":"https://www.admatic.com.tr"},"imptrackers":["https://www.admatic.com.tr"]}}', @@ -863,7 +859,10 @@ describe('admaticBidAdapter', () => { ad: '
', creativeId: '374', meta: { - model: 'iframe', + model: { + 'name': 'backfill', + 'force': false + }, advertiserDomains: ['admatic.com.tr'] }, ttl: 60, @@ -877,11 +876,13 @@ describe('admaticBidAdapter', () => { currency: 'TRY', mediaType: 'video', netRevenue: true, - vastImpUrl: 'https://www.admatic.com.tr', vastXml: '', creativeId: '3741', meta: { - model: 'iframe', + model: { + 'name': 'backfill', + 'force': false + }, advertiserDomains: ['admatic.com.tr'] }, ttl: 60, @@ -890,24 +891,6 @@ describe('admaticBidAdapter', () => { { requestId: 3, cpm: 0.01, - width: 300, - height: 250, - currency: 'TRY', - mediaType: 'video', - netRevenue: true, - vastImpUrl: 'https://www.admatic.com.tr', - vastXml: 'https://www.admatic.com.tr', - creativeId: '3741', - meta: { - model: 'iframe', - advertiserDomains: ['admatic.com.tr'] - }, - ttl: 60, - bidder: 'admatic' - }, - { - requestId: 4, - cpm: 0.01, width: 1, height: 1, currency: 'TRY', @@ -933,7 +916,10 @@ describe('admaticBidAdapter', () => { }, creativeId: '3742', meta: { - model: 'iframe', + model: { + 'name': 'backfill', + 'force': false + }, advertiserDomains: ['admatic.com.tr'] }, ttl: 60, @@ -944,8 +930,206 @@ describe('admaticBidAdapter', () => { ext: { 'cur': 'TRY', 'type': 'admatic' - } + }, + imp: [ + { + 'size': [ + { + 'w': 320, + 'h': 100 + } + ], + 'type': 'banner', + 'mediatype': {}, + 'ext': { + 'instl': 0, + 'gpid': 'desktop-standard', + 'pxid': [ + '1111111111' + ], + 'pxtype': 'pixad', + 'ortbstatus': true, + 'viewability': 100, + 'data': { + 'pbadslot': 'desktop-standard' + }, + 'ae': 1 + }, + 'id': 1, + 'floors': { + 'banner': { + '320x100': { + 'floor': 0.1, + 'currency': 'TRY' + } + } + } + }, + { + 'size': [ + { + 'w': 320, + 'h': 100 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 320, + 100 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 0, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2, + 'plcmt': 4 + }, + 'ext': { + 'gpid': 'outstream-desktop-standard', + 'pxtype': 'pixad', + 'pxid': [ + '1111111111' + ], + 'ortbstatus': true, + 'viewability': 100, + 'data': { + 'pbadslot': 'outstream-desktop-standard' + }, + 'ae': 1 + }, + 'id': 2, + 'floors': { + 'video': { + '320x100': { + 'floor': 0.1, + 'currency': 'TRY' + } + } + } + }, + { + 'size': [ + { + 'w': 1, + 'h': 1 + } + ], + 'type': 'native', + 'mediatype': { + 'sendTargetingKeys': false, + 'ortb': { + 'ver': '1.1', + 'context': 2, + 'plcmttype': 1, + 'privacy': 1, + 'assets': [ + { + 'id': 1, + 'required': 1, + 'title': { + 'len': 120 + } + }, + { + 'id': 2, + 'required': 1, + 'img': { + 'type': 3, + 'w': 640, + 'h': 480 + } + }, + { + 'id': 3, + 'required': 0, + 'img': { + 'type': 1, + 'w': 640, + 'h': 480 + } + }, + { + 'id': 4, + 'required': 0, + 'data': { + 'type': 2 + } + }, + { + 'id': 5, + 'required': 0, + 'data': { + 'type': 1 + } + }, + { + 'id': 6, + 'required': 0, + 'data': { + 'type': 11 + } + } + ], + 'eventtrackers': [ + { + 'event': 1, + 'methods': [ + 1, + 2 + ] + } + ] + } + }, + 'ext': { + 'gpid': 'native-desktop-standard', + 'pxtype': 'pixad', + 'pxid': [ + '1111111111' + ], + 'ortbstatus': true, + 'viewability': 100, + 'data': { + 'pbadslot': 'native-desktop-standard' + }, + 'ae': 1 + }, + 'id': 3, + 'floors': { + 'native': { + '*': { + 'floor': 0.1, + 'currency': 'TRY' + } + } + } + } + ] }; + let result = spec.interpretResponse(bids, {data: request}); expect(result).to.eql(expectedResponse); }); diff --git a/test/spec/modules/anyclipBidAdapter_spec.js b/test/spec/modules/anyclipBidAdapter_spec.js new file mode 100644 index 00000000000..3de36f9fe06 --- /dev/null +++ b/test/spec/modules/anyclipBidAdapter_spec.js @@ -0,0 +1,160 @@ +import { expect } from 'chai'; +import { spec } from 'modules/anyclipBidAdapter.js'; + +describe('anyclipBidAdapter', function () { + afterEach(function () { + global._anyclip = undefined; + }); + + let bid; + + function mockBidRequest() { + return { + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90], + [468, 60] + ] + } + }, + bidder: 'anyclip', + params: { + publisherId: '12345', + supplyTagId: '-mptNo0BycUG4oCDgGrU' + } + }; + }; + + describe('isBidRequestValid', function () { + this.beforeEach(function () { + bid = mockBidRequest(); + }); + + it('should return true if all required fields are present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('should return false if bidder does not correspond', function () { + bid.bidder = 'abc'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if params object is missing', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if publisherId is missing from params', function () { + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if supplyTagId is missing from params', function () { + delete bid.params.supplyTagId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if mediaTypes is missing', function () { + delete bid.mediaTypes; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if banner is missing from mediaTypes ', function () { + delete bid.mediaTypes.banner; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if sizes is missing from banner object', function () { + delete bid.mediaTypes.banner.sizes; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if sizes is not an array', function () { + bid.mediaTypes.banner.sizes = 'test'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if sizes is an empty array', function () { + bid.mediaTypes.banner.sizes = []; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let bidderRequest = { + refererInfo: { + page: 'http://example.com', + domain: 'example.com', + }, + timeout: 3000 + }; + + this.beforeEach(function () { + bid = mockBidRequest(); + Object.assign(bid, { + adUnitCode: '1', + transactionId: '123', + sizes: bid.mediaTypes.banner.sizes + }); + }); + + it('when pubtag is not available, return undefined', function () { + expect(spec.buildRequests([bid], bidderRequest)).to.undefined; + }); + it('when pubtag is available, creates a ServerRequest object with method, URL and data', function() { + global._anyclip = { + PubTag: function() {}, + pubTag: { + requestBids: function() {} + } + }; + expect(spec.buildRequests([bid], bidderRequest)).to.exist; + }); + }); + + describe('interpretResponse', function() { + it('should return an empty array when parsing a no bid response', function () { + const response = {}; + const request = {}; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + it('should return bids array', function() { + const response = {}; + const request = { + bidRequest: { + bidId: 'test-bidId', + transactionId: '123' + } + }; + + global._anyclip = { + PubTag: function() {}, + pubTag: { + getBids: function(transactionId) { + return { + adServer: { + bid: { + ad: 'test-ad', + creativeId: 'test-crId', + meta: { + advertiserDomains: ['anyclip.com'] + }, + width: 300, + height: 250, + ttl: 300 + } + }, + cpm: 1.23, + } + } + } + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].ad).to.equal('test-ad'); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].netRevenue).to.false; + expect(bids[0].meta.advertiserDomains[0]).to.equal('anyclip.com'); + }); + }); +}); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index cf6a1704bde..cc86a8a0aaa 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -343,7 +343,7 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].hb_source).to.deep.equal(1); }); - it('should include ORTB video values when video params were not set', function () { + it('should include ORTB video values when matching video params were not all set', function () { let bidRequest = deepClone(bidRequests[0]); bidRequest.params = { placementId: '1234235', @@ -377,6 +377,61 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) }); + it('should include ORTB video values when video params is empty - case 1', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + placement: 3, + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: false, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + + it('should include ORTB video values when video params is empty - case 2', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + plcmt: 2, + startdelay: -1, + mimes: ['video/mp4'], + skip: 1, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 9 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + it('should add video property when adUnit includes a renderer', function () { const videoData = { mediaTypes: { diff --git a/test/spec/modules/asoBidAdapter_spec.js b/test/spec/modules/asoBidAdapter_spec.js index 88016d1902c..e317a8828e7 100644 --- a/test/spec/modules/asoBidAdapter_spec.js +++ b/test/spec/modules/asoBidAdapter_spec.js @@ -1,30 +1,30 @@ import {expect} from 'chai'; import {spec} from 'modules/asoBidAdapter.js'; -import {parseUrl} from 'src/utils.js'; -import {BANNER, VIDEO} from 'src/mediaTypes.js'; +import {BANNER, VIDEO, NATIVE} from 'src/mediaTypes.js'; +import {OUTSTREAM} from 'src/video.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; +import {parseUrl} from '../../../src/utils'; + +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; describe('Adserver.Online bidding adapter', function () { const bannerRequest = { bidder: 'aso', params: { - zone: 1, - attr: { - keywords: ['a', 'b'], - tags: ['t1', 't2'] - } + zone: 1 }, adUnitCode: 'adunit-banner', + bidId: 'bid-banner', mediaTypes: { - banner: { + [BANNER]: { sizes: [ [300, 250], [240, 400], ] } }, - bidId: 'bidid1', - bidderRequestId: 'bidreq1', - auctionId: 'auctionid1', userIdAsEids: [{ source: 'src1', uids: [ @@ -38,32 +38,67 @@ describe('Adserver.Online bidding adapter', function () { const videoRequest = { bidder: 'aso', params: { - zone: 2, - video: { - api: [2], - maxduration: 30 - } + zone: 2 }, + adUnitCode: 'adunit-video', + bidId: 'bid-video', mediaTypes: { - video: { - context: 'outstream', + [VIDEO]: { + context: OUTSTREAM, playerSize: [[640, 480]], protocols: [1, 2], mimes: ['video/mp4'], } + } + }; + + const nativeOrtbRequest = { + assets: [ + { + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600 + } + }] + }; + + const nativeRequest = { + bidder: 'aso', + params: { + zone: 3 + }, + adUnitCode: 'adunit-native', + bidId: 'bid-native', + mediaTypes: { + [NATIVE]: { + ortb: { + ...nativeOrtbRequest + } + } }, - adUnitCode: 'adunit-video', - bidId: 'bidid12', - bidderRequestId: 'bidreq2', - auctionId: 'auctionid12' + nativeOrtbRequest }; const bidderRequest = { refererInfo: { - numIframes: 0, + page: 'https://example.com/page.html', + topmostLocation: 'https://example.com/page.html', reachedTop: true, - page: 'https://example.com', - domain: 'example.com' + numIframes: 1, + stack: [ + 'https://example.com/page.html', + 'https://example.com/iframe1.html' + ] } }; @@ -81,6 +116,14 @@ describe('Adserver.Online bidding adapter', function () { } }; + const gdprNotApplies = { + gdprApplies: false, + consentString: '', + vendorData: { + purpose: {} + } + }; + const uspConsent = 'usp_consent'; describe('isBidRequestValid', function () { @@ -110,81 +153,121 @@ describe('Adserver.Online bidding adapter', function () { }); }); - describe('buildRequests', function () { - it('creates a valid banner request', function () { - bannerRequest.getFloor = () => ({ currency: 'USD', floor: 0.5 }); + describe('requests builder', function () { + it('should add bid floor', function () { + const bidRequest = Object.assign({}, bannerRequest); + + bidRequest.getFloor = () => { + return { + currency: 'USD', + floor: 0.5 + } + }; + + const payload = spec.buildRequests([bidRequest], bidderRequest)[0].data; + + expect(payload.imp[0].bidfloor).to.equal(0.5); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); + }); + it('endpoint is valid', function () { const requests = spec.buildRequests([bannerRequest], bidderRequest); expect(requests).to.have.lengthOf(1); const request = requests[0]; expect(request).to.exist; expect(request.method).to.equal('POST'); - const parsedRequestUrl = parseUrl(request.url); - expect(parsedRequestUrl.hostname).to.equal('srv.aso1.net'); - expect(parsedRequestUrl.pathname).to.equal('/prebid/bidder'); + const parsedUrl = parseUrl(request.url); + expect(parsedUrl.hostname).to.equal('srv.aso1.net'); + expect(parsedUrl.pathname).to.equal('/prebid/bidder'); - const query = parsedRequestUrl.search; + const query = parsedUrl.search; expect(query.pbjs).to.contain('$prebid.version$'); expect(query.zid).to.equal('1'); + }); + it('creates a valid banner request', function () { + const requests = spec.buildRequests([bannerRequest], syncAddFPDToBidderRequest(bidderRequest)); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request).to.exist; expect(request.data).to.exist; const payload = request.data; - expect(payload.site).to.not.equal(null); - expect(payload.site.ref).to.equal(''); - expect(payload.site.page).to.equal('https://example.com'); + expect(payload.site).to.exist; + expect(payload.site.page).to.equal('https://example.com/page.html'); - expect(payload.device).to.not.equal(null); + expect(payload.device).to.exist; expect(payload.device.w).to.equal(window.innerWidth); expect(payload.device.h).to.equal(window.innerHeight); expect(payload.imp).to.have.lengthOf(1); expect(payload.imp[0].tagid).to.equal('adunit-banner'); - expect(payload.imp[0].banner).to.not.equal(null); - expect(payload.imp[0].banner.w).to.equal(300); - expect(payload.imp[0].banner.h).to.equal(250); - expect(payload.imp[0].bidfloor).to.equal(0.5); - expect(payload.imp[0].bidfloorcur).to.equal('USD'); + expect(payload.imp[0].banner).to.not.null; + expect(payload.imp[0].banner.format).to.have.lengthOf(2); + expect(payload.imp[0].banner.format[0].w).to.equal(300); + expect(payload.imp[0].banner.format[0].h).to.equal(250); + expect(payload.imp[0].banner.format[1].w).to.equal(240); + expect(payload.imp[0].banner.format[1].h).to.equal(400); }); - it('creates a valid video request', function () { - const requests = spec.buildRequests([videoRequest], bidderRequest); - expect(requests).to.have.lengthOf(1); - const request = requests[0]; + if (FEATURES.VIDEO) { + it('creates a valid video request', function () { + const requests = spec.buildRequests([videoRequest], syncAddFPDToBidderRequest(bidderRequest)); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; - expect(request).to.exist; - expect(request.method).to.equal('POST'); - const parsedRequestUrl = parseUrl(request.url); - expect(parsedRequestUrl.hostname).to.equal('srv.aso1.net'); - expect(parsedRequestUrl.pathname).to.equal('/prebid/bidder'); + expect(request).to.exist; + expect(request.data).to.not.be.empty; - const query = parsedRequestUrl.search; - expect(query.pbjs).to.contain('$prebid.version$'); - expect(query.zid).to.equal('2'); + const payload = request.data; - expect(request.data).to.not.be.empty; + expect(payload.site).to.exist; + expect(payload.site.page).to.equal('https://example.com/page.html'); - const payload = request.data; + expect(payload.device).to.exist; + expect(payload.device.w).to.equal(window.innerWidth); + expect(payload.device.h).to.equal(window.innerHeight); - expect(payload.site).to.not.equal(null); - expect(payload.site.ref).to.equal(''); - expect(payload.site.page).to.equal('https://example.com'); + expect(payload.imp).to.have.lengthOf(1); - expect(payload.device).to.not.equal(null); - expect(payload.device.w).to.equal(window.innerWidth); - expect(payload.device.h).to.equal(window.innerHeight); + expect(payload.imp[0].tagid).to.equal('adunit-video'); + expect(payload.imp[0].video).to.exist; - expect(payload.imp).to.have.lengthOf(1); + expect(payload.imp[0].video.w).to.equal(640); + expect(payload.imp[0].video.h).to.equal(480); + expect(payload.imp[0].banner).to.not.exist; + }); + } - expect(payload.imp[0].tagid).to.equal('adunit-video'); - expect(payload.imp[0].video).to.not.equal(null); - expect(payload.imp[0].video.w).to.equal(640); - expect(payload.imp[0].video.h).to.equal(480); - expect(payload.imp[0].banner).to.be.undefined; - }); + if (FEATURES.NATIVE) { + it('creates a valid native request', function () { + const requests = spec.buildRequests([nativeRequest], syncAddFPDToBidderRequest(bidderRequest)); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request).to.exist; + expect(request.data).to.not.be.empty; + + const payload = request.data; + + expect(payload.site).to.exist; + expect(payload.site.page).to.equal('https://example.com/page.html'); + + expect(payload.device).to.exist; + expect(payload.device.w).to.equal(window.innerWidth); + expect(payload.device.h).to.equal(window.innerHeight); + + expect(payload.imp).to.have.lengthOf(1); + + expect(payload.imp[0].tagid).to.equal('adunit-native'); + expect(payload.imp[0].native).to.exist; + expect(payload.imp[0].native.request).to.exist; + }); + } }); describe('GDPR/USP compliance', function () { @@ -192,7 +275,7 @@ describe('Adserver.Online bidding adapter', function () { bidderRequest.gdprConsent = gdprConsent; bidderRequest.uspConsent = uspConsent; - const requests = spec.buildRequests([bannerRequest], bidderRequest); + const requests = spec.buildRequests([bannerRequest], syncAddFPDToBidderRequest(bidderRequest)); expect(requests).to.have.lengthOf(1); const request = requests[0]; @@ -209,7 +292,7 @@ describe('Adserver.Online bidding adapter', function () { bidderRequest.gdprConsent = null; bidderRequest.uspConsent = null; - const requests = spec.buildRequests([bannerRequest], bidderRequest); + const requests = spec.buildRequests([bannerRequest], syncAddFPDToBidderRequest(bidderRequest)); expect(requests).to.have.lengthOf(1); const request = requests[0]; @@ -226,18 +309,21 @@ describe('Adserver.Online bidding adapter', function () { describe('response handler', function () { const bannerResponse = { body: { - id: 'auctionid1', - bidid: 'bidid1', seatbid: [{ bid: [ { - impid: 'impid1', + impid: 'bid-banner', price: 0.3, crid: 321, adm: '', w: 300, h: 250, adomain: ['example.com'], + ext: { + prebid: { + type: 'banner' + } + } } ] }], @@ -255,18 +341,48 @@ describe('Adserver.Online bidding adapter', function () { const videoResponse = { body: { - id: 'auctionid2', - bidid: 'bidid2', seatbid: [{ bid: [ { - impid: 'impid2', + impid: 'bid-video', price: 0.5, crid: 123, adm: '', adomain: ['example.com'], w: 640, h: 480, + ext: { + prebid: { + type: 'video' + } + } + } + ] + }], + cur: 'USD' + }, + }; + + const nativeResponse = { + body: { + seatbid: [{ + bid: [ + { + impid: 'bid-native', + price: 0.5, + crid: 123, + adm: JSON.stringify({ + assets: [ + {id: 0, title: {text: 'Title'}}, + {id: 1, img: {type: 3, url: 'https://img'}}, + ], + }), + adomain: ['example.com'], + ext: { + prebid: { + type: 'native' + } + } } ] }], @@ -275,47 +391,59 @@ describe('Adserver.Online bidding adapter', function () { }; it('handles banner responses', function () { - bannerRequest.bidRequest = { - mediaType: BANNER - }; - const result = spec.interpretResponse(bannerResponse, bannerRequest); - - expect(result).to.have.lengthOf(1); - - expect(result[0]).to.exist; - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].mediaType).to.equal(BANNER); - expect(result[0].creativeId).to.equal(321); - expect(result[0].cpm).to.be.within(0.1, 0.5); - expect(result[0].ad).to.equal(''); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(300); - expect(result[0].dealId).to.not.exist; - expect(result[0].meta.advertiserDomains[0]).to.equal('example.com'); + const request = spec.buildRequests([bannerRequest], bidderRequest)[0]; + const bids = spec.interpretResponse(bannerResponse, request); + + expect(bids).to.have.lengthOf(1); + + expect(bids[0]).to.exist; + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].mediaType).to.equal(BANNER); + expect(bids[0].creativeId).to.equal(321); + expect(bids[0].cpm).to.be.within(0.1, 0.5); + expect(bids[0].ad).to.equal(''); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].dealId).to.not.exist; + expect(bids[0].meta.advertiserDomains[0]).to.equal('example.com'); }); - it('handles video responses', function () { - const request = { - bidRequest: videoRequest - }; - request.bidRequest.mediaType = VIDEO; - - const result = spec.interpretResponse(videoResponse, request); - expect(result).to.have.lengthOf(1); - - expect(result[0].width).to.equal(640); - expect(result[0].height).to.equal(480); - expect(result[0].mediaType).to.equal(VIDEO); - expect(result[0].creativeId).to.equal(123); - expect(result[0].cpm).to.equal(0.5); - expect(result[0].vastXml).to.equal(''); - expect(result[0].renderer).to.be.a('object'); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(300); - }); + if (FEATURES.VIDEO) { + it('handles video responses', function () { + const request = spec.buildRequests([videoRequest], bidderRequest)[0]; + const bids = spec.interpretResponse(videoResponse, request); + expect(bids).to.have.lengthOf(1); + + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(480); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].creativeId).to.equal(123); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].vastXml).to.equal(''); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ttl).to.equal(300); + }); + } + + if (FEATURES.NATIVE) { + it('handles native responses', function () { + const request = spec.buildRequests([nativeRequest], bidderRequest)[0]; + const bids = spec.interpretResponse(nativeResponse, request); + expect(bids).to.have.lengthOf(1); + + expect(bids[0].mediaType).to.equal(NATIVE); + expect(bids[0].creativeId).to.equal(123); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ttl).to.equal(300); + + expect(bids[0].native.ortb.assets).to.have.lengthOf(2); + }); + } it('handles empty responses', function () { const response = []; @@ -331,11 +459,27 @@ describe('Adserver.Online bidding adapter', function () { }; it('should return iframe sync option', function () { - expect(spec.getUserSyncs(syncOptions, [bannerResponse], gdprConsent, uspConsent)[0].type).to.equal('iframe'); - expect(spec.getUserSyncs(syncOptions, [bannerResponse], gdprConsent, uspConsent)[0].url).to.equal( - 'sync_url?gdpr=1&consents_str=consentString&consents=1%2C2&us_privacy=usp_consent&' + const syncs = spec.getUserSyncs(syncOptions, [bannerResponse], gdprConsent, uspConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal( + 'sync_url?gdpr=1&consents_str=consentString&consents=1%2C2&us_privacy=usp_consent' ); }); + + it('should return iframe sync option - gdpr not applies', function () { + const syncs = spec.getUserSyncs(syncOptions, [bannerResponse], gdprNotApplies, uspConsent); + expect(syncs).to.have.lengthOf(1); + + expect(syncs[0].url).to.equal( + 'sync_url?us_privacy=usp_consent' + ); + }); + + it('should return no sync option', function () { + const syncs = spec.getUserSyncs(syncOptions, [videoResponse], gdprNotApplies, uspConsent); + expect(syncs).to.have.lengthOf(0); + }); }); }); }); diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js index 8c7f0e84bce..ab99ba2aa0c 100644 --- a/test/spec/modules/debugging_mod_spec.js +++ b/test/spec/modules/debugging_mod_spec.js @@ -103,8 +103,8 @@ describe('bid interceptor', () => { }); describe('rule', () => { - function matchingRule({replace, options}) { - setRules({when: {}, then: replace, options: options}); + function matchingRule({replace, options, paapi}) { + setRules({when: {}, then: replace, options: options, paapi}); return interceptor.match({}); } @@ -164,6 +164,24 @@ describe('bid interceptor', () => { }); }); + describe('paapi', () => { + it('should accept literals', () => { + const mockConfig = [ + {paapi: 1}, + {paapi: 2} + ] + const paapi = matchingRule({paapi: mockConfig}).paapi({}); + expect(paapi).to.eql(mockConfig); + }); + + it('should accept a function and pass extra args to it', () => { + const paapiDef = sinon.stub(); + const args = [{}, {}, {}]; + matchingRule({paapi: paapiDef}).paapi(...args); + expect(paapiDef.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }) + }) + describe('.options', () => { it('should include default rule options', () => { const optDef = {someOption: 'value'}; @@ -181,16 +199,17 @@ describe('bid interceptor', () => { }); describe('intercept()', () => { - let done, addBid; + let done, addBid, addPaapiConfig; function intercept(args = {}) { const bidRequest = {bids: args.bids || []}; - return interceptor.intercept(Object.assign({bidRequest, done, addBid}, args)); + return interceptor.intercept(Object.assign({bidRequest, done, addBid, addPaapiConfig}, args)); } beforeEach(() => { done = sinon.spy(); addBid = sinon.spy(); + addPaapiConfig = sinon.spy(); }); describe('on no match', () => { @@ -253,6 +272,29 @@ describe('bid interceptor', () => { }); }); + it('should call addPaapiConfigs when provided', () => { + const mockPaapiConfigs = [ + {paapi: 1}, + {paapi: 2} + ] + setRules({ + when: {id: 2}, + paapi: mockPaapiConfigs, + }); + intercept({bidRequest: REQUEST}); + expect(addPaapiConfig.callCount).to.eql(2); + mockPaapiConfigs.forEach(cfg => sinon.assert.calledWith(addPaapiConfig, cfg)) + }) + + it('should not call onBid when then is null', () => { + setRules({ + when: {id: 2}, + then: null + }); + intercept({bidRequest: REQUEST}); + sinon.assert.notCalled(addBid); + }) + it('should call done()', () => { intercept({bidRequest: REQUEST}); expect(done.calledOnce).to.be.true; diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index d2a351b4089..644e9255789 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -357,5 +357,34 @@ describe('Deepintent adapter', function () { let response = spec.interpretResponse(invalidResponse, bRequest); expect(response[0].mediaType).to.equal(undefined); }); - }) + }); + describe('GPP and coppa', function() { + it('Request params check with GPP Consent', function () { + let bidderReq = {gppConsent: {gppString: 'gpp-string-test', applicableSections: [5]}}; + let bRequest = spec.buildRequests(request, bidderReq); + let data = JSON.parse(bRequest.data); + expect(data.regs.gpp).to.equal('gpp-string-test'); + expect(data.regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let bRequest = spec.buildRequests(request, bidderReq); + let data = JSON.parse(bRequest.data); + expect(data.regs.gpp).to.equal('gpp-test-string'); + expect(data.regs.gpp_sid[0]).to.equal(5); + }); + it('should include coppa flag in bid request if coppa is set to true', () => { + let bidderReq = {ortb2: {regs: {coppa: 1}}}; + let bRequest = spec.buildRequests(request, bidderReq); + let data = JSON.parse(bRequest.data); + expect(data.regs.coppa).to.equal(1); + }); + }); }); diff --git a/test/spec/modules/gammaBidAdapter_spec.js b/test/spec/modules/gammaBidAdapter_spec.js index 35394df7d11..f3a28c08576 100644 --- a/test/spec/modules/gammaBidAdapter_spec.js +++ b/test/spec/modules/gammaBidAdapter_spec.js @@ -8,8 +8,9 @@ describe('gammaBidAdapter', function() { let bid = { 'bidder': 'gamma', 'params': { - siteId: '1465446377', - zoneId: '1515999290' + siteId: '1398219351', + zoneId: '1398219417', + region: 'SGP' }, 'adUnitCode': 'adunit-code', 'sizes': [ @@ -84,7 +85,7 @@ describe('gammaBidAdapter', function() { 'width': 300, 'height': 250, 'creativeId': '1515999070', - 'dealId': 'gax-paj2qarjf2g', + 'dealId': 'gax-lvpjgs5b9k4n', 'currency': 'USD', 'netRevenue': true, 'ttl': 300, diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 52cfd0294e7..29e372d0f87 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -192,6 +192,8 @@ describe('gumgumAdapter', function () { const request = { ...bidRequests[0] }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.aun).to.equal(bidRequests[0].adUnitCode); + expect(bidRequest.data.displaymanager).to.equal('Prebid.js - gumgum'); + expect(bidRequest.data.displaymanagerver).to.equal(JCSI.pbv); }); it('should set pubProvidedId if the uid and pubProvidedId are available', function () { const request = { ...bidRequests[0] }; diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 40f8833c6ec..20a43d0397f 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -479,6 +479,21 @@ describe('kargo adapter tests', function () { floor: 1, fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' + }, + ext: { + ortb2Imp: { + ext: { + tid: '10101', + data: { + adServer: { + name: 'gam', + adslot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + } } }, { @@ -492,7 +507,21 @@ describe('kargo adapter tests', function () { fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' }, - floor: 2 + floor: 2, + ext: { + ortb2Imp: { + ext: { + tid: '20202', + data: { + adServer: { + name: 'gam', + adslot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } + } }, { code: '303', @@ -505,7 +534,21 @@ describe('kargo adapter tests', function () { fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' }, - floor: 3 + floor: 3, + ext: { + ortb2Imp: { + ext: { + tid: '30303', + data: { + adServer: { + name: 'gam', + adslot: '/22558409563,18834096/dfy_mobile_adhesion' + } + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } } ], socan: { @@ -555,6 +598,56 @@ describe('kargo adapter tests', function () { ] } ] + }, + ext: { + ortb2: { + device: { + sua: { + platform: { + brand: 'macOS', + version: ['12', '6', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 1, + model: 'model', + source: 1, + } + }, + site: { + id: '1234', + name: 'SiteName', + cat: ['IAB1', 'IAB2', 'IAB3'] + }, + user: { + data: [ + { + name: 'prebid.org', + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + { + id: '133' + }, + ] + }, + ] + } + } } }; diff --git a/test/spec/modules/lassoBidAdapter_spec.js b/test/spec/modules/lassoBidAdapter_spec.js index 3695889aca0..ad4040c0452 100644 --- a/test/spec/modules/lassoBidAdapter_spec.js +++ b/test/spec/modules/lassoBidAdapter_spec.js @@ -126,6 +126,7 @@ describe('lassoBidAdapter', function () { it('should get the correct bid response', function () { let expectedResponse = { requestId: '123456789', + bidId: '123456789', cpm: 1, currency: 'USD', width: 728, diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 5c0f5fc3f4d..ff58671b17b 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -63,6 +63,7 @@ describe('nextMillenniumBidAdapterTests', () => { mimes: ['video/mp4', 'video/x-ms-wmv', 'application/javascript'], api: [2], placement: 1, + plcmt: 1, w: 400, h: 300, }, diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index a7b7ba09113..10a9c4c946c 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -247,6 +247,28 @@ describe('omsBidAdapter', function () { expect(data.user.ext.ids).is.deep.equal(userId); }); + it('sends gpid parameters', function () { + bidRequests[0].ortb2Imp = { + 'ext': { + 'gpid': '/1111/home-left', + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/1111/home' + }, + 'pbadslot': '/1111/home-left' + } + } + } + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.imp[0].ext).to.not.be.undefined; + expect(data.imp[0].ext.gpid).to.not.be.undefined; + expect(data.imp[0].ext.adserverName).to.not.be.undefined; + expect(data.imp[0].ext.adslot).to.not.be.undefined; + expect(data.imp[0].ext.pbadslot).to.not.be.undefined; + }); + context('when element is fully in view', function () { it('returns 100', function () { Object.assign(element, {width: 600, height: 400}); diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js index c515c21690a..5a0264f00b8 100644 --- a/test/spec/modules/openwebBidAdapter_spec.js +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -2,386 +2,624 @@ import { expect } from 'chai'; import { spec } from 'modules/openwebBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from 'src/utils.js'; -const DEFAULT_ADATPER_REQ = { bidderCode: 'openweb' }; -const DISPLAY_REQUEST = { - 'bidder': 'openweb', - 'params': { - 'aid': 12345 - }, - 'schain': { ver: 1 }, - 'userId': { criteo: 2 }, - 'mediaTypes': { 'banner': { 'sizes': [300, 250] } }, - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '2e41f65424c87c', - 'adUnitCode': 'adunit-code', - 'bidId': '84ab500420319d', -}; - -const VIDEO_REQUEST = { - 'bidder': 'openweb', - 'mediaTypes': { - 'video': { - 'playerSize': [[480, 360], [640, 480]] - } - }, - 'params': { - 'aid': 12345 - }, - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '2e41f65424c87c', - 'adUnitCode': 'adunit-code', - 'bidId': '84ab500420319d' -}; - -const ADPOD_REQUEST = { - 'bidder': 'openweb', - 'mediaTypes': { - 'video': { - 'context': 'adpod', - 'playerSize': [[640, 480]], - 'anyField': 10 - } - }, - 'params': { - 'aid': 12345 - }, - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '2e41f65424c87c', - 'adUnitCode': 'adunit-code', - 'bidId': '2e41f65424c87c' -}; - -const SERVER_VIDEO_RESPONSE = { - 'source': { 'aid': 12345, 'pubId': 54321 }, - 'bids': [{ - 'vastUrl': 'vastUrl', - 'requestId': '2e41f65424c87c', - 'url': '44F2AEB9BFC881B3', - 'creative_id': 342516, - 'durationSeconds': 30, - 'cmpId': 342516, - 'height': 480, - 'cur': 'USD', - 'width': 640, - 'cpm': 0.9, - 'adomain': ['a.com'] - }] -}; -const SERVER_OUSTREAM_VIDEO_RESPONSE = SERVER_VIDEO_RESPONSE; -const SERVER_DISPLAY_RESPONSE = { - 'source': { 'aid': 12345, 'pubId': 54321 }, - 'bids': [{ - 'ad': '', - 'adUrl': 'adUrl', - 'requestId': '2e41f65424c87c', - 'creative_id': 342516, - 'cmpId': 342516, - 'height': 250, - 'cur': 'USD', - 'width': 300, - 'cpm': 0.9 - }], - 'cookieURLs': ['link1', 'link2'] -}; -const SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS = { - 'source': { 'aid': 12345, 'pubId': 54321 }, - 'bids': [{ - 'ad': '', - 'requestId': '2e41f65424c87c', - 'creative_id': 342516, - 'cmpId': 342516, - 'height': 250, - 'cur': 'USD', - 'width': 300, - 'cpm': 0.9 - }], - 'cookieURLs': ['link3', 'link4'], - 'cookieURLSTypes': ['image', 'iframe'] -}; - -const videoBidderRequest = { - bidderCode: 'bidderCode', - bids: [{ mediaTypes: { video: {} }, bidId: '2e41f65424c87c' }] -}; - -const displayBidderRequest = { - bidderCode: 'bidderCode', - bids: [{ bidId: '2e41f65424c87c' }] -}; - -const displayBidderRequestWithConsents = { - bidderCode: 'bidderCode', - bids: [{ bidId: '2e41f65424c87c' }], - gdprConsent: { - gdprApplies: true, - consentString: 'test' - }, - uspConsent: 'iHaveIt' -}; - -const videoEqResponse = [{ - vastUrl: 'vastUrl', - requestId: '2e41f65424c87c', - creativeId: 342516, - mediaType: 'video', - netRevenue: true, - currency: 'USD', - height: 480, - width: 640, - ttl: 300, - cpm: 0.9, - meta: { - advertiserDomains: ['a.com'] - } -}]; - -const displayEqResponse = [{ - requestId: '2e41f65424c87c', - creativeId: 342516, - mediaType: 'banner', - netRevenue: true, - currency: 'USD', - ad: '', - adUrl: 'adUrl', - height: 250, - width: 300, - ttl: 300, - cpm: 0.9, - meta: { - advertiserDomains: [] - } - -}]; - -describe('openwebBidAdapter', () => { +const ENDPOINT = 'https://hb.openwebmp.com/hb-multi'; +const TEST_ENDPOINT = 'https://hb.openwebmp.com/hb-multi-test'; +const TTL = 360; +/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ + +describe('openwebAdapter', function () { const adapter = newBidder(spec); - describe('inherited functions', () => { - it('exists and is a function', () => { + + describe('inherited functions', function () { + it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('user syncs', () => { - describe('as image', () => { - it('should be returned if pixel enabled', () => { - const syncs = spec.getUserSyncs({ pixelEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); - - expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[0]]); - expect(syncs.map(s => s.type)).to.deep.equal(['image']); - }) - }) + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); - describe('as iframe', () => { - it('should be returned if iframe enabled', () => { - const syncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'org': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); - expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[1]]); - expect(syncs.map(s => s.type)).to.deep.equal(['iframe']); - }) - }) + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + } + }, + 'vastXml': '"..."' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + } + }, + 'ad': '""' + } + ]; + + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001', + 'testMode': true + }, + 'bidId': '299ffc8cca0b87', + 'loop': 2, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const bidderRequest = { + bidderCode: 'openweb', + } + const placementId = '12345678'; + const api = [1, 2]; + const mimes = ['application/javascript', 'video/mp4', 'video/quicktime']; + const protocols = [2, 3, 5, 6]; + + it('sends the placementId to ENDPOINT via POST', function () { + bidRequests[0].params.placementId = placementId; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].placementId).to.equal(placementId); + }); - describe('user sync', () => { - it('should not be returned if passed syncs where already used', () => { - const syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + it('sends the plcmt to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].plcmt).to.equal(1); + }); - expect(syncs).to.deep.equal([]); - }) + it('sends the is_wrapper parameter to ENDPOINT via POST', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('is_wrapper'); + expect(request.data.params.is_wrapper).to.equal(false); + }); - it('should not be returned if pixel not set', () => { - const syncs = spec.getUserSyncs({}, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); - expect(syncs).to.be.empty; - }); + it('sends bid request to TEST ENDPOINT via POST', function () { + const request = spec.buildRequests(testModeBidRequests, bidderRequest); + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('POST'); }); - describe('user syncs with both types', () => { - it('should be returned if pixel and iframe enabled', () => { - const mockedServerResponse = Object.assign({}, SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS, { 'cookieURLs': ['link5', 'link6'] }); - const syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [{ body: mockedServerResponse }]); - expect(syncs.map(s => s.url)).to.deep.equal(mockedServerResponse.cookieURLs); - expect(syncs.map(s => s.type)).to.deep.equal(mockedServerResponse.cookieURLSTypes); - }); + it('should send the correct bid Id', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); }); - }); - describe('isBidRequestValid', () => { - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(VIDEO_REQUEST)).to.equal(true); + it('should send the correct supported api array', function () { + bidRequests[0].mediaTypes.video.api = api; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].api).to.be.an('array'); + expect(request.data.bids[0].api).to.eql([1, 2]); }); - it('should return false when required params are not passed', () => { - let bid = Object.assign({}, VIDEO_REQUEST); - delete bid.params; - expect(spec.isBidRequestValid(bid)).to.equal(false); + it('should send the correct mimes array', function () { + bidRequests[1].mediaTypes.banner.mimes = mimes; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[1].mimes).to.be.an('array'); + expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); - }); - describe('buildRequests', () => { - let videoBidRequests = [VIDEO_REQUEST]; - let displayBidRequests = [DISPLAY_REQUEST]; - let videoAndDisplayBidRequests = [DISPLAY_REQUEST, VIDEO_REQUEST]; - const displayRequest = spec.buildRequests(displayBidRequests, DEFAULT_ADATPER_REQ); - const videoRequest = spec.buildRequests(videoBidRequests, DEFAULT_ADATPER_REQ); - const videoAndDisplayRequests = spec.buildRequests(videoAndDisplayBidRequests, DEFAULT_ADATPER_REQ); - - it('building requests as arrays', () => { - expect(videoRequest).to.be.a('array'); - expect(displayRequest).to.be.a('array'); - expect(videoAndDisplayRequests).to.be.a('array'); - }) + it('should send the correct protocols array', function () { + bidRequests[0].mediaTypes.video.protocols = protocols; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].protocols).to.be.an('array'); + expect(request.data.bids[0].protocols).to.eql([2, 3, 5, 6]); + }); - it('sending as POST', () => { - const postActionMethod = 'POST' - const comparator = br => br.method === postActionMethod; - expect(videoRequest.every(comparator)).to.be.true; - expect(displayRequest.every(comparator)).to.be.true; - expect(videoAndDisplayRequests.every(comparator)).to.be.true; - }); - it('forms correct ADPOD request', () => { - const pbBidReqData = spec.buildRequests([ADPOD_REQUEST], DEFAULT_ADATPER_REQ)[0].data; - const impRequest = pbBidReqData.BidRequests[0] - expect(impRequest.AdType).to.be.equal('video'); - expect(impRequest.Adpod).to.be.a('object'); - expect(impRequest.Adpod.anyField).to.be.equal(10); - }) - it('sends correct video bid parameters', () => { - const data = videoRequest[0].data; - - const eq = { - CallbackId: '84ab500420319d', - AdType: 'video', - Aid: 12345, - Sizes: '480x360,640x480', - PlacementId: 'adunit-code' - }; - expect(data.BidRequests[0]).to.deep.equal(eq); + it('should send the correct sizes array', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sizes).to.be.an('array'); + expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) + expect(request.data.bids[1].sizes).to.be.an('array'); + expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) }); - it('sends correct display bid parameters', () => { - const data = displayRequest[0].data; + it('should send the correct media type', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].mediaType).to.equal(VIDEO) + expect(request.data.bids[1].mediaType).to.equal(BANNER) + }); - const eq = { - CallbackId: '84ab500420319d', - AdType: 'display', - Aid: 12345, - Sizes: '300x250', - PlacementId: 'adunit-code' + it('should send the correct currency in bid request', function () { + const bid = utils.deepClone(bidRequests[0]); + bid.params = { + 'currency': 'EUR' }; + const expectedCurrency = bid.params.currency; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0].currency).to.equal(expectedCurrency); + }); - expect(data.BidRequests[0]).to.deep.equal(eq); - }); - - it('sends correct video and display bid parameters', () => { - const bidRequests = videoAndDisplayRequests[0].data; - const expectedBidReqs = [{ - CallbackId: '84ab500420319d', - AdType: 'display', - Aid: 12345, - Sizes: '300x250', - PlacementId: 'adunit-code' - }, { - CallbackId: '84ab500420319d', - AdType: 'video', - Aid: 12345, - Sizes: '480x360,640x480', - PlacementId: 'adunit-code' - }] + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); - expect(bidRequests.BidRequests).to.deep.equal(expectedBidReqs); + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); }); - describe('publisher environment', () => { - const sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'coppa': true - }; - return config[key]; + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } }); - const bidRequestWithPubSettingsData = spec.buildRequests([DISPLAY_REQUEST], displayBidderRequestWithConsents)[0].data; - sandbox.restore(); - it('sets GDPR', () => { - expect(bidRequestWithPubSettingsData.GDPR).to.be.equal(1); - expect(bidRequestWithPubSettingsData.GDPRConsent).to.be.equal(displayBidderRequestWithConsents.gdprConsent.consentString); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.resetConfig(); + config.setConfig({ + userSync: { + syncEnabled: true, + } }); - it('sets USP', () => { - expect(bidRequestWithPubSettingsData.USP).to.be.equal(displayBidderRequestWithConsents.uspConsent); - }) - it('sets Coppa', () => { - expect(bidRequestWithPubSettingsData.Coppa).to.be.equal(1); - }) - it('sets Schain', () => { - expect(bidRequestWithPubSettingsData.Schain).to.be.deep.equal(DISPLAY_REQUEST.schain); - }) - it('sets UserId\'s', () => { - expect(bidRequestWithPubSettingsData.UserIds).to.be.deep.equal(DISPLAY_REQUEST.userId); - }) - }) - }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'pixel'); + }); - describe('interpretResponse', () => { - let serverResponse; - let adapterRequest; - let eqResponse; + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); - afterEach(() => { - serverResponse = null; - adapterRequest = null; - eqResponse = null; + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('us_privacy', '1YNN'); }); - it('should get correct video bid response', () => { - serverResponse = SERVER_VIDEO_RESPONSE; - adapterRequest = videoBidderRequest; - eqResponse = videoEqResponse; + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('us_privacy'); + }); - bidServerResponseCheck(); + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gdpr'); + expect(request.data.params).to.not.have.property('gdpr_consent'); }); - it('should get correct display bid response', () => { - serverResponse = SERVER_DISPLAY_RESPONSE; - adapterRequest = displayBidderRequest; - eqResponse = displayEqResponse; + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gdpr', true); + expect(request.data.params).to.have.property('gdpr_consent', 'test-consent-string'); + }); - bidServerResponseCheck(); + it('should not send the gpp param if gppConsent is false in the bidRequest', function () { + const bidderRequestWithGPP = Object.assign({gppConsent: false}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGPP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gpp'); + expect(request.data.params).to.not.have.property('gpp_sid'); }); - function bidServerResponseCheck() { - const result = spec.interpretResponse({ body: serverResponse }, { adapterRequest }); + it('should send the gpp param if gppConsent is true in the bidRequest', function () { + const bidderRequestWithGPP = Object.assign({gppConsent: {gppString: 'test-consent-string', applicableSections: [7]}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGPP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gpp', 'test-consent-string'); + expect(request.data.params.gpp_sid[0]).to.be.equal(7); + }); - expect(result).to.deep.equal(eqResponse); - } + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); + }); + + it('should set flooPrice to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 3.32 + } + } + bid.params.floorPrice = 0.64; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 3.32); + }); - function nobidServerResponseCheck() { - const noBidServerResponse = { bids: [] }; - const noBidResult = spec.interpretResponse({ body: noBidServerResponse }, { adapterRequest }); + it('should set floorPrice to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 0.8 + } + } + bid.params.floorPrice = 1.5; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); + }); - expect(noBidResult.length).to.equal(0); - } + it('should check sua param in bid request', function() { + const sua = { + 'platform': { + 'brand': 'macOS', + 'version': ['12', '4', '0'] + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Not;A=Brand', + 'version': [ '99', '0', '0', '0' ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'device': { + 'sua': { + 'platform': { + 'brand': 'macOS', + 'version': [ '12', '4', '0' ] + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Not;A=Brand', + 'version': [ '99', '0', '0', '0' ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } + const requestWithSua = spec.buildRequests([bid], bidderRequest); + const data = requestWithSua.data; + expect(data.bids[0].sua).to.exist; + expect(data.bids[0].sua).to.deep.equal(sua); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sua).to.not.exist; + }); + + describe('COPPA Param', function() { + it('should set coppa equal 0 in bid request if coppa is set to false', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(0); + }); + + it('should set coppa equal 1 in bid request if coppa is set to true', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'regs': { + 'coppa': true, + } + }; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(1); + }); + }); + }); + + describe('interpretResponse', function () { + const response = { + params: { + currency: 'USD', + netRevenue: true, + }, + bids: [{ + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: VIDEO + }, + { + cpm: 12.5, + ad: '""', + width: 300, + height: 250, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: BANNER + }] + }; + + const expectedVideoResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 640, + height: 480, + ttl: TTL, + creativeId: '21e12606d47ba7', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: VIDEO, + meta: { + mediaType: VIDEO, + advertiserDomains: ['abc.com'] + }, + vastXml: '', + }; + + const expectedBannerResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 640, + height: 480, + ttl: TTL, + creativeId: '21e12606d47ba7', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: BANNER, + meta: { + mediaType: BANNER, + advertiserDomains: ['abc.com'] + }, + ad: '""' + }; + + it('should get correct bid response', function () { + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + }); + + it('video type should have vastXml key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[0].vastXml).to.equal(expectedVideoResponse.vastXml) + }); + + it('banner type should have ad key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[1].ad).to.equal(expectedBannerResponse.ad) + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + params: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + } + }; + + const iframeSyncResponse = { + body: { + params: { + userSyncURL: 'https://iframe-sync-url.test' + } + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); - it('handles video nobid responses', () => { - adapterRequest = videoBidderRequest; + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); - nobidServerResponseCheck(); + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); }); - it('handles display nobid responses', () => { - adapterRequest = displayBidderRequest; + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); - nobidServerResponseCheck(); + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); }); + }) - it('forms correct ADPOD response', () => { - const videoBids = spec.interpretResponse({ body: SERVER_VIDEO_RESPONSE }, { adapterRequest: { bids: [ADPOD_REQUEST] } }); - expect(videoBids[0].video.durationSeconds).to.be.equal(30); - expect(videoBids[0].video.context).to.be.equal('adpod'); + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should trigger pixel if bid nurl', function() { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'nurl': 'http://example.com/win/1234', + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + spec.onBidWon(bid); + expect(utils.triggerPixel.callCount).to.equal(1) }) - }); + }) }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 5d59ff99a89..fda2c853e87 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1781,6 +1781,37 @@ describe('PubMatic adapter', function () { expect(data2.regs).to.equal(undefined);// USP/CCPAs }); + it('Request params should include DSA signals if present', function () { + const dsa = { + dsarequired: 3, + pubrender: 0, + datatopub: 2, + transparency: [ + { + domain: 'platform1domain.com', + dsaparams: [1] + }, + { + domain: 'SSP2domain.com', + dsaparams: [1, 2] + } + ] + }; + + let bidRequest = { + ortb2: { + regs: { + ext: { + dsa + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + assert.deepEqual(data.regs.ext.dsa, dsa); + }); + it('Request params check with JW player params', function() { let bidRequests = [ { @@ -3753,6 +3784,16 @@ describe('PubMatic adapter', function () { describe('Preapare metadata', function () { it('Should copy all fields from ext to meta', function () { + const dsa = { + behalf: 'Advertiser', + paid: 'Advertiser', + transparency: [{ + domain: 'dsp1domain.com', + dsaparams: [1, 2] + }], + adrender: 1 + }; + const bid = { 'adomain': [ 'mystartab.com' @@ -3764,6 +3805,7 @@ describe('PubMatic adapter', function () { 'deal_channel': 1, 'bidtype': 0, advertiserId: 'adid', + dsa, // networkName: 'nwnm', // primaryCatId: 'pcid', // advertiserName: 'adnm', @@ -3795,6 +3837,7 @@ describe('PubMatic adapter', function () { expect(br.meta.secondaryCatIds[0]).to.equal('IAB_CATEGORY'); expect(br.meta.advertiserDomains).to.be.an('array').with.length.above(0); // adomain expect(br.meta.clickUrl).to.equal('mystartab.com'); // adomain + expect(br.meta.dsa).to.equal(dsa); // dsa }); it('Should be empty, when ext and adomain is absent in bid object', function () { diff --git a/test/spec/modules/pxyzBidAdapter_spec.js b/test/spec/modules/pxyzBidAdapter_spec.js index 3a336c86e46..36e7a1e9ad6 100644 --- a/test/spec/modules/pxyzBidAdapter_spec.js +++ b/test/spec/modules/pxyzBidAdapter_spec.js @@ -210,30 +210,15 @@ describe('pxyzBidAdapter', function () { }); describe('getUserSyncs', function () { - const syncUrl = '//ib.adnxs.com/getuidnb?https://ads.playground.xyz/usersync?partner=appnexus&uid=$UID'; - - describe('when iframeEnabled is true', function () { - const syncOptions = { - 'iframeEnabled': true - } - it('should return one image type user sync pixel', function () { - let result = spec.getUserSyncs(syncOptions); - expect(result.length).to.equal(1); - expect(result[0].type).to.equal('image') - expect(result[0].url).to.equal(syncUrl); - }); - }); - - describe('when iframeEnabled is false', function () { - const syncOptions = { - 'iframeEnabled': false - } - it('should return one image type user sync pixel', function () { - let result = spec.getUserSyncs(syncOptions); - expect(result.length).to.equal(1); - expect(result[0].type).to.equal('image') - expect(result[0].url).to.equal(syncUrl); - }); + const syncImageUrl = '//ib.adnxs.com/getuidnb?https://ads.playground.xyz/usersync?partner=appnexus&uid=$UID'; + const syncIframeUrl = '//rtb.gumgum.com/getuid/15801?r=https%3A%2F%2Fads.playground.xyz%2Fusersync%3Fpartner%3Dgumgum%26uid%3D'; + it('should return one image type user sync pixel', function () { + let result = spec.getUserSyncs(); + expect(result.length).to.equal(2); + expect(result[0].type).to.equal('image') + expect(result[0].url).to.equal(syncImageUrl); + expect(result[1].type).to.equal('iframe') + expect(result[1].url).to.equal(syncIframeUrl); }); }) }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 09418caef53..55e8909f6c8 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2268,17 +2268,6 @@ describe('the rubicon adapter', function () { expect(payload.ext.prebid.analytics).to.be.undefined; }); - it('should send video exp param correctly when set', function () { - const bidderRequest = createVideoBidderRequest(); - config.setConfig({s2sConfig: {defaultTtl: 600}}); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; - - // should exp set to the right value according to config - let imp = post.imp[0]; - expect(imp.exp).to.equal(600); - }); - it('should not send video exp at all if not set in s2sConfig config', function () { const bidderRequest = createVideoBidderRequest(); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); @@ -3087,7 +3076,7 @@ describe('the rubicon adapter', function () { expect(bids[0].width).to.equal(320); expect(bids[0].height).to.equal(50); expect(bids[0].cpm).to.equal(0.911); - expect(bids[0].ttl).to.equal(300); + expect(bids[0].ttl).to.equal(360); expect(bids[0].netRevenue).to.equal(true); expect(bids[0].rubicon.advertiserId).to.equal(7); expect(bids[0].rubicon.networkId).to.equal(8); @@ -3104,7 +3093,7 @@ describe('the rubicon adapter', function () { expect(bids[1].width).to.equal(300); expect(bids[1].height).to.equal(250); expect(bids[1].cpm).to.equal(0.811); - expect(bids[1].ttl).to.equal(300); + expect(bids[1].ttl).to.equal(360); expect(bids[1].netRevenue).to.equal(true); expect(bids[1].rubicon.advertiserId).to.equal(7); expect(bids[1].rubicon.networkId).to.equal(8); @@ -3916,7 +3905,7 @@ describe('the rubicon adapter', function () { expect(bids[0].seatBidId).to.equal('0'); expect(bids[0].creativeId).to.equal('4259970'); expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); + expect(bids[0].ttl).to.equal(360); expect(bids[0].netRevenue).to.equal(true); expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); expect(bids[0].mediaType).to.equal('video'); @@ -4008,7 +3997,7 @@ describe('the rubicon adapter', function () { expect(bids[0].seatBidId).to.equal('0'); expect(bids[0].creativeId).to.equal('4259970'); expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); + expect(bids[0].ttl).to.equal(360); expect(bids[0].netRevenue).to.equal(true); expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); expect(bids[0].mediaType).to.equal('video'); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 58b4cd8c0d0..b01d95e2a4c 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -787,7 +787,7 @@ describe('Smart bid adapter tests', function () { const requestContent = JSON.parse(request[0].data); expect(requestContent).to.have.property('videoData'); expect(requestContent.videoData).not.to.have.property('videoProtocol').eq(true); - expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(1); }); it('Verify videoData params override meta values', function () { @@ -1097,7 +1097,7 @@ describe('Smart bid adapter tests', function () { const requestContent = JSON.parse(request[0].data); expect(requestContent).to.have.property('videoData'); expect(requestContent.videoData).not.to.have.property('videoProtocol').eq(true); - expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(1); }); it('Verify videoData params override meta values', function () { @@ -1143,6 +1143,50 @@ describe('Smart bid adapter tests', function () { expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(3); }); + + it('should handle value of videoMediaType.startdelay', function () { + const request = spec.buildRequests([{ + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]], + startdelay: -2 + } + }, + params: { + siteId: 123, + pageId: 456, + formatId: 78 + } + }]); + + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(3); + }); + + it('should return specified value of videoMediaType.startdelay', function () { + const request = spec.buildRequests([{ + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]], + startdelay: 60 + } + }, + params: { + siteId: 123, + pageId: 456, + formatId: 78 + } + }]); + + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); + }); }); describe('External ids tests', function () { diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 458ccc37759..1b592e142c3 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -61,7 +61,7 @@ describe('SmartyadsAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa', 'eeid', 'ifa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.coppa).to.be.a('number'); diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 3c842c3a308..f37ef9178dd 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -414,6 +414,25 @@ describe('YieldmoAdapter', function () { })); }); + it('should add cdep to the banner bid request', function () { + const biddata = build( + [mockBannerBid()], + mockBidderRequest({ + ortb2: { + device: { + ext: { + cdep: 'test_cdep' + }, + }, + }, + }) + ); + + expect(biddata[0].data.cdep).to.equal( + 'test_cdep' + ); + }); + it('should send gpc in the banner bid request', function () { const biddata = build( [mockBannerBid()], diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index aba64733f90..5fe5a1accfc 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1456,67 +1456,95 @@ describe('bidderFactory', () => { transactionId: 'au', }] }; - const fledgeAuctionConfig = { + const paapiConfig = { bidId: '1', config: { foo: 'bar' } } - describe('when response has FLEDGE auction config', function() { - let fledgeStub; - function fledgeHook(next, ...args) { - fledgeStub(...args); + it('should unwrap bids', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bids[0])); + }); + + it('does not unwrap bids from a bid that happens to have a "bids" property', () => { + const bidder = newBidder(spec); + const bid = Object.assign({ + bids: ['a', 'b'] + }, bids[0]); + spec.interpretResponse.returns(bid); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bid)); + }) + + describe('when response has PAAPI auction config', function() { + let paapiStub; + + function paapiHook(next, ...args) { + paapiStub(...args); } before(() => { - addComponentAuction.before(fledgeHook); + addComponentAuction.before(paapiHook); }); after(() => { - addComponentAuction.getHooks({hook: fledgeHook}).remove(); + addComponentAuction.getHooks({hook: paapiHook}).remove(); }) beforeEach(function () { - fledgeStub = sinon.stub(); + paapiStub = sinon.stub(); }); - it('should unwrap bids', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: bids, - fledgeAuctionConfigs: [] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - }); + const PAAPI_PROPS = ['fledgeAuctionConfigs', 'paapiAuctionConfigs']; - it('should call fledgeManager with FLEDGE configs', function() { + it(`should not accept both ${PAAPI_PROPS.join(' and ')}`, () => { const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: bids, - fledgeAuctionConfigs: [fledgeAuctionConfig] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - expect(fledgeStub.calledOnce).to.equal(true); - sinon.assert.calledWith(fledgeStub, bidRequest.bids[0], fledgeAuctionConfig.config); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + spec.interpretResponse.returns(Object.fromEntries(PAAPI_PROPS.map(prop => [prop, [paapiConfig]]))) + expect(() => { + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + }).to.throw; }) - it('should call fledgeManager with FLEDGE configs even if no bids returned', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: [], - fledgeAuctionConfigs: [fledgeAuctionConfig] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + PAAPI_PROPS.forEach(paapiProp => { + describe(`using ${paapiProp}`, () => { + it('should call paapi hook with PAAPI configs', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + [paapiProp]: [paapiConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(fledgeStub.calledOnce).to.be.true; - sinon.assert.calledWith(fledgeStub, bidRequest.bids[0], fledgeAuctionConfig.config); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(paapiStub.calledOnce).to.equal(true); + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig.config); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + }) + + Object.entries({ + 'missing': undefined, + 'an empty array': [] + }).forEach(([t, bids]) => { + it(`should call paapi hook with PAAPI configs even when bids is ${t}`, function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids, + [paapiProp]: [paapiConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(paapiStub.calledOnce).to.be.true; + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig.config); + expect(addBidResponseStub.calledOnce).to.equal(false); + }) + }) + }) }) }) }) diff --git a/webpack.conf.js b/webpack.conf.js index 0ead550e446..c934b09d439 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -128,10 +128,37 @@ module.exports = { return [lib, def]; }) ); + const core = path.resolve('./src'); + const paapiMod = path.resolve('./modules/paapi.js'); + const nodeModules = path.resolve('./node_modules'); + return Object.assign(libraries, { + common_deps: { + name: 'common_deps', + test(module) { + return module.resource && module.resource.startsWith(nodeModules); + } + }, + core: { + name: 'chunk-core', + test: (module) => { + return module.resource && module.resource.startsWith(core); + } + }, + paapi: { + // fledgeForGpt imports paapi to keep backwards compat for NPM consumers + // this makes the paapi module its own chunk, pulled in by both paapi and fledgeForGpt entry points, + // to avoid duplication + // TODO: remove this in prebid 9 + name: 'chunk-paapi', + test: (module) => { + return module.resource === paapiMod; + } + } + }, { default: false, defaultVendors: false - }) + }); })() } },