From 3cfdf9ddf84faedcfc27bd6eb0c2c9286c007395 Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Sun, 3 Jan 2021 18:49:53 +0200 Subject: [PATCH 01/24] add Rise adapter --- modules/riseBidAdapter.js | 250 ++++++++++++++++ modules/riseBidAdapter.md | 51 ++++ test/spec/modules/riseBidAdapter_spec.js | 363 +++++++++++++++++++++++ 3 files changed, 664 insertions(+) create mode 100644 modules/riseBidAdapter.js create mode 100644 modules/riseBidAdapter.md create mode 100644 test/spec/modules/riseBidAdapter_spec.js diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js new file mode 100644 index 00000000000..d9764349a88 --- /dev/null +++ b/modules/riseBidAdapter.js @@ -0,0 +1,250 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; + +const SUPPORTED_AD_TYPES = [VIDEO]; +const BIDDER_CODE = 'rise'; +const BIDDER_VERSION = '4.0.0'; +const TTL = 360; +const SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; +const MODES = { + PRODUCTION: 'hb', + TEST: 'hb-test' +} +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function(bidRequest) { + return !!(bidRequest.params.isOrg); + }, + buildRequests: function (bidRequests, bidderRequest) { + if (bidRequests.length === 0) { + return []; + } + + const requests = []; + + bidRequests.forEach(bid => { + requests.push(buildVideoRequest(bid, bidderRequest)); + }); + + return requests; + }, + interpretResponse: function({body}) { + const bidResponses = []; + + const bidResponse = { + requestId: body.requestId, + cpm: body.cpm, + width: body.width, + height: body.height, + creativeId: body.requestId, + currency: body.currency, + netRevenue: body.netRevenue, + ttl: body.ttl || TTL, + vastXml: body.vastXml, + mediaType: VIDEO + }; + + bidResponses.push(bidResponse); + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && response.body.userSyncURL) { + syncs.push({ + type: 'iframe', + url: response.body.userSyncURL + }); + } + if (syncOptions.pixelEnabled && utils.isArray(response.body.userSyncPixels)) { + const pixels = response.body.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }) + syncs.push(...pixels) + } + } + return syncs; + } +}; + +registerBidder(spec); + +/** + * Build the video request + * @param bid {bid} + * @param bidderRequest {bidderRequest} + * @returns {Object} + */ +function buildVideoRequest(bid, bidderRequest) { + const sellerParams = generateParameters(bid, bidderRequest); + const {params} = bid; + return { + method: 'GET', + url: getEndpoint(params.testMode), + data: sellerParams + }; +} + +/** + * Get the the ad size from the bid + * @param bid {bid} + * @returns {Array} + */ +function getSizes(bid) { + if (utils.deepAccess(bid, 'mediaTypes.video.sizes')) { + return bid.mediaTypes.video.sizes[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + return bid.sizes[0]; + } + return []; +} + +/** + * Get schain string value + * @param schainObject {Object} + * @returns {string} + */ +function getSupplyChain(schainObject) { + if (utils.isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${getEncodedValIfNotEmpty(node.hp)},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; + }); + return scStr; +} + +/** + * Get encoded node value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !utils.isEmpty(val) ? encodeURIComponent(val) : ''; +} + +/** + * Get preferred user-sync method based on publisher configuration + * @param bidderCode {string} + * @returns {string} + */ +function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return SUPPORTED_SYNC_METHODS.IFRAME; + } + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; + } +} + +/** + * Check if sync rule is supported + * @param syncRule {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; + } + const isInclude = syncRule.filter === 'include'; + const bidders = utils.isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && utils.contains(bidders, bidderCode); +} + +/** + * Get the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : SELLER_ENDPOINT + MODES.PRODUCTION; +} + +/** + * Generate query parameters for the request + * @param bid {bid} + * @param bidderRequest {bidderRequest} + * @returns {Object} + */ +function generateParameters(bid, bidderRequest) { + const timeout = config.getConfig('bidderTimeout'); + const { syncEnabled, filterSettings } = config.getConfig('userSync'); + const [ width, height ] = getSizes(bid); + const { params } = bid; + const { bidderCode } = bidderRequest; + const domain = window.location.hostname; + + const requestParams = { + auction_start: utils.timestamp(), + ad_unit_code: utils.getBidIdParameter('adUnitCode', bid), + tmax: timeout, + width: width, + height: height, + publisher_id: params.isOrg, + floor_price: params.floorPrice, + ua: navigator.userAgent, + bid_id: utils.getBidIdParameter('bidId', bid), + bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), + transaction_id: utils.getBidIdParameter('transactionId', bid), + session_id: utils.getBidIdParameter('auctionId', bid), + publisher_name: domain, + site_domain: domain, + bidder_version: BIDDER_VERSION + }; + + if (syncEnabled) { + const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + requestParams.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest.uspConsent) { + requestParams.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (params.ifa) { + requestParams.ifa = params.ifa; + } + + if (bid.schain) { + requestParams.schain = getSupplyChain(bid.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + requestParams.referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer'); + requestParams.page_url = config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + } + + return requestParams; +} diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md new file mode 100644 index 00000000000..6cc81f9df10 --- /dev/null +++ b/modules/riseBidAdapter.md @@ -0,0 +1,51 @@ +#Overview + +Module Name: Rise Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: prebid-digital-brands@ironsrc.com + + +# Description + +Module that connects to Rise's demand sources. + +The Rise adapter requires setup and approval from the Rise. Please reach out to prebid-digital-brands@ironsrc.com to create an Rise account. + +The adapter supports Video(instream). For the integration, Rise returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction. + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `isOrg` | required | String | Rise publisher Id provided by your Rise representative | "56f91cd4d3e360002000033" +| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 +| `ifa` | optional | String | The ID for advertisers (also referred to as "IDFA") | "XXX-XXX" +| `testMode` | optional | Boolean | This activates the test mode | false + +# Test Parameters +```javascript +var adUnits = [ + { + code: 'dfp-video-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + } + }, + bids: [{ + bidder: 'rise', + params: { + isOrg: '56f91cd4d3e360002000033', // Required + floorPrice: 2.00, // Optional + ifa: 'XXX-XXX', // Optional + testMode: false // Optional + } + }] + } + ]; +``` diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js new file mode 100644 index 00000000000..3fb8ae14df3 --- /dev/null +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -0,0 +1,363 @@ +import { expect } from 'chai'; +import { spec } from 'modules/riseBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { VIDEO } from '../../../src/mediaTypes.js'; + +const ENDPOINT = 'https://hb.yellowblue.io/hb'; +const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-test'; +const TTL = 360; + +describe('riseAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'isOrg': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'isOrg': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'isOrg': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'isOrg': 'jdye8weeyirk00000001', + 'testMode': true + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const bidderRequest = { + bidderCode: 'rise', + } + + it('sends bid request to ENDPOINT via GET', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('sends bid request to test ENDPOINT via GET', function () { + const requests = spec.buildRequests(testModeBidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('should send the correct bid Id', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.bid_id).to.equal('299ffc8cca0b87'); + } + }); + + it('should send the correct width and height', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('width', 640); + expect(request.data).to.have.property('height', 480); + } + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.setConfig({ + userSync: { + syncEnabled: true + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'pixel'); + } + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('us_privacy', '1YNN'); + } + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('us_privacy'); + } + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('gdpr'); + expect(request.data).to.not.have.property('gdpr_consent'); + } + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('gdpr', true); + expect(request.data).to.have.property('gdpr_consent', 'test-consent-string'); + } + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,,,,'); + } + }); + }); + + describe('interpretResponse', function () { + const response = { + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + netRevenue: true, + currency: 'USD' + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '21e12606d47ba7', + cpm: 12.5, + width: 640, + height: 480, + creativeId: '21e12606d47ba7', + currency: 'USD', + netRevenue: true, + ttl: TTL, + vastXml: '', + mediaType: VIDEO + } + ]; + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + }; + + const iframeSyncResponse = { + body: { + userSyncURL: 'https://iframe-sync-url.test' + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) +}); From 923e646411dada6c1ed53cf74a614b4e1c40ab88 Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Sun, 3 Jan 2021 18:53:09 +0200 Subject: [PATCH 02/24] fixes --- modules/riseBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 6cc81f9df10..0e138b7b977 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -20,7 +20,7 @@ The adapter supports Video(instream). For the integration, Rise returns content | Name | Scope | Type | Description | Example | ---- | ----- | ---- | ----------- | ------- -| `isOrg` | required | String | Rise publisher Id provided by your Rise representative | "56f91cd4d3e360002000033" +| `isOrg` | required | String | Rise publisher Id provided by your Rise representative | "56f91cd4d3e3660002000033" | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `ifa` | optional | String | The ID for advertisers (also referred to as "IDFA") | "XXX-XXX" | `testMode` | optional | Boolean | This activates the test mode | false @@ -40,7 +40,7 @@ var adUnits = [ bids: [{ bidder: 'rise', params: { - isOrg: '56f91cd4d3e360002000033', // Required + isOrg: '56f91cd4d3e3660002000033', // Required floorPrice: 2.00, // Optional ifa: 'XXX-XXX', // Optional testMode: false // Optional From 499f36f178dcc2036dd3b5bb2d146832f8e2519a Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Tue, 5 Jan 2021 15:35:54 +0200 Subject: [PATCH 03/24] change param isOrg to org --- modules/riseBidAdapter.js | 4 ++-- modules/riseBidAdapter.md | 4 ++-- test/spec/modules/riseBidAdapter_spec.js | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index d9764349a88..8b662fe6232 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -22,7 +22,7 @@ export const spec = { version: BIDDER_VERSION, supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function(bidRequest) { - return !!(bidRequest.params.isOrg); + return !!(bidRequest.params.org); }, buildRequests: function (bidRequests, bidderRequest) { if (bidRequests.length === 0) { @@ -205,7 +205,7 @@ function generateParameters(bid, bidderRequest) { tmax: timeout, width: width, height: height, - publisher_id: params.isOrg, + publisher_id: params.org, floor_price: params.floorPrice, ua: navigator.userAgent, bid_id: utils.getBidIdParameter('bidId', bid), diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 0e138b7b977..7798e5edb27 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -20,7 +20,7 @@ The adapter supports Video(instream). For the integration, Rise returns content | Name | Scope | Type | Description | Example | ---- | ----- | ---- | ----------- | ------- -| `isOrg` | required | String | Rise publisher Id provided by your Rise representative | "56f91cd4d3e3660002000033" +| `org` | required | String | Rise publisher Id provided by your Rise representative | "56f91cd4d3e3660002000033" | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `ifa` | optional | String | The ID for advertisers (also referred to as "IDFA") | "XXX-XXX" | `testMode` | optional | Boolean | This activates the test mode | false @@ -40,7 +40,7 @@ var adUnits = [ bids: [{ bidder: 'rise', params: { - isOrg: '56f91cd4d3e3660002000033', // Required + org: '56f91cd4d3e3660002000033', // Required floorPrice: 2.00, // Optional ifa: 'XXX-XXX', // Optional testMode: false // Optional diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 3fb8ae14df3..176437c4f27 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -23,7 +23,7 @@ describe('riseAdapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [['640', '480']], 'params': { - 'isOrg': 'jdye8weeyirk00000001' + 'org': 'jdye8weeyirk00000001' } }; @@ -35,7 +35,7 @@ describe('riseAdapter', function () { const newBid = Object.assign({}, bid); delete newBid.params; newBid.params = { - 'isOrg': null + 'org': null }; expect(spec.isBidRequestValid(newBid)).to.equal(false); }); @@ -48,7 +48,7 @@ describe('riseAdapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [[640, 480]], 'params': { - 'isOrg': 'jdye8weeyirk00000001' + 'org': 'jdye8weeyirk00000001' }, 'bidId': '299ffc8cca0b87', 'bidderRequestId': '1144f487e563f9', @@ -62,7 +62,7 @@ describe('riseAdapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [[640, 480]], 'params': { - 'isOrg': 'jdye8weeyirk00000001', + 'org': 'jdye8weeyirk00000001', 'testMode': true }, 'bidId': '299ffc8cca0b87', From d33a8aff7431413e314f3b85544ec92cb461664e Mon Sep 17 00:00:00 2001 From: noamtzuberi Date: Tue, 5 Jan 2021 15:56:32 +0200 Subject: [PATCH 04/24] Rise adapter --- modules/riseBidAdapter.js | 250 ++++++++++++++++ modules/riseBidAdapter.md | 51 ++++ test/spec/modules/riseBidAdapter_spec.js | 363 +++++++++++++++++++++++ 3 files changed, 664 insertions(+) create mode 100644 modules/riseBidAdapter.js create mode 100644 modules/riseBidAdapter.md create mode 100644 test/spec/modules/riseBidAdapter_spec.js diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js new file mode 100644 index 00000000000..8b662fe6232 --- /dev/null +++ b/modules/riseBidAdapter.js @@ -0,0 +1,250 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; + +const SUPPORTED_AD_TYPES = [VIDEO]; +const BIDDER_CODE = 'rise'; +const BIDDER_VERSION = '4.0.0'; +const TTL = 360; +const SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; +const MODES = { + PRODUCTION: 'hb', + TEST: 'hb-test' +} +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function(bidRequest) { + return !!(bidRequest.params.org); + }, + buildRequests: function (bidRequests, bidderRequest) { + if (bidRequests.length === 0) { + return []; + } + + const requests = []; + + bidRequests.forEach(bid => { + requests.push(buildVideoRequest(bid, bidderRequest)); + }); + + return requests; + }, + interpretResponse: function({body}) { + const bidResponses = []; + + const bidResponse = { + requestId: body.requestId, + cpm: body.cpm, + width: body.width, + height: body.height, + creativeId: body.requestId, + currency: body.currency, + netRevenue: body.netRevenue, + ttl: body.ttl || TTL, + vastXml: body.vastXml, + mediaType: VIDEO + }; + + bidResponses.push(bidResponse); + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && response.body.userSyncURL) { + syncs.push({ + type: 'iframe', + url: response.body.userSyncURL + }); + } + if (syncOptions.pixelEnabled && utils.isArray(response.body.userSyncPixels)) { + const pixels = response.body.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }) + syncs.push(...pixels) + } + } + return syncs; + } +}; + +registerBidder(spec); + +/** + * Build the video request + * @param bid {bid} + * @param bidderRequest {bidderRequest} + * @returns {Object} + */ +function buildVideoRequest(bid, bidderRequest) { + const sellerParams = generateParameters(bid, bidderRequest); + const {params} = bid; + return { + method: 'GET', + url: getEndpoint(params.testMode), + data: sellerParams + }; +} + +/** + * Get the the ad size from the bid + * @param bid {bid} + * @returns {Array} + */ +function getSizes(bid) { + if (utils.deepAccess(bid, 'mediaTypes.video.sizes')) { + return bid.mediaTypes.video.sizes[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + return bid.sizes[0]; + } + return []; +} + +/** + * Get schain string value + * @param schainObject {Object} + * @returns {string} + */ +function getSupplyChain(schainObject) { + if (utils.isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${getEncodedValIfNotEmpty(node.hp)},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; + }); + return scStr; +} + +/** + * Get encoded node value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !utils.isEmpty(val) ? encodeURIComponent(val) : ''; +} + +/** + * Get preferred user-sync method based on publisher configuration + * @param bidderCode {string} + * @returns {string} + */ +function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return SUPPORTED_SYNC_METHODS.IFRAME; + } + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; + } +} + +/** + * Check if sync rule is supported + * @param syncRule {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; + } + const isInclude = syncRule.filter === 'include'; + const bidders = utils.isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && utils.contains(bidders, bidderCode); +} + +/** + * Get the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : SELLER_ENDPOINT + MODES.PRODUCTION; +} + +/** + * Generate query parameters for the request + * @param bid {bid} + * @param bidderRequest {bidderRequest} + * @returns {Object} + */ +function generateParameters(bid, bidderRequest) { + const timeout = config.getConfig('bidderTimeout'); + const { syncEnabled, filterSettings } = config.getConfig('userSync'); + const [ width, height ] = getSizes(bid); + const { params } = bid; + const { bidderCode } = bidderRequest; + const domain = window.location.hostname; + + const requestParams = { + auction_start: utils.timestamp(), + ad_unit_code: utils.getBidIdParameter('adUnitCode', bid), + tmax: timeout, + width: width, + height: height, + publisher_id: params.org, + floor_price: params.floorPrice, + ua: navigator.userAgent, + bid_id: utils.getBidIdParameter('bidId', bid), + bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), + transaction_id: utils.getBidIdParameter('transactionId', bid), + session_id: utils.getBidIdParameter('auctionId', bid), + publisher_name: domain, + site_domain: domain, + bidder_version: BIDDER_VERSION + }; + + if (syncEnabled) { + const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + requestParams.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest.uspConsent) { + requestParams.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (params.ifa) { + requestParams.ifa = params.ifa; + } + + if (bid.schain) { + requestParams.schain = getSupplyChain(bid.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + requestParams.referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer'); + requestParams.page_url = config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + } + + return requestParams; +} diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md new file mode 100644 index 00000000000..7798e5edb27 --- /dev/null +++ b/modules/riseBidAdapter.md @@ -0,0 +1,51 @@ +#Overview + +Module Name: Rise Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: prebid-digital-brands@ironsrc.com + + +# Description + +Module that connects to Rise's demand sources. + +The Rise adapter requires setup and approval from the Rise. Please reach out to prebid-digital-brands@ironsrc.com to create an Rise account. + +The adapter supports Video(instream). For the integration, Rise returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction. + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `org` | required | String | Rise publisher Id provided by your Rise representative | "56f91cd4d3e3660002000033" +| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 +| `ifa` | optional | String | The ID for advertisers (also referred to as "IDFA") | "XXX-XXX" +| `testMode` | optional | Boolean | This activates the test mode | false + +# Test Parameters +```javascript +var adUnits = [ + { + code: 'dfp-video-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + } + }, + bids: [{ + bidder: 'rise', + params: { + org: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + ifa: 'XXX-XXX', // Optional + testMode: false // Optional + } + }] + } + ]; +``` diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js new file mode 100644 index 00000000000..176437c4f27 --- /dev/null +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -0,0 +1,363 @@ +import { expect } from 'chai'; +import { spec } from 'modules/riseBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { VIDEO } from '../../../src/mediaTypes.js'; + +const ENDPOINT = 'https://hb.yellowblue.io/hb'; +const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-test'; +const TTL = 360; + +describe('riseAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'org': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001', + 'testMode': true + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const bidderRequest = { + bidderCode: 'rise', + } + + it('sends bid request to ENDPOINT via GET', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('sends bid request to test ENDPOINT via GET', function () { + const requests = spec.buildRequests(testModeBidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('should send the correct bid Id', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.bid_id).to.equal('299ffc8cca0b87'); + } + }); + + it('should send the correct width and height', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('width', 640); + expect(request.data).to.have.property('height', 480); + } + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.setConfig({ + userSync: { + syncEnabled: true + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'pixel'); + } + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('us_privacy', '1YNN'); + } + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('us_privacy'); + } + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('gdpr'); + expect(request.data).to.not.have.property('gdpr_consent'); + } + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('gdpr', true); + expect(request.data).to.have.property('gdpr_consent', 'test-consent-string'); + } + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,,,,'); + } + }); + }); + + describe('interpretResponse', function () { + const response = { + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + netRevenue: true, + currency: 'USD' + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '21e12606d47ba7', + cpm: 12.5, + width: 640, + height: 480, + creativeId: '21e12606d47ba7', + currency: 'USD', + netRevenue: true, + ttl: TTL, + vastXml: '', + mediaType: VIDEO + } + ]; + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + }; + + const iframeSyncResponse = { + body: { + userSyncURL: 'https://iframe-sync-url.test' + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) +}); From b2cdd97fee0b47e7346ffc8204ac2857d30512e1 Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Tue, 5 Jan 2021 16:56:42 +0200 Subject: [PATCH 05/24] change email for rise --- modules/riseBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 7798e5edb27..67eeab18226 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -4,14 +4,14 @@ Module Name: Rise Bidder Adapter Module Type: Bidder Adapter -Maintainer: prebid-digital-brands@ironsrc.com +Maintainer: prebid-rise-engage@risecodes.com # Description Module that connects to Rise's demand sources. -The Rise adapter requires setup and approval from the Rise. Please reach out to prebid-digital-brands@ironsrc.com to create an Rise account. +The Rise adapter requires setup and approval from the Rise. Please reach out to prebid-rise-engage@risecodes.com to create an Rise account. The adapter supports Video(instream). For the integration, Rise returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction. From 3ce6a3bec9b95d3a1acc660995469e152562f567 Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Tue, 5 Jan 2021 18:24:53 +0200 Subject: [PATCH 06/24] fix circle failed --- modules/riseBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 8b662fe6232..a891e0aa883 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -193,7 +193,7 @@ function getEndpoint(testMode) { */ function generateParameters(bid, bidderRequest) { const timeout = config.getConfig('bidderTimeout'); - const { syncEnabled, filterSettings } = config.getConfig('userSync'); + const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; const [ width, height ] = getSizes(bid); const { params } = bid; const { bidderCode } = bidderRequest; From dd10c2557151ef915310257397fef10612b6fb20 Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Tue, 5 Jan 2021 18:39:20 +0200 Subject: [PATCH 07/24] bump From 464d2b742ba59bc8366536cb8a735ec1cc2af036 Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Tue, 5 Jan 2021 18:40:20 +0200 Subject: [PATCH 08/24] bump From 1c520667183a4ac04550fead944b56875f9fe097 Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Tue, 5 Jan 2021 18:41:31 +0200 Subject: [PATCH 09/24] bump --- modules/riseBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index a891e0aa883..209ff06240f 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -191,7 +191,7 @@ function getEndpoint(testMode) { * @param bidderRequest {bidderRequest} * @returns {Object} */ -function generateParameters(bid, bidderRequest) { +function generateParameters(bid, bidderRequest) { const timeout = config.getConfig('bidderTimeout'); const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; const [ width, height ] = getSizes(bid); From 2b1fad5c1ae1ba38e0a7c47d48fd317fcfad90bf Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Wed, 6 Jan 2021 08:38:50 +0200 Subject: [PATCH 10/24] remove space --- modules/riseBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 209ff06240f..a891e0aa883 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -191,7 +191,7 @@ function getEndpoint(testMode) { * @param bidderRequest {bidderRequest} * @returns {Object} */ -function generateParameters(bid, bidderRequest) { +function generateParameters(bid, bidderRequest) { const timeout = config.getConfig('bidderTimeout'); const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; const [ width, height ] = getSizes(bid); From 924bb156405742e3d104913dd25cadf9e0417713 Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Tue, 6 Jul 2021 15:13:44 +0300 Subject: [PATCH 11/24] Upgrade Rise adapter to 5.0 --- modules/riseBidAdapter.js | 26 +++++++++++++++-- test/spec/modules/riseBidAdapter_spec.js | 37 ++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index a891e0aa883..b03c5c15056 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -5,8 +5,9 @@ import {config} from '../src/config.js'; const SUPPORTED_AD_TYPES = [VIDEO]; const BIDDER_CODE = 'rise'; -const BIDDER_VERSION = '4.0.0'; +const BIDDER_VERSION = '4.0.1'; const TTL = 360; +const CURRENCY = 'USD'; const SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; const MODES = { PRODUCTION: 'hb', @@ -53,6 +54,10 @@ export const spec = { mediaType: VIDEO }; + if (body.adomain && body.adomain.length) { + bidResponse.meta = {}; + bidResponse.meta.advertiserDomains = body.adomain + } bidResponses.push(bidResponse); return bidResponses; @@ -82,6 +87,23 @@ export const spec = { registerBidder(spec); +/** + * Get floor price + * @param bid {bid} + * @returns {Number} + */ +function getFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return 0; + } + let floorResult = bid.getFloor({ + currency: CURRENCY, + mediaType: VIDEO, + size: '*' + }); + return floorResult.currency === CURRENCY ? floorResult.floor : 0; +} + /** * Build the video request * @param bid {bid} @@ -206,7 +228,7 @@ function generateParameters(bid, bidderRequest) { width: width, height: height, publisher_id: params.org, - floor_price: params.floorPrice, + floor_price: Math.max(getFloor(bid), params.floorPrice), ua: navigator.userAgent, bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 176437c4f27..b6c2f4fc61a 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/riseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; import { VIDEO } from '../../../src/mediaTypes.js'; +import { deepClone } from 'src/utils.js'; const ENDPOINT = 'https://hb.yellowblue.io/hb'; const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-test'; @@ -250,6 +251,34 @@ describe('riseAdapter', function () { expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,,,,'); } }); + + it('should set floor_price to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 3.32 + } + } + bid.params.floorPrice = 0.64; + const request = spec.buildRequests([bid], bidderRequest)[0]; + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('floor_price', 3.32); + }); + + it('should set floor_price to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 0.8 + } + } + bid.params.floorPrice = 1.5; + const request = spec.buildRequests([bid], bidderRequest)[0]; + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('floor_price', 1.5); + }); }); describe('interpretResponse', function () { @@ -260,7 +289,8 @@ describe('riseAdapter', function () { height: 480, requestId: '21e12606d47ba7', netRevenue: true, - currency: 'USD' + currency: 'USD', + adomain: ['abc.com'] }; it('should get correct bid response', function () { @@ -275,7 +305,10 @@ describe('riseAdapter', function () { netRevenue: true, ttl: TTL, vastXml: '', - mediaType: VIDEO + mediaType: VIDEO, + meta: { + advertiserDomains: ['abc.com'] + } } ]; const result = spec.interpretResponse({ body: response }); From 7cdb4e57187769676080318c85977f5bb11e2857 Mon Sep 17 00:00:00 2001 From: innay Date: Mon, 12 Dec 2022 12:27:35 +0200 Subject: [PATCH 12/24] added isWrapper param --- modules/riseBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 4325a699a3b..0c4d6148f2b 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -400,7 +400,8 @@ function generateGeneralParams(generalObject, bidderRequest) { dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, device_type: getDeviceType(navigator.userAgent), ua: navigator.userAgent, - session_id: getBidIdParameter('auctionId', generalObject), + is_wrapper: !!generalBidParams.isWrapper, + session_id: generalBidParams.sessionId || getBidIdParameter('auctionId', generalObject), tmax: timeout }; From 79acf2147866613de08cd38b5396492b5093bdb0 Mon Sep 17 00:00:00 2001 From: innay Date: Wed, 28 Dec 2022 12:42:26 +0200 Subject: [PATCH 13/24] addes is_wrapper parameter to documentation --- modules/riseBidAdapter.md | 2 ++ test/spec/modules/riseBidAdapter_spec.js | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 9dab3ec2a30..f0837cb5508 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -25,6 +25,8 @@ The adapter supports Video(instream). | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false | `rtbDomain` | optional | String | Sets the seller end point | "www.test.com" +| `is_wrapper` | private | Boolean | Please don't use unless your account manager asked you to | false + # Test Parameters ```javascript diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 37100073407..00e7581795b 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -109,6 +109,7 @@ describe('riseAdapter', function () { const api = [1, 2]; const mimes = ['application/javascript', 'video/mp4', 'video/quicktime']; const protocols = [2, 3, 5, 6]; + const isWrapper = false; it('sends the placementId to ENDPOINT via POST', function () { bidRequests[0].params.placementId = placementId; @@ -116,6 +117,12 @@ describe('riseAdapter', function () { expect(request.data.bids[0].placementId).to.equal(placementId); }); + it('sends the is_wrapper parameter to ENDPOINT via POST', function() { + bidRequests[0].params.isWrapper = isWrapper; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].is_wrapper).to.equal(isWrapper); + }); + it('sends bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.url).to.equal(ENDPOINT); From 36d0c50e298fbec0d0ac77f95d847a5369099ce3 Mon Sep 17 00:00:00 2001 From: innay Date: Wed, 28 Dec 2022 13:27:18 +0200 Subject: [PATCH 14/24] added is_wrapper to test --- test/spec/modules/riseBidAdapter_spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 00e7581795b..bba8c1935ac 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -118,9 +118,11 @@ describe('riseAdapter', function () { }); it('sends the is_wrapper parameter to ENDPOINT via POST', function() { - bidRequests[0].params.isWrapper = isWrapper; + const bidderRequestWithIsWrapper = Object.assign({isWrapper}, bidderRequest); const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[0].is_wrapper).to.equal(isWrapper); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('is_wrapper'); + expect(request.data.params.is_wrapper).to.equal(false); }); it('sends bid request to ENDPOINT via POST', function () { From 3b8885e2b0fc53be64d46ddfdf7449e61d110617 Mon Sep 17 00:00:00 2001 From: innay Date: Wed, 28 Dec 2022 13:37:16 +0200 Subject: [PATCH 15/24] removed isWrapper --- test/spec/modules/riseBidAdapter_spec.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index bba8c1935ac..4fa4ff354ec 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -109,7 +109,6 @@ describe('riseAdapter', function () { const api = [1, 2]; const mimes = ['application/javascript', 'video/mp4', 'video/quicktime']; const protocols = [2, 3, 5, 6]; - const isWrapper = false; it('sends the placementId to ENDPOINT via POST', function () { bidRequests[0].params.placementId = placementId; @@ -118,7 +117,6 @@ describe('riseAdapter', function () { }); it('sends the is_wrapper parameter to ENDPOINT via POST', function() { - const bidderRequestWithIsWrapper = Object.assign({isWrapper}, bidderRequest); const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.params).to.be.an('object'); expect(request.data.params).to.have.property('is_wrapper'); From 63bfe48744776bb30137cf047592b4048e038730 Mon Sep 17 00:00:00 2001 From: YakirLavi <73641910+YakirLavi@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:20:53 +0300 Subject: [PATCH 16/24] Rise Bid Adapter: support Coppa param (#24) --- modules/riseBidAdapter.js | 4 ++++ test/spec/modules/riseBidAdapter_spec.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 89e4e85c627..b536c29e034 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -449,5 +449,9 @@ function generateGeneralParams(generalObject, bidderRequest) { generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); } + if (config.getConfig('coppa') === true) { + generalParams.coppa = 1; + } + return generalParams; } diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 4fa4ff354ec..0d2a5c73105 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -341,6 +341,20 @@ describe('riseAdapter', function () { expect(request.data.bids[0]).to.be.an('object'); expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); + + describe('COPPA param', function () { + it('should add COPPA param to payload when COPPA equal to true', function () { + config.setConfig({ coppa: true }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params.coppa).to.equal(1); + }); + + it('should not add COPPA param to payload when prebid config has parameter COPPA equal to false', function () { + config.setConfig({ coppa: false }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params.coppa).to.be.undefined; + }); + }) }); describe('interpretResponse', function () { From 66c4e7b46121afc5331c8bca6e2fc972fc55f090 Mon Sep 17 00:00:00 2001 From: YakirLavi <73641910+YakirLavi@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:26:39 +0300 Subject: [PATCH 17/24] MinuteMedia Bid Adapter: support Coppa param (#25) --- modules/minutemediaBidAdapter.js | 4 ++++ test/spec/modules/minutemediaBidAdapter_spec.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index a80a37f5ead..82eb48a5a2b 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -428,5 +428,9 @@ function generateGeneralParams(generalObject, bidderRequest) { generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || window.location.href } + if (config.getConfig('coppa') === true) { + generalParams.coppa = 1; + } + return generalParams } diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index bbd23918031..ca1c237914d 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -293,6 +293,20 @@ describe('minutemediaAdapter', function () { expect(request.data.bids[0]).to.be.an('object'); expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); + + describe('COPPA param', function () { + it('should add COPPA param to payload when COPPA equal to true', function () { + config.setConfig({ coppa: true }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params.coppa).to.equal(1); + }); + + it('should not add COPPA param to payload when prebid config has parameter COPPA equal to false', function () { + config.setConfig({ coppa: false }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params.coppa).to.be.undefined; + }); + }) }); describe('interpretResponse', function () { From 6079b08f5435f106c1fee1b080ca5116ac84e4e1 Mon Sep 17 00:00:00 2001 From: YakirLavi <73641910+YakirLavi@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:39:15 +0300 Subject: [PATCH 18/24] Revert "MinuteMedia Bid Adapter: support Coppa param (#25)" (#26) This reverts commit 66c4e7b46121afc5331c8bca6e2fc972fc55f090. --- modules/minutemediaBidAdapter.js | 4 ---- test/spec/modules/minutemediaBidAdapter_spec.js | 14 -------------- 2 files changed, 18 deletions(-) diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index 82eb48a5a2b..a80a37f5ead 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -428,9 +428,5 @@ function generateGeneralParams(generalObject, bidderRequest) { generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || window.location.href } - if (config.getConfig('coppa') === true) { - generalParams.coppa = 1; - } - return generalParams } diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index ca1c237914d..bbd23918031 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -293,20 +293,6 @@ describe('minutemediaAdapter', function () { expect(request.data.bids[0]).to.be.an('object'); expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); - - describe('COPPA param', function () { - it('should add COPPA param to payload when COPPA equal to true', function () { - config.setConfig({ coppa: true }); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.params.coppa).to.equal(1); - }); - - it('should not add COPPA param to payload when prebid config has parameter COPPA equal to false', function () { - config.setConfig({ coppa: false }); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.params.coppa).to.be.undefined; - }); - }) }); describe('interpretResponse', function () { From 4ba684665d97dc638ddcf8e73654bf42c9a94a66 Mon Sep 17 00:00:00 2001 From: YakirLavi Date: Thu, 20 Apr 2023 17:53:30 +0300 Subject: [PATCH 19/24] bump From b42186b0cc06ec255c61217f7e30ac77e9877902 Mon Sep 17 00:00:00 2001 From: YakirLavi Date: Tue, 2 May 2023 15:32:52 +0300 Subject: [PATCH 20/24] update coppa fetch --- modules/riseBidAdapter.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index b536c29e034..22fdb60249e 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -364,6 +364,13 @@ function generateBidParameters(bid, bidderRequest) { if (protocols) { bidObject.protocols = protocols; } + + const coppa = deepAccess(bid, 'ortb2.regs.coppa') + if (coppa) { + bidObject.coppa = 1 + } else { + bidObject.coppa = 0 + } } return bidObject; @@ -449,9 +456,5 @@ function generateGeneralParams(generalObject, bidderRequest) { generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); } - if (config.getConfig('coppa') === true) { - generalParams.coppa = 1; - } - return generalParams; } From db9ede32a114818b77de246fd60f17ecdd762e3a Mon Sep 17 00:00:00 2001 From: YakirLavi Date: Tue, 2 May 2023 16:03:18 +0300 Subject: [PATCH 21/24] setting coppa param update --- modules/riseBidAdapter.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 22fdb60249e..e0f196fb072 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -292,6 +292,7 @@ function generateBidParameters(bid, bidderRequest) { bidderRequestId: getBidIdParameter('bidderRequestId', bid), loop: getBidIdParameter('bidderRequestsCount', bid), transactionId: getBidIdParameter('transactionId', bid), + coppa: 0 }; const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); @@ -367,9 +368,7 @@ function generateBidParameters(bid, bidderRequest) { const coppa = deepAccess(bid, 'ortb2.regs.coppa') if (coppa) { - bidObject.coppa = 1 - } else { - bidObject.coppa = 0 + bidObject.coppa = 1; } } From c587ad6fe74cb70b003f125dc7ef8a619b9fe2b8 Mon Sep 17 00:00:00 2001 From: YakirLavi Date: Thu, 4 May 2023 14:17:52 +0300 Subject: [PATCH 22/24] update Coppa tests --- test/spec/modules/riseBidAdapter_spec.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 0d2a5c73105..94db571f440 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -342,19 +342,23 @@ describe('riseAdapter', function () { expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); - describe('COPPA param', function () { - it('should add COPPA param to payload when COPPA equal to true', function () { - config.setConfig({ coppa: true }); + describe('COPPA Param', function() { + it('should not include coppa flag in bid request if coppa is set to false', function() { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.params.coppa).to.equal(1); + expect(request.data.bids[0].coppa).to.be.equal(0); }); - it('should not add COPPA param to payload when prebid config has parameter COPPA equal to false', function () { - config.setConfig({ coppa: false }); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.params.coppa).to.be.undefined; + it('should include coppa flag in bid request if coppa is set to true', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'regs': { + 'coppa': true, + } + }; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(1); }); - }) + }); }); describe('interpretResponse', function () { From 55f35f7b366c98550767d94cc5bad60408034cd8 Mon Sep 17 00:00:00 2001 From: YakirLavi Date: Thu, 4 May 2023 17:04:48 +0300 Subject: [PATCH 23/24] update test naming --- test/spec/modules/riseBidAdapter_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 94db571f440..d22cbc01d39 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -343,12 +343,12 @@ describe('riseAdapter', function () { }); describe('COPPA Param', function() { - it('should not include coppa flag in bid request if coppa is set to false', function() { + it('should set coppa equal 0 in bid request if coppa is set to false', function() { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].coppa).to.be.equal(0); }); - it('should include coppa flag in bid request if coppa is set to true', function() { + it('should set coppa equal 1 in bid request if coppa is set to true', function() { const bid = utils.deepClone(bidRequests[0]); bid.ortb2 = { 'regs': { From 589f0ec10dae6c11031659eb5dddffdcf5e2433a Mon Sep 17 00:00:00 2001 From: YakirLavi <73641910+YakirLavi@users.noreply.github.com> Date: Wed, 24 May 2023 15:58:35 +0300 Subject: [PATCH 24/24] Rise Bid Adapter: support plcmt and sua (#27) --- modules/riseBidAdapter.js | 16 +++++- test/spec/modules/riseBidAdapter_spec.js | 70 +++++++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index e0f196fb072..1e04ab6239f 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -320,6 +320,16 @@ function generateBidParameters(bid, bidderRequest) { bidObject.api = api; } + const sua = deepAccess(bid, `ortb2.device.sua`); + if (sua) { + bidObject.sua = sua; + } + + const coppa = deepAccess(bid, `ortb2.regs.coppa`) + if (coppa) { + bidObject.coppa = 1; + } + if (mediaType === VIDEO) { const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); let playbackMethodValue; @@ -366,9 +376,9 @@ function generateBidParameters(bid, bidderRequest) { bidObject.protocols = protocols; } - const coppa = deepAccess(bid, 'ortb2.regs.coppa') - if (coppa) { - bidObject.coppa = 1; + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); + if (plcmt) { + bidObject.plcmt = plcmt; } } diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index d22cbc01d39..eed8d74f271 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -62,7 +62,8 @@ describe('riseAdapter', function () { 'mediaTypes': { 'video': { 'playerSize': [[640, 480]], - 'context': 'instream' + 'context': 'instream', + 'plcmt': 1 } }, 'vastXml': '"..."' @@ -116,6 +117,11 @@ describe('riseAdapter', function () { expect(request.data.bids[0].placementId).to.equal(placementId); }); + it('sends the plcmt to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].plcmt).to.equal(1); + }); + it('sends the is_wrapper parameter to ENDPOINT via POST', function() { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.params).to.be.an('object'); @@ -342,6 +348,68 @@ describe('riseAdapter', function () { expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); + it('should check sua param in bid request', function() { + const sua = { + 'platform': { + 'brand': 'macOS', + 'version': ['12', '4', '0'] + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Not;A=Brand', + 'version': [ '99', '0', '0', '0' ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'device': { + 'sua': { + 'platform': { + 'brand': 'macOS', + 'version': [ '12', '4', '0' ] + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Not;A=Brand', + 'version': [ '99', '0', '0', '0' ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } + const requestWithSua = spec.buildRequests([bid], bidderRequest); + const data = requestWithSua.data; + expect(data.bids[0].sua).to.exist; + expect(data.bids[0].sua).to.deep.equal(sua); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sua).to.not.exist; + }); + describe('COPPA Param', function() { it('should set coppa equal 0 in bid request if coppa is set to false', function() { const request = spec.buildRequests(bidRequests, bidderRequest);