From 2c0f9f79490abf2f3f15c1beabd2f77bbb136e17 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Mon, 25 Oct 2021 08:00:15 -0400 Subject: [PATCH 01/70] appnexus bid adapter - update impression urls logic (#7618) --- modules/appnexusBidAdapter.js | 8 +++++--- test/spec/modules/appnexusBidAdapter_spec.js | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 7dcbd74d779..20e002cdc1a 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -696,9 +696,11 @@ function newBid(serverBid, rtbBid, bidderRequest) { }); try { if (rtbBid.rtb.trackers) { - const url = rtbBid.rtb.trackers[0].impression_urls[0]; - const tracker = createTrackPixelHtml(url); - bid.ad += tracker; + for (let i = 0; i < rtbBid.rtb.trackers[0].impression_urls.length; i++) { + const url = rtbBid.rtb.trackers[0].impression_urls[i]; + const tracker = createTrackPixelHtml(url); + bid.ad += tracker; + } } } catch (error) { logError('Error appending tracking pixel', error); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 9396c1e1928..0d553aba705 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1047,7 +1047,8 @@ describe('AppNexusAdapter', function () { 'trackers': [ { 'impression_urls': [ - 'https://lax1-ib.adnxs.com/impression' + 'https://lax1-ib.adnxs.com/impression', + 'https://www.test.com/tracker' ], 'video_events': {} } From efb81c1d5d21cbcc72803a2a14130f9632f9eec3 Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Mon, 25 Oct 2021 15:54:10 +0200 Subject: [PATCH 02/70] Send info about original CPM bid (#7623) --- modules/livewrappedAnalyticsAdapter.js | 5 ++++ .../livewrappedAnalyticsAdapter_spec.js | 27 ++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 64806b793c2..5ef109aef96 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -3,6 +3,7 @@ import {ajax} from '../src/ajax.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; +import { getGlobal } from '../src/prebidGlobal.js'; const ANALYTICSTYPE = 'endpoint'; const URL = 'https://lwadm.com/analytics/10'; @@ -14,6 +15,7 @@ const TIMEOUTSENT = 8; const ADRENDERFAILEDSENT = 16; let initOptions; +let prebidGlobal = getGlobal(); export const BID_WON_TIMEOUT = 500; const cache = { @@ -79,6 +81,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE bidResponse.width = args.width; bidResponse.height = args.height; bidResponse.cpm = args.cpm; + bidResponse.originalCpm = prebidGlobal.convertCurrency(args.originalCpm, args.originalCurrency, args.currency); bidResponse.ttr = args.timeToRespond; bidResponse.readyToSend = 1; bidResponse.mediaType = args.mediaType == 'native' ? 2 : (args.mediaType == 'video' ? 4 : 1); @@ -237,6 +240,7 @@ function getResponses(gdpr, auctionIds) { width: bid.width, height: bid.height, cpm: bid.cpm, + orgCpm: bid.originalCpm, ttr: bid.ttr, IsBid: bid.isBid, mediaType: bid.mediaType, @@ -276,6 +280,7 @@ function getWins(gdpr, auctionIds) { width: bid.width, height: bid.height, cpm: bid.cpm, + orgCpm: bid.originalCpm, mediaType: bid.mediaType, gdpr: gdprPos, floor: bid.lwFloor ? bid.lwFloor : (bid.floorData ? bid.floorData.floorValue : undefined), diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index fc0d4a55e54..3e568c1175d 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -2,6 +2,7 @@ import livewrappedAnalyticsAdapter, { BID_WON_TIMEOUT } from 'modules/livewrappe import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; +import { setConfig } from 'modules/currency.js'; let events = require('src/events'); let utils = require('src/utils'); @@ -28,6 +29,9 @@ const BID1 = { width: 980, height: 240, cpm: 1.1, + originalCpm: 12.0, + currency: 'USD', + originalCurrency: 'FOO', timeToRespond: 200, bidId: '2ecff0db240757', requestId: '2ecff0db240757', @@ -43,6 +47,9 @@ const BID2 = Object.assign({}, BID1, { width: 300, height: 250, cpm: 2.2, + originalCpm: 23.0, + currency: 'USD', + originalCurrency: 'FOO', timeToRespond: 300, bidId: '3ecff0db240757', requestId: '3ecff0db240757', @@ -178,6 +185,7 @@ const ANALYTICS_MESSAGE = { width: 980, height: 240, cpm: 1.1, + orgCpm: 120, ttr: 200, IsBid: true, mediaType: 1, @@ -192,6 +200,7 @@ const ANALYTICS_MESSAGE = { width: 300, height: 250, cpm: 2.2, + orgCpm: 230, ttr: 300, IsBid: true, mediaType: 1, @@ -219,6 +228,7 @@ const ANALYTICS_MESSAGE = { width: 980, height: 240, cpm: 1.1, + orgCpm: 120, mediaType: 1, gdpr: 0, auctionId: 0 @@ -231,6 +241,7 @@ const ANALYTICS_MESSAGE = { width: 300, height: 250, cpm: 2.2, + orgCpm: 230, mediaType: 1, gdpr: 0, auctionId: 0 @@ -279,6 +290,14 @@ describe('Livewrapped analytics adapter', function () { sandbox.stub(document, 'getElementById').returns(element); clock = sandbox.useFakeTimers(1519767013781); + setConfig({ + adServerCurrency: 'USD', + rates: { + USD: { + FOO: 0.1 + } + } + }); }); afterEach(function () { @@ -453,7 +472,7 @@ describe('Livewrapped analytics adapter', function () { { 'floorData': { 'floorValue': 1.1, - 'floorCurrency': 'SEK' + 'floorCurrency': 'FOO' } })); events.emit(BID_WON, Object.assign({}, @@ -461,7 +480,7 @@ describe('Livewrapped analytics adapter', function () { { 'floorData': { 'floorValue': 1.1, - 'floorCurrency': 'SEK' + 'floorCurrency': 'FOO' } })); events.emit(AUCTION_END, MOCK.AUCTION_END); @@ -476,11 +495,11 @@ describe('Livewrapped analytics adapter', function () { expect(message.responses.length).to.equal(1); expect(message.responses[0].floor).to.equal(1.1); - expect(message.responses[0].floorCur).to.equal('SEK'); + expect(message.responses[0].floorCur).to.equal('FOO'); expect(message.wins.length).to.equal(1); expect(message.wins[0].floor).to.equal(1.1); - expect(message.wins[0].floorCur).to.equal('SEK'); + expect(message.wins[0].floorCur).to.equal('FOO'); }); it('should forward Livewrapped floor data', function () { From 6729f8f8239011705fccb1ea6a84159ca7e0ab29 Mon Sep 17 00:00:00 2001 From: mamatic <52153441+mamatic@users.noreply.github.com> Date: Mon, 25 Oct 2021 16:05:23 +0200 Subject: [PATCH 03/70] Ats Analytics Adapter: listen to bid won events (#7577) * ATS-analytics-adapter - add bid_won logic * ATS-analytics-adapter - increase timeout in order to get bid_won events * ATS-analytics-adapter - make bid_won timeout configurable * ATS-analytics-adapter - change readme file --- modules/atsAnalyticsAdapter.js | 113 +++++++++++------- modules/atsAnalyticsAdapter.md | 1 + test/spec/modules/atsAnalyticsAdapter_spec.js | 51 ++++++-- 3 files changed, 110 insertions(+), 55 deletions(-) diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index d1e520b4b8f..f45d2e80055 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -20,7 +20,7 @@ export const analyticsUrl = 'https://analytics.rlcdn.com'; let handlerRequest = []; let handlerResponse = []; -let atsAnalyticsAdapterVersion = 2; +let atsAnalyticsAdapterVersion = 3; let browsersList = [ /* Googlebot */ @@ -222,7 +222,8 @@ function bidRequestedHandler(args) { auction_start: new Date(args.auctionStart).toJSON(), domain: window.location.hostname, pid: atsAnalyticsAdapter.context.pid, - adapter_version: atsAnalyticsAdapterVersion + adapter_version: atsAnalyticsAdapterVersion, + bid_won: false }; }); return requests; @@ -251,13 +252,14 @@ export function parseBrowser() { } } -function sendDataToAnalytic () { +function sendDataToAnalytic (events) { // send data to ats analytic endpoint try { - let dataToSend = {'Data': atsAnalyticsAdapter.context.events}; + let dataToSend = {'Data': events}; let strJSON = JSON.stringify(dataToSend); logInfo('ATS Analytics - tried to send analytics data!'); ajax(analyticsUrl, function () { + logInfo('ATS Analytics - events sent successfully!'); }, strJSON, {method: 'POST', contentType: 'application/json'}); } catch (err) { logError('ATS Analytics - request encounter an error: ', err); @@ -265,7 +267,7 @@ function sendDataToAnalytic () { } // preflight request, to check did publisher have permission to send data to analytics endpoint -function preflightRequest (envelopeSourceCookieValue) { +function preflightRequest (envelopeSourceCookieValue, events) { logInfo('ATS Analytics - preflight request!'); ajax(preflightUrl + atsAnalyticsAdapter.context.pid, { @@ -276,7 +278,8 @@ function preflightRequest (envelopeSourceCookieValue) { atsAnalyticsAdapter.setSamplingCookie(samplingRate); let samplingRateNumber = Number(samplingRate); if (data && samplingRate && atsAnalyticsAdapter.shouldFireRequest(samplingRateNumber) && envelopeSourceCookieValue != null) { - sendDataToAnalytic(); + logInfo('ATS Analytics - events to send: ', events); + sendDataToAnalytic(events); } }, error: function () { @@ -286,29 +289,6 @@ function preflightRequest (envelopeSourceCookieValue) { }, undefined, {method: 'GET', crossOrigin: true}); } -function callHandler(evtype, args) { - if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) { - handlerRequest = handlerRequest.concat(bidRequestedHandler(args)); - } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) { - handlerResponse.push(bidResponseHandler(args)); - } - if (evtype === CONSTANTS.EVENTS.AUCTION_END) { - if (handlerRequest.length) { - let events = []; - if (handlerResponse.length) { - events = handlerRequest.filter(request => handlerResponse.filter(function(response) { - if (request.bid_id === response.bid_id) { - Object.assign(request, response); - } - })); - } else { - events = handlerRequest; - } - atsAnalyticsAdapter.context.events = events; - } - } -} - let atsAnalyticsAdapter = Object.assign(adapter( { analyticsType @@ -316,22 +296,7 @@ let atsAnalyticsAdapter = Object.assign(adapter( { track({eventType, args}) { if (typeof args !== 'undefined') { - callHandler(eventType, args); - } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); - try { - let samplingRateCookie = storage.getCookie('_lr_sampling_rate'); - if (!samplingRateCookie) { - preflightRequest(envelopeSourceCookieValue); - } else { - if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) { - sendDataToAnalytic(); - } - } - } catch (err) { - logError('ATS Analytics - preflight request encounter an error: ', err); - } + atsAnalyticsAdapter.callHandler(eventType, args); } } }); @@ -369,13 +334,69 @@ atsAnalyticsAdapter.enableAnalytics = function (config) { } atsAnalyticsAdapter.context = { events: [], - pid: config.options.pid + pid: config.options.pid, + bidWonTimeout: config.options.bidWonTimeout }; let initOptions = config.options; logInfo('ATS Analytics - adapter enabled! '); atsAnalyticsAdapter.originEnableAnalytics(initOptions); // call the base class function }; +atsAnalyticsAdapter.callHandler = function (evtype, args) { + if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) { + handlerRequest = handlerRequest.concat(bidRequestedHandler(args)); + } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) { + handlerResponse.push(bidResponseHandler(args)); + } + if (evtype === CONSTANTS.EVENTS.AUCTION_END) { + let bidWonTimeout = atsAnalyticsAdapter.context.bidWonTimeout ? atsAnalyticsAdapter.context.bidWonTimeout : 2000; + let events = []; + setTimeout(() => { + let winningBids = $$PREBID_GLOBAL$$.getAllWinningBids(); + logInfo('ATS Analytics - winning bids: ', winningBids) + // prepare format data for sending to analytics endpoint + if (handlerRequest.length) { + let wonEvent = {}; + if (handlerResponse.length) { + events = handlerRequest.filter(request => handlerResponse.filter(function (response) { + if (request.bid_id === response.bid_id) { + Object.assign(request, response); + } + })); + if (winningBids.length) { + events = events.filter(event => winningBids.filter(function (won) { + wonEvent.bid_id = won.requestId; + wonEvent.bid_won = true; + if (event.bid_id === wonEvent.bid_id) { + Object.assign(event, wonEvent); + } + })) + } + } else { + events = handlerRequest; + } + // check should we send data to analytics or not, check first cookie value _lr_sampling_rate + try { + let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); + let samplingRateCookie = storage.getCookie('_lr_sampling_rate'); + if (!samplingRateCookie) { + preflightRequest(envelopeSourceCookieValue, events); + } else { + if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) { + logInfo('ATS Analytics - events to send: ', events); + sendDataToAnalytic(events); + } + } + // empty events array to not send duplicate events + events = []; + } catch (err) { + logError('ATS Analytics - preflight request encounter an error: ', err); + } + } + }, bidWonTimeout); + } +} + adaptermanager.registerAnalyticsAdapter({ adapter: atsAnalyticsAdapter, code: 'atsAnalytics', diff --git a/modules/atsAnalyticsAdapter.md b/modules/atsAnalyticsAdapter.md index 7c634f39ae2..17819ac61b3 100644 --- a/modules/atsAnalyticsAdapter.md +++ b/modules/atsAnalyticsAdapter.md @@ -17,6 +17,7 @@ Analytics adapter for Authenticated Traffic Solution(ATS), provided by LiveRamp. provider: 'atsAnalytics', options: { pid: '999', // publisher ID + bidWonTimeout: 2000 // on auction end for how long to wait for bid_won events, by default it's 2000 miliseconds, if it's not set it will be 2000 miliseconds. } } ``` diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js index 7f662ffd06d..cae90a19223 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -12,11 +12,15 @@ let constants = require('src/constants.json'); export const storage = getStorageManager(); let sandbox; +let clock; +let now = new Date(); + describe('ats analytics adapter', function () { beforeEach(function () { sinon.stub(events, 'getEvents').returns([]); storage.setCookie('_lr_env_src_ats', 'true', 'Thu, 01 Jan 1970 00:00:01 GMT'); sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(now.getTime()); }); afterEach(function () { @@ -25,18 +29,20 @@ describe('ats analytics adapter', function () { atsAnalyticsAdapter.disableAnalytics(); Math.random.restore(); sandbox.restore(); + clock.restore(); }); describe('track', function () { it('builds and sends request and response data', function () { sinon.stub(Math, 'random').returns(0.99); - sinon.stub(atsAnalyticsAdapter, 'shouldFireRequest').returns(true); sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); - let now = new Date(); + now.setTime(now.getTime() + 3600000); storage.setCookie('_lr_env_src_ats', 'true', now.toUTCString()); storage.setCookie('_lr_sampling_rate', '10', now.toUTCString()); + this.timeout(2100); + let initOptions = { pid: '10433394' }; @@ -62,7 +68,7 @@ describe('ats analytics adapter', function () { 'refererInfo': { 'referer': 'https://example.com/dev' }, - 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' }; // prepare general auction - response let bidResponse = { @@ -90,7 +96,7 @@ describe('ats analytics adapter', function () { let expectedAfterBid = { 'Data': [{ 'has_envelope': true, - 'adapter_version': 2, + 'adapter_version': 3, 'bidder': 'appnexus', 'bid_id': '30c77d079cdf17', 'auction_id': 'a5b849e5-87d7-4205-8300-d063084fcfb7', @@ -103,10 +109,30 @@ describe('ats analytics adapter', function () { 'response_time_stamp': '2020-02-03T14:23:11.978Z', 'currency': 'USD', 'cpm': 0.5, - 'net_revenue': true + 'net_revenue': true, + 'bid_won': true }] }; + let wonRequest = { + 'adId': '2eddfdc0c791dc', + 'mediaType': 'banner', + 'requestId': '30c77d079cdf17', + 'cpm': 0.5, + 'creativeId': 29681110, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'statusMessage': 'Bid available', + 'responseTimestamp': 1633525319061, + 'requestTimestamp': 1633525319258, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'size': '300x250', + 'status': 'rendered' + }; + // lets simulate that some bidders timeout let bidTimeoutArgsV1 = [ { @@ -148,6 +174,14 @@ describe('ats analytics adapter', function () { // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, {}); + // Step 6: Send bid won event + events.emit(constants.EVENTS.BID_WON, wonRequest); + + sandbox.stub($$PREBID_GLOBAL$$, 'getAllWinningBids').callsFake((key) => { + return [wonRequest] + }); + + clock.tick(2000); let requests = server.requests.filter(req => { return req.url.indexOf(analyticsUrl) > -1; @@ -156,13 +190,12 @@ describe('ats analytics adapter', function () { expect(requests.length).to.equal(1); let realAfterBid = JSON.parse(requests[0].requestBody); - // Step 6: assert real data after bid and expected data + + // Step 7: assert real data after bid and expected data expect(realAfterBid['Data']).to.deep.equal(expectedAfterBid['Data']); // check that the publisher ID is configured via options expect(atsAnalyticsAdapter.context.pid).to.equal(initOptions.pid); - - atsAnalyticsAdapter.shouldFireRequest.restore(); }) it('check browser is safari', function () { sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); @@ -204,7 +237,7 @@ describe('ats analytics adapter', function () { sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); sinon.stub(Math, 'random').returns(0.99); // publisher can try to pass anything they want but we will set sampling rate to 100, which means we will have 1% of requests - let result = atsAnalyticsAdapter.shouldFireRequest(10); + let result = atsAnalyticsAdapter.shouldFireRequest(8); expect(result).to.equal(true); }) it('should not fire analytics request if math random is something other then 0.99', function () { From be35c1c28aab4e507b310724f1c2e23c3cacb0eb Mon Sep 17 00:00:00 2001 From: jsfledd Date: Mon, 25 Oct 2021 11:35:24 -0700 Subject: [PATCH 04/70] Nativo Bid Adapter - Refactored spread adapter for IE11 support (#7625) * Initial nativoBidAdapter document creation (js, md and spec) * Fulling working prebid using nativoBidAdapter. Support for GDPR and CCPA in user syncs. * Added defult size settings based on the largest ad unit. Added response body validation. Added consent to request url qs params. * Changed bidder endpoint url * Changed double quotes to single quotes. * Reverted package-json.lock to remove modifications from PR * Added optional bidder param 'url' so the ad server can force- match an existing placement * Lint fix. Added space after if. * Added new QS param to send various adUnit data to adapter endpopint * Updated unit test for new QS param * Added qs param to keep track of ad unit refreshes * Updated bidMap key default value * Updated refresh increment logic * Refactored spread operator for IE11 support Co-authored-by: Joshua Fledderjohn --- modules/nativoBidAdapter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index 9b7ffef8957..06586b80102 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -98,7 +98,11 @@ export const spec = { ] if (placementIds.size > 0) { - params.unshift({ key: 'ntv_ptd', value: placementIds.toString() }) + // Convert Set to Array (IE 11 Safe) + const placements = [] + placementIds.forEach((value) => placements.push(value)) + // Append to query string paramters + params.unshift({ key: 'ntv_ptd', value: placements.join(',') }) } if (bidderRequest.gdprConsent) { From aee50395ad134549463a50dab529aabd085dfec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Mon, 25 Oct 2021 22:25:11 +0200 Subject: [PATCH 05/70] support eids in outbrain (#7567) --- modules/outbrainBidAdapter.js | 5 +++++ test/spec/modules/outbrainBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 3dd9c67dc98..439570e976e 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -40,6 +40,7 @@ export const spec = { const publisher = setOnAny(validBidRequests, 'params.publisher'); const bcat = setOnAny(validBidRequests, 'params.bcat'); const badv = setOnAny(validBidRequests, 'params.badv'); + const eids = setOnAny(validBidRequests, 'userIdAsEids') const cur = CURRENCY; const endpointUrl = config.getConfig('outbrain.bidderUrl'); const timeout = bidderRequest.timeout; @@ -105,6 +106,10 @@ export const spec = { deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1) } + if (eids) { + deepSetValue(request, 'user.ext.eids', eids); + } + return { method: 'POST', url: endpointUrl, diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index a5f23240a7c..4bc163aefe6 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -2,6 +2,7 @@ import {expect} from 'chai'; import {spec} from 'modules/outbrainBidAdapter.js'; import {config} from 'src/config.js'; import {server} from 'test/mocks/xhr'; +import { createEidsArray } from 'modules/userId/eids.js'; describe('Outbrain Adapter', function () { describe('Bid request and response', function () { @@ -344,6 +345,23 @@ describe('Outbrain Adapter', function () { config.resetConfig() }); + + it('should pass extended ids', function () { + let bidRequest = { + bidId: 'bidId', + params: {}, + userIdAsEids: createEidsArray({ + idl_env: 'id-value', + }), + ...commonBidRequest, + }; + + let res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data) + expect(resData.user.ext.eids).to.deep.equal([ + {source: 'liveramp.com', uids: [{id: 'id-value', atype: 3}]} + ]); + }); }) describe('interpretResponse', function () { From f54ccecc29f41245d2a8639779f9413fae0636c1 Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Mon, 25 Oct 2021 17:05:20 -0400 Subject: [PATCH 06/70] Send ortb2 object to sonobi bidding endpoint as fpd param (#7612) --- modules/sonobiBidAdapter.js | 7 +++++ test/spec/modules/sonobiBidAdapter_spec.js | 35 +++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 01966f3d6b1..c5fc07320d8 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -83,8 +83,15 @@ export const spec = { 'lib_name': 'prebid', 'lib_v': '$prebid.version$', 'us': 0, + }; + const fpd = config.getConfig('ortb2'); + + if (fpd) { + payload.fpd = JSON.stringify(fpd); + } + if (config.getConfig('userSync') && config.getConfig('userSync').syncsPerBidder) { payload.us = config.getConfig('userSync').syncsPerBidder; } diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 05ba3f0897b..f56f4e0c12b 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -238,14 +238,17 @@ describe('SonobiBidAdapter', function () { }); describe('.buildRequests', function () { + let sandbox; beforeEach(function() { sinon.stub(userSync, 'canBidderRegisterSync'); sinon.stub(utils, 'getGptSlotInfoForAdUnitCode') - .onFirstCall().returns({gptSlot: '/123123/gpt_publisher/adunit-code-3', divId: 'adunit-code-3-div-id'}) + .onFirstCall().returns({gptSlot: '/123123/gpt_publisher/adunit-code-3', divId: 'adunit-code-3-div-id'}); + sandbox = sinon.createSandbox(); }); afterEach(function() { userSync.canBidderRegisterSync.restore(); utils.getGptSlotInfoForAdUnitCode.restore(); + sandbox.restore(); }); let bidRequest = [{ 'schain': { @@ -333,6 +336,36 @@ describe('SonobiBidAdapter', function () { uspConsent: 'someCCPAString' }; + it('should set fpd if there is any data in ortb2', function() { + const ortb2 = { + site: { + ext: { + data: { + pageType: 'article', + category: 'tools' + } + } + }, + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + }; + + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + ortb2: ortb2 + }; + return utils.deepAccess(config, key); + }); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.fpd).to.equal(JSON.stringify(ortb2)); + }); + it('should populate coppa as 1 if set in config', function () { config.setConfig({coppa: true}); const bidRequests = spec.buildRequests(bidRequest, bidderRequests); From 4dc1a22482ade356830af1e1ec7902977f9fe66c Mon Sep 17 00:00:00 2001 From: nwlosinski Date: Mon, 25 Oct 2021 23:25:34 +0200 Subject: [PATCH 07/70] support for adserverTargeting in response (#7605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Norbert Włosiński --- modules/justpremiumBidAdapter.js | 7 ++++++- test/spec/modules/justpremiumBidAdapter_spec.js | 11 +++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index fa0f939affc..56f9935ea6e 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -4,7 +4,7 @@ import { deepAccess } from '../src/utils.js'; const BIDDER_CODE = 'justpremium' const GVLID = 62 const ENDPOINT_URL = 'https://pre.ads.justpremium.com/v/2.0/t/xhr' -const JP_ADAPTER_VERSION = '1.8' +const JP_ADAPTER_VERSION = '1.8.1' const pixels = [] export const spec = { @@ -101,6 +101,11 @@ export const spec = { advertiserDomains: bid.adomain && bid.adomain.length > 0 ? bid.adomain : [] } } + if (bid.ext && bid.ext.pg) { + bidResponse.adserverTargeting = { + 'hb_deal_justpremium': 'jp_pg' + } + } bidResponses.push(bidResponse) } }) diff --git a/test/spec/modules/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index 74526660f61..edc5325def3 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -97,7 +97,7 @@ describe('justpremium adapter', function () { expect(jpxRequest.id).to.equal(adUnits[0].params.zone) expect(jpxRequest.mediaTypes && jpxRequest.mediaTypes.banner && jpxRequest.mediaTypes.banner.sizes).to.not.equal('undefined') expect(jpxRequest.version.prebid).to.equal('$prebid.version$') - expect(jpxRequest.version.jp_adapter).to.equal('1.8') + expect(jpxRequest.version.jp_adapter).to.equal('1.8.1') expect(jpxRequest.pubcid).to.equal('0000000') expect(jpxRequest.uids.tdid).to.equal('1111111') expect(jpxRequest.uids.id5id.uid).to.equal('2222222') @@ -118,7 +118,10 @@ describe('justpremium adapter', function () { 'price': 0.52, 'format': 'lb', 'adm': 'creative code', - 'adomain': ['justpremium.com'] + 'adomain': ['justpremium.com'], + 'ext': { + 'pg': true + } }] }, 'pass': { @@ -142,6 +145,9 @@ describe('justpremium adapter', function () { meta: { advertiserDomains: ['justpremium.com'] }, + adserverTargeting: { + 'hb_deal_justpremium': 'jp_pg' + } } ] @@ -159,6 +165,7 @@ describe('justpremium adapter', function () { expect(result[0].netRevenue).to.equal(true) expect(result[0].format).to.equal('lb') expect(result[0].meta.advertiserDomains[0]).to.equal('justpremium.com') + expect(result[0].adserverTargeting).to.deep.equal({'hb_deal_justpremium': 'jp_pg'}) }) it('Verify wrong server response', function () { From df13627d34fce5a04356875f90d26fd99d0627a0 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 26 Oct 2021 06:19:48 -0400 Subject: [PATCH 08/70] Between Bid Adapter & Airgrid rtd module: fix support for ie11 (#7619) * Update betweenBidAdapter.js * Update yahoosspBidAdapter.js * Update adapterManager_spec.js * Update adapterManager_spec.js * Update airgridRtdProvider.js * Update yahoosspBidAdapter.js --- modules/airgridRtdProvider.js | 2 +- modules/betweenBidAdapter.js | 3 ++- test/spec/unit/core/adapterManager_spec.js | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 8d212204da8..f5403cca3eb 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -33,7 +33,7 @@ export function attachScriptTagToDOM(rtdConfig) { edktInitializor.load = function(e) { var p = e || 'sdk'; var n = document.createElement('script'); - n.type = 'text/javascript'; + n.type = 'module'; n.async = true; n.src = 'https://cdn.edkt.io/' + p + '/edgekit.min.js'; document.getElementsByTagName('head')[0].appendChild(n); diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index 09c8678d1ff..b2f63488e12 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -6,6 +6,7 @@ const BIDDER_CODE = 'between'; let ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid'; const CODE_TYPES = ['inpage', 'preroll', 'midroll', 'postroll']; +const includes = require('core-js-pure/features/array/includes.js'); export const spec = { code: BIDDER_CODE, aliases: ['btw'], @@ -53,7 +54,7 @@ export const spec = { params.mind = video.mind; params.pos = 'atf'; ENDPOINT += '&jst=pvc'; - params.codeType = CODE_TYPES.includes(video.codeType) ? video.codeType : 'inpage'; + params.codeType = includes(CODE_TYPES, video.codeType) ? video.codeType : 'inpage'; } if (i.params.itu !== undefined) { diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 75fd74748a0..30aa30c52e9 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -456,7 +456,8 @@ describe('adapterManager tests', function () { }); it('should call spec\'s onBidderError callback when callBidderError is called', function () { - const bidderRequest = getBidRequests().find(bidRequest => bidRequest.bidderCode === bidder); + const bidRequests = getBidRequests(); + const bidderRequest = find(bidRequests, bidRequest => bidRequest.bidderCode === bidder); const xhrErrorMock = { status: 500, statusText: 'Internal Server Error' From 6c14892fb673f33b4024e8374d31fdc8ff1b3921 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 26 Oct 2021 03:31:43 -0700 Subject: [PATCH 09/70] Yahoo Bid Adapter: Support IE11 -> includes to indexOf (#7627) * includes to indexOf * lint --- modules/yahoosspBidAdapter.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/yahoosspBidAdapter.js b/modules/yahoosspBidAdapter.js index ac91596f8d0..101cb0ca9e3 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahoosspBidAdapter.js @@ -144,9 +144,9 @@ function getAdapterMode() { function getResponseFormat(bid) { const adm = bid.adm; - if (adm.includes('o2playerSettings') || adm.includes('YAHOO.VideoPlatform.VideoPlayer') || adm.includes('AdPlacement')) { + if (adm.indexOf('o2playerSettings') !== -1 || adm.indexOf('YAHOO.VideoPlatform.VideoPlayer') !== -1 || adm.indexOf('AdPlacement') !== -1) { return BANNER; - } else if (adm.includes('VAST')) { + } else if (adm.indexOf('VAST') !== -1) { return VIDEO; } }; @@ -188,23 +188,23 @@ function validateAppendObject(validationType, allowedKeys, inputObject, appendTo for (const objectKey in inputObject) { switch (validationType) { case 'string': - if (allowedKeys.includes(objectKey) && isStr(inputObject[objectKey])) { + if (allowedKeys.indexOf(objectKey) !== -1 && isStr(inputObject[objectKey])) { outputObject[objectKey] = inputObject[objectKey]; }; break; case 'number': - if (allowedKeys.includes(objectKey) && isNumber(inputObject[objectKey])) { + if (allowedKeys.indexOf(objectKey) !== -1 && isNumber(inputObject[objectKey])) { outputObject[objectKey] = inputObject[objectKey]; }; break; case 'array': - if (allowedKeys.includes(objectKey) && isArray(inputObject[objectKey])) { + if (allowedKeys.indexOf(objectKey) !== -1 && isArray(inputObject[objectKey])) { outputObject[objectKey] = inputObject[objectKey]; }; break; case 'object': - if (allowedKeys.includes(objectKey) && isPlainObject(inputObject[objectKey])) { + if (allowedKeys.indexOf(objectKey) !== -1 && isPlainObject(inputObject[objectKey])) { outputObject[objectKey] = inputObject[objectKey]; }; break; From 523a16c3cc0ce71235add8493b4336164800c50c Mon Sep 17 00:00:00 2001 From: PWyrembak Date: Tue, 26 Oct 2021 15:15:39 +0300 Subject: [PATCH 10/70] TrustX Bid Adapter: fix for segments format (#7629) * Add trustx adapter and tests for it * update integration example * Update trustx adapter * Post-review fixes of Trustx adapter * Code improvement for trustx adapter: changed default price type from gross to net * Update TrustX adapter to support the 1.0 version * Make requested changes for TrustX adapter * Updated markdown file for TrustX adapter * Fix TrustX adapter and spec file * Update TrustX adapter: r parameter was added to ad request as cache buster * Add support of gdpr to Trustx Bid Adapter * Add wtimeout to ad request params for TrustX Bid Adapter * TrustX Bid Adapter: remove last ampersand in the ad request * Update TrustX Bid Adapter to support identical uids in parameters * Update TrustX Bid Adapter to ignore bids that sizes do not match the size of the request * Update TrustX Bid Adapter to support instream and outstream video * Added wrapperType and wrapperVersion parameters in ad request for TrustX Bid Adapter * Update TrustX Bid Adapter to use refererInfo instead depricated function utils.getTopWindowUrl * HOTFIX for referrer encodind in TrustX Bid Adapter * Fix test for TrustX Bid Adapter * TrustX Bid Adapter: added keywords passing support * TrustX Bid Adapter: added us_privacy parameter in bid request * TrustX Bid Adapter: fix us_privacy parameter in bid request * Fix alias error for TrustX Bid Adapter * TrustX Bid Adapter: added new request format * TrustX Bid adapter: fix new format endpoint * TrustX Bid Adapter: update md file to support useNewFormat parameter * TrustX Bid Adapter: added additional sync url * TrustX Bid Adapter: added check for enabled syncs number + added gdpr data to sync urls * TrustX Bid Adapter: added support of meta.advertiserDomains * TrustX Bid Adapter: added support rtd permutive and jwplayer for new and old request format * TrustX Bid Adapter: Use new format by default + new keywords logic * TrustX Bid Adapter: fix md file * TrustX: Convert all id-like request fields to a string * TrustX: added vastUrl support * TrustX: fix segments format --- modules/trustxBidAdapter.js | 14 ++++++++------ test/spec/modules/trustxBidAdapter_spec.js | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index e74dbd6b4d8..9907d1b2ff4 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -429,9 +429,10 @@ function addSegments(name, segName, segments, data, bidConfigName) { if (segments && segments.length) { data.push({ name: name, - segment: segments.map((seg) => { - return {name: segName, value: seg}; - }) + segment: segments + .map((seg) => seg && (seg.id || seg)) + .filter((seg) => seg && (typeof seg === 'string' || typeof seg === 'number')) + .map((seg) => ({ name: segName, value: seg.toString() })) }); } else if (bidConfigName) { const configData = config.getConfig('ortb2.user.data'); @@ -445,9 +446,10 @@ function addSegments(name, segName, segments, data, bidConfigName) { if (segData && segData.length) { data.push({ name: name, - segment: segData.map((seg) => { - return {name: segName, value: seg}; - }) + segment: segData + .map((seg) => seg && (seg.id || seg)) + .filter((seg) => seg && (typeof seg === 'string' || typeof seg === 'number')) + .map((seg) => ({ name: segName, value: seg.toString() })) }); } } diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 435f0402f3a..73bc9d45365 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -402,7 +402,7 @@ describe('TrustXAdapter', function () { }); it('if segment is present in permutive targeting, payload must have right params', function () { - const permSegments = ['test_perm_1', 'test_perm_2']; + const permSegments = [{id: 'test_perm_1'}, {id: 'test_perm_2'}]; const bidRequestsWithPermutiveTargeting = bidRequests.map((bid) => { return Object.assign({ rtd: { @@ -421,8 +421,8 @@ describe('TrustXAdapter', function () { expect(payload.user.data).to.deep.equal([{ name: 'permutive', segment: [ - {name: 'p_standard', value: permSegments[0]}, - {name: 'p_standard', value: permSegments[1]} + {name: 'p_standard', value: permSegments[0].id}, + {name: 'p_standard', value: permSegments[1].id} ] }]); }); From d0605aeb28f5b4c7b4d4c2c57f1a35c3410b7e62 Mon Sep 17 00:00:00 2001 From: Rikard Drugge <38916683+rikdru@users.noreply.github.com> Date: Tue, 26 Oct 2021 16:02:39 +0200 Subject: [PATCH 11/70] Delta Projects bid adapter: add new bid adapter (#7564) * Delta Projects bid adapter: add new bid adapter * Delta Projects bid adapter: revert accidental change to hello_world.html * Remove unsupported functions by IE, add support for floor price remove bidderParams which is not currently supported remove bid parameter floor remove unused function so linting is happy Remove unused params in tests use adservercurrency include .js to make linter happy again Co-authored-by: Boris-Tang --- modules/deltaprojectsBidAdapter.js | 252 +++++++++++ modules/deltaprojectsBidAdapter.md | 32 ++ .../modules/deltaprojectsBidAdapter_spec.js | 399 ++++++++++++++++++ 3 files changed, 683 insertions(+) create mode 100644 modules/deltaprojectsBidAdapter.js create mode 100644 modules/deltaprojectsBidAdapter.md create mode 100644 test/spec/modules/deltaprojectsBidAdapter_spec.js diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js new file mode 100644 index 00000000000..33df5bd252e --- /dev/null +++ b/modules/deltaprojectsBidAdapter.js @@ -0,0 +1,252 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { + _each, _map, isFn, isNumber, createTrackPixelHtml, deepAccess, parseUrl, logWarn, logError +} from '../src/utils.js'; +import {config} from '../src/config.js'; + +export const BIDDER_CODE = 'deltaprojects'; +export const BIDDER_ENDPOINT_URL = 'https://d5p.de17a.com/dogfight/prebid'; +export const USERSYNC_URL = 'https://userservice.de17a.com/getuid/prebid'; + +/** -- isBidRequestValid --**/ +function isBidRequestValid(bid) { + if (!bid) return false; + + if (bid.bidder !== BIDDER_CODE) return false; + + // publisher id is required + const publisherId = deepAccess(bid, 'params.publisherId') + if (!publisherId) { + logError('Invalid bid request, missing publisher id in params'); + return false; + } + + return true; +} + +/** -- Build requests --**/ +function buildRequests(validBidRequests, bidderRequest) { + /** == shared ==**/ + // -- build id + const id = bidderRequest.auctionId; + + // -- build site + const loc = parseUrl(bidderRequest.refererInfo.referer); + const publisherId = setOnAny(validBidRequests, 'params.publisherId'); + const siteId = setOnAny(validBidRequests, 'params.siteId'); + const site = { + id: siteId, + domain: loc.hostname, + page: loc.href, + ref: loc.href, + publisher: { id: publisherId }, + }; + + // -- build device + const ua = navigator.userAgent; + const device = { + ua, + w: screen.width, + h: screen.height + } + + // -- build user, reg + let user = { ext: {} }; + const regs = { ext: {} }; + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + if (gdprConsent) { + user.ext = { consent: gdprConsent.consentString }; + if (typeof gdprConsent.gdprApplies == 'boolean') { + regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0 + } + } + + // -- build tmax + let tmax = (bidderRequest && bidderRequest.timeout > 0) ? bidderRequest.timeout : undefined; + + // build bid specific + return validBidRequests.map(validBidRequest => { + const openRTBRequest = buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs); + return { + method: 'POST', + url: BIDDER_ENDPOINT_URL, + data: openRTBRequest, + options: { contentType: 'application/json' }, + bids: [validBidRequest], + }; + }); +} + +function buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs) { + // build cur + const currency = config.getConfig('currency.adServerCurrency') || deepAccess(validBidRequest, 'params.currency'); + const cur = currency && [currency]; + + // build impression + const impression = buildImpression(validBidRequest, currency); + + // build test + const test = deepAccess(validBidRequest, 'params.test') ? 1 : 0 + + const at = 1 + + // build source + const source = { + tid: validBidRequest.transactionId, + fd: 1, + } + + return { + id, + at, + imp: [impression], + site, + device, + user, + test, + tmax, + cur, + source, + regs, + ext: {}, + }; +} + +function buildImpression(bid, currency) { + const impression = { + id: bid.bidId, + tagid: bid.params.tagId, + ext: {}, + }; + + const bannerMediaType = deepAccess(bid, `mediaTypes.${BANNER}`); + impression.banner = buildImpressionBanner(bid, bannerMediaType); + + // bid floor + const bidFloor = getBidFloor(bid, BANNER, '*', currency); + if (bidFloor) { + impression.bidfloor = bidFloor.floor; + impression.bidfloorcur = bidFloor.currency; + } + + return impression; +} + +function buildImpressionBanner(bid, bannerMediaType) { + const bannerSizes = (bannerMediaType && bannerMediaType.sizes) || bid.sizes; + return { + format: _map(bannerSizes, ([width, height]) => ({ w: width, h: height })), + }; +} + +/** -- Interpret response --**/ +function interpretResponse(serverResponse) { + if (!serverResponse.body) { + logWarn('Response body is invalid, return !!'); + return []; + } + + const { body: { id, seatbid, cur } } = serverResponse; + if (!id || !seatbid) { + logWarn('Id / seatbid of response is invalid, return !!'); + return []; + } + + const bidResponses = []; + + _each(seatbid, seatbid => { + _each(seatbid.bid, bid => { + const bidObj = { + requestId: bid.impid, + cpm: parseFloat(bid.price), + width: parseInt(bid.w), + height: parseInt(bid.h), + creativeId: bid.crid || bid.id, + dealId: bid.dealid || null, + currency: cur, + netRevenue: true, + ttl: 60, + }; + + bidObj.mediaType = BANNER; + bidObj.ad = bid.adm; + if (bid.nurl) { + bidObj.ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); + } + if (bid.ext) { + bidObj[BIDDER_CODE] = bid.ext; + } + bidResponses.push(bidObj); + }); + }); + return bidResponses; +} + +/** -- On Bid Won -- **/ +function onBidWon(bid) { + let cpm = bid.cpm; + if (bid.currency && bid.currency !== bid.originalCurrency && typeof bid.getCpmInNewCurrency === 'function') { + cpm = bid.getCpmInNewCurrency(bid.originalCurrency); + } + const wonPrice = Math.round(cpm * 1000000); + const wonPriceMacroPatten = /\$\{AUCTION_PRICE:B64\}/g; + bid.ad = bid.ad.replace(wonPriceMacroPatten, wonPrice); +} + +/** -- Get user syncs --**/ +function getUserSyncs(syncOptions, serverResponses, gdprConsent) { + const syncs = [] + + if (syncOptions.pixelEnabled) { + let gdprParams; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + } + } else { + gdprParams = ''; + } + syncs.push({ + type: 'image', + url: USERSYNC_URL + gdprParams + }); + } + return syncs; +} + +/** -- Get bid floor --**/ +export function getBidFloor(bid, mediaType, size, currency) { + if (isFn(bid.getFloor)) { + const bidFloorCurrency = currency || 'USD'; + const bidFloor = bid.getFloor({currency: bidFloorCurrency, mediaType: mediaType, size: size}); + if (isNumber(bidFloor.floor)) { + return bidFloor; + } + } +} + +/** -- Helper methods --**/ +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +/** -- Register -- */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidWon, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/deltaprojectsBidAdapter.md b/modules/deltaprojectsBidAdapter.md new file mode 100644 index 00000000000..97cef4dd228 --- /dev/null +++ b/modules/deltaprojectsBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: Delta Projects Bid Adapter +Module Type: Bidder Adapter +Maintainer: dev@deltaprojects.com +``` + +# Description + +Connects to Delta Projects DSP for bids. + +# Test Parameters +``` +// define banner unit +var bannerUnit = { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'deltaprojects', + params: { + publisherId: '4' //required + } + }] +}; +``` + diff --git a/test/spec/modules/deltaprojectsBidAdapter_spec.js b/test/spec/modules/deltaprojectsBidAdapter_spec.js new file mode 100644 index 00000000000..382415eab62 --- /dev/null +++ b/test/spec/modules/deltaprojectsBidAdapter_spec.js @@ -0,0 +1,399 @@ +import { expect } from 'chai'; +import { + BIDDER_CODE, + BIDDER_ENDPOINT_URL, + spec, USERSYNC_URL, + getBidFloor +} from 'modules/deltaprojectsBidAdapter.js'; + +const BID_REQ_REFER = 'http://example.com/page?param=val'; + +describe('deltaprojectsBidAdapter', function() { + describe('isBidRequestValid', function () { + function makeBid() { + return { + bidder: BIDDER_CODE, + params: { + publisherId: '12345' + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }; + } + + it('should return true when bidder set correctly', function () { + expect(spec.isBidRequestValid(makeBid())).to.equal(true); + }); + + it('should return false when bid request is null', function () { + expect(spec.isBidRequestValid(undefined)).to.equal(false); + }); + + it('should return false when bidder not set correctly', function () { + let bid = makeBid(); + delete bid.bidder; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisher id is not set', function () { + let bid = makeBid(); + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const BIDREQ = { + bidder: BIDDER_CODE, + params: { + tagId: '403370', + siteId: 'example.com', + }, + sizes: [ + [300, 250], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + } + const bidRequests = [BIDREQ]; + const bannerRequest = spec.buildRequests(bidRequests, {refererInfo: { referer: BID_REQ_REFER }})[0]; + const bannerRequestBody = bannerRequest.data; + + it('send bid request with test tag if it is set in the param', function () { + const TEST_TAG = 1; + const bidRequest = Object.assign({}, BIDREQ, { + params: { ...BIDREQ.params, test: TEST_TAG }, + }); + const bidderRequest = { refererInfo: { referer: BID_REQ_REFER } }; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + expect(request.data.test).to.equal(TEST_TAG); + }); + + it('send bid request with correct timeout', function () { + const TMAX = 10; + const bidderRequest = { refererInfo: { referer: BID_REQ_REFER }, timeout: TMAX }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.tmax).to.equal(TMAX); + }); + + it('send bid request to the correct endpoint URL', function () { + expect(bannerRequest.url).to.equal(BIDDER_ENDPOINT_URL); + }); + + it('sends bid request to our endpoint via POST', function () { + expect(bannerRequest.method).to.equal('POST'); + }); + + it('sends screen dimensions', function () { + expect(bannerRequestBody.device.w).to.equal(screen.width); + expect(bannerRequestBody.device.h).to.equal(screen.height); + }); + + it('includes the ad size in the bid request', function () { + expect(bannerRequestBody.imp[0].banner.format[0].w).to.equal(BIDREQ.sizes[0][0]); + expect(bannerRequestBody.imp[0].banner.format[0].h).to.equal(BIDREQ.sizes[0][1]); + }); + + it('sets domain and href correctly', function () { + expect(bannerRequestBody.site.domain).to.equal(BIDREQ.params.siteId); + expect(bannerRequestBody.site.page).to.equal(BID_REQ_REFER); + }); + + const gdprBidRequests = [{ + bidder: BIDDER_CODE, + params: { + tagId: '403370', + siteId: 'example.com' + }, + sizes: [ + [300, 250] + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }]; + const consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; + + const GDPR_REQ_REFERER = 'http://localhost:9876/' + function getGdprRequestBody(gdprApplies, consentString) { + const gdprRequest = spec.buildRequests(gdprBidRequests, { + gdprConsent: { + gdprApplies: gdprApplies, + consentString: consentString, + }, + refererInfo: { + referer: GDPR_REQ_REFERER, + }, + })[0]; + return gdprRequest.data; + } + + it('should handle gdpr applies being present and true', function() { + const gdprRequestBody = getGdprRequestBody(true, consentString); + expect(gdprRequestBody.regs.ext.gdpr).to.equal(1); + expect(gdprRequestBody.user.ext.consent).to.equal(consentString); + }) + + it('should handle gdpr applies being present and false', function() { + const gdprRequestBody = getGdprRequestBody(false, consentString); + expect(gdprRequestBody.regs.ext.gdpr).to.equal(0); + expect(gdprRequestBody.user.ext.consent).to.equal(consentString); + }) + + it('should handle gdpr applies being undefined', function() { + const gdprRequestBody = getGdprRequestBody(undefined, consentString); + expect(gdprRequestBody.regs).to.deep.equal({ext: {}}); + expect(gdprRequestBody.user.ext.consent).to.equal(consentString); + }) + + it('should handle gdpr consent being undefined', function() { + const gdprRequest = spec.buildRequests(gdprBidRequests, {refererInfo: { referer: GDPR_REQ_REFERER }})[0]; + const gdprRequestBody = gdprRequest.data; + expect(gdprRequestBody.regs).to.deep.equal({ ext: {} }); + expect(gdprRequestBody.user).to.deep.equal({ ext: {} }); + }) + }); + + describe('interpretResponse', function () { + const bidRequests = [ + { + bidder: BIDDER_CODE, + params: { + tagId: '403370', + siteId: 'example.com', + currency: 'USD', + }, + sizes: [ + [300, 250], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + const request = spec.buildRequests(bidRequests, {refererInfo: { referer: BID_REQ_REFER }})[0]; + function makeResponse() { + return { + body: { + id: '5e5c23a5ba71e78', + seatbid: [ + { + bid: [ + { + id: '6vmb3isptf', + crid: 'deltaprojectscreative', + impid: '322add653672f68', + price: 1.22, + adm: '', + attr: [5], + h: 90, + nurl: 'http://nurl', + w: 728, + } + ], + seat: 'MOCK' + } + ], + bidid: '5e5c23a5ba71e78', + cur: 'USD' + } + }; + } + const expectedBid = { + requestId: '322add653672f68', + cpm: 1.22, + width: 728, + height: 90, + creativeId: 'deltaprojectscreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'banner', + ttl: 60, + ad: '
' + }; + + it('should get incorrect bid response if response body is missing', function () { + let response = makeResponse(); + delete response.body; + let result = spec.interpretResponse(response, request); + expect(result.length).to.equal(0); + }); + + it('should get incorrect bid response if id or seat id of response body is missing', function () { + let response1 = makeResponse(); + delete response1.body.id; + let result1 = spec.interpretResponse(response1, request); + expect(result1.length).to.equal(0); + + let response2 = makeResponse(); + delete response2.body.seatbid; + let result2 = spec.interpretResponse(response2, request); + expect(result2.length).to.equal(0); + }); + + it('should get the correct bid response', function () { + let result = spec.interpretResponse(makeResponse(), request); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedBid); + }); + + it('should handle a missing crid', function () { + let noCridResponse = makeResponse(); + delete noCridResponse.body.seatbid[0].bid[0].crid; + const fallbackCrid = noCridResponse.body.seatbid[0].bid[0].id; + let noCridResult = Object.assign({}, expectedBid, {'creativeId': fallbackCrid}); + let result = spec.interpretResponse(noCridResponse, request); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(noCridResult); + }); + + it('should handle a missing nurl', function () { + let noNurlResponse = makeResponse(); + delete noNurlResponse.body.seatbid[0].bid[0].nurl; + let noNurlResult = Object.assign({}, expectedBid); + noNurlResult.ad = ''; + let result = spec.interpretResponse(noNurlResponse, request); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(noNurlResult); + }); + + it('handles empty bid response', function () { + let response = { + body: { + id: '5e5c23a5ba71e78', + seatbid: [] + } + }; + let result = spec.interpretResponse(response, request); + expect(result.length).to.equal(0); + }); + + it('should keep custom properties', () => { + const customProperties = {test: 'a test message', param: {testParam: 1}}; + const expectedResult = Object.assign({}, expectedBid, {[spec.code]: customProperties}); + const response = makeResponse(); + response.body.seatbid[0].bid[0].ext = customProperties; + const result = spec.interpretResponse(response, request); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedResult); + }); + }); + + describe('onBidWon', function () { + const OPEN_RTB_RESP = { + body: { + id: 'abc', + seatbid: [ + { + bid: [ + { + 'id': 'abc*123*456', + 'impid': 'xxxxxxx', + 'price': 46.657196, + 'adm': '