diff --git a/integrationExamples/gpt/ixMultiFormat.html b/integrationExamples/gpt/ixMultiFormat.html new file mode 100644 index 00000000000..c4ed5bb9b1e --- /dev/null +++ b/integrationExamples/gpt/ixMultiFormat.html @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + + diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 4837c9c061f..d326cbaf9b1 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -50,18 +50,36 @@ const PROVIDERS = [ 'flocId' ] +const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration']; // note: protocol/protocols is also reqd + +const VIDEO_PARAMS_ALLOW_LIST = [ + 'mimes', 'minduration', 'maxduration', 'protocols', 'protocol', + 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', + 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', + 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', + 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext', + 'playerSize', 'w', 'h' +]; + /** * Transform valid bid request config object to banner impression object that will be sent to ad server. * - * @param {object} bid A valid bid request config object. + * @param {object} bid A valid bid request config object * @return {object} A impression object that will be sent to ad server. */ function bidToBannerImp(bid) { const imp = bidToImp(bid); - imp.banner = {}; - imp.banner.w = bid.params.size[0]; - imp.banner.h = bid.params.size[1]; + const impSize = utils.deepAccess(bid, 'params.size'); + if (impSize) { + imp.banner.w = impSize[0]; + imp.banner.h = impSize[1]; + // populate sid with size if not id + if (!(utils.deepAccess(imp, 'ext.sid'))) { + imp.ext.sid = `${impSize[0]}x${impSize[1]}`; + } + } + imp.banner.topframe = utils.inIframe() ? 0 : 1; _applyFloor(bid, imp, BANNER); @@ -78,18 +96,27 @@ function bidToBannerImp(bid) { function bidToVideoImp(bid) { const imp = bidToImp(bid); const videoAdUnitRef = utils.deepAccess(bid, 'mediaTypes.video'); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - const videoAdUnitAllowlist = [ - 'mimes', 'minduration', 'maxduration', 'protocols', 'protocol', - 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', - 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', - 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', - 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext' - ]; - - imp.video = utils.deepClone(bid.params.video) - imp.video.w = bid.params.size[0]; - imp.video.h = bid.params.size[1]; + const videoParamRef = utils.deepAccess(bid, 'params.video'); + + if (!checkVideoParams(bid, videoAdUnitRef, videoParamRef)) { + return {}; + } + + imp.video = videoParamRef ? utils.deepClone(bid.params.video) : {}; + + for (const adUnitProperty in videoAdUnitRef) { + if (VIDEO_PARAMS_ALLOW_LIST.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { + imp.video[adUnitProperty] = videoAdUnitRef[adUnitProperty]; + } + } + + if (imp.video.minduration > imp.video.maxduration) { + utils.logError('IX Bid Adapter: video minduration [' + imp.video.minduration + + '] cannot be greater than video maxduration [' + imp.video.maxduration + ']'); + return {}; + } + + const context = (videoParamRef && videoParamRef.context) || (videoAdUnitRef && videoAdUnitRef.context); if (context) { if (context === 'instream') { @@ -97,13 +124,22 @@ function bidToVideoImp(bid) { } else if (context === 'outstream') { imp.video.placement = 4; } else { - utils.logWarn(`ix bidder params: video context '${context}' is not supported`); + utils.logWarn(`IX Bid Adapter: video context '${context}' is not supported`); } } - for (const adUnitProperty in videoAdUnitRef) { - if (videoAdUnitAllowlist.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { - imp.video[adUnitProperty] = videoAdUnitRef[adUnitProperty]; + if (!(imp.video.w && imp.video.h)) { + // Getting impression Size + const impSize = getFirstSize(utils.deepAccess(imp, 'video.playerSize')) || getFirstSize(utils.deepAccess(bid, 'params.size')); + if (impSize) { + imp.video.w = impSize[0]; + imp.video.h = impSize[1]; + if (!(utils.deepAccess(imp, 'ext.sid'))) { + imp.ext.sid = `${impSize[0]}x${impSize[1]}`; + } + } else { + utils.logWarn('IX Bid Adapter: Video size is missing in [mediaTypes.video] missing'); + return {}; } } @@ -128,8 +164,6 @@ function bidToImp(bid) { if (bid.params.hasOwnProperty('id') && (typeof bid.params.id === 'string' || typeof bid.params.id === 'number')) { imp.ext.sid = String(bid.params.id); - } else { - imp.ext.sid = `${bid.params.size[0]}x${bid.params.size[1]}`; } const dfpAdUnitCode = utils.deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot'); @@ -278,16 +312,68 @@ function isValidSize(size) { * @return {boolean} True if the size object is an element of the size array, and false * otherwise. */ -function includesSize(sizeArray, size) { +function includesSize(sizeArray = [], size) { if (isValidSize(sizeArray)) { return sizeArray[0] === size[0] && sizeArray[1] === size[1]; } - for (let i = 0; i < sizeArray.length; i++) { if (sizeArray[i][0] === size[0] && sizeArray[i][1] === size[1]) { return true; } } + return false; +} + +/** + * Checks if all required video params are present + * @param {object} bid Bid Object + * @param {object} mediaTypeVideoRef Ad unit level mediaTypes object + * @param {object} paramsVideoRef IX bidder params level video object + * @returns bool Are the required video params available + */ +function checkVideoParams(bid, mediaTypeVideoRef, paramsVideoRef) { + let reqParamsPresent = true; + + if (!mediaTypeVideoRef) { + utils.logWarn('IX Bid Adapter: mediaTypes.video is the preferred location for video params in ad unit'); + } + + for (let property of REQUIRED_VIDEO_PARAMS) { + const propInMediaType = mediaTypeVideoRef && mediaTypeVideoRef.hasOwnProperty(property); + const propInVideoRef = paramsVideoRef && paramsVideoRef.hasOwnProperty(property); + + if (!propInMediaType && !propInVideoRef) { + utils.logError('IX Bid Adapter: ' + property + ' is not included in either the adunit or params level'); + reqParamsPresent = false; + } + } + + // early return + if (!reqParamsPresent) { + return false; + } + + // check protocols/protocol + const protocolMediaType = mediaTypeVideoRef && mediaTypeVideoRef.hasOwnProperty('protocol'); + const protocolsMediaType = mediaTypeVideoRef && mediaTypeVideoRef.hasOwnProperty('protocols'); + const protocolVideoRef = paramsVideoRef && paramsVideoRef.hasOwnProperty('protocol'); + const protocolsVideoRef = paramsVideoRef && paramsVideoRef.hasOwnProperty('protocols'); + + return protocolMediaType || protocolsMediaType || protocolVideoRef || protocolsVideoRef; +} + +/** + * Get One size from Size Array + * [[250,350]] -> [250, 350] + * [250, 350] -> [250, 350] + * @param {array} sizes array of sizes + */ +function getFirstSize(sizes = []) { + if (isValidSize(sizes)) { + return sizes; + } else if (isValidSize(sizes[0])) { + return sizes[0]; + } return false; } @@ -295,9 +381,9 @@ function includesSize(sizeArray, size) { /** * Determines whether or not the given bidFloor parameters are valid. * - * @param {*} bidFloor The bidFloor parameter inside bid request config. - * @param {*} bidFloorCur The bidFloorCur parameter inside bid request config. - * @return {boolean} True if this is a valid bidFloor parameters format, and false + * @param {number} bidFloor The bidFloor parameter inside bid request config. + * @param {number} bidFloorCur The bidFloorCur parameter inside bid request config. + * @return {bool} True if this is a valid bidFloor parameters format, and false * otherwise. */ function isValidBidFloorParams(bidFloor, bidFloorCur) { @@ -534,31 +620,31 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { let currMissingImps = []; while (i < transactionIds.length && requests.length < MAX_REQ_LIMIT) { - if (impressions[transactionIds[i]].hasOwnProperty('missingCount')) { - msd = impressions[transactionIds[i]].missingCount; - } + const impObj = impressions[transactionIds[i]]; + msd = utils.deepAccess(impObj, 'missingCount') && utils.deepAccess(impObj, 'ixImps') ? impObj.missingCount : 0; if (BASE_REQ_SIZE < MAX_REQ_SIZE) { - trimImpressions(impressions[transactionIds[i]], MAX_REQ_SIZE - BASE_REQ_SIZE); + trimImpressions(impObj, MAX_REQ_SIZE - BASE_REQ_SIZE); } else { utils.logError('ix bidder: Base request size has exceeded maximum request size.'); } - if (impressions[transactionIds[i]].hasOwnProperty('missingImps')) { - msi = impressions[transactionIds[i]].missingImps.length; - } + msi = utils.deepAccess(impObj, 'missingImps') && utils.deepAccess(impObj, 'ixImps') ? impObj.missingImps.length : 0; - let currImpsSize = new Blob([encodeURIComponent(JSON.stringify(impressions[transactionIds[i]]))]).size; + let currImpsSize = new Blob([encodeURIComponent(JSON.stringify(impObj))]).size; currReqSize += currImpsSize; if (currReqSize < MAX_REQ_SIZE) { - // pushing ix configured sizes first - r.imp.push(...impressions[transactionIds[i]].ixImps); + // since not reading params.size, ixImps can be undefined/empty + if (impObj.ixImps && impObj.ixImps.length > 0) { + // pushing ix configured sizes first + r.imp.push(...impObj.ixImps); + } // update msd msi r.ext.ixdiag.msd += msd; r.ext.ixdiag.msi += msi; - if (impressions[transactionIds[i]].hasOwnProperty('missingImps')) { - currMissingImps.push(...impressions[transactionIds[i]].missingImps); + if (impObj.hasOwnProperty('missingImps') && impObj.missingImps.length > 0) { + currMissingImps.push(...impObj.missingImps); } i++; @@ -699,6 +785,7 @@ function trimImpressions(impressions, maxSize) { currSize = new Blob([encodeURIComponent(JSON.stringify(impressions))]).size; } } + /** * * @param {array} bannerSizeList list of banner sizes @@ -707,14 +794,15 @@ function trimImpressions(impressions, maxSize) { */ function removeFromSizes(bannerSizeList, bannerSize) { + if (!bannerSize) return; + for (let i = 0; i < bannerSizeList.length; i++) { - if (bannerSize[0] == bannerSizeList[i][0] && bannerSize[1] == bannerSizeList[i][1]) { + const size = bannerSizeList[i]; + if (bannerSize[0] === size[0] && bannerSize[1] === size[1]) { bannerSizeList.splice(i, 1); - return true; + break; } } - // size not found - return false; } /** @@ -790,40 +878,26 @@ export const spec = { const hasBidFloor = bid.params.hasOwnProperty('bidFloor'); const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); - if (!isValidSize(bid.params.size)) { - utils.logError('ix bidder params: bid size has invalid format.'); - return false; - } - if (bid.hasOwnProperty('mediaType') && !(utils.contains(SUPPORTED_AD_TYPES, bid.mediaType))) { return false; } - if (bid.hasOwnProperty('mediaTypes') && !(mediaTypeBannerSizes || mediaTypeVideoPlayerSize)) { + if (utils.deepAccess(bid, 'mediaTypes.banner') && !mediaTypeBannerSizes) { return false; } - if (!includesSize(bid.sizes, paramsSize) && !((mediaTypeVideoPlayerSize && includesSize(mediaTypeVideoPlayerSize, paramsSize)) || - (mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) { - utils.logError('ix bidder params: bid size is not included in ad unit sizes or player size.'); - return false; - } - - if (mediaTypeVideoRef && paramsVideoRef) { - const requiredIXParams = ['mimes', 'minduration', 'maxduration', 'protocols']; - let isParamsLevelValid = true; - for (let property of requiredIXParams) { - if (!mediaTypeVideoRef.hasOwnProperty(property) && !paramsVideoRef.hasOwnProperty(property)) { - const isProtocolsValid = (property === 'protocols' && (mediaTypeVideoRef.hasOwnProperty('protocol') || paramsVideoRef.hasOwnProperty('protocol'))); - if (isProtocolsValid) { - continue; - } - utils.logError('ix bidder params: ' + property + ' is not included in either the adunit or params level'); - isParamsLevelValid = false; - } + if (paramsSize) { + // since there is an ix bidder level size, make sure its valid + const ixSize = getFirstSize(paramsSize); + if (!ixSize) { + utils.logError('ix bidder params: size has invalid format.'); + return false; } - - if (!isParamsLevelValid) { + // check if the ix bidder level size, is present in ad unit level + if (!includesSize(bid.sizes, ixSize) && + !(includesSize(mediaTypeVideoPlayerSize, ixSize)) && + !(includesSize(mediaTypeBannerSizes, ixSize))) { + utils.logError('ix bidder params: bid size is not included in ad unit sizes or player size.'); return false; } } @@ -839,7 +913,10 @@ export const spec = { return false; } } - + // For multi format unit + if (!mediaTypeBannerSizes && (mediaTypeVideoRef || paramsVideoRef)) { + return checkVideoParams(bid, mediaTypeVideoRef, paramsVideoRef); + } return true; }, @@ -867,30 +944,35 @@ export const spec = { for (let i = 0; i < validBidRequests.length; i++) { validBidRequest = validBidRequests[i]; - - if (validBidRequest.mediaType === VIDEO || utils.deepAccess(validBidRequest, 'mediaTypes.video')) { - if (validBidRequest.mediaType === VIDEO || includesSize(validBidRequest.mediaTypes.video.playerSize, validBidRequest.params.size)) { - if (!videoImps.hasOwnProperty(validBidRequest.transactionId)) { + const videoAdUnitRef = utils.deepAccess(validBidRequest, 'mediaTypes.video'); + const videoParamRef = utils.deepAccess(validBidRequest, 'params.video'); + + // identify video ad unit + if (validBidRequest.mediaType === VIDEO || videoAdUnitRef || videoParamRef) { + if (!videoImps.hasOwnProperty(validBidRequest.transactionId)) { + const imp = bidToVideoImp(validBidRequest); + if (Object.keys(imp).length != 0) { videoImps[validBidRequest.transactionId] = {}; - } - if (!videoImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { videoImps[validBidRequest.transactionId].ixImps = []; + videoImps[validBidRequest.transactionId].ixImps.push(imp); } - videoImps[validBidRequest.transactionId].ixImps.push(bidToVideoImp(validBidRequest)); } } + if (validBidRequest.mediaType === BANNER || - (utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) || + (utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes')) || (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { let imp = bidToBannerImp(validBidRequest); - - if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { - bannerImps[validBidRequest.transactionId] = {}; - } - if (!bannerImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { - bannerImps[validBidRequest.transactionId].ixImps = [] + // Create IX imps from params.size + if (utils.deepAccess(validBidRequest, 'params.size')) { + if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { + bannerImps[validBidRequest.transactionId] = {}; + } + if (!bannerImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { + bannerImps[validBidRequest.transactionId].ixImps = [] + } + bannerImps[validBidRequest.transactionId].ixImps.push(imp); } - bannerImps[validBidRequest.transactionId].ixImps.push(imp); if (ixConfig.hasOwnProperty('detectMissingSizes') && ixConfig.detectMissingSizes) { updateMissingSizes(validBidRequest, missingBannerSizes, imp); } diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index c358b19a0a2..59b699bad2d 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -13,30 +13,10 @@ Description This module connects publishers to Index Exchange's (IX) network of demand sources through Prebid.js. This module is GDPR and CCPA compliant. -It is compatible with both the older ad unit format where the `sizes` and -`mediaType` properties are placed at the top-level of the ad unit, and the newer -format where this information is encapsulated within the `mediaTypes` object. We -recommend that you use the newer format when possible as it will be better able +It is compatible with the newer PrebidJS 5.0 ad unit format where the `banner` and/or `video` properties are encapsulated within the `adUnits[].mediaTypes` object. We +recommend that you use this newer format when possible as it will be better able to accommodate new feature additions. -If a mix of properties from both formats are present within an ad unit, the -newer format's properties will take precedence. - -Here are examples of both formats. - -##### Older Format -```javascript -var adUnits = [{ - // ... - - sizes: [ - [300, 250], - [300, 600] - ] - - // ... -}]; -``` ##### Newer Format ```javascript @@ -51,10 +31,7 @@ var adUnits = [{ }, video: { context: 'instream', - playerSize: [ - [300, 250], - [300, 600] - ] + playerSize: [300, 250] } }, // ... @@ -69,30 +46,59 @@ var adUnits = [{ | Video | Fully supported for all IX approved sizes. | Native | Not supported. -# Bid Parameters + + +# Ad unit or Bidder Parameters + +These params can be specified in the ad unit level `adUnits[].mediaTypes`, which will be the preferred way going forward with PBJS 5.0 Each of the IX-specific parameters provided under the `adUnits[].bids[].params` object are detailed here. + ### Banner | Key | Scope | Type | Description | --- | --- | --- | --- -| siteId | Required | String | An IX-specific identifier that is associated with a specific size on this ad unit. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` -| size | Required | Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.banner.sizes`. Examples: `[300, 250]`, `[300, 600]`, `[728, 90]` +| siteId | Required | String | An IX-specific identifier that is associated with this ad unit. It will be associated to the single size, if the size provided. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` +| sizes | Required | Number[Number[]] | The size / sizes associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].mediaTypes.banner.sizes`. Examples: `[300, 250]`, `[300, 600]`, `[728, 90]` ### Video | Key | Scope | Type | Description | --- | --- | --- | --- -| siteId | Required | String | An IX-specific identifier that is associated with a specific size on this ad unit. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` -| size | Required | Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.video.playerSize`. Examples: `[300, 250]`, `[300, 600]` -| video | Required | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. Properties not defined at this level, will be pulled from the Adunit level. +| siteId | Required | String | An IX-specific identifier that is associated with this ad unit. It will be associated to the single size, if the size is provided. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` +| size | Optional (Deprecated)| Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.video.playerSize`. Examples: `[300, 250]`, `[300, 600]` +| video | Optional | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. Properties not defined at this level, will be pulled from the Adunit level. +|video.w| Required | Integer | The video player size width in pixels that will be passed to demand partners. +|video.h| Required | Integer | The video player size height in pixels that will be passed to demand partners. +|video.playerSize| Optional* | Integer | The video player size that will be passed to demand partners. * In the absence of `video.w` and `video.h`, this field is required. | video.mimes | Required | String[] | Array list of content MIME types supported. Popular MIME types include, but are not limited to, `"video/x-ms- wmv"` for Windows Media and `"video/x-flv"` for Flash Video. |video.minduration| Required | Integer | Minimum video ad duration in seconds. |video.maxduration| Required | Integer | Maximum video ad duration in seconds. |video.protocol / video.protocols| Required | Integer / Integer[] | Either a single protocol provided as an integer, or protocols provided as a list of integers. `2` - VAST 2.0, `3` - VAST 3.0, `5` - VAST 2.0 Wrapper, `6` - VAST 3.0 Wrapper +## Deprecation warning + +We are deprecating the older format +of having `mediaType` and `sizes` at the ad unit level. + +Here are examples of the format. + +##### Older Deprecated Format +```javascript +var adUnits = [{ + // ... + + sizes: [ + [300, 250], + [300, 600] + ] + + // ... +}]; +``` + Setup Guide =========== @@ -100,10 +106,15 @@ Setup Guide Follow these steps to configure and add the IX module to your Prebid.js integration. +Both video and banner params will be read from the `adUnits[].mediaTypes.video` and `adUnits[].mediaTypes.banner` respectively. + The examples in this guide assume the following starting configuration (you may remove banner or video, if either does not apply). + In regards to video, `context` can either be `'instream'` or `'outstream'`. Note that `outstream` requires additional configuration on the adUnit. + + ```javascript var adUnits = [{ code: 'banner-div-a', @@ -134,21 +145,23 @@ var adUnits = [{ ### 1. Add IX to the appropriate ad units -For each size in an ad unit that IX will be bidding on, add one of the following +For each ad unit that IX will be bidding on, add one of the following bid objects under `adUnits[].bids`: +- size is optional and deprecated ```javascript { bidder: 'ix', params: { siteId: '', - size: [] + size: [] // deprecated } } ``` -Set `params.siteId` and `params.size` in each bid object to the values provided +Set `params.siteId` in the bid object to the values provided by your IX representative. +- `params.size` is not required anymore **Examples** @@ -167,14 +180,7 @@ var adUnits = [{ bids: [{ bidder: 'ix', params: { - siteId: '12345', - size: [300, 250] - } - }, { - bidder: 'ix', - params: { - siteId: '12345', - size: [300, 600] + siteId: '12345' } }] }]; @@ -185,35 +191,30 @@ var adUnits = [{ code: 'video-div-a', mediaTypes: { video: { + // Preferred location for openrtb v2.5 compatible video obj context: 'instream', - playerSize: [ - [300, 250], - [300, 600] - ] + playerSize: [300, 250], + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [6] } }, bids: [{ bidder: 'ix', params: { - siteId: '12345', - size: [300, 250], - video: { - mimes: [ - 'video/mp4', - 'video/webm' - ], - minduration: 0, - maxduration: 60, - protocols: [6] - } + siteId: '12345' } }, { bidder: 'ix', params: { siteId: '12345', - size: [300, 600], video: { // openrtb v2.5 compatible video obj + // If required, use this to override mediaTypes.video.XX properties } } }] @@ -231,7 +232,14 @@ var adUnits = [{ mediaTypes: { video: { context: 'outstream', - playerSize: [[300, 250]] + playerSize: [300, 250], + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [6] } }, renderer: { @@ -244,15 +252,8 @@ var adUnits = [{ bidder: 'ix', params: { siteId: '12345', - size: [300, 250], video: { - mimes: [ - 'video/mp4', - 'video/webm' - ], - minduration: 0, - maxduration: 60, - protocols: [6] + // If required, use this to override mediaTypes.video.XX properties } } }] @@ -413,11 +414,9 @@ FAQs ### Why do I have to input size in `adUnits[].bids[].params` for IX when the size is already in the ad unit? -There is one important reason why we recommend it: +No, only `siteId` is required. -1. An IX site ID is recommended to map to a single size, whereas an ad unit can have multiple -sizes. To ensure that the right site ID is mapped to the correct size in the ad unit, -we require the size to be explicitly stated or our bidder will auto assign the site ID to sizes that are not assigned. +The `size` parameter is no longer a required field, the `siteId` will now be associated with all the sizes in the ad unit. ### How do I view IX's bid request in the network? diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 48c8e48e6af..cf716fd594b 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -212,11 +212,10 @@ describe('IndexexchangeAdapter', function () { siteId: '123', size: [300, 250] }, - sizes: [[300, 250], [300, 600]], mediaTypes: { video: { context: 'outstream', - playerSize: [[400, 100]] + playerSize: [600, 700] }, banner: { sizes: [[300, 250], [300, 600]] @@ -246,20 +245,19 @@ describe('IndexexchangeAdapter', function () { maxduration: 60, protocols: [1] }, - size: [400, 100] + size: [300, 250] }, - sizes: [[300, 250], [300, 600]], mediaTypes: { video: { context: 'outstream', - playerSize: [[400, 100]] + playerSize: [300, 250] }, banner: { sizes: [[300, 250], [300, 600]] } }, adUnitCode: 'div-gpt-ad-1460505748562-0', - transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + transactionId: '273f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', bidderRequestId: '11a22b33c44e', auctionId: '1aa2bb3cc4de', @@ -531,10 +529,10 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when size is missing', function () { + it('should return True when size is missing ', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); delete bid.params.size; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when size array is wrong length', function () { @@ -553,33 +551,47 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when mediaTypes is not banner or video', function () { + it('should return false when mediaTypes.banner does not have sizes', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes = { - native: { - sizes: [[300, 250]] + banner: { + size: [[300, 250]] } }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when mediaTypes.banner does not have sizes', function () { - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + it('should return false when mediaTypes.video.playerSize does not include params.size', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes = { - banner: { - size: [[300, 250]] + video: { + playerSize: [300, 250] } }; + bid.params.size = [100, 200]; expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when mediaTypes.video does not have sizes', function () { + it('should return true when mediaTypes.video.playerSize includes params.size', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes = { video: { - size: [[300, 250]] + playerSize: [[300, 250], [200, 300]] } }; + bid.params.size = [[300, 250]]; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when bid.params.size is missing', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.size; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when minduration is missing', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.minduration; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -709,7 +721,7 @@ describe('IndexexchangeAdapter', function () { const request = spec.buildRequests(cloneValidBid, ALIAS_OPTIONS); const payload = JSON.parse(request[0].data.r); expect(request).to.be.an('array'); - expect(request).to.have.lengthOf(1); + expect(request).to.have.lengthOf.above(0); // should be 1 or more expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); }); @@ -1517,7 +1529,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); expect(impression.banner).to.exist; - expect(impression.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); + expect(impression.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]);// undefined - 300 expect(impression.banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); expect(impression.banner.topframe).to.exist; expect(impression.banner.topframe).to.be.oneOf([0, 1]); @@ -1858,25 +1870,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.video.skippable).to.equal(false); expect(impression.ext).to.exist; expect(impression.ext.siteID).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.siteId.toString()); - expect(impression.ext.sid).to.equal(sidValue); - }); - - it('impression should have correct format when mediaType is specified.', function () { - const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); - delete bid.mediaTypes; - bid.mediaType = 'video'; - const requestBidFloor = spec.buildRequests([bid])[0]; - const impression = JSON.parse(requestBidFloor.data.r).imp[0]; - const sidValue = `${DEFAULT_VIDEO_VALID_BID[0].params.size[0].toString()}x${DEFAULT_VIDEO_VALID_BID[0].params.size[1].toString()}`; - - expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); - expect(impression.video).to.exist; - expect(impression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); - expect(impression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); - expect(impression.video.placement).to.not.exist; - expect(impression.ext).to.exist; - expect(impression.ext.siteID).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.siteId.toString()); - expect(impression.ext.sid).to.equal(sidValue); + expect(impression.ext.sid).to.equal(sidValue); // TODO undefined - 400x600 }); it('should set correct placement if context is outstream', function () { @@ -1891,6 +1885,14 @@ describe('IndexexchangeAdapter', function () { expect(impression.video.placement).to.equal(4); }); + it('should handle unexpected context', function() { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'VaccineJanssen'; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + expect(impression.video.placement).to.be.undefined; + }); + it('should not override video properties if they are already configured at the params video level', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; @@ -1944,22 +1946,31 @@ describe('IndexexchangeAdapter', function () { }); describe('only video bidder params set', function () { - const request = spec.buildRequests(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID); - - const videoImp = JSON.parse(request[0].data.r).imp[0]; - expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(1); - expect(JSON.parse(request[0].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); - expect(videoImp.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); - expect(videoImp.video).to.exist; - expect(videoImp.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[0]); - expect(videoImp.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[1]); + it('should generate video impression', function () { + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID); + const videoImp = JSON.parse(request[1].data.r).imp[0]; + expect(JSON.parse(request[1].data.r).imp).to.have.lengthOf(1); + expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + expect(videoImp.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); + expect(videoImp.video).to.exist; + expect(videoImp.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[0]); + expect(videoImp.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[1]); + }); + it('should get missing sizes count 0 when params.size not used', function () { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + delete bid.params.size; + const request = spec.buildRequests([bid]); + const diagObj = JSON.parse(request[0].data.r).ext.ixdiag; + expect(diagObj.msd).to.equal(0); + expect(diagObj.msi).to.equal(0); + }); }); describe('both banner and video bidder params set', function () { const request = spec.buildRequests([DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]); it('should return valid banner and video requests', function () { const bannerImp = JSON.parse(request[0].data.r).imp[0]; - expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(2); + expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(4); expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); expect(bannerImp.id).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].bidId); expect(bannerImp.banner).to.exist; @@ -1971,18 +1982,18 @@ describe('IndexexchangeAdapter', function () { expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); expect(videoImp.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); expect(videoImp.video).to.exist; - expect(videoImp.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[0]); - expect(videoImp.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[1]); + expect(videoImp.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[0]); + expect(videoImp.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[1]); }); it('should contain all correct IXdiag properties', function () { const diagObj = JSON.parse(request[0].data.r).ext.ixdiag; expect(diagObj.iu).to.equal(0); expect(diagObj.nu).to.equal(0); - expect(diagObj.ou).to.equal(1); + expect(diagObj.ou).to.equal(2); expect(diagObj.ren).to.equal(false); - expect(diagObj.mfu).to.equal(1); - expect(diagObj.allu).to.equal(1); + expect(diagObj.mfu).to.equal(2); + expect(diagObj.allu).to.equal(2); expect(diagObj.version).to.equal('$prebid.version$'); }); });