From ab55073d787279fab845a695b085c3285f10c62d Mon Sep 17 00:00:00 2001 From: Tulio Duarte Date: Fri, 22 Nov 2024 17:53:43 -0300 Subject: [PATCH 1/9] add blue --- modules/blueBidAdapter.js | 300 ++++++++++++++++++++++++++++++++++++++ modules/blueBidAdapter.md | 27 ++++ 2 files changed, 327 insertions(+) create mode 100644 modules/blueBidAdapter.js create mode 100644 modules/blueBidAdapter.md diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js new file mode 100644 index 00000000000..0d7a606b60f --- /dev/null +++ b/modules/blueBidAdapter.js @@ -0,0 +1,300 @@ +import { deepAccess, deepSetValue, logError } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { ortb25Translator } from '../libraries/ortb2.5Translator/translator.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid + */ + +const BIDDER_CODE = 'blue'; +const GVLID = 620; +const CDB_ENDPOINT = 'https://bidder-us-east-1.getblue.io/engine/?src=prebid'; + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); +const TRANSLATOR = ortb25Translator(); + +/** + * Defines the generic oRTB converter and all customization functions. + */ +const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: 60, + }, + imp, + request, + bidResponse, + response, +}); + +/** + * Builds an impression object for the ORTB 2.5 request. + * + * @param {function} buildImp - The function for building an imp object. + * @param {Object} bidRequest - The bid request object. + * @param {Object} context - The context object. + * @returns {Object} The ORTB 2.5 imp object. + */ +function imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + const params = bidRequest.params; + + imp.tagid = bidRequest.adUnitCode; + deepSetValue(imp, 'ext', { + ...bidRequest.params.ext, + ...imp.ext, + rwdd: imp.rwdd, + floors: getFloors(bidRequest), + bidder: { + publishersubid: params?.publisherSubId, + zoneid: params?.zoneId, + uid: params?.uid, + }, + }); + + delete imp.rwdd; // oRTB 2.6 field moved to ext + + return imp; +} + +/** + * Builds a request object for the ORTB 2.5 request. + * + * @param {function} buildRequest - The function for building a request object. + * @param {Array} imps - An array of ORTB 2.5 impression objects. + * @param {Object} bidderRequest - The bidder request object. + * @param {Object} context - The context object. + * @returns {Object} The ORTB 2.5 request object. + */ +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + + // params.pubid should override publisher id + if (typeof context.publisherId !== 'undefined') { + if (typeof request.app !== 'undefined') { + deepSetValue(request, 'app.publisher.id', context.publisherId); + } else { + deepSetValue(request, 'site.publisher.id', context.publisherId); + } + } + + if (bidderRequest && bidderRequest.gdprConsent) { + deepSetValue( + request, + 'regs.ext.gdprversion', + bidderRequest.gdprConsent.apiVersion + ); + } + + // Translate 2.6 OpenRTB request into 2.5 OpenRTB request + request = TRANSLATOR(request); + + return request; +} + +/** + * Build bid from oRTB 2.5 bid. + * + * @param buildBidResponse + * @param bid + * @param context + * @returns {*} + */ +function bidResponse(buildBidResponse, bid, context) { + context.mediaType = deepAccess(bid, 'ext.mediatype'); + let bidResponse = buildBidResponse(bid, context); + + bidResponse.currency = deepAccess(bid, 'ext.cur'); + + if (typeof deepAccess(bid, 'ext.meta') !== 'undefined') { + deepSetValue(bidResponse, 'meta', { + ...bidResponse.meta, + ...bid.ext.meta, + }); + } + if (typeof deepAccess(bid, 'ext.paf.content_id') !== 'undefined') { + deepSetValue(bidResponse, 'meta.paf.content_id', bid.ext.paf.content_id); + } + + return bidResponse; +} + +/** + * Builds bid response from the oRTB 2.5 bid response. + * + * @param buildResponse + * @param bidResponses + * @param ortbResponse + * @param context + * @returns * + */ +function response(buildResponse, bidResponses, ortbResponse, context) { + let response = buildResponse(bidResponses, ortbResponse, context); + + const pafTransmission = deepAccess(ortbResponse, 'ext.paf.transmission'); + response.bids.forEach((bid) => { + if ( + typeof pafTransmission !== 'undefined' && + typeof deepAccess(bid, 'meta.paf.content_id') !== 'undefined' + ) { + deepSetValue(bid, 'meta.paf.transmission', pafTransmission); + } else { + delete bid.meta.paf; + } + }); + + return response; +} + +/** @type {BidderSpec} */ +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + /** + * f + * @param {object} bid + * @return {boolean} + */ + isBidRequestValid: (bid) => { + // either one of zoneId or networkId should be set + if (!(bid && bid.params && bid.params.publisherId)) { + return false; + } + + return true; + }, + + /** + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ + buildRequests: (bidRequests, bidderRequest) => { + const context = buildContext(bidRequests, bidderRequest); + const url = buildCdbUrl(context); + const data = CONVERTER.toORTB({ bidderRequest, bidRequests, context }); + + if (data) { + return { method: 'POST', url, data, bidRequests }; + } + }, + + /** + * @param {*} response + * @param {ServerRequest} request + * @return {Bid[] | {bids: Bid[], fledgeAuctionConfigs: object[]}} + */ + interpretResponse: (response, request) => { + if (typeof response?.body == 'undefined') { + return []; // no bid + } + + const interpretedResponse = CONVERTER.fromORTB({ + response: response.body, + request: request.data, + }); + const bids = interpretedResponse.bids || []; + + return bids; + }, +}; + +/** + * @param {BidRequest[]} bidRequests + * @param bidderRequest + */ +function buildContext(bidRequests, bidderRequest) { + return { + url: bidderRequest?.refererInfo?.page || '', + publisherId: bidRequests.find((bidRequest) => bidRequest.params?.pubid) + ?.params.pubid, + }; +} + +/** + * @param {Object} context + * @return {string} + */ +function buildCdbUrl(context) { + let url = CDB_ENDPOINT; + url += '&wv=' + encodeURIComponent('$prebid.version$'); + url += '&cb=' + String(Math.floor(Math.random() * 99999999999)); + + if (context.publisherId) { + url += `&publisherId=` + context.publisherId; + } + + return url; +} + +function parseSizes(sizes, parser = (s) => s) { + if (sizes == undefined) { + return []; + } + if (Array.isArray(sizes[0])) { + // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map((size) => parser(size)); + } + return [parser(sizes)]; // or a single one ? (ie. [728,90]) +} + +function parseSize(size) { + return size[0] + 'x' + size[1]; +} + +function pickAvailableGetFloorFunc(bidRequest) { + if (bidRequest.getFloor) { + return bidRequest.getFloor; + } + if (bidRequest.params.bidFloor && bidRequest.params.bidFloorCur) { + try { + const floor = parseFloat(bidRequest.params.bidFloor); + return () => { + return { + currency: bidRequest.params.bidFloorCur, + floor: floor, + }; + }; + } catch {} + } + return undefined; +} + +function getFloors(bidRequest) { + try { + const floors = {}; + + const getFloor = pickAvailableGetFloorFunc(bidRequest); + + if (getFloor) { + if (bidRequest.mediaTypes?.banner) { + floors.banner = {}; + const bannerSizes = parseSizes( + deepAccess(bidRequest, 'mediaTypes.banner.sizes') + ); + bannerSizes.forEach( + (bannerSize) => + (floors.banner[parseSize(bannerSize).toString()] = getFloor.call( + bidRequest, + { size: bannerSize, mediaType: BANNER } + )) + ); + } + + return floors; + } + } catch (e) { + logError('Could not parse floors from Prebid: ' + e); + } +} + +registerBidder(spec); diff --git a/modules/blueBidAdapter.md b/modules/blueBidAdapter.md new file mode 100644 index 00000000000..91edde8556a --- /dev/null +++ b/modules/blueBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +Module Name: Blue Bidder Adapter +Module Type: Bidder Adapter +Maintainer: celsooliveira@getblue.io + +# Description + +Module that connects to Blue's demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + sizes: [[300, 250], [728, 90]], + bids: [ + { + bidder: 'blue', + params: { + zoneId: 497747 + } + } + ] + } + ]; +``` From fb57fb14d42eceebfd330221b0032bc9f8c039ef Mon Sep 17 00:00:00 2001 From: Tulio Duarte Date: Wed, 27 Nov 2024 14:48:24 -0300 Subject: [PATCH 2/9] uncommited stuff --- integrationExamples/gpt/hello_world.html | 4 +- modules/blueBidAdapter.js | 12 +- test/spec/modules/blueBidAdapter_spec.js | 1253 ++++++++++++++++++++++ 3 files changed, 1266 insertions(+), 3 deletions(-) create mode 100755 test/spec/modules/blueBidAdapter_spec.js diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index 03a2356f0ef..cb9bb70ed33 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -26,9 +26,9 @@ // Replace this object to test a new Adapter! bids: [{ - bidder: 'appnexus', + bidder: 'blue', params: { - placementId: 13144370 + publisherId: 13144370 } }] diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js index 0d7a606b60f..ca43a9eb9fc 100644 --- a/modules/blueBidAdapter.js +++ b/modules/blueBidAdapter.js @@ -16,6 +16,7 @@ import { ortb25Translator } from '../libraries/ortb2.5Translator/translator.js'; const BIDDER_CODE = 'blue'; const GVLID = 620; const CDB_ENDPOINT = 'https://bidder-us-east-1.getblue.io/engine/?src=prebid'; +const BUNDLE_COOKIE_NAME = 'ckid'; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const TRANSLATOR = ortb25Translator(); @@ -180,9 +181,19 @@ export const spec = { */ buildRequests: (bidRequests, bidderRequest) => { const context = buildContext(bidRequests, bidderRequest); + const blueId = storage.cookiesAreEnabled() && storage.getCookie(BUNDLE_COOKIE_NAME); const url = buildCdbUrl(context); const data = CONVERTER.toORTB({ bidderRequest, bidRequests, context }); + // put user id in the request + if (data.user == undefined) { + data.user = {}; + } + if (data.user.ext == undefined) { + data.user.ext = { + buyerid: blueId + }; + } if (data) { return { method: 'POST', url, data, bidRequests }; } @@ -268,7 +279,6 @@ function pickAvailableGetFloorFunc(bidRequest) { } return undefined; } - function getFloors(bidRequest) { try { const floors = {}; diff --git a/test/spec/modules/blueBidAdapter_spec.js b/test/spec/modules/blueBidAdapter_spec.js new file mode 100755 index 00000000000..b3b2dd0a438 --- /dev/null +++ b/test/spec/modules/blueBidAdapter_spec.js @@ -0,0 +1,1253 @@ +import { expect } from 'chai'; +import { + spec, + storage, +} from 'modules/blueBidAdapter.js'; +import * as utils from 'src/utils.js'; +import * as refererDetection from 'src/refererDetection.js'; +import * as ajax from 'src/ajax.js'; +import { config } from '../../../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import 'modules/userId/index.js'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; +import 'modules/schain.js'; +import {hook} from '../../../src/hook.js'; + +describe('The Blue bidding adapter', function () { + let utilsMock, sandbox, ajaxStub; + + beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + blue: { + storageAllowed: true + } + }; + // Remove FastBid to avoid side effects + localStorage.removeItem('blue_fast_bid'); + utilsMock = sinon.mock(utils); + + sandbox = sinon.sandbox.create(); + ajaxStub = sandbox.stub(ajax, 'ajax'); + }); + + afterEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + global.Blue = undefined; + utilsMock.restore(); + sandbox.restore(); + ajaxStub.restore(); + }); + + describe('isBidRequestValid', function () { + it('should return false when given an invalid bid', function () { + const bid = { + bidder: 'blue', + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it('should return true when given a zoneId bid', function () { + const bid = { + bidder: 'blue', + params: { + publisherId: 123, + }, + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const refererUrl = 'https://blue.com?pbt_debug=1&pbt_nolog=1'; + const bidderRequest = { + refererInfo: { + page: refererUrl, + topmostLocation: refererUrl + }, + timeout: 3000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '91': 1 + }, + }, + apiVersion: 1, + }, + }; + + let localStorageIsEnabledStub; + + before(() => { + hook.ready(); + }); + + this.beforeEach(function () { + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + localStorageIsEnabledStub.returns(true); + }); + + afterEach(function () { + localStorageIsEnabledStub.restore(); + config.resetConfig(); + }); + + it('should properly build a request using random uuid as auction id', function () { + const generateUUIDStub = sinon.stub(utils, 'generateUUID'); + generateUUIDStub.returns('def'); + const bidderRequest = {}; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.id).to.equal('def'); + generateUUIDStub.restore(); + }); + + it('should properly transmit source.tid if available', function () { + const bidderRequest = { + ortb2: { + source: { + tid: 'abc' + } + } + }; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.source.tid).to.equal('abc'); + }); + + it('should properly transmit tmax if available', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); + }); + + it('should properly transmit bidId if available', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].id).to.equal('bidId'); + }); + + it('should properly forward eids', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: {} + }, + ]; + const br = { + ...bidderRequest, + ortb2: { + user: { + ext: { + eids: [ + { + source: 'blue.com', + uids: [{ + id: 'abc', + atype: 1 + }] + } + ] + } + } + } + } + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); + const ortbRequest = request.data; + expect(ortbRequest.user.ext.eids).to.deep.equal([ + { + source: 'blue.com', + uids: [{ + id: 'abc', + atype: 1 + }] + } + ]); + }); + + it('should properly build a publisherId request', function () { + const bidderRequest = { + refererInfo: { + page: refererUrl, + topmostLocation: refererUrl, + }, + timeout: 3000, + gdprConsent: { + gdprApplies: false, + consentString: undefined, + vendorData: { + vendorConsents: { + '1': 0 + }, + }, + }, + }; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + ortb2Imp: { + ext: { + tid: 'transaction-123', + }, + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + params: { + networkId: 456, + }, + }, + ]; + + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.contain('https://bidder-us-east-1.getblue.io/engine/?src=prebid'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(2); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(250); + expect(ortbRequest.imp[0].banner.format[1].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[1].h).to.equal(90); + expect(ortbRequest.user?.ext?.consent).to.equal(undefined); + expect(ortbRequest.regs.ext.gdpr).to.equal(0); + }); + + it('should properly build a request with undefined gdpr consent fields when they are not provided', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + timeout: 3000, + gdprConsent: {}, + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.user?.ext?.consent).to.equal(undefined); + expect(ortbRequest.regs?.ext?.gdpr).to.equal(undefined); + }); + + it('should properly build a request with ccpa consent field', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + timeout: 3000, + uspConsent: '1YNY', + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.ext.us_privacy).to.equal('1YNY'); + }); + + it('should properly build a request with overridden tmax', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + timeout: 1234 + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.tmax).to.equal(1234); + }); + + it('should properly build a request with device sua field', function () { + const sua = { + platform: { + brand: 'abc' + } + } + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + timeout: 3000, + uspConsent: '1YNY', + ortb2: { + device: { + sua: sua + } + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.device.ext.sua).not.to.be.null; + expect(ortbRequest.device.ext.sua.platform.brand).to.equal('abc'); + }); + + it('should properly build a request with gpp consent field', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const ortb2 = { + regs: { + gpp: 'gpp_consent_string', + gpp_sid: [0, 1, 2], + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.regs.ext.gpp).to.equal('gpp_consent_string'); + expect(ortbRequest.regs.ext.gpp_sid).to.deep.equal([0, 1, 2]); + }); + + it('should properly build a request with dsa object', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + let dsa = { + required: 3, + pubrender: 0, + datatopub: 2, + transparency: [{ + domain: 'platform1domain.com', + params: [1] + }, { + domain: 'SSP2domain.com', + params: [1, 2] + }] + }; + const ortb2 = { + regs: { + ext: { + dsa: dsa + } + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.regs.ext.dsa).to.deep.equal(dsa); + }); + + it('should properly build a request with schain object', function () { + const expectedSchain = { + someProperty: 'someValue' + }; + const bidRequests = [ + { + bidder: 'blue', + schain: expectedSchain, + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.source.ext.schain).to.equal(expectedSchain); + }); + + it('should properly build a request with bcat field', function () { + const bcat = ['IAB1', 'IAB2']; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + ortb2: { + bcat + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bcat).to.deep.equal(bcat); + }); + + it('should properly build a request with badv field', function () { + const badv = ['ford.com']; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + ortb2: { + badv + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.badv).to.deep.equal(badv); + }); + + it('should properly build a request with bapp field', function () { + const bapp = ['com.foo.mygame']; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + ortb2: { + bapp + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bapp).to.deep.equal(bapp); + }); + + it('should properly build a request without first party data', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123 + } + }, + ]; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2: {} })).data; + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(1); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(90); + expect(ortbRequest.imp[0].ext.bidder.zoneid).to.equal(123); + expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); + expect(ortbRequest.regs.ext.gdpr).to.equal(1); + expect(ortbRequest.regs.ext.gdprversion).to.equal(1); + }); + + it('should properly build a request with first party data', function () { + const siteData = { + keywords: ['power tools'], + content: { + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }] + }, + ext: { + data: { + pageType: 'article' + } + } + }; + const userData = { + gender: 'M', + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }], + ext: { + data: { + registered: true + } + } + }; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + ext: { + bidfloor: 0.75 + } + }, + ortb2Imp: { + ext: { + data: { + someContextAttribute: 'abc' + } + } + } + }, + ]; + + const ortb2 = { + site: siteData, + user: userData + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.user).to.deep.equal({ ...userData, ext: { ...userData.ext, consent: 'consentDataString' } }); + expect(ortbRequest.site).to.deep.equal({ ...siteData, page: refererUrl, domain: 'blue.com', publisher: { ...ortbRequest.site.publisher, domain: 'blue.com' } }); + expect(ortbRequest.imp[0].ext.bidfloor).to.equal(0.75); + expect(ortbRequest.imp[0].ext.data.someContextAttribute).to.equal('abc') + }); + + it('should properly build a request when coppa flag is true', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: true }); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(1); + }); + + it('should properly build a request when coppa flag is false', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: false }); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(0); + }); + + it('should properly build a request when coppa flag is not defined', function () { + const bidRequests = []; + const bidderRequest = {}; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs?.coppa).to.be.undefined; + }); + + it('should properly build a banner request with floors', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + params: { + networkId: 456, + }, + + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else { + return {} + } + } + }, + ]; + const bidderRequest = {}; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + }); + }); + + it('should properly build a request with static floors', function () { + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + params: { + networkId: 456, + bidFloor: 1, + bidFloorCur: 'EUR' + }, + }, + ]; + const bidderRequest = {}; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ + 'banner': { + '300x250': { 'currency': 'EUR', 'floor': 1 }, + '728x90': { 'currency': 'EUR', 'floor': 1 } + } + }); + }); + + it('should properly build a request when imp.rwdd is present', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123 + }, + ortb2Imp: { + rwdd: 1 + } + }, + ]; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.rwdd).to.equal(1); + }); + + it('should properly build a request when imp.rwdd is false', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123 + }, + ortb2Imp: { + rwdd: 0 + } + }, + ]; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext?.rwdd).to.equal(0); + }); + + it('should properly build a request when FLEDGE is enabled', function () { + const bidderRequest = { + paapi: { + enabled: true + } + }; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123 + }, + ortb2Imp: { + ext: { + igs: { + ae: 1 + } + } + } + }, + ]; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.igs.ae).to.equal(1); + }); + + it('should properly build a request when FLEDGE is disabled', function () { + const bidderRequest = { + paapi: { + enabled: false + }, + }; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123 + }, + ortb2Imp: { + ext: { + igs: { + ae: 1 + } + } + } + }, + ]; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.igs?.ae).to.be.undefined; + }); + + it('should properly transmit the pubid and slot uid if available', function () { + const bidderRequest = { + ortb2: { + site: { + publisher: { + id: 'pub-777' + } + } + } + }; + const bidRequests = [ + { + bidder: 'blue', + adUnitCode: 'bid-123', + ortb2Imp: { + ext: { + tid: 'transaction-123', + }, + }, + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + { + bidder: 'blue', + adUnitCode: 'bid-234', + ortb2Imp: { + ext: { + tid: 'transaction-234', + }, + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + params: { + networkId: 456, + pubid: 'pub-888', + uid: 888 + }, + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.site.publisher.id).to.equal('pub-888'); + expect(ortbRequest.imp[0].ext.bidder.uid).to.be.undefined; + expect(ortbRequest.imp[1].ext.bidder.uid).to.equal(888); + }); + + it('should properly transmit device.ext.cdep if available', function () { + const bidderRequest = { + ortb2: { + device: { + ext: { + cdep: 'cookieDeprecationLabel' + } + } + } + }; + const bidRequests = []; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.device.ext.cdep).to.equal('cookieDeprecationLabel'); + }); + }); + + describe('interpretResponse', function () { + const refererUrl = 'https://blue.com?pbt_debug=1&pbt_nolog=1'; + const bidderRequest = { + refererInfo: { + page: refererUrl, + topmostLocation: refererUrl + }, + timeout: 3000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '91': 1 + }, + }, + apiVersion: 1, + }, + }; + + function mockResponse(winningBidId, mediaType) { + return { + id: 'test-requestId', + seatbid: [ + { + seat: 'blue', + bid: [ + { + id: 'test-bidderId', + impid: winningBidId, + price: 1.23, + adomain: ['blue.com'], + bundle: '', + iurl: 'http://some_image/', + cid: '123456', + crid: 'test-crId', + dealid: 'deal-code', + w: 728, + h: 90, + adm: 'test-ad', + adm_native: mediaType === NATIVE ? { + ver: '1.2', + assets: [ + { + id: 10, + title: { + text: 'Some product' + } + }, + { + id: 11, + img: { + type: 3, + url: 'https://main_image_url.com', + w: 400, + h: 400 + } + }, + { + id: 12, + data: { + value: 'Some product' + } + }, + { + id: 13, + data: { + value: '1,499 TL' + } + }, + { + id: 15, + data: { + value: 'CTA' + }, + link: { + url: 'https://cta_url.com' + } + }, + { + id: 17, + img: { + type: 1, + url: 'https://main_image_url.com', + w: 200, + h: 200 + }, + link: { + url: 'https://icon_image_url.com' + } + }, + { + id: 16, + data: { + value: 'Some brand' + } + } + ], + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://eventtrackers.com' + }, + { + event: 1, + method: 1, + url: 'https://test_in_isolation.blue.com/tpd?dd=HTlW9l9xTEZqRHVlSHFiSWx5Q2VQMlEwSTJhNCUyQkxNazQ1Y29LR3ZmS2VTSDFsUGdkRHNoWjQ2UWp0SGtVZ1RTbHI0TFRpTlVqNWxiUkZOeGVFNjVraW53R0loRVJQNDJOY2R1eWxVdjBBQ1BEdVFvTyUyRlg3aWJaeUFha3UyemNNVGpmJTJCS1prc0FwRjZRJTJCQ2dpaFBJeVhZRmQlMkZURVZocUFRdm03OTdFZHZSbURNZWt4Uzh2M1NSUUxmTmhaTnNnRXd4VkZlOTdJOXdnNGZjaVolMkZWYmdYVjJJMkQ0eGxQaFIwQmVtWk1sQ09tNXlGY0Nwc09GTDladzExJTJGVExGNXJsdGpneERDeTMlMkJuNUlUcEU4NDFLMTZPc2ZoWFUwMmpGbDFpVjBPZUVtTlEwaWNOeHRyRFYyenRKd0lpJTJGTTElMkY1WGZ3Smo3aTh0bUJzdzZRdlZUSXppanNkamo3ekZNZjhKdjl2VDJ5eHV1YnVzdmdRdk5iWnprNXVFMVdmbGs0QU1QY0ozZQ' + } + ], + privacy: 'https://cta_url.com', + ext: { + privacy: { + imageurl: 'https://icon_image_url.com', + clickurl: 'https://cta_url.com', + longlegaltext: '' + } + } + } : undefined, + ext: { + mediatype: mediaType, + displayurl: mediaType === VIDEO ? 'http://test-ad' : undefined, + dsa: { + adrender: 1 + }, + meta: { + networkName: 'Blue' + }, + videoPlayerType: mediaType === VIDEO ? 'RadiantMediaPlayer' : undefined, + videoPlayerConfig: mediaType === VIDEO ? {} : undefined, + cur: 'CUR' + } + } + ] + } + ] + }; + } + + it('should return an empty array when parsing an empty bid response', function () { + const bidRequests = []; + const response = {}; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should return an empty array when parsing a well-formed no bid response', function () { + const bidRequests = []; + const response = { seatbid: [] }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a banner bid response', function () { + const bidRequests = [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + networkId: 456, + } + }]; + const response = mockResponse('test-bidId', BANNER); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(BANNER); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ad).to.equal('test-ad'); + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('blue.com'); + expect(bids[0].meta.networkName).to.equal('Blue'); + expect(bids[0].meta.dsa.adrender).to.equal(1); + }); + + it('should properly parse a bid response when banner win with twin ad units', function () { + const bidRequests = [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + }, + }, { + adUnitCode: 'test-requestId', + bidId: 'test-bidId2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + networkId: 456, + } + }]; + const response = mockResponse('test-bidId2', BANNER); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(BANNER); + expect(bids[0].requestId).to.equal('test-bidId2'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ad).to.equal('test-ad'); + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('blue.com'); + expect(bids[0].meta.networkName).to.equal('Blue'); + expect(bids[0].meta.dsa.adrender).to.equal(1); + }); + + [{ + hasBidResponseLevelPafData: true, + hasBidResponseBidLevelPafData: true, + shouldContainsBidMetaPafData: true + }, + { + hasBidResponseLevelPafData: false, + hasBidResponseBidLevelPafData: true, + shouldContainsBidMetaPafData: false + }, + { + hasBidResponseLevelPafData: true, + hasBidResponseBidLevelPafData: false, + shouldContainsBidMetaPafData: false + }, + { + hasBidResponseLevelPafData: false, + hasBidResponseBidLevelPafData: false, + shouldContainsBidMetaPafData: false + }].forEach(testCase => + it('should properly forward or not meta paf data', () => { + const bidPafContentId = 'abcdef'; + const pafTransmission = { + version: '12' + }; + const bidRequests = [{ + bidId: 'test-bidId', + adUnitCode: 'adUnitId', + sizes: [[300, 250]], + params: { + networkId: 456, + } + }]; + const response = { + id: 'test-requestId', + seatbid: [{ + seat: 'blue', + bid: [ + { + id: 'test-bidderId', + impid: 'test-bidId', + w: 728, + h: 90, + ext: { + mediatype: BANNER, + paf: testCase.hasBidResponseBidLevelPafData ? { + content_id: bidPafContentId + } : undefined + } + } + ] + }], + ext: (testCase.hasBidResponseLevelPafData ? { + paf: { + transmission: pafTransmission + } + } : undefined) + }; + + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + + expect(bids).to.have.lengthOf(1); + + const expectedBidMetaPafData = { + paf: { + content_id: bidPafContentId, + transmission: pafTransmission + } + }; + + if (testCase.shouldContainsBidMetaPafData) { + expect(bids[0].meta).to.deep.equal(expectedBidMetaPafData); + } else { + expect(bids[0].meta).not.to.deep.equal(expectedBidMetaPafData); + } + }) + ) + }); +}); From d69a4c2ddbc320ccf02f7be0b1625c90352e303b Mon Sep 17 00:00:00 2001 From: Tulio Duarte Date: Wed, 27 Nov 2024 14:51:59 -0300 Subject: [PATCH 3/9] uncommited stuff --- integrationExamples/gpt/hello_world.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index cb9bb70ed33..03a2356f0ef 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -26,9 +26,9 @@ // Replace this object to test a new Adapter! bids: [{ - bidder: 'blue', + bidder: 'appnexus', params: { - publisherId: 13144370 + placementId: 13144370 } }] From e29efa1e169e2c176a97693b6db140fe3e7c258a Mon Sep 17 00:00:00 2001 From: Tulio Duarte Date: Wed, 27 Nov 2024 14:53:59 -0300 Subject: [PATCH 4/9] uncommited stuff --- modules/blueBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/blueBidAdapter.md b/modules/blueBidAdapter.md index 91edde8556a..80c05019d52 100644 --- a/modules/blueBidAdapter.md +++ b/modules/blueBidAdapter.md @@ -18,7 +18,7 @@ Module that connects to Blue's demand sources. { bidder: 'blue', params: { - zoneId: 497747 + publisherId: "xpto" } } ] From 5c18e1b954b426b43adf94641399ec8ebeff72f5 Mon Sep 17 00:00:00 2001 From: Tulio Duarte Date: Thu, 5 Dec 2024 14:31:01 -0300 Subject: [PATCH 5/9] less duplicated code --- modules/blueBidAdapter.js | 116 +++++++++++++++----------------------- 1 file changed, 47 insertions(+), 69 deletions(-) diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js index ca43a9eb9fc..529979214f0 100644 --- a/modules/blueBidAdapter.js +++ b/modules/blueBidAdapter.js @@ -180,9 +180,21 @@ export const spec = { * @return {ServerRequest} */ buildRequests: (bidRequests, bidderRequest) => { - const context = buildContext(bidRequests, bidderRequest); - const blueId = storage.cookiesAreEnabled() && storage.getCookie(BUNDLE_COOKIE_NAME); - const url = buildCdbUrl(context); + const context = { + url: bidderRequest?.refererInfo?.page || '', + publisherId: bidRequests.find((bidRequest) => bidRequest.params?.pubid) + ?.params.pubid, + }; + const blueId = + storage.cookiesAreEnabled() && storage.getCookie(BUNDLE_COOKIE_NAME); + let url = CDB_ENDPOINT; + url += '&wv=' + encodeURIComponent('$prebid.version$'); + url += '&cb=' + String(Math.floor(Math.random() * 99999999999)); + + if (context.publisherId) { + url += `&publisherId=` + context.publisherId; + } + const data = CONVERTER.toORTB({ bidderRequest, bidRequests, context }); // put user id in the request if (data.user == undefined) { @@ -191,7 +203,7 @@ export const spec = { if (data.user.ext == undefined) { data.user.ext = { - buyerid: blueId + buyerid: blueId, }; } if (data) { @@ -219,81 +231,47 @@ export const spec = { }, }; -/** - * @param {BidRequest[]} bidRequests - * @param bidderRequest - */ -function buildContext(bidRequests, bidderRequest) { - return { - url: bidderRequest?.refererInfo?.page || '', - publisherId: bidRequests.find((bidRequest) => bidRequest.params?.pubid) - ?.params.pubid, - }; -} - -/** - * @param {Object} context - * @return {string} - */ -function buildCdbUrl(context) { - let url = CDB_ENDPOINT; - url += '&wv=' + encodeURIComponent('$prebid.version$'); - url += '&cb=' + String(Math.floor(Math.random() * 99999999999)); - - if (context.publisherId) { - url += `&publisherId=` + context.publisherId; - } - - return url; -} - -function parseSizes(sizes, parser = (s) => s) { - if (sizes == undefined) { - return []; - } - if (Array.isArray(sizes[0])) { - // is there several sizes ? (ie. [[728,90],[200,300]]) - return sizes.map((size) => parser(size)); - } - return [parser(sizes)]; // or a single one ? (ie. [728,90]) -} - -function parseSize(size) { - return size[0] + 'x' + size[1]; -} - -function pickAvailableGetFloorFunc(bidRequest) { - if (bidRequest.getFloor) { - return bidRequest.getFloor; - } - if (bidRequest.params.bidFloor && bidRequest.params.bidFloorCur) { - try { - const floor = parseFloat(bidRequest.params.bidFloor); - return () => { - return { - currency: bidRequest.params.bidFloorCur, - floor: floor, - }; - }; - } catch {} - } - return undefined; -} function getFloors(bidRequest) { try { const floors = {}; - const getFloor = pickAvailableGetFloorFunc(bidRequest); + let getFloor; + + if (bidRequest.getFloor) { + getFloor = bidRequest.getFloor; + } + if (bidRequest.params.bidFloor && bidRequest.params.bidFloorCur) { + try { + const floor = parseFloat(bidRequest.params.bidFloor); + return () => { + getFloor = { + currency: bidRequest.params.bidFloorCur, + floor: floor, + }; + }; + } catch {} + } + getFloor = undefined; if (getFloor) { if (bidRequest.mediaTypes?.banner) { floors.banner = {}; - const bannerSizes = parseSizes( - deepAccess(bidRequest, 'mediaTypes.banner.sizes') - ); + + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + const parser = (s) => s; + + if (sizes == undefined) { + return []; + } + if (Array.isArray(sizes[0])) { + // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map((size) => parser(size)); + } + const bannerSizes = [parser(sizes)]; // or a single one ? (ie. [728,90]) + bannerSizes.forEach( (bannerSize) => - (floors.banner[parseSize(bannerSize).toString()] = getFloor.call( + (floors.banner[(bannerSize[0] + 'x' + bannerSize[1]).toString()] = getFloor.call( bidRequest, { size: bannerSize, mediaType: BANNER } )) From 6ac6a61905e1733dab0f552a30379c13f255c47e Mon Sep 17 00:00:00 2001 From: Tulio Duarte Date: Thu, 5 Dec 2024 14:33:11 -0300 Subject: [PATCH 6/9] less duplicated code --- modules/blueBidAdapter.js | 72 ++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js index 529979214f0..b9c7d14a788 100644 --- a/modules/blueBidAdapter.js +++ b/modules/blueBidAdapter.js @@ -235,53 +235,49 @@ function getFloors(bidRequest) { try { const floors = {}; - let getFloor; - - if (bidRequest.getFloor) { - getFloor = bidRequest.getFloor; - } - if (bidRequest.params.bidFloor && bidRequest.params.bidFloorCur) { - try { - const floor = parseFloat(bidRequest.params.bidFloor); - return () => { - getFloor = { + const parseBidFloor = () => { + if (bidRequest.params?.bidFloor && bidRequest.params?.bidFloorCur) { + try { + const floor = parseFloat(bidRequest.params.bidFloor); + return { currency: bidRequest.params.bidFloorCur, floor: floor, }; - }; - } catch {} - } - getFloor = undefined; - - if (getFloor) { - if (bidRequest.mediaTypes?.banner) { - floors.banner = {}; - - const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); - const parser = (s) => s; - - if (sizes == undefined) { - return []; + } catch { + return undefined; } - if (Array.isArray(sizes[0])) { - // is there several sizes ? (ie. [[728,90],[200,300]]) - return sizes.map((size) => parser(size)); - } - const bannerSizes = [parser(sizes)]; // or a single one ? (ie. [728,90]) - - bannerSizes.forEach( - (bannerSize) => - (floors.banner[(bannerSize[0] + 'x' + bannerSize[1]).toString()] = getFloor.call( - bidRequest, - { size: bannerSize, mediaType: BANNER } - )) - ); } + return undefined; + }; + + const calculateBannerFloors = (getFloor) => { + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + if (!sizes) return []; + + const normalizeSizes = (sizes) => + Array.isArray(sizes[0]) ? sizes : [sizes]; // Normalize to array of sizes + const bannerSizes = normalizeSizes(sizes); + + return bannerSizes.reduce((bannerFloors, bannerSize) => { + const sizeKey = `${bannerSize[0]}x${bannerSize[1]}`; + bannerFloors[sizeKey] = getFloor.call(bidRequest, { + size: bannerSize, + mediaType: BANNER, + }); + return bannerFloors; + }, {}); + }; + + let getFloor = bidRequest.getFloor || parseBidFloor(); - return floors; + if (getFloor && bidRequest.mediaTypes?.banner) { + floors.banner = calculateBannerFloors(getFloor); } + + return floors; } catch (e) { logError('Could not parse floors from Prebid: ' + e); + return {}; } } From 968b798f3b6c56e494fa11f831d7732878877e80 Mon Sep 17 00:00:00 2001 From: Tulio Duarte Date: Thu, 5 Dec 2024 14:34:18 -0300 Subject: [PATCH 7/9] less duplicated code --- modules/blueBidAdapter.js | 137 +++++++++++++++++++++++++------------- 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js index b9c7d14a788..5cba985dc48 100644 --- a/modules/blueBidAdapter.js +++ b/modules/blueBidAdapter.js @@ -161,75 +161,120 @@ export const spec = { supportedMediaTypes: [BANNER], /** - * f + * Validates the bid request. * @param {object} bid * @return {boolean} */ - isBidRequestValid: (bid) => { - // either one of zoneId or networkId should be set - if (!(bid && bid.params && bid.params.publisherId)) { - return false; - } - - return true; - }, + isBidRequestValid: (bid) => isValidBidRequest(bid), /** + * Builds requests for the bidder. * @param {BidRequest[]} bidRequests * @param {*} bidderRequest * @return {ServerRequest} */ buildRequests: (bidRequests, bidderRequest) => { - const context = { - url: bidderRequest?.refererInfo?.page || '', - publisherId: bidRequests.find((bidRequest) => bidRequest.params?.pubid) - ?.params.pubid, - }; - const blueId = - storage.cookiesAreEnabled() && storage.getCookie(BUNDLE_COOKIE_NAME); - let url = CDB_ENDPOINT; - url += '&wv=' + encodeURIComponent('$prebid.version$'); - url += '&cb=' + String(Math.floor(Math.random() * 99999999999)); - - if (context.publisherId) { - url += `&publisherId=` + context.publisherId; - } - - const data = CONVERTER.toORTB({ bidderRequest, bidRequests, context }); - // put user id in the request - if (data.user == undefined) { - data.user = {}; - } + const context = buildContext(bidRequests, bidderRequest); + const url = buildUrl(context.publisherId); + const data = prepareData(bidRequests, bidderRequest, context); - if (data.user.ext == undefined) { - data.user.ext = { - buyerid: blueId, - }; - } if (data) { return { method: 'POST', url, data, bidRequests }; } }, /** + * Interprets the server response. * @param {*} response * @param {ServerRequest} request * @return {Bid[] | {bids: Bid[], fledgeAuctionConfigs: object[]}} */ - interpretResponse: (response, request) => { - if (typeof response?.body == 'undefined') { - return []; // no bid - } + interpretResponse: (response, request) => interpretServerResponse(response, request), +}; - const interpretedResponse = CONVERTER.fromORTB({ - response: response.body, - request: request.data, - }); - const bids = interpretedResponse.bids || []; +// Helper functions - return bids; - }, -}; +/** + * Validates a bid request. + * @param {object} bid + * @return {boolean} + */ +function isValidBidRequest(bid) { + return bid?.params?.publisherId ? true : false; +} + +/** + * Builds the request context. + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @return {object} + */ +function buildContext(bidRequests, bidderRequest) { + const publisherId = bidRequests.find((bidRequest) => bidRequest.params?.pubid)?.params.pubid; + return { + url: bidderRequest?.refererInfo?.page || '', + publisherId, + }; +} + +/** + * Builds the request URL. + * @param {string} publisherId + * @return {string} + */ +function buildUrl(publisherId) { + let url = CDB_ENDPOINT; + url += '&wv=' + encodeURIComponent('$prebid.version$'); + url += '&cb=' + String(Math.floor(Math.random() * 99999999999)); + + if (publisherId) { + url += `&publisherId=` + publisherId; + } + + return url; +} + +/** + * Prepares the request data. + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @param {object} context + * @return {object} + */ +function prepareData(bidRequests, bidderRequest, context) { + const data = CONVERTER.toORTB({ bidderRequest, bidRequests, context }); + + if (!data.user) { + data.user = {}; + } + + if (!data.user.ext) { + data.user.ext = { + buyerid: storage.cookiesAreEnabled() ? storage.getCookie(BUNDLE_COOKIE_NAME) : undefined, + }; + } + + return data; +} + +/** + * Interprets the server response. + * @param {*} response + * @param {ServerRequest} request + * @return {Bid[] | {bids: Bid[], fledgeAuctionConfigs: object[]}} + */ +function interpretServerResponse(response, request) { + if (!response?.body) { + return []; // No bids + } + + const interpretedResponse = CONVERTER.fromORTB({ + response: response.body, + request: request.data, + }); + + return interpretedResponse.bids || []; +} function getFloors(bidRequest) { try { From 82077213ddaccbd602d236ae6592b9bad0e6cb62 Mon Sep 17 00:00:00 2001 From: Tulio Duarte Date: Thu, 5 Dec 2024 14:35:16 -0300 Subject: [PATCH 8/9] less duplicated code --- modules/blueBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js index 5cba985dc48..40fd4af9979 100644 --- a/modules/blueBidAdapter.js +++ b/modules/blueBidAdapter.js @@ -200,7 +200,7 @@ export const spec = { * @return {boolean} */ function isValidBidRequest(bid) { - return bid?.params?.publisherId ? true : false; + return bid && bid.params && bid.params.publisherId; } /** From b980c32cbcd07c098221f0df24e1e05346f6f6e8 Mon Sep 17 00:00:00 2001 From: Tulio Duarte Date: Thu, 5 Dec 2024 14:40:02 -0300 Subject: [PATCH 9/9] less duplicated code --- modules/blueBidAdapter.js | 73 +++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js index 40fd4af9979..10450ca1c10 100644 --- a/modules/blueBidAdapter.js +++ b/modules/blueBidAdapter.js @@ -44,27 +44,38 @@ const CONVERTER = ortbConverter({ * @returns {Object} The ORTB 2.5 imp object. */ function imp(buildImp, bidRequest, context) { - let imp = buildImp(bidRequest, context); - const params = bidRequest.params; + const imp = buildImp(bidRequest, context); + const params = bidRequest.params || {}; imp.tagid = bidRequest.adUnitCode; - deepSetValue(imp, 'ext', { - ...bidRequest.params.ext, + + const ext = { + ...params.ext, ...imp.ext, rwdd: imp.rwdd, floors: getFloors(bidRequest), - bidder: { - publishersubid: params?.publisherSubId, - zoneid: params?.zoneId, - uid: params?.uid, - }, - }); + bidder: buildBidderExt(params), + }; - delete imp.rwdd; // oRTB 2.6 field moved to ext + deepSetValue(imp, 'ext', ext); return imp; } +/** + * Builds the bidder extension object for the impression. + * + * @param {Object} params - The parameters from the bid request. + * @returns {Object} The bidder extension object. + */ +function buildBidderExt(params) { + return { + publishersubid: params.publisherSubId, + zoneid: params.zoneId, + uid: params.uid, + }; +} + /** * Builds a request object for the ORTB 2.5 request. * @@ -77,27 +88,37 @@ function imp(buildImp, bidRequest, context) { function request(buildRequest, imps, bidderRequest, context) { let request = buildRequest(imps, bidderRequest, context); - // params.pubid should override publisher id - if (typeof context.publisherId !== 'undefined') { - if (typeof request.app !== 'undefined') { - deepSetValue(request, 'app.publisher.id', context.publisherId); - } else { - deepSetValue(request, 'site.publisher.id', context.publisherId); - } + if (context.publisherId !== undefined) { + setPublisherId(request, context.publisherId); } - if (bidderRequest && bidderRequest.gdprConsent) { - deepSetValue( - request, - 'regs.ext.gdprversion', - bidderRequest.gdprConsent.apiVersion - ); + if (bidderRequest?.gdprConsent) { + setGdprVersion(request, bidderRequest.gdprConsent.apiVersion); } // Translate 2.6 OpenRTB request into 2.5 OpenRTB request - request = TRANSLATOR(request); + return TRANSLATOR(request); +} + +/** + * Sets the publisher ID in the request object based on the context. + * + * @param {Object} request - The ORTB 2.5 request object. + * @param {string} publisherId - The publisher ID to set. + */ +function setPublisherId(request, publisherId) { + const targetPath = request.app ? 'app.publisher.id' : 'site.publisher.id'; + deepSetValue(request, targetPath, publisherId); +} - return request; +/** + * Sets the GDPR version in the request object if GDPR consent is provided. + * + * @param {Object} request - The ORTB 2.5 request object. + * @param {string} gdprVersion - The GDPR API version. + */ +function setGdprVersion(request, gdprVersion) { + deepSetValue(request, 'regs.ext.gdprversion', gdprVersion); } /**