From 3098b96e32b9ec449bba98fdc1122f7761ce5093 Mon Sep 17 00:00:00 2001 From: "Roseller M. Velicaria, Jr" Date: Mon, 24 Feb 2020 11:46:57 -0800 Subject: [PATCH 1/7] initial commit --- modules/openxRtbBidAdapter.js | 263 ++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 modules/openxRtbBidAdapter.js diff --git a/modules/openxRtbBidAdapter.js b/modules/openxRtbBidAdapter.js new file mode 100644 index 00000000000..dc5681b1833 --- /dev/null +++ b/modules/openxRtbBidAdapter.js @@ -0,0 +1,263 @@ +import { config } from 'src/config'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import {BANNER, VIDEO} from '../src/mediaTypes'; +import * as utils from '../src/utils'; + +const bidderConfig = 'hb_pb_rtb'; +const bidderVersion = '3.x.x'; + +export const spec = { + code: 'openxrtb', + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); + +/** + * from openxBidAdapter + * ported for feature parity + * @param bidRequest + * @return {boolean} + */ +function isBidRequestValid(bidRequest) { + const hasDelDomainOrPlatform = bidRequest.params.delDomain + || bidRequest.params.platform; + + if (utils.deepAccess(bidRequest, 'mediaTypes.banner') + && hasDelDomainOrPlatform) { + return !!bidRequest.params.unit + || utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; + } + + return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +} + +function buildRequests(validBidRequests, bidderRequest) { + const hasBids = bidderRequest.bids.length > 0; + const transactionID = hasBids ? bidderRequest.bids[0].transactionId : null; + if (!hasBids || !transactionID) { + return []; + } + + const bc = bidderRequest.bids[0].params.bc || bidderConfig; + const delDomain = bidderRequest.bids[0].params.delDomain || null; + const platformId = bidderRequest.bids[0].params.platform || null; + const oxDefaultBidRespTTLSecs = 300; + const configPageUrl = config.getConfig('pageUrl'); + const commonImpFieldsMap = getCommonImpFieldsMap(bidderRequest, + delDomain, platformId); + const maybeDoNotTrack = () => !window.navigator.doNotTrack + ? {} + : {dnt: window.navigator.doNotTrack}; + const maybePlatformIdOrDelDomain = ((delDomain, platId) => { + let fields = {}; + if (platId) { + fields = {...fields, platformId: platId}; + } + if (delDomain) { + fields = {...fields, delDomain}; + } + return fields; + }); + console.log('bidder request', bidderRequest); + const data = { + id: bidderRequest.auctionId, + test: config.getConfig('debug') ? 1 : 0, + cur: ['USD'], + at: 1, // (1: first-price-, 2: second-price-) auction + tmax: (config.getConfig('ttl') || oxDefaultBidRespTTLSecs) * 1000, + site: { + domain: configPageUrl || utils.getWindowTop().location.hostname, + page: configPageUrl + || bidderRequest.refererInfo.canonicalUrl + || bidderRequest.refererInfo.referer, + ref: bidderRequest.refererInfo.referer, + }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + }, + ext: { + ...maybePlatformIdOrDelDomain(delDomain, platformId), + bc, + }, + imp: getImps(validBidRequests, commonImpFieldsMap), + device: { + ...maybeDoNotTrack(), + ua: window.navigator.userAgent, + language: window.navigator.language.split('-').shift(), + }, + }; + return [{ + method: 'POST', + url: 'https://rtb.openx.net/openrtb/prebid', + data, + }]; +} + +/** + * converts any valid bid request to an impression field + * see: http://prebid.org/dev-docs/bidder-adaptor.html#bidrequest-parameters + * @param validBidRequests + * @param commonImpFieldsMap + * @return openRTB imp[] + */ +function getImps(validBidRequests, commonImpFieldsMap) { + const maybeImpExt = customParams => customParams ? {ext: {customParams}} : {}; + const maybeImpRegs = regs => Object.keys(regs.ext).length > 0 ? {regs} : null; + const maybeImpUser = user => Object.keys(user.ext).length > 0 ? {user} : null; + + return validBidRequests.map(bidRequest => ({ + id: bidRequest.transactionId, + tagid: bidRequest.params.unit, + bidfloor: bidRequest.params.customFloor || 0, //default bidfloorcurrency is USD + ...getBannerImp(bidRequest), + ...getVideoImp(bidRequest), + ...maybeImpRegs(commonImpFieldsMap.regs), + ...maybeImpUser(commonImpFieldsMap.user), + ...maybeImpExt(bidRequest.params.customParams), + })); +} + +function getBannerImp(bidRequest) { + if (!bidRequest.mediaTypes.banner) { + return null; + } + // each size element is of the format [w, h] + // mediaTypeSizes is an array of size elements, e.g. [[w, h], [w, h], ...] + const toBannerImpFormatArray = mediaTypeSizes => + mediaTypeSizes.map(([w, h]) => ({w, h})); + return { + banner: { + id: bidRequest.bidId, + topframe: utils.inIframe() ? 1 : 0, + format: toBannerImpFormatArray(bidRequest.mediaTypes.banner.sizes), + }, + }; +} + +/** + * for the openrtb param, see: https://docs.openx.com/Content/developers/containers/prebid-video-adapter.html + * @param bidRequest + * @return {null|{video: {w: *, h: *, id: number}}} + */ +function getVideoImp(bidRequest) { + if (!bidRequest.mediaTypes.video) { + return null; + } + if (bidRequest.params.openrtb) { + return { + video: {...bidRequest.params.openrtb, id: 1}, + }; + } + const [w, h] = bidRequest.mediaTypes.video.playerSize[0]; + return { + video: { + id: bidRequest.bidId, + w, + h, + }, + }; +} + +/** + * typical fields are gdpr, usp and maybe global hb_pb settings + * @param bidderRequest see auction.js:L30 + * @param delDomain string? + * @param platformId string? + * @return {{ext: {customParams: ?}, regs: {ext: {us_privacy: string, gdpr: boolean}}, user: {ext: {consent: string}}}} + */ +function getCommonImpFieldsMap(bidderRequest, delDomain, platformId) { + const doesGdprApply = utils.deepAccess(bidderRequest, + 'gdprConsent.gdprApplies', null); + const maybeEmptyGdprConsentString = utils.deepAccess(bidderRequest, + 'gdprConsent.consentString', null); + + const stripNullVals = map => + Object.entries(map) + .filter(([key, val]) => null !== val) // filter out null values + // convert the rest back to fields + .reduce((newMap, [key, val]) => ({ + ...newMap, + [key]: typeof val !== 'object' ? val : stripNullVals(val), + }), {}); + + return stripNullVals({ + regs: { + ext: { + gdpr: doesGdprApply !== null ? !!doesGdprApply : null, + us_privacy: bidderRequest.uspConsent || null, + }, + }, + user: { + ext: { + consent: maybeEmptyGdprConsentString, + }, + }, + }); +} + +function interpretResponse(resp, req) { + console.log('interpretating response', req, resp); + const respBody = resp.body; + if ('nbr' in respBody) { + return []; + } + const bids = utils.deepAccess(respBody, 'seatbid.bids', []); + + return bids.map(bid => ({ + requestId: bid.id, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + dealId: bid.dealid, + currency: respBody.cur || "USD", + netRevenue: true, // true? + // ttl: 360, // secs before the bid expires and become unusable + ad: bid.adm, + })); +} + +/** + * from openxBidAdapter + * ported for feature parity + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let url = utils.deepAccess(responses, '0.body.ads.pixels') || + utils.deepAccess(responses, '0.body.pixels') || + getDefaultSyncUrl(gdprConsent, uspConsent); + + return [{ + type: pixelType, + url: url + }]; + } +} + +function getDefaultSyncUrl(gdprConsent, uspConsent) { + let url = 'https://u.openx.net/w/1.0/pd'; + let queryParamStrings = []; + + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + + // CCPA + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + + return `${url}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}`; +} From 04699ca6dd401bf58f52257f30de1e07a2019afb Mon Sep 17 00:00:00 2001 From: "Roseller M. Velicaria, Jr" Date: Mon, 24 Feb 2020 15:44:53 -0800 Subject: [PATCH 2/7] adding custom header for content type, ignore interpretResponse section for now --- modules/openxRtbBidAdapter.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/openxRtbBidAdapter.js b/modules/openxRtbBidAdapter.js index dc5681b1833..789cd946679 100644 --- a/modules/openxRtbBidAdapter.js +++ b/modules/openxRtbBidAdapter.js @@ -87,6 +87,7 @@ function buildRequests(validBidRequests, bidderRequest) { imp: getImps(validBidRequests, commonImpFieldsMap), device: { ...maybeDoNotTrack(), + ip: "209.182.152.7", ua: window.navigator.userAgent, language: window.navigator.language.split('-').shift(), }, @@ -95,6 +96,9 @@ function buildRequests(validBidRequests, bidderRequest) { method: 'POST', url: 'https://rtb.openx.net/openrtb/prebid', data, + options: { + contentType: 'application/json', + } }]; } @@ -206,10 +210,10 @@ function interpretResponse(resp, req) { if ('nbr' in respBody) { return []; } - const bids = utils.deepAccess(respBody, 'seatbid.bids', []); + const bids = utils.deepAccess(respBody, 'seatbid[0].bids', []); return bids.map(bid => ({ - requestId: bid.id, + requestId: respBody.id, cpm: bid.price, width: bid.w, height: bid.h, From 168cb4b19350b00820b7a8845c4f96d87eb791b2 Mon Sep 17 00:00:00 2001 From: "Roseller M. Velicaria, Jr" Date: Tue, 25 Feb 2020 10:15:03 -0800 Subject: [PATCH 3/7] interpretResponse now returns valid bids --- modules/openxRtbBidAdapter.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/openxRtbBidAdapter.js b/modules/openxRtbBidAdapter.js index 789cd946679..1181a732e54 100644 --- a/modules/openxRtbBidAdapter.js +++ b/modules/openxRtbBidAdapter.js @@ -36,6 +36,7 @@ function isBidRequestValid(bidRequest) { return !!(bidRequest.params.unit && hasDelDomainOrPlatform); } +let impToBidIdMap = {}; function buildRequests(validBidRequests, bidderRequest) { const hasBids = bidderRequest.bids.length > 0; const transactionID = hasBids ? bidderRequest.bids[0].transactionId : null; @@ -63,7 +64,12 @@ function buildRequests(validBidRequests, bidderRequest) { } return fields; }); - console.log('bidder request', bidderRequest); + + // update imp to bid map with current request bids + impToBidIdMap = validBidRequests.reduce((impMap, bidRequest) => ({ + ...impMap, + [bidRequest.transactionId]: bidRequest.bidId, + }), {}); const data = { id: bidderRequest.auctionId, test: config.getConfig('debug') ? 1 : 0, @@ -205,15 +211,16 @@ function getCommonImpFieldsMap(bidderRequest, delDomain, platformId) { } function interpretResponse(resp, req) { - console.log('interpretating response', req, resp); + const oxSeatBidName = 'OpenX'; const respBody = resp.body; if ('nbr' in respBody) { return []; } - const bids = utils.deepAccess(respBody, 'seatbid[0].bids', []); + const oxSeatBid = respBody.seatbid + .find(seatbid => seatbid.seat === oxSeatBidName) || {bid: []}; - return bids.map(bid => ({ - requestId: respBody.id, + return oxSeatBid.bid.map(bid => ({ + requestId: impToBidIdMap[bid.impid], cpm: bid.price, width: bid.w, height: bid.h, @@ -221,7 +228,7 @@ function interpretResponse(resp, req) { dealId: bid.dealid, currency: respBody.cur || "USD", netRevenue: true, // true? - // ttl: 360, // secs before the bid expires and become unusable + ttl: 300, // secs before the bid expires and become unusable, from oxBidAdapter ad: bid.adm, })); } From 1caddcd4832bb03717d88cce57dfdbfc65d9255e Mon Sep 17 00:00:00 2001 From: "Roseller M. Velicaria, Jr" Date: Thu, 2 Apr 2020 11:30:05 -0700 Subject: [PATCH 4/7] changed urls to reflect supplier changes --- modules/openxRtbBidAdapter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/openxRtbBidAdapter.js b/modules/openxRtbBidAdapter.js index 1181a732e54..e6373911dfc 100644 --- a/modules/openxRtbBidAdapter.js +++ b/modules/openxRtbBidAdapter.js @@ -93,14 +93,13 @@ function buildRequests(validBidRequests, bidderRequest) { imp: getImps(validBidRequests, commonImpFieldsMap), device: { ...maybeDoNotTrack(), - ip: "209.182.152.7", ua: window.navigator.userAgent, language: window.navigator.language.split('-').shift(), }, }; return [{ method: 'POST', - url: 'https://rtb.openx.net/openrtb/prebid', + url: 'https://rtb.openx.net/openrtbb/prebidjs', data, options: { contentType: 'application/json', From 4e153b48710e6d03ed27efa51431374b2e725f67 Mon Sep 17 00:00:00 2001 From: "Roseller M. Velicaria, Jr" Date: Tue, 7 Apr 2020 12:02:16 -0700 Subject: [PATCH 5/7] - removed test option; debuggers would still want valid bids - tmax is now set to pbjs.bidderTimeout - ttl rewritten to use constant instead of magic number --- modules/openxRtbBidAdapter.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/openxRtbBidAdapter.js b/modules/openxRtbBidAdapter.js index e6373911dfc..63c22b4a0ed 100644 --- a/modules/openxRtbBidAdapter.js +++ b/modules/openxRtbBidAdapter.js @@ -47,7 +47,6 @@ function buildRequests(validBidRequests, bidderRequest) { const bc = bidderRequest.bids[0].params.bc || bidderConfig; const delDomain = bidderRequest.bids[0].params.delDomain || null; const platformId = bidderRequest.bids[0].params.platform || null; - const oxDefaultBidRespTTLSecs = 300; const configPageUrl = config.getConfig('pageUrl'); const commonImpFieldsMap = getCommonImpFieldsMap(bidderRequest, delDomain, platformId); @@ -72,10 +71,9 @@ function buildRequests(validBidRequests, bidderRequest) { }), {}); const data = { id: bidderRequest.auctionId, - test: config.getConfig('debug') ? 1 : 0, cur: ['USD'], at: 1, // (1: first-price-, 2: second-price-) auction - tmax: (config.getConfig('ttl') || oxDefaultBidRespTTLSecs) * 1000, + tmax: config.getConfig('bidderTimeout'), // defaults to 3000msecs site: { domain: configPageUrl || utils.getWindowTop().location.hostname, page: configPageUrl @@ -211,6 +209,7 @@ function getCommonImpFieldsMap(bidderRequest, delDomain, platformId) { function interpretResponse(resp, req) { const oxSeatBidName = 'OpenX'; + const oxDefaultBidRespTTLSecs = 300; const respBody = resp.body; if ('nbr' in respBody) { return []; @@ -227,7 +226,7 @@ function interpretResponse(resp, req) { dealId: bid.dealid, currency: respBody.cur || "USD", netRevenue: true, // true? - ttl: 300, // secs before the bid expires and become unusable, from oxBidAdapter + ttl: oxDefaultBidRespTTLSecs, // secs before the bid expires and become unusable, from oxBidAdapter ad: bid.adm, })); } From 0a25469e2332336df1cb659cea60eb06d0c18e12 Mon Sep 17 00:00:00 2001 From: "Roseller M. Velicaria, Jr" Date: Tue, 14 Apr 2020 13:49:47 -0700 Subject: [PATCH 6/7] added user ID params to request --- modules/openxRtbBidAdapter.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/modules/openxRtbBidAdapter.js b/modules/openxRtbBidAdapter.js index 63c22b4a0ed..255d3f0d52e 100644 --- a/modules/openxRtbBidAdapter.js +++ b/modules/openxRtbBidAdapter.js @@ -81,6 +81,7 @@ function buildRequests(validBidRequests, bidderRequest) { || bidderRequest.refererInfo.referer, ref: bidderRequest.refererInfo.referer, }, + user: getUser(validBidRequests[0].userId, validBidRequests[0].userIdAsEids), regs: { coppa: config.getConfig('coppa') === true ? 1 : 0, }, @@ -207,6 +208,34 @@ function getCommonImpFieldsMap(bidderRequest, delDomain, platformId) { }); } +/** + * gets a userId field by parsing pbjs user id module enrichments + * @param userIdDataMap + * @param eids openrtb eids https://github.com/prebid/Prebid.js/blob/3.12.0/modules/userId/index.js#L280 + */ +function getUser(userIdDataMap, eids) { + if (!userIdDataMap) { + return {}; + } + + const maybeDigitrust = getMaybeDigitrustId(userIdDataMap); + return { + ext: { + eids, + ...maybeDigitrust, + } + }; + + function getMaybeDigitrustId(userIdDataMap) { + const maybeDigitrustData = userIdDataMap.digitrustid && userIdDataMap.digitrustid.data; + const {id, keyv} = maybeDigitrustData || {}; + if (!id) { + return null; + } + return {digitrust: {id, keyv,}}; + } +} + function interpretResponse(resp, req) { const oxSeatBidName = 'OpenX'; const oxDefaultBidRespTTLSecs = 300; From 475312795c82668085ed652f84a4096513d6d3f0 Mon Sep 17 00:00:00 2001 From: Harman Goei Date: Tue, 26 May 2020 16:08:07 -0700 Subject: [PATCH 7/7] fix(openxRtbBidAdapter): missing commit to fix bc param --- modules/openxRtbBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/openxRtbBidAdapter.js b/modules/openxRtbBidAdapter.js index 255d3f0d52e..78e98a873f4 100644 --- a/modules/openxRtbBidAdapter.js +++ b/modules/openxRtbBidAdapter.js @@ -44,7 +44,7 @@ function buildRequests(validBidRequests, bidderRequest) { return []; } - const bc = bidderRequest.bids[0].params.bc || bidderConfig; + const bc = bidderRequest.bids[0].params.bc || `${bidderConfig}_${bidderVersion}`; const delDomain = bidderRequest.bids[0].params.delDomain || null; const platformId = bidderRequest.bids[0].params.platform || null; const configPageUrl = config.getConfig('pageUrl'); @@ -299,3 +299,4 @@ function getDefaultSyncUrl(gdprConsent, uspConsent) { return `${url}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}`; } +