From 16772650f283f2bb74f0057db3ea29371c0c3368 Mon Sep 17 00:00:00 2001 From: pedrolopezmrf Date: Tue, 10 Mar 2020 10:41:16 +0100 Subject: [PATCH] update ixBidAdapter --- modules/ixBidAdapter.js | 377 ++++++++++++++++++++++++++-------------- 1 file changed, 246 insertions(+), 131 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index f33a1828091..18e9dc5bddb 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -1,36 +1,72 @@ -import * as utils from '../src/utils'; -import { BANNER } from '../src/mediaTypes'; -import { config } from '../src/config'; -import isInteger from 'core-js/library/fn/number/is-integer'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import find from 'core-js/library/fn/array/find.js'; +import isInteger from 'core-js/library/fn/number/is-integer.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'ix'; -const BANNER_SECURE_BID_URL = 'https://as-sec.casalemedia.com/cygnus'; -const SUPPORTED_AD_TYPES = [BANNER]; -const ENDPOINT_VERSION = 7.2; +const SECURE_BID_URL = 'https://as-sec.casalemedia.com/cygnus'; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BANNER_ENDPOINT_VERSION = 7.2; +const VIDEO_ENDPOINT_VERSION = 8.1; const CENT_TO_DOLLAR_FACTOR = 100; -const TIME_TO_LIVE = 35; +const BANNER_TIME_TO_LIVE = 35; +const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NET_REVENUE = true; const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; /** - * Transform valid bid request config object to impression object that will be sent to ad server. + * 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. * @return {object} A impression object that will be sent to ad server. */ function bidToBannerImp(bid) { - const imp = {}; - - imp.id = bid.bidId; + const imp = bidToImp(bid); imp.banner = {}; imp.banner.w = bid.params.size[0]; imp.banner.h = bid.params.size[1]; imp.banner.topframe = utils.inIframe() ? 0 : 1; + return imp; +} + +/** + * Transform valid bid request config object to video impression object that will be sent to ad server. + * + * @param {object} bid A valid bid request config object. + * @return {object} A impression object that will be sent to ad server. + */ +function bidToVideoImp(bid) { + const imp = bidToImp(bid); + + imp.video = utils.deepClone(bid.params.video) + imp.video.w = bid.params.size[0]; + imp.video.h = bid.params.size[1]; + + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + if (context) { + if (context === 'instream') { + imp.video.placement = 1; + } else if (context === 'outstream') { + imp.video.placement = 4; + } else { + utils.logWarn(`ix bidder params: video context '${context}' is not supported`); + } + } + + return imp; +} + +function bidToImp(bid) { + const imp = {}; + + imp.id = bid.bidId; + imp.ext = {}; imp.ext.siteID = bid.params.siteId; @@ -56,7 +92,7 @@ function bidToBannerImp(bid) { * @param {string} currency Global currency in bid response. * @return {object} bid The parsed bid. */ -function parseBid(rawBid, currency) { +function parseBid(rawBid, currency, bidRequest) { const bid = {}; if (PRICE_TO_DOLLAR_FACTOR.hasOwnProperty(currency)) { @@ -66,15 +102,27 @@ function parseBid(rawBid, currency) { } bid.requestId = rawBid.impid; - bid.width = rawBid.w; - bid.height = rawBid.h; - bid.ad = rawBid.adm; + bid.dealId = utils.deepAccess(rawBid, 'ext.dealid'); - bid.ttl = TIME_TO_LIVE; bid.netRevenue = NET_REVENUE; bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; + // in the event of a video + if (utils.deepAccess(rawBid, 'ext.vasturl')) { + bid.vastUrl = rawBid.ext.vasturl + bid.width = bidRequest.video.w; + bid.height = bidRequest.video.h; + bid.mediaType = VIDEO; + bid.ttl = VIDEO_TIME_TO_LIVE; + } else { + bid.ad = rawBid.adm; + bid.width = rawBid.w; + bid.height = rawBid.h; + bid.mediaType = BANNER; + bid.ttl = BANNER_TIME_TO_LIVE; + } + bid.meta = {}; bid.meta.networkId = utils.deepAccess(rawBid, 'ext.dspid'); bid.meta.brandId = utils.deepAccess(rawBid, 'ext.advbrandid'); @@ -131,6 +179,152 @@ function isValidBidFloorParams(bidFloor, bidFloorCur) { bidFloorCur.match(curRegex)); } +/** + * Finds the impression with the associated id. + * + * @param {*} id Id of the impression. + * @param {array} impressions List of impressions sent in the request. + * @return {object} The impression with the associated id. + */ +function getBidRequest(id, impressions) { + if (!id) { + return; + } + return find(impressions, imp => imp.id === id); +} + +/** + * Builds a request object to be sent to the ad server based on bid requests. + * + * @param {array} validBidRequests A list of valid bid request config objects. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {array} impressions List of impression objects describing the bids. + * @param {array} version Endpoint version denoting banner or video. + * @return {object} Info describing the request to the server. + * + */ +function buildRequest(validBidRequests, bidderRequest, impressions, version) { + const userEids = []; + + // Always use secure HTTPS protocol. + let baseUrl = SECURE_BID_URL; + + // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded + // and if the data for the partner exist + if (window.headertag && typeof window.headertag.getIdentityInfo === 'function') { + let identityInfo = window.headertag.getIdentityInfo(); + if (identityInfo && typeof identityInfo === 'object') { + for (const partnerName in identityInfo) { + if (identityInfo.hasOwnProperty(partnerName)) { + let response = identityInfo[partnerName]; + if (!response.responsePending && response.data && typeof response.data === 'object' && Object.keys(response.data).length) { + userEids.push(response.data); + } + } + } + } + } + const r = {}; + + // Since bidderRequestId are the same for different bid request, just use the first one. + r.id = validBidRequests[0].bidderRequestId; + + r.imp = impressions; + + r.site = {}; + r.ext = {}; + r.ext.source = 'prebid'; + + // if an schain is provided, send it along + if (validBidRequests[0].schain) { + r.source = { + ext: { + schain: validBidRequests[0].schain + } + }; + } + + if (userEids.length > 0) { + r.user = {}; + r.user.eids = userEids; + } + + if (document.referrer && document.referrer !== '') { + r.site.ref = document.referrer; + } + + // Apply GDPR information to the request if GDPR is enabled. + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent.hasOwnProperty('gdprApplies')) { + r.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 + } + }; + } + + if (gdprConsent.hasOwnProperty('consentString')) { + r.user = r.user || {}; + r.user.ext = { + consent: gdprConsent.consentString || '' + }; + } + } + + if (bidderRequest.uspConsent) { + utils.deepSetValue(r, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (bidderRequest.refererInfo) { + r.site.page = bidderRequest.refererInfo.referer; + } + } + + const payload = {}; + + // Parse additional runtime configs. + const otherIxConfig = config.getConfig('ix'); + if (otherIxConfig) { + // Append firstPartyData to r.site.page if firstPartyData exists. + if (typeof otherIxConfig.firstPartyData === 'object') { + const firstPartyData = otherIxConfig.firstPartyData; + let firstPartyString = '?'; + for (const key in firstPartyData) { + if (firstPartyData.hasOwnProperty(key)) { + firstPartyString += `${encodeURIComponent(key)}=${encodeURIComponent(firstPartyData[key])}&`; + } + } + firstPartyString = firstPartyString.slice(0, -1); + + r.site.page += firstPartyString; + } + + // Create t in payload if timeout is configured. + if (typeof otherIxConfig.timeout === 'number') { + payload.t = otherIxConfig.timeout; + } + } + + // Use the siteId in the first bid request as the main siteId. + payload.s = validBidRequests[0].params.siteId; + payload.v = version; + payload.r = JSON.stringify(r); + payload.ac = 'j'; + payload.sd = 1; + if (version === VIDEO_ENDPOINT_VERSION) { + payload.nf = 1; + } + + return { + method: 'GET', + url: baseUrl, + data: payload + }; +} + export const spec = { code: BIDDER_CODE, @@ -144,22 +338,25 @@ export const spec = { */ isBidRequestValid: function (bid) { if (!isValidSize(bid.params.size)) { + utils.logError('ix bidder params: bid size has invalid format.'); return false; } if (!includesSize(bid.sizes, bid.params.size)) { + utils.logError('ix bidder params: bid size is not included in ad unit sizes.'); return false; } - if (bid.hasOwnProperty('mediaType') && bid.mediaType !== 'banner') { + if (bid.hasOwnProperty('mediaType') && !(utils.contains(SUPPORTED_AD_TYPES, bid.mediaType))) { return false; } - if (bid.hasOwnProperty('mediaTypes') && !utils.deepAccess(bid, 'mediaTypes.banner.sizes')) { + if (bid.hasOwnProperty('mediaTypes') && !(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || utils.deepAccess(bid, 'mediaTypes.video.playerSize'))) { return false; } if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { + utils.logError('ix bidder params: siteId must be string or number value.'); return false; } @@ -167,8 +364,10 @@ export const spec = { const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); if (hasBidFloor || hasBidFloorCur) { - return hasBidFloor && hasBidFloorCur && - isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur); + if (!(hasBidFloor && hasBidFloorCur && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur))) { + utils.logError('ix bidder params: bidFloor / bidFloorCur parameter has invalid format.'); + return false; + } } return true; @@ -178,137 +377,50 @@ export const spec = { * Make a server request from the list of BidRequests. * * @param {array} validBidRequests A list of valid bid request config objects. - * @param {object} options A object contains bids and other info like gdprConsent. + * @param {object} bidderRequest A object contains bids and other info like gdprConsent. * @return {object} Info describing the request to the server. */ - buildRequests: function (validBidRequests, options) { - const bannerImps = []; - const userEids = []; + buildRequests: function (validBidRequests, bidderRequest) { + let reqs = []; + let bannerImps = []; + let videoImps = []; let validBidRequest = null; - let bannerImp = null; - - // Always use secure HTTPS protocol. - let baseUrl = BANNER_SECURE_BID_URL; for (let i = 0; i < validBidRequests.length; i++) { validBidRequest = validBidRequests[i]; - // Transform the bid request based on the banner format. - bannerImp = bidToBannerImp(validBidRequest); - bannerImps.push(bannerImp); - } - - // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded - // and if the data for the partner exist - if (window.headertag && typeof window.headertag.getIdentityInfo === 'function') { - let identityInfo = window.headertag.getIdentityInfo(); - if (identityInfo && typeof identityInfo === 'object') { - for (const partnerName in identityInfo) { - if (identityInfo.hasOwnProperty(partnerName)) { - let response = identityInfo[partnerName]; - if (!response.responsePending && response.data && typeof response.data === 'object' && Object.keys(response.data).length) { - userEids.push(response.data); - } - } - } - } - } - const r = {}; - - // Since bidderRequestId are the same for different bid request, just use the first one. - r.id = validBidRequests[0].bidderRequestId; - - const page = validBidRequests[0].params.referrer; - - r.imp = bannerImps; - r.site = {}; - r.site.page = page || utils.getTopWindowUrl(); - r.ext = {}; - r.ext.source = 'prebid'; - if (userEids.length > 0) { - r.user = {}; - r.user.eids = userEids; - } - - if (document.referrer && document.referrer !== '') { - r.site.ref = document.referrer; - } - - r.site.ref = utils.getTopWindowReferrer(); - - // Apply GDPR information to the request if GDPR is enabled. - if (options) { - if (options.gdprConsent) { - const gdprConsent = options.gdprConsent; - - if (gdprConsent.hasOwnProperty('gdprApplies')) { - r.regs = { - ext: { - gdpr: gdprConsent.gdprApplies ? 1 : 0 - } - }; - } - - if (gdprConsent.hasOwnProperty('consentString')) { - r.user = r.user || {}; - r.user.ext = { - consent: gdprConsent.consentString || '' - }; + if (validBidRequest.mediaType === VIDEO || utils.deepAccess(validBidRequest, 'mediaTypes.video')) { + if (validBidRequest.mediaType === VIDEO || includesSize(validBidRequest.mediaTypes.video.playerSize, validBidRequest.params.size)) { + videoImps.push(bidToVideoImp(validBidRequest)); + } else { + utils.logError('Bid size is not included in video playerSize') } } - if (options.refererInfo) { - r.site.page = options.refererInfo.referer; + if (validBidRequest.mediaType === BANNER || utils.deepAccess(validBidRequest, 'mediaTypes.banner') || + (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { + bannerImps.push(bidToBannerImp(validBidRequest)); } } - const payload = {}; - - // Parse additional runtime configs. - const otherIxConfig = config.getConfig('ix'); - if (otherIxConfig) { - // Append firstPartyData to r.site.page if firstPartyData exists. - if (typeof otherIxConfig.firstPartyData === 'object') { - const firstPartyData = otherIxConfig.firstPartyData; - let firstPartyString = '?'; - for (const key in firstPartyData) { - if (firstPartyData.hasOwnProperty(key)) { - firstPartyString += `${encodeURIComponent(key)}=${encodeURIComponent(firstPartyData[key])}&`; - } - } - firstPartyString = firstPartyString.slice(0, -1); - - r.site.page += firstPartyString; - } - - // Create t in payload if timeout is configured. - if (typeof otherIxConfig.timeout === 'number') { - payload.t = otherIxConfig.timeout; - } + if (bannerImps.length > 0) { + reqs.push(buildRequest(validBidRequests, bidderRequest, bannerImps, BANNER_ENDPOINT_VERSION)); + } + if (videoImps.length > 0) { + reqs.push(buildRequest(validBidRequests, bidderRequest, videoImps, VIDEO_ENDPOINT_VERSION)); } - // Use the siteId in the first bid request as the main siteId. - payload.s = validBidRequests[0].params.siteId; - - payload.v = ENDPOINT_VERSION; - payload.r = JSON.stringify(r); - payload.ac = 'j'; - payload.sd = 1; - - return { - method: 'GET', - url: baseUrl, - data: payload - }; + return reqs; }, /** * Unpack the response from the server into a list of bids. * * @param {object} serverResponse A successful response from the server. + * @param {object} bidderRequest The bid request sent to the server. * @return {array} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse) { + interpretResponse: function (serverResponse, bidderRequest) { const bids = []; let bid = null; @@ -325,8 +437,11 @@ export const spec = { // Transform rawBid in bid response to the format that will be accepted by prebid. const innerBids = seatbid[i].bid; + let requestBid = JSON.parse(bidderRequest.data.r); + for (let j = 0; j < innerBids.length; j++) { - bid = parseBid(innerBids[j], responseBody.cur); + const bidRequest = getBidRequest(innerBids[j].impid, requestBid.imp); + bid = parseBid(innerBids[j], responseBody.cur, bidRequest); bids.push(bid); } }