diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index eb8a082f61f..cc42031b44d 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,6 +1,6 @@ import * as utils from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; -import { BANNER, VIDEO } from 'src/mediaTypes'; +import { BANNER, VIDEO, NATIVE } from 'src/mediaTypes'; import {config} from 'src/config'; const constants = require('src/constants.json'); @@ -11,6 +11,9 @@ const DEFAULT_CURRENCY = 'USD'; const AUCTION_TYPE = 1; const PUBMATIC_DIGITRUST_KEY = 'nFIn8aLzbd'; const UNDEFINED = undefined; +const DEFAULT_WIDTH = 0; +const DEFAULT_HEIGHT = 0; +const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html'; const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url 'gender': '', // User gender @@ -25,7 +28,8 @@ const DATA_TYPES = { 'NUMBER': 'number', 'STRING': 'string', 'BOOLEAN': 'boolean', - 'ARRAY': 'array' + 'ARRAY': 'array', + 'OBJECT': 'object' }; const VIDEO_CUSTOM_PARAMS = { 'mimes': DATA_TYPES.ARRAY, @@ -43,6 +47,117 @@ const VIDEO_CUSTOM_PARAMS = { 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER } + +const NATIVE_ASSET_ID = { + 'TITLE': 1, + 'IMAGE': 2, + 'ICON': 3, + 'SPONSOREDBY': 4, + 'BODY': 5, + 'CLICKURL': 6, + 'VIDEO': 7, + 'EXT': 8, + 'DATA': 9, + 'LOGO': 10, + 'SPONSORED': 11, + 'DESC': 12, + 'RATING': 13, + 'LIKES': 14, + 'DOWNLOADS': 15, + 'PRICE': 16, + 'SALEPRICE': 17, + 'PHONE': 18, + 'ADDRESS': 19, + 'DESC2': 20, + 'DISPLAYURL': 21, + 'CTA': 22 +} + +const NATIVE_ASSET_REVERSE_ID = { + 4: 'sponsoredBy', + 5: 'body', + 6: 'clickUrl', + 7: 'video', + 8: 'ext', + 9: 'data', + 10: 'logo', + 11: 'sponsored', + 12: 'desc', + 13: 'rating', + 14: 'likes', + 15: 'downloads', + 16: 'price', + 17: 'saleprice', + 18: 'phone', + 19: 'address', + 20: 'desc2', + 21: 'displayurl', + 22: 'cta' +} + +const NATIVE_ASSET_KEY = { + 'TITLE': 'title', + 'IMAGE': 'image', + 'ICON': 'icon', + 'SPONSOREDBY': 'sponsoredBy', + 'BODY': 'body', + 'VIDEO': 'video', + 'EXT': 'ext', + 'DATA': 'data', + 'LOGO': 'logo', + 'DESC': 'desc', + 'RATING': 'rating', + 'LIKES': 'likes', + 'DOWNLOADS': 'downloads', + 'PRICE': 'price', + 'SALEPRICE': 'saleprice', + 'PHONE': 'phone', + 'ADDRESS': 'address', + 'DESC2': 'desc2', + 'DISPLAYURL': 'displayurl', + 'CTA': 'cta' +} + +const NATIVE_ASSET_IMAGE_TYPE = { + 'ICON': 1, + 'LOGO': 2, + 'IMAGE': 3 +} + +const NATIVE_ASSET_DATA_TYPE = { + 'SPONSORED': 1, + 'DESC': 2, + 'RATING': 3, + 'LIKES': 4, + 'DOWNLOADS': 5, + 'PRICE': 6, + 'SALEPRICE': 7, + 'PHONE': 8, + 'ADDRESS': 9, + 'DESC2': 10, + 'DISPLAYURL': 11, + 'CTA': 12 +} + +// check if title, image can be added with mandatory field default values +const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ + { + id: NATIVE_ASSET_ID.SPONSOREDBY, + required: true, + data: { + type: 1 + } + }, + { + id: NATIVE_ASSET_ID.TITLE, + required: true, + }, + { + id: NATIVE_ASSET_ID.IMAGE, + required: true, + } +] + const NET_REVENUE = false; const dealChannelValues = { 1: 'PMP', @@ -51,6 +166,7 @@ const dealChannelValues = { }; let publisherId = 0; +let isInvalidNativeRequest = false; function _getDomainFromURL(url) { let anchor = document.createElement('a'); @@ -126,10 +242,14 @@ function _parseAdSlot(bid) { } } -function _initConf() { +function _initConf(refererInfo) { var conf = {}; conf.pageURL = utils.getTopWindowUrl(); - conf.refURL = utils.getTopWindowReferrer(); + if (refererInfo && refererInfo.referer) { + conf.refURL = refererInfo.referer; + } else { + conf.refURL = ''; + } return conf; } @@ -217,6 +337,256 @@ function _checkParamDataType(key, value, datatype) { } } +function _createNativeRequest(params) { + var nativeRequestObject = { + assets: [] + }; + for (var key in params) { + if (params.hasOwnProperty(key)) { + var assetObj = {}; + if (!(nativeRequestObject.assets && nativeRequestObject.assets.length > 0 && nativeRequestObject.assets.hasOwnProperty(key))) { + switch (key) { + case NATIVE_ASSET_KEY.TITLE: + if (params[key].len || params[key].length) { + assetObj = { + id: NATIVE_ASSET_ID.TITLE, + required: params[key].required ? 1 : 0, + title: { + len: params[key].len || params[key].length, + ext: params[key].ext + } + }; + } else { + utils.logWarn(BIDDER_CODE + ' Error: Title Length is required for native ad: ' + JSON.stringify(params)); + } + break; + case NATIVE_ASSET_KEY.IMAGE: + if (params[key].sizes && params[key].sizes.length > 0) { + assetObj = { + id: NATIVE_ASSET_ID.IMAGE, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.IMAGE, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : undefined), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : undefined), + wmin: params[key].wmin || params[key].minimumWidth || (params[key].minsizes ? params[key].minsizes[0] : undefined), + hmin: params[key].hmin || params[key].minimumHeight || (params[key].minsizes ? params[key].minsizes[1] : undefined), + mimes: params[key].mimes, + ext: params[key].ext, + } + }; + } else { + // Log Warn + utils.logWarn(BIDDER_CODE + ' Error: Image sizes is required for native ad: ' + JSON.stringify(params)); + } + break; + case NATIVE_ASSET_KEY.ICON: + if (params[key].sizes && params[key].sizes.length > 0) { + assetObj = { + id: NATIVE_ASSET_ID.ICON, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.ICON, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : undefined), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : undefined), + } + }; + } else { + // Log Warn + utils.logWarn(BIDDER_CODE + ' Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); + }; + break; + case NATIVE_ASSET_KEY.SPONSOREDBY: + assetObj = { + id: NATIVE_ASSET_ID.SPONSOREDBY, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.SPONSORED, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.BODY: + assetObj = { + id: NATIVE_ASSET_ID.BODY, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.DESC, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.VIDEO: + assetObj = { + id: NATIVE_ASSET_ID.VIDEO, + required: params[key].required ? 1 : 0, + video: { + minduration: params[key].minduration, + maxduration: params[key].maxduration, + protocols: params[key].protocols, + mimes: params[key].mimes, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.EXT: + assetObj = { + id: NATIVE_ASSET_ID.EXT, + required: params[key].required ? 1 : 0, + }; + break; + case NATIVE_ASSET_KEY.LOGO: + assetObj = { + id: NATIVE_ASSET_ID.LOGO, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.LOGO, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : undefined), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : undefined) + } + }; + break; + case NATIVE_ASSET_KEY.RATING: + assetObj = { + id: NATIVE_ASSET_ID.RATING, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.RATING, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.LIKES: + assetObj = { + id: NATIVE_ASSET_ID.LIKES, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.LIKES, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.DOWNLOADS: + assetObj = { + id: NATIVE_ASSET_ID.DOWNLOADS, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.DOWNLOADS, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.PRICE: + assetObj = { + id: NATIVE_ASSET_ID.PRICE, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.PRICE, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.SALEPRICE: + assetObj = { + id: NATIVE_ASSET_ID.SALEPRICE, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.SALEPRICE, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.PHONE: + assetObj = { + id: NATIVE_ASSET_ID.PHONE, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.PHONE, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.ADDRESS: + assetObj = { + id: NATIVE_ASSET_ID.ADDRESS, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.ADDRESS, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.DESC2: + assetObj = { + id: NATIVE_ASSET_ID.DESC2, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.DESC2, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.DISPLAYURL: + assetObj = { + id: NATIVE_ASSET_ID.DISPLAYURL, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.DISPLAYURL, + len: params[key].len, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSET_KEY.CTA: + assetObj = { + id: NATIVE_ASSET_ID.CTA, + required: params[key].required ? 1 : 0, + data: { + type: NATIVE_ASSET_DATA_TYPE.CTA, + len: params[key].len, + ext: params[key].ext + } + }; + break; + } + } + } + if (assetObj && assetObj.id) { + nativeRequestObject.assets[nativeRequestObject.assets.length] = assetObj; + } + }; + + // for native image adtype prebid has to have few required assests i.e. title,sponsoredBy, image + // if any of these are missing from the request then request will not be sent + var requiredAssetCount = NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.length; + var presentrequiredAssetCount = 0; + NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.forEach(ele => { + var lengthOfExistingAssets = nativeRequestObject.assets.length; + for (var i = 0; i < lengthOfExistingAssets; i++) { + if (ele.id == nativeRequestObject.assets[i].id) { + presentrequiredAssetCount++; + break; + } + } + }); + if (requiredAssetCount == presentrequiredAssetCount) { + isInvalidNativeRequest = false; + } else { + isInvalidNativeRequest = true; + } + return nativeRequestObject; +} + function _createImpressionObject(bid, conf) { var impObj = {}; var bannerObj = {}; @@ -257,6 +627,9 @@ function _createImpressionObject(bid, conf) { } impObj.video = videoObj; + } else if (bid.nativeParams) { + impObj.native = {}; + impObj.native['request'] = JSON.stringify(_createNativeRequest(bid.nativeParams)); } else { bannerObj = { pos: 0, @@ -268,12 +641,19 @@ function _createImpressionObject(bid, conf) { sizes = sizes.splice(1, sizes.length - 1); var format = []; sizes.forEach(size => { - format.push({w: size[0], h: size[1]}); + format.push({ + w: size[0], + h: size[1] + }); }); bannerObj.format = format; } impObj.banner = bannerObj; } + if (isInvalidNativeRequest && impObj.hasOwnProperty('native')) { + utils.logWarn(BIDDER_CODE + ': Call to OpenBid will not be sent for native ad unit as it does not contain required valid native params.' + JSON.stringify(bid) + ' Refer:' + PREBID_NATIVE_HELP_LINK); + return; + } return impObj; } @@ -295,15 +675,13 @@ function _handleDigitrustId(eids) { if (digiTrustId !== null) { eids.push({ 'source': 'digitru.st', - 'uids': [ - { - 'id': digiTrustId.id || '', - 'atype': 1, - 'ext': { - 'keyv': parseInt(digiTrustId.keyv) || 0 - } + 'uids': [{ + 'id': digiTrustId.id || '', + 'atype': 1, + 'ext': { + 'keyv': parseInt(digiTrustId.keyv) || 0 } - ] + }] }); } } @@ -313,15 +691,13 @@ function _handleTTDId(eids) { if (adsrvrOrgId && utils.isStr(adsrvrOrgId.TDID)) { eids.push({ 'source': 'adserver.org', - 'uids': [ - { - 'id': adsrvrOrgId.TDID, - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } + 'uids': [{ + 'id': adsrvrOrgId.TDID, + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' } - ] + }] }); } } @@ -335,9 +711,71 @@ function _handleEids(payload) { } } +function _parseNativeResponse(bid, newBid) { + newBid.native = {}; + if (bid.hasOwnProperty('adm')) { + var adm = ''; + try { + adm = JSON.parse(bid.adm.replace(/\\/g, '')); + } catch (ex) { + utils.logWarn(BIDDER_CODE + ' Error: Cannot parse native reponse for ad response: ' + newBid.adm); + return; + } + if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { + newBid.mediaType = 'native'; + for (let i = 0, len = adm.native.assets.length; i < len; i++) { + switch (adm.native.assets[i].id) { + case NATIVE_ASSET_ID.TITLE: + newBid.native.title = adm.native.assets[i].title && adm.native.assets[i].title.text; + break; + case NATIVE_ASSET_ID.IMAGE: + newBid.native.image = { + url: adm.native.assets[i].img && adm.native.assets[i].img.url, + height: adm.native.assets[i].img && adm.native.assets[i].img.h, + width: adm.native.assets[i].img && adm.native.assets[i].img.w, + }; + break; + case NATIVE_ASSET_ID.ICON: + newBid.native.icon = { + url: adm.native.assets[i].img && adm.native.assets[i].img.url, + height: adm.native.assets[i].img && adm.native.assets[i].img.h, + width: adm.native.assets[i].img && adm.native.assets[i].img.w, + }; + break; + case NATIVE_ASSET_ID.SPONSOREDBY: + case NATIVE_ASSET_ID.BODY: + case NATIVE_ASSET_ID.LIKES: + case NATIVE_ASSET_ID.DOWNLOADS: + case NATIVE_ASSET_ID.PRICE: + case NATIVE_ASSET_ID.SALEPRICE: + case NATIVE_ASSET_ID.PHONE: + case NATIVE_ASSET_ID.ADDRESS: + case NATIVE_ASSET_ID.DESC2: + case NATIVE_ASSET_ID.CTA: + case NATIVE_ASSET_ID.RATING: + case NATIVE_ASSET_ID.DISPLAYURL: + // Remove Redundant code + newBid.native[NATIVE_ASSET_REVERSE_ID[adm.native.assets[i].id]] = adm.native.assets[i].data && adm.native.assets[i].data.value; + break; + } + } + newBid.native.clickUrl = adm.native.link && adm.native.link.url; + newBid.native.clickTrackers = (adm.native.link && adm.native.link.clicktrackers) || []; + newBid.native.impressionTrackers = adm.native.imptrackers || []; + newBid.native.jstracker = adm.native.jstracker || []; + if (!newBid.width) { + newBid.width = DEFAULT_WIDTH; + } + if (!newBid.height) { + newBid.height = DEFAULT_HEIGHT; + } + } + } +} + export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid * @@ -347,17 +785,17 @@ export const spec = { isBidRequestValid: bid => { if (bid && bid.params) { if (!utils.isStr(bid.params.publisherId)) { - utils.logWarn(BIDDER_CODE + ' Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent.'); + utils.logWarn(BIDDER_CODE + ' Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } if (!utils.isStr(bid.params.adSlot)) { - utils.logWarn(BIDDER_CODE + ': adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent.'); + utils.logWarn(BIDDER_CODE + ': adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } // video ad validation if (bid.params.hasOwnProperty('video')) { if (!bid.params.video.hasOwnProperty('mimes') || !utils.isArray(bid.params.video.mimes) || bid.params.video.mimes.length === 0) { - utils.logWarn(BIDDER_CODE + ': For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent.'); + utils.logWarn(BIDDER_CODE + ': For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); return false; } } @@ -367,13 +805,17 @@ export const spec = { }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: (validBidRequests, bidderRequest) => { - var conf = _initConf(); + var refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + var conf = _initConf(refererInfo); var payload = _createOrtbTemplate(conf); var bidCurrency = ''; var dctr = ''; @@ -385,12 +827,12 @@ export const spec = { _parseAdSlot(bid); if (bid.params.hasOwnProperty('video')) { if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex)) { - utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, bid); + utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); return; } } else { if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex && bid.params.width && bid.params.height)) { - utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, bid); + utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); return; } } @@ -407,7 +849,10 @@ export const spec = { if (bid.params.hasOwnProperty('dctr') && utils.isStr(bid.params.dctr)) { dctrArr.push(bid.params.dctr); } - payload.imp.push(_createImpressionObject(bid, conf)); + var impObj = _createImpressionObject(bid, conf); + if (impObj) { + payload.imp.push(impObj); + } }); if (payload.imp.length == 0) { @@ -477,7 +922,6 @@ export const spec = { } _handleEids(payload); - return { method: 'POST', url: ENDPOINT, @@ -486,11 +930,11 @@ export const spec = { }, /** - * Unpack the response from the server into a list of bids. - * - * @param {*} response A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {*} response A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: (response, request) => { const bidResponses = []; var respCur = DEFAULT_CURRENCY; @@ -500,38 +944,41 @@ export const spec = { respCur = response.body.cur || respCur; response.body.seatbid.forEach(seatbidder => { seatbidder.bid && - utils.isArray(seatbidder.bid) && - seatbidder.bid.forEach(bid => { - let newBid = { - requestId: bid.impid, - cpm: (parseFloat(bid.price) || 0).toFixed(2), - width: bid.w, - height: bid.h, - creativeId: bid.crid || bid.id, - dealId: bid.dealid, - currency: respCur, - netRevenue: NET_REVENUE, - ttl: 300, - referrer: utils.getTopWindowUrl(), - ad: bid.adm - }; - let parsedRequest = JSON.parse(request.data); - if (parsedRequest.imp && parsedRequest.imp.length > 0) { - parsedRequest.imp.forEach(req => { - if (bid.impid === req.id && req.hasOwnProperty('video')) { - newBid.mediaType = 'video'; - newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; - newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; - newBid.vastXml = bid.adm; - } - }); - } - if (bid.ext && bid.ext.deal_channel) { - newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; - } + utils.isArray(seatbidder.bid) && + seatbidder.bid.forEach(bid => { + let parsedRequest = JSON.parse(request.data); + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid, + currency: respCur, + netRevenue: NET_REVENUE, + ttl: 300, + referrer: parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : '', + ad: bid.adm + }; + if (parsedRequest.imp && parsedRequest.imp.length > 0) { + parsedRequest.imp.forEach(req => { + if (bid.impid === req.id && req.hasOwnProperty('video')) { + newBid.mediaType = 'video'; + newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; + newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; + newBid.vastXml = bid.adm; + } + if (bid.impid === req.id && req.hasOwnProperty('native')) { + _parseNativeResponse(bid, newBid); + } + }); + } + if (bid.ext && bid.ext.deal_channel) { + newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; + } - bidResponses.push(newBid); - }); + bidResponses.push(newBid); + }); }); } } catch (error) { @@ -541,8 +988,8 @@ export const spec = { }, /** - * Register User Sync. - */ + * Register User Sync. + */ getUserSyncs: (syncOptions, responses, gdprConsent) => { let syncurl = USYNCURL + publisherId; @@ -568,7 +1015,7 @@ export const spec = { * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol * @return {Object} params bid params */ - transformBidParams: function(params, isOpenRtb) { + transformBidParams: function (params, isOpenRtb) { return utils.convertTypes({ 'publisherId': 'string', 'adSlot': 'string' diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index 3f28d4586e7..60c45bfd8a9 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -10,7 +10,7 @@ Maintainer: header-bidding@pubmatic.com Connects to PubMatic exchange for bids. -PubMatic bid adapter supports Video and Banner currently. +PubMatic bid adapter supports Video, Banner and Native currently. # Sample Banner Ad Unit: For Publishers ``` @@ -76,7 +76,40 @@ var adVideoAdUnits = [ }] ``` -### Configuration +# Sample Native Ad Unit: For Publishers +``` +var adUnits = [ +{ + code: 'test-div', + mediaTypes: { + native: { + image: { + required: true, + sizes: [150, 50] + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + } + } + }, + bids: [{ + bidder: 'pubmatic', + params: { + publisherId: '156295', // required + adSlot: 'pubmatic_test2@1x1', // required + } + }] +}]; +``` + +# ## Configuration PubMatic recommends the UserSync configuration below. Without it, the PubMatic adapter will not able to perform user syncs, which lowers match rate and reduces monetization. diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 05aaa191207..19996be06bf 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -9,6 +9,15 @@ describe('PubMatic adapter', function () { let videoBidRequests; let multipleMediaRequests; let bidResponses; + let nativeBidRequests; + let nativeBidRequestsWithAllParams; + let nativeBidRequestsWithoutAsset; + let nativeBidRequestsWithRequiredParam; + let nativeBidResponse; + let validnativeBidImpression; + let validnativeBidImpressionWithRequiredParam; + let nativeBidImpressionWithoutRequiredParams; + let validnativeBidImpressionWithAllParams; beforeEach(function () { bidRequests = [ @@ -124,6 +133,145 @@ describe('PubMatic adapter', function () { } ]; + nativeBidRequests = [{ + code: '/19968336/prebid_native_example_1', + sizes: [ + [300, 250] + ], + mediaTypes: { + native: { + title: { + required: true, + length: 80 + }, + image: { + required: true, + sizes: [300, 250] + }, + sponsoredBy: { + required: true + } + } + }, + nativeParams: { + title: { required: true, length: 80 }, + image: { required: true, sizes: [300, 250] }, + sponsoredBy: { required: true } + }, + bidder: 'pubmatic', + params: { + publisherId: '5670', + adSlot: '/43743431/NativeAutomationPrebid@1x1', + }, + bidId: '2a5571261281d4', + requestId: 'B68287E1-DC39-4B38-9790-FE4F179739D6', + bidderRequestId: '1c56ad30b9b8ca8', + }]; + + nativeBidRequestsWithAllParams = [{ + code: '/19968336/prebid_native_example_1', + sizes: [ + [300, 250] + ], + mediaTypes: { + native: { + title: {required: true, len: 80, ext: {'title1': 'title2'}}, + icon: {required: true, sizes: [50, 50], ext: {'icon1': 'icon2'}}, + image: {required: true, sizes: [728, 90], ext: {'image1': 'image2'}, 'mimes': ['image/png', 'image/gif']}, + sponsoredBy: {required: true, len: 10, ext: {'sponsor1': 'sponsor2'}}, + body: {required: true, len: 10, ext: {'body1': 'body2'}}, + rating: {required: true, len: 10, ext: {'rating1': 'rating2'}}, + likes: {required: true, len: 10, ext: {'likes1': 'likes2'}}, + downloads: {required: true, len: 10, ext: {'downloads1': 'downloads2'}}, + price: {required: true, len: 10, ext: {'price1': 'price2'}}, + saleprice: {required: true, len: 10, ext: {'saleprice1': 'saleprice2'}}, + phone: {required: true, len: 10, ext: {'phone1': 'phone2'}}, + address: {required: true, len: 10, ext: {'address1': 'address2'}}, + desc2: {required: true, len: 10, ext: {'desc21': 'desc22'}}, + displayurl: {required: true, len: 10, ext: {'displayurl1': 'displayurl2'}} + } + }, + nativeParams: { + title: {required: true, len: 80, ext: {'title1': 'title2'}}, + icon: {required: true, sizes: [50, 50], ext: {'icon1': 'icon2'}}, + image: {required: true, sizes: [728, 90], ext: {'image1': 'image2'}, 'mimes': ['image/png', 'image/gif']}, + sponsoredBy: {required: true, len: 10, ext: {'sponsor1': 'sponsor2'}}, + body: {required: true, len: 10, ext: {'body1': 'body2'}}, + rating: {required: true, len: 10, ext: {'rating1': 'rating2'}}, + likes: {required: true, len: 10, ext: {'likes1': 'likes2'}}, + downloads: {required: true, len: 10, ext: {'downloads1': 'downloads2'}}, + price: {required: true, len: 10, ext: {'price1': 'price2'}}, + saleprice: {required: true, len: 10, ext: {'saleprice1': 'saleprice2'}}, + phone: {required: true, len: 10, ext: {'phone1': 'phone2'}}, + address: {required: true, len: 10, ext: {'address1': 'address2'}}, + desc2: {required: true, len: 10, ext: {'desc21': 'desc22'}}, + displayurl: {required: true, len: 10, ext: {'displayurl1': 'displayurl2'}} + }, + bidder: 'pubmatic', + params: { + publisherId: '5670', + adSlot: '/43743431/NativeAutomationPrebid@1x1', + }, + bidId: '2a5571261281d4', + requestId: 'B68287E1-DC39-4B38-9790-FE4F179739D6', + bidderRequestId: '1c56ad30b9b8ca8', + }]; + + nativeBidRequestsWithoutAsset = [{ + code: '/19968336/prebid_native_example_1', + sizes: [ + [300, 250] + ], + mediaTypes: { + native: { + type: 'image' + } + }, + nativeParams: { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + }, + bidder: 'pubmatic', + params: { + publisherId: '5670', + adSlot: '/43743431/NativeAutomationPrebid@1x1', + } + }]; + + nativeBidRequestsWithRequiredParam = [{ + code: '/19968336/prebid_native_example_1', + sizes: [ + [300, 250] + ], + mediaTypes: { + native: { + title: { + required: false, + length: 80 + }, + image: { + required: false, + sizes: [300, 250] + }, + sponsoredBy: { + required: true + } + } + }, + nativeParams: { + title: { required: false, length: 80 }, + image: { required: false, sizes: [300, 250] }, + sponsoredBy: { required: true } + }, + bidder: 'pubmatic', + params: { + publisherId: '5670', + adSlot: '/43743431/NativeAutomationPrebid@1x1', + } + }]; + bidResponses = { 'body': { 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', @@ -154,6 +302,52 @@ describe('PubMatic adapter', function () { }] } }; + + nativeBidResponse = { + 'body': { + 'id': '1544691825939', + 'seatbid': [{ + 'bid': [{ + 'id': 'B68287E1-DC39-4B38-9790-FE4F179739D6', + 'impid': '2a5571261281d4', + 'price': 0.01, + 'adm': "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Native Test Title\"}},{\"id\":2,\"img\":{\"h\":627,\"url\":\"http://stagingpub.net/native_ads/PM-Native-Ad-1200x627.png\",\"w\":1200}},{\"data\":{\"value\":\"Sponsored By PubMatic\"},\"id\":4}],\"imptrackers\":[\"http://imptracker.com/main/9bde02d0-6017-11e4-9df7-005056967c35\",\"http://172.16.4.213/AdServer/AdDisplayTrackerServlet?operId=1&pubId=5890&siteId=5892&adId=6016&adType=12&adServerId=243&kefact=0.010000&kaxefact=0.010000&kadNetFrequecy=0&kadwidth=0&kadheight=0&kadsizeid=7&kltstamp=1544692761&indirectAdId=0&adServerOptimizerId=2&ranreq=0.1&kpbmtpfact=1.000000&dcId=1&tldId=0&passback=0&svr=MADS1107&ekefact=GSQSXOLKDgBAvRnoiNj0LxtpAnNEO30u1ZI5sITloOsP7gzh&ekaxefact=GSQSXAXLDgD0fOZLCjgbnVJiyS3D65dqDkxfs2ArpC3iugXw&ekpbmtpfact=GSQSXCDLDgB5mcooOvXtCKmx7TnNDJDY2YuHFOL3o9ceoU4H&crID=campaign111&lpu=advertiserdomain.com&ucrid=273354366805642829&campaignId=16981&creativeId=0&pctr=0.000000&wDSPByrId=511&wDspId=6&wbId=0&wrId=0&wAdvID=1&isRTB=1&rtbId=C09BB577-B8C1-4C3E-A0FF-73F6F631C80A&imprId=B68287E1-DC39-4B38-9790-FE4F179739D6&oid=B68287E1-DC39-4B38-9790-FE4F179739D6&pageURL=http%3A%2F%2Ftest.com%2FTestPages%2Fnativead.html\"],\"jstracker\":\"