From 5a67d89dbe687b6a5375a8476d7d872c2f801446 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Wed, 6 Mar 2019 14:22:19 -0500 Subject: [PATCH 01/33] fix hb_cache_id for adpod bids (#3617) --- modules/adpod.js | 1 + test/spec/auctionmanager_spec.js | 85 ++++++++++++++++++++++++++++++-- test/spec/modules/adpod_spec.js | 11 +++++ 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/modules/adpod.js b/modules/adpod.js index 87e5869e17b..9cb8c3528a5 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -132,6 +132,7 @@ function attachPriceIndustryDurationKeyToBid(bid, brandCategoryExclusion) { } bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] = pcd; bid.adserverTargeting[TARGETING_KEY_CACHE_ID] = initialCacheKey; + bid.videoCacheKey = initialCacheKey; bid.customCacheKey = `${pcd}_${initialCacheKey}`; } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 9110e1fcdc0..aab1da11653 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -139,7 +139,10 @@ describe('auctionmanager.js', function () { expected[ CONSTANTS.TARGETING_KEYS.SIZE ] = bid.getSize(); expected[ CONSTANTS.TARGETING_KEYS.SOURCE ] = bid.source; expected[ CONSTANTS.TARGETING_KEYS.FORMAT ] = bid.mediaType; - + if (bid.mediaType === 'video') { + expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey; + expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey; + } if (!keys) { return expected; } @@ -157,8 +160,20 @@ describe('auctionmanager.js', function () { }); it('No bidder level configuration defined - default', function () { - var expected = getDefaultExpected(bid); - var response = getKeyValueTargetingPairs(bid.bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); + $$PREBID_GLOBAL$$.bidderSettings = {}; + let expected = getDefaultExpected(bid); + let response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + + it('No bidder level configuration defined - default for video', function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; + + let expected = getDefaultExpected(videoBid); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); assert.deepEqual(response, expected); }); @@ -213,6 +228,70 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); + it('Custom configuration for all bidders with video bid', function () { + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; + + $$PREBID_GLOBAL$$.bidderSettings = + { + standard: { + adserverTargeting: [ + { + key: CONSTANTS.TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: CONSTANTS.TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + return bidResponse.pbMg; + } + }, { + key: CONSTANTS.TARGETING_KEYS.SIZE, + val: function (bidResponse) { + return bidResponse.size; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.SOURCE, + val: function (bidResponse) { + return bidResponse.source; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.FORMAT, + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.UUID, + val: function (bidResponse) { + return bidResponse.videoCacheKey; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.CACHE_ID, + val: function (bidResponse) { + return bidResponse.videoCacheKey; + } + } + ] + + } + }; + + let expected = getDefaultExpected(videoBid); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); + assert.deepEqual(response, expected); + }); + it('Custom configuration for one bidder', function () { $$PREBID_GLOBAL$$.bidderSettings = { diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js index 16e94fd569f..b38f7ad545e 100644 --- a/test/spec/modules/adpod_spec.js +++ b/test/spec/modules/adpod_spec.js @@ -150,11 +150,13 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_test_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_test_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal(bidResponse2.adId); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^12\.00_value_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[1].adserverTargeting.hb_cache_id).to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); }); it('should send prebid cache call once bid queue is full', function () { @@ -220,10 +222,12 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_airline_30s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_airline_30s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('adId234'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should send prebid cache call after set period of time (even if queue is not full)', function () { @@ -276,6 +280,7 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should execute multiple prebid cache calls when number of bids exceeds queue size', function () { @@ -360,14 +365,17 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('multi_ad2'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_news_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_news_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[2].adId).to.equal('multi_ad3'); expect(auctionBids[2].customCacheKey).to.exist.and.to.match(/^10\.00_sports_15s_.*/); expect(auctionBids[2].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_sports_15s'); expect(auctionBids[2].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[2].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should cache the bids with a shortened custom key when adpod.brandCategoryExclusion is false', function() { @@ -436,10 +444,12 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('nocat_ad2'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should not add bid to auction when config adpod.brandCategoryExclusion is true but bid is missing adServerCatId', function() { @@ -564,6 +574,7 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_tech_45s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_tech_45s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should not add bids to auction if PBC returns an error', function() { From fc378b2fb9b3d62628adecc9466615c0b5cb05c7 Mon Sep 17 00:00:00 2001 From: Jaimin Panchal Date: Wed, 6 Mar 2019 15:08:10 -0500 Subject: [PATCH 02/33] Prebid 2.5.1 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cd78a3036b9..ff6b4954c8a 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.6.0-pre", + "version": "2.5.1", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 24d09ad6f1369a9b5e44be621d2dacee495c1125 Mon Sep 17 00:00:00 2001 From: Jaimin Panchal Date: Wed, 6 Mar 2019 15:16:59 -0500 Subject: [PATCH 03/33] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ff6b4954c8a..cd78a3036b9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.5.1", + "version": "2.6.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 7ce5df6d56188f7e95646b8b70a9a549cdb6bef8 Mon Sep 17 00:00:00 2001 From: naoto yamaguchi Date: Fri, 8 Mar 2019 04:10:12 +0900 Subject: [PATCH 04/33] update AJA adaptor: support native format (#3504) * aja adapter support native format * not encode lp link * update adapter md * update aja adaptor md --- modules/ajaBidAdapter.js | 39 ++++++- modules/ajaBidAdapter.md | 48 ++++++++- test/spec/modules/ajaBidAdapter_spec.js | 134 ++++++++++++++++++++---- 3 files changed, 192 insertions(+), 29 deletions(-) diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 4cbe9a840ea..5b826d1e725 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -1,7 +1,7 @@ import { Renderer } from '../src/Renderer'; import * as utils from '../src/utils'; import { registerBidder } from '../src/adapters/bidderFactory'; -import { VIDEO, BANNER } from '../src/mediaTypes'; +import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes'; const BIDDER_CODE = 'aja'; const URL = '//ad.as.amanad.adtdp.com/v2/prebid'; @@ -14,7 +14,7 @@ const AD_TYPE = { export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [VIDEO, BANNER], + supportedMediaTypes: [VIDEO, BANNER, NATIVE], isBidRequestValid: function(bid) { return !!(bid.params.asi); @@ -86,6 +86,41 @@ export const spec = { } catch (error) { utils.logError('Error appending tracking pixel', error); } + } else if (AD_TYPE.NATIVE === ad.ad_type) { + const nativeAds = ad.native.template_and_ads.ads; + + nativeAds.forEach(nativeAd => { + const assets = nativeAd.assets; + + Object.assign(bid, { + mediaType: NATIVE + }); + + bid.native = { + title: assets.title, + body: assets.description, + cta: assets.cta_text, + sponsoredBy: assets.sponsor, + clickUrl: assets.lp_link, + impressionTrackers: nativeAd.imps, + }; + + if (assets.img_main !== undefined) { + bid.native.image = { + url: assets.img_main, + width: parseInt(assets.img_main_width, 10), + height: parseInt(assets.img_main_height, 10) + }; + } + + if (assets.img_icon !== undefined) { + bid.native.icon = { + url: assets.img_icon, + width: parseInt(assets.img_icon_width, 10), + height: parseInt(assets.img_icon_height, 10) + }; + } + }); } return [bid]; diff --git a/modules/ajaBidAdapter.md b/modules/ajaBidAdapter.md index 6e513b94ff0..4f5aa7074cb 100644 --- a/modules/ajaBidAdapter.md +++ b/modules/ajaBidAdapter.md @@ -11,11 +11,11 @@ Connects to Aja exchange for bids. Aja bid adapter supports Banner and Outstream Video. # Test Parameters -``` +```js var adUnits = [ // Banner adUnit { - code: 'banner-div', + code: 'prebid_banner', mediaTypes: { banner: { sizes: [ @@ -26,13 +26,13 @@ var adUnits = [ bids: [{ bidder: 'aja', params: { - asi: 'szs4htFiR' + asi: 'tk82gbLmg' } }] }, // Video outstream adUnit { - code: 'video-outstream', + code: 'prebid_video', mediaTypes: { video: { context: 'outstream', @@ -42,7 +42,45 @@ var adUnits = [ bids: [{ bidder: 'aja', params: { - asi: 'Kp2O2tFig' + asi: '1-KwEG_iR' + } + }] + }, + // Native adUnit + { + code: 'prebid_native', + mediaTypes: { + native: { + image: { + required: true, + sendId: false + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false, + sendId: true + }, + clickUrl: { + required: false, + sendId: true + }, + body: { + required: false, + sendId: true + }, + icon: { + required: false, + sendId: false + } + } + }, + bids: [{ + bidder: 'aja', + params: { + asi: 'qxueUGliR' } }] } diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index 00dafcb7b11..7539848d5bd 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -54,30 +54,31 @@ describe('AjaAdapter', function () { expect(requests[0].method).to.equal('GET'); }); }); - describe('interpretResponse', function () { - let response = { - 'is_ad_return': true, - 'ad': { - 'ad_type': 1, - 'prebid_id': '51ef8751f9aead', - 'price': 12.34, - 'currency': 'USD', - 'creative_id': '123abc', - 'banner': { - 'w': 300, - 'h': 250, - 'tag': '
', - 'imps': [ - '//as.amanad.adtdp.com/v1/imp' - ] - } - }, - 'syncs': [ - 'https://example.com' - ] - }; + describe('interpretResponse', function () { it('should get correct banner bid response', function () { + let response = { + 'is_ad_return': true, + 'ad': { + 'ad_type': 1, + 'prebid_id': '51ef8751f9aead', + 'price': 12.34, + 'currency': 'USD', + 'creative_id': '123abc', + 'banner': { + 'w': 300, + 'h': 250, + 'tag': '
', + 'imps': [ + '//as.amanad.adtdp.com/v1/imp' + ] + } + }, + 'syncs': [ + 'https://example.com' + ] + }; + let expectedResponse = [ { 'requestId': '51ef8751f9aead', @@ -130,6 +131,95 @@ describe('AjaAdapter', function () { expect(result[0]).to.have.property('mediaType', 'video'); }); + it('handles native response', function () { + let response = { + 'is_ad_return': true, + 'ad': { + 'ad_type': 2, + 'prebid_id': '51ef8751f9aead', + 'price': 12.34, + 'currency': 'JPY', + 'creative_id': '123abc', + 'native': { + 'template_and_ads': { + 'head': '', + 'body_wrapper': '', + 'body': '', + 'ads': [ + { + 'ad_format_id': 10, + 'assets': { + 'ad_spot_id': '123abc', + 'index': 0, + 'adchoice_url': 'https://aja-kk.co.jp/optout', + 'cta_text': 'cta', + 'img_icon': 'https://example.com/img_icon', + 'img_icon_width': '50', + 'img_icon_height': '50', + 'img_main': 'https://example.com/img_main', + 'img_main_width': '200', + 'img_main_height': '100', + 'lp_link': 'https://example.com/lp?k=v', + 'sponsor': 'sponsor', + 'title': 'ad_title', + 'description': 'ad_desc' + }, + 'imps': [ + 'https://example.com/imp' + ], + 'inviews': [ + 'https://example.com/inview' + ], + 'jstracker': '', + 'disable_trimming': false + } + ] + } + } + }, + 'syncs': [ + 'https://example.com' + ] + }; + + let expectedResponse = [ + { + 'requestId': '51ef8751f9aead', + 'cpm': 12.34, + 'creativeId': '123abc', + 'dealId': undefined, + 'mediaType': 'native', + 'currency': 'JPY', + 'ttl': 300, + 'netRevenue': true, + 'native': { + 'title': 'ad_title', + 'body': 'ad_desc', + 'cta': 'cta', + 'sponsoredBy': 'sponsor', + 'image': { + 'url': 'https://example.com/img_main', + 'width': 200, + 'height': 100 + }, + 'icon': { + 'url': 'https://example.com/img_icon', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://example.com/lp?k=v', + 'impressionTrackers': [ + 'https://example.com/imp' + ] + } + } + ]; + + let bidderRequest; + let result = spec.interpretResponse({ body: response }, {bidderRequest}) + expect(result).to.deep.equal(expectedResponse) + }); + it('handles nobid responses', function () { let response = { 'is_ad_return': false, From 41867ad73de4c7732855ae49416805d3e1be3e53 Mon Sep 17 00:00:00 2001 From: Vladislav Yatsun Date: Fri, 8 Mar 2019 19:43:46 +0400 Subject: [PATCH 05/33] Submit Brightcom Bid Adapter (#3614) * Add Brightcom Bid Adapter * brightcomBidAdapter: remove getTopWindowLocation call * brightcomBidAdapter: fix naming --- modules/brightcomBidAdapter.js | 246 +++++++++++++++ modules/brightcomBidAdapter.md | 38 +++ test/spec/modules/brightcomBidAdapter_spec.js | 283 ++++++++++++++++++ 3 files changed, 567 insertions(+) create mode 100644 modules/brightcomBidAdapter.js create mode 100644 modules/brightcomBidAdapter.md create mode 100644 test/spec/modules/brightcomBidAdapter_spec.js diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js new file mode 100644 index 00000000000..626aa99f5de --- /dev/null +++ b/modules/brightcomBidAdapter.js @@ -0,0 +1,246 @@ +import * as utils from '../src/utils'; +import * as url from '../src/url'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; +import { config } from '../src/config'; + +const BIDDER_CODE = 'brightcom'; +const URL = 'https://brightcombid.marphezis.com/hb'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.referer; + } + const brightcomImps = []; + const publisherId = utils.getBidIdParameter('publisherId', bidReqs[0].params); + utils._each(bidReqs, function (bid) { + bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); + bid.sizes = bid.sizes.filter(size => utils.isArray(size)); + const processedSizes = bid.sizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) + ? _getViewability(element, utils.getWindowTop(), minSize) + : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + if (bidFloor) { + imp.bidfloor = bidFloor; + } + brightcomImps.push(imp); + }); + const brightcomBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: brightcomImps, + site: { + domain: url.parse(referrer).host, + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(), + w: screen.width, + h: screen.height + }, + tmax: config.getConfig('bidderTimeout') + }; + + return { + method: 'POST', + url: URL, + data: JSON.stringify(brightcomBidReq), + options: {contentType: 'text/plain', withCredentials: false} + }; + } catch (e) { + utils.logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + if (typeof bid.params.publisherId === 'undefined') { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + if (!serverResponse.body || typeof serverResponse.body != 'object') { + utils.logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return []; + } + const { body: {id, seatbid} } = serverResponse; + try { + const brightcomBidResponses = []; + if (id && + seatbid && + seatbid.length > 0 && + seatbid[0].bid && + seatbid[0].bid.length > 0) { + seatbid[0].bid.map(brightcomBid => { + brightcomBidResponses.push({ + requestId: brightcomBid.impid, + cpm: parseFloat(brightcomBid.price), + width: parseInt(brightcomBid.w), + height: parseInt(brightcomBid.h), + creativeId: brightcomBid.crid || brightcomBid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(brightcomBid), + ttl: 60 + }); + }); + } + return brightcomBidResponses; + } catch (e) { + utils.logError(e, {id, seatbid}); + } +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += utils.createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, { w, h } = {}) { + return utils.getWindowTop().document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; +} + +function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +function _getBoundingBox(element, { w, h } = {}) { + let { width, height, left, top, right, bottom } = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return { width, height, left, top, right, bottom }; +} + +function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, + right: rects[0].right, + top: rects[0].top, + bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + +function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = _getIntersectionOfRects([ { + left: 0, + top: 0, + right: topWin.innerWidth, + bottom: topWin.innerHeight + }, elementBoundingBox ]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +registerBidder(spec); diff --git a/modules/brightcomBidAdapter.md b/modules/brightcomBidAdapter.md new file mode 100644 index 00000000000..badc6ea94a4 --- /dev/null +++ b/modules/brightcomBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Brightcom Bid Adapter +Module Type: Bidder Adapter +Maintainer: vladislavy@brightcom.com +``` + +# Description + +Brightcom's adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + sizes: [[728, 90]], + bids: [{ + bidder: 'brightcom', + params: { + publisherId: 2141020, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + sizes: [[300, 250]], + bids: [{ + bidder: 'brightcom', + params: { + publisherId: 2141020 + } + }] + } +] +``` diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js new file mode 100644 index 00000000000..73186250f5b --- /dev/null +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -0,0 +1,283 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils'; +import { spec } from 'modules/brightcomBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const URL = 'https://brightcombid.marphezis.com/hb'; + +describe('brightcomBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'brightcom', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e' + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'brightcom', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when tagid not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250 + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); From f25bc65a54eae3d2c4ab85721b562f2c2a9aaa83 Mon Sep 17 00:00:00 2001 From: afsheenb Date: Fri, 8 Mar 2019 17:06:07 -0500 Subject: [PATCH 06/33] ozone adapter - fixup for gdpr and device objects (#3593) * fixup for gdpr and device objects * v1.4.6 - clean up for unit test following changes submitted in 1.4.5 - removed 3 IF statements because the condition was already tested in the validation method. - added more tests to the spec file (check ozoneData, customData & lotameData are in the right place, and also NOT in the old location as well as some GDPR based unit tests) * explicitly added bidder name to debug logging statements --- modules/ozoneBidAdapter.js | 91 ++++++++--------- test/spec/modules/ozoneBidAdapter_spec.js | 117 ++++++++++++++++++++-- 2 files changed, 155 insertions(+), 53 deletions(-) diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 7a7033a79ac..406bffdf4df 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -7,7 +7,7 @@ const BIDDER_CODE = 'ozone'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const OZONECOOKIESYNC = 'https://elb.the-ozone-project.com/static/load-cookie.html'; -const OZONEVERSION = '1.4.4'; +const OZONEVERSION = '1.4.7'; export const spec = { code: BIDDER_CODE, @@ -18,72 +18,78 @@ export const spec = { */ isBidRequestValid(bid) { if (!(bid.params.hasOwnProperty('placementId'))) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED'); return false; } if (!(bid.params.placementId).toString().match(/^[0-9]{10}$/)) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters'); return false; } if (!(bid.params.hasOwnProperty('publisherId'))) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED'); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens'); return false; } if (!(bid.params.hasOwnProperty('siteId'))) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED'); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters'); return false; } if (bid.params.hasOwnProperty('customData')) { if (typeof bid.params.customData !== 'object') { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : customData is not an object'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is not an object'); return false; } } if (bid.params.hasOwnProperty('customParams')) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData'); return false; } if (bid.params.hasOwnProperty('ozoneData')) { if (typeof bid.params.ozoneData !== 'object') { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : ozoneData is not an object'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : ozoneData is not an object'); return false; } } if (bid.params.hasOwnProperty('lotameData')) { if (typeof bid.params.lotameData !== 'object') { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object'); return false; } } return true; }, buildRequests(validBidRequests, bidderRequest) { - utils.logInfo('ozone v' + OZONEVERSION + ' validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); - utils.logInfo('buildRequests setting auctionId', bidderRequest.auctionId); + utils.logInfo('OZONE: ozone v' + OZONEVERSION + ' validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); + utils.logInfo('OZONE: buildRequests setting auctionId', bidderRequest.auctionId); let singleRequest = config.getConfig('ozone.singleRequest'); + singleRequest = singleRequest !== false; // undefined & true will be true - utils.logInfo('config ozone.singleRequest : ', singleRequest); + utils.logInfo('OZONE: config ozone.singleRequest : ', singleRequest); let htmlParams = validBidRequests[0].params; // the html page config params will be included in each element let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params // ozoneRequest['id'] = utils.generateUUID(); delete ozoneRequest.test; // don't allow test to be set in the config - ONLY use $_GET['pbjs_debug'] if (bidderRequest.gdprConsent) { + utils.logInfo('OZONE: ADDING GDPR info'); ozoneRequest.regs = {}; ozoneRequest.regs.ext = {}; ozoneRequest.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; if (ozoneRequest.regs.ext.gdpr) { - ozoneRequest.regs.ext.consent = bidderRequest.gdprConsent.consentString; + ozoneRequest.user = {}; + ozoneRequest.user.ext = {'consent': bidderRequest.gdprConsent.consentString}; } + } else { + utils.logInfo('OZONE: WILL NOT ADD GDPR info'); } + ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; obj.id = ozoneBidRequest.bidId; // this causes a failure if we change it to something else @@ -95,25 +101,25 @@ export const spec = { /* NOTE - if there is sizes element in the config root then there will be a mediaTypes.banner element automatically generated for us, so this code is deprecated */ if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { - utils.logInfo('no mediaTypes detected - will use the sizes array in the config root'); + utils.logInfo('OZONE: no mediaTypes detected - will use the sizes array in the config root'); arrBannerSizes = ozoneBidRequest.sizes; } else { - utils.logInfo('no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); + utils.logInfo('OZONE: no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); } } else { if (ozoneBidRequest.mediaTypes.hasOwnProperty(BANNER)) { arrBannerSizes = ozoneBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ - utils.logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); + utils.logInfo('OZONE: setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); } // Video integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { obj.video = ozoneBidRequest.mediaTypes[VIDEO]; - utils.logInfo('setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video); + utils.logInfo('OZONE: setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video); } // Native integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { obj.native = ozoneBidRequest.mediaTypes[NATIVE]; - utils.logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + utils.logInfo('OZONE: setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); } } // build the banner request using banner sizes we found in either possible location: @@ -127,15 +133,10 @@ export const spec = { }) }; } - if (ozoneBidRequest.params.hasOwnProperty('placementId')) { - obj.placementId = (ozoneBidRequest.params.placementId).toString(); - } - if (ozoneBidRequest.params.hasOwnProperty('publisherId')) { - obj.publisherId = (ozoneBidRequest.params.publisherId).toString(); - } - if (ozoneBidRequest.params.hasOwnProperty('siteId')) { - obj.siteId = (ozoneBidRequest.params.siteId).toString(); - } + // these 3 MUST exist - we check them in the validation method + obj.placementId = (ozoneBidRequest.params.placementId).toString(); + obj.publisherId = (ozoneBidRequest.params.publisherId).toString(); + obj.siteId = (ozoneBidRequest.params.siteId).toString(); // build the imp['ext'] object obj.ext = {'prebid': {'storedrequest': {'id': (ozoneBidRequest.params.placementId).toString()}}, 'ozone': {}}; obj.ext.ozone.adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' @@ -161,7 +162,7 @@ export const spec = { // utils.logInfo('_ozoneInternal is', _ozoneInternal); // return the single request object OR the array: if (singleRequest) { - utils.logInfo('buildRequests starting to generate response for a single request'); + utils.logInfo('OZONE: buildRequests starting to generate response for a single request'); ozoneRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. ozoneRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? ozoneRequest.imp = tosendtags; @@ -172,21 +173,21 @@ export const spec = { data: JSON.stringify(ozoneRequest), bidderRequest: bidderRequest }; - utils.logInfo('buildRequests ozoneRequest for single = ', ozoneRequest); - utils.logInfo('buildRequests going to return for single: ', ret); + utils.logInfo('OZONE: buildRequests ozoneRequest for single = ', ozoneRequest); + utils.logInfo('OZONE: buildRequests going to return for single: ', ret); return ret; } // not single request - pull apart the tosendtags array & return an array of objects each containing one element in the imp array. let arrRet = tosendtags.map(imp => { - utils.logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); + utils.logInfo('OZONE: buildRequests starting to generate non-single response, working on imp : ', imp); let ozoneRequestSingle = Object.assign({}, ozoneRequest); imp.ext.ozone.pageAuctionId = bidderRequest['auctionId']; // make a note in the ext object of what the original auctionId was, in the bidderRequest object ozoneRequestSingle.id = imp.ext.ozone.transactionId; // Unique ID of the bid request, provided by the exchange. ozoneRequestSingle.auctionId = imp.ext.ozone.transactionId; // not sure if this should be here? ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.source = {'tid': imp.ext.ozone.transactionId}; - utils.logInfo('buildRequests ozoneRequestSingle (for non-single) = ', ozoneRequestSingle); + utils.logInfo('OZONE: buildRequests ozoneRequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', url: OZONEURI, @@ -194,7 +195,7 @@ export const spec = { bidderRequest: bidderRequest }; }); - utils.logInfo('buildRequests going to return for non-single: ', arrRet); + utils.logInfo('OZONE: buildRequests going to return for non-single: ', arrRet); return arrRet; }, /** @@ -205,7 +206,7 @@ export const spec = { * @returns {*} */ interpretResponse(serverResponse, request) { - utils.logInfo('ozone v' + OZONEVERSION + ' interpretResponse', serverResponse, request); + utils.logInfo('OZONE: version' + OZONEVERSION + ' interpretResponse', serverResponse, request); serverResponse = serverResponse.body || {}; if (serverResponse.seatbid) { if (utils.isArray(serverResponse.seatbid)) { @@ -219,19 +220,19 @@ export const spec = { let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid, serverResponse.seatbid); if (winningBid == null) { - utils.logInfo('FAILED to get winning bid for bid : ', thisBid, 'will skip. Possibly a non-single request, which will be missing some bid IDs'); + utils.logInfo('OZONE: FAILED to get winning bid for bid : ', thisBid, 'will skip. Possibly a non-single request, which will be missing some bid IDs'); continue; } const {defaultWidth, defaultHeight} = defaultSize(arrRequestBids[i]); winningBid = ozoneAddStandardProperties(winningBid, defaultWidth, defaultHeight); - utils.logInfo('Going to add the adserverTargeting custom parameters for key: ', ozoneInternalKey); + utils.logInfo('OZONE: Going to add the adserverTargeting custom parameters for key: ', ozoneInternalKey); let adserverTargeting = {}; let allBidsForThisBidid = ozoneGetAllBidsForBidId(ozoneInternalKey, serverResponse.seatbid); // add all the winning & non-winning bids for this bidId: Object.keys(allBidsForThisBidid).forEach(function(bidderName, index, ar2) { - utils.logInfo('inside allBidsForThisBidid:foreach', bidderName, index, ar2, allBidsForThisBidid); + utils.logInfo('OZONE: inside allBidsForThisBidid:foreach', bidderName, index, ar2, allBidsForThisBidid); adserverTargeting['oz_' + bidderName] = bidderName; adserverTargeting['oz_' + bidderName + '_pb'] = String(allBidsForThisBidid[bidderName].price); adserverTargeting['oz_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); @@ -246,14 +247,14 @@ export const spec = { adserverTargeting['oz_response_id'] = String(serverResponse.id); winningBid.adserverTargeting = adserverTargeting; - utils.logInfo('winner is', winningBid); + utils.logInfo('OZONE: winner is', winningBid); arrWinners.push(winningBid); - utils.logInfo('arrWinners is', arrWinners); + utils.logInfo('OZONE: arrWinners is', arrWinners); } let winnersClean = arrWinners.filter(w => { return (w.bidId); // will be cast to boolean }); - utils.logInfo('going to return winnersClean:', winnersClean); + utils.logInfo('OZONE: going to return winnersClean:', winnersClean); return winnersClean; } else { return []; @@ -326,7 +327,7 @@ export function ozoneGetWinnerForRequestBid(requestBid, serverResponseSeatBid) { * @returns {} = {ozone:{obj}, appnexus:{obj}, ... } */ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { - utils.logInfo('ozoneGetAllBidsForBidId - starting, with: ', matchBidId, serverResponseSeatBid); + utils.logInfo('OZONE: ozoneGetAllBidsForBidId - starting, with: ', matchBidId, serverResponseSeatBid); let objBids = {}; for (let j = 0; j < serverResponseSeatBid.length; j++) { let theseBids = serverResponseSeatBid[j].bid; @@ -338,7 +339,7 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { } } } - utils.logInfo('ozoneGetAllBidsForBidId - going to return: ', objBids); + utils.logInfo('OZONE: ozoneGetAllBidsForBidId - going to return: ', objBids); return objBids; } @@ -379,4 +380,4 @@ export function getTestQuerystringValue() { } registerBidder(spec); -utils.logInfo('ozoneBidAdapter ended'); +utils.logInfo('OZONE: ozoneBidAdapter ended'); diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index c404d9003fe..995ec93ad99 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -1,8 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/ozoneBidAdapter'; - +import { config } from 'src/config'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; -// const OZONEURI = 'https://www.1in39.co.uk/openrtb2/auction'; const BIDDER_CODE = 'ozone'; /* @@ -23,6 +22,19 @@ var validBidRequests = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +var validBidRequestsMinimal = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; var validBidRequestsNoSizes = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -37,7 +49,7 @@ var validBidRequestsNoSizes = [ } ]; -var validBidRequestsWithMediaTypes = [ +var validBidRequestsWithBannerMediaType = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -51,6 +63,20 @@ var validBidRequestsWithMediaTypes = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +var validBidRequestsWithNonBannerMediaTypes = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, ozoneData: {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + mediaTypes: {video:{info:'dummy data'}, native:{info:'dummy data'}}, + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; var validBidderRequest = { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -152,11 +178,11 @@ describe('ozone Adapter', function () { params: { placementId: '1310000099', publisherId: '9876abcd12-3', - siteId: '1234567890' + siteId: '1234567890', + customData: {'gender': 'bart', 'age': 'low'}, + ozoneData: {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}, + lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, }, - customData: {'gender': 'bart', 'age': 'low'}, - ozoneData: {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}, - lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, siteId: 1234567890 } @@ -456,13 +482,35 @@ describe('ozone Adapter', function () { expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); + it('adds all parameters inside the ext object only', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.data).to.be.a('string'); + var data = JSON.parse(request.data); + expect(data.imp[0].ext.ozone.ozoneData).to.be.an('object'); + expect(data.imp[0].ext.ozone.lotameData).to.be.an('object'); + expect(data.imp[0].ext.ozone.customData).to.be.an('object'); + expect(request).not.to.have.key('ozoneData'); + expect(request).not.to.have.key('lotameData'); + expect(request).not.to.have.key('customData'); + }); + it('has correct bidder', function () { const request = spec.buildRequests(validBidRequests, validBidderRequest); expect(request.bidderRequest.bids[0].bidder).to.equal(BIDDER_CODE); }); it('handles mediaTypes element correctly', function () { - const request = spec.buildRequests(validBidRequestsWithMediaTypes, validBidderRequest); + const request = spec.buildRequests(validBidRequestsWithBannerMediaType, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('handles no ozone, lotame or custom data', function () { + const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('handles missing banner mediaType element correctly', function () { + const request = spec.buildRequests(validBidRequestsWithNonBannerMediaTypes, validBidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); @@ -470,6 +518,37 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); + + it('should add gdpr consent information to the request', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'ozone', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + } + }; + bidderRequest.bids = validBidRequests; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.user.ext).to.exist; + expect(payload.user.ext.consent).to.exist.and.to.equal(consentString); + expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); + }); + + it('should be able to handle non-single requests', function () { + config.setConfig({'ozone': {'singleRequest': false}}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + expect(request).to.be.a('array'); + expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + // need to reset the singleRequest config flag: + config.setConfig({'ozone': {'singleRequest': true}}); + }); }); describe('interpretResponse', function () { @@ -495,5 +574,27 @@ describe('ozone Adapter', function () { const result = spec.interpretResponse(validResponse, request); expect(result.length).to.equal(1); }); + it('should fail ok if no seatbid in server response', function () { + const result = spec.interpretResponse({}, {}); + expect(result).to.be.an('array'); + expect(result).to.be.empty; + }); + it('should fail ok if seatbid is not an array', function () { + const result = spec.interpretResponse({'body':{'seatbid':'nothing_here'}}, {}); + expect(result).to.be.an('array'); + expect(result).to.be.empty; + }); + }); + + describe('userSyncs', function () { + it('should fail gracefully if no server response', function () { + const result = spec.getUserSyncs('bad', false); + expect(result).to.be.empty; + }); + it('should fail gracefully if server response is empty', function () { + const result = spec.getUserSyncs('bad', []); + expect(result).to.be.empty; + }); }); + }); From d00c92afde2e9503208e76029cb5e2428b9ce0ab Mon Sep 17 00:00:00 2001 From: "Takaaki.Kojima" Date: Tue, 12 Mar 2019 00:57:36 +0900 Subject: [PATCH 07/33] update AdGeneration adapter. (#3613) --- modules/adgenerationBidAdapter.js | 5 ++++- modules/adgenerationBidAdapter.md | 5 ++++- test/spec/modules/adgenerationBidAdapter_spec.js | 4 +++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index 1e00485a6d9..77b6acbf0e0 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -1,7 +1,7 @@ import * as utils from '../src/utils'; import {registerBidder} from '../src/adapters/bidderFactory'; import {BANNER, NATIVE} from '../src/mediaTypes'; -import { config } from '../src/config'; +import {config} from '../src/config'; const ADG_BIDDER_CODE = 'adgeneration'; export const spec = { @@ -156,6 +156,9 @@ function createNativeAd(body) { case 6: native.cta = assets[i].data.value; break; + case 502: + native.privacyLink = encodeURIComponent(assets[i].data.value); + break; } } native.clickUrl = body.native_ad.link.url; diff --git a/modules/adgenerationBidAdapter.md b/modules/adgenerationBidAdapter.md index 7d8281be9b2..7dfc301e657 100644 --- a/modules/adgenerationBidAdapter.md +++ b/modules/adgenerationBidAdapter.md @@ -52,7 +52,10 @@ var adUnits = [ }, icon: { required: true - } + }, + privacyLink: { + required: true + }, }, }, bids: [ diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index a2c8125850a..28bccd7844e 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -238,7 +238,7 @@ describe('AdgenerationAdapter', function () { { data: { label: 'optout_url', - value: 'https://supership.jp/optout/' + value: 'https://supership.jp/optout/#' }, id: 502 }, @@ -350,6 +350,7 @@ describe('AdgenerationAdapter', function () { sponsoredBy: 'Sponsored', body: 'Description', cta: 'CTA', + privacyLink: 'https://supership.jp/optout/#', clickUrl: 'https://supership.jp', clickTrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1_clicktracker_access.gif'], impressionTrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1.gif'] @@ -396,6 +397,7 @@ describe('AdgenerationAdapter', function () { expect(result.native.sponsoredBy).to.equal(bidResponses.native.native.sponsoredBy); expect(result.native.body).to.equal(bidResponses.native.native.body); expect(result.native.cta).to.equal(bidResponses.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.native.native.privacyLink); expect(result.native.clickUrl).to.equal(bidResponses.native.native.clickUrl); expect(result.native.impressionTrackers[0]).to.equal(bidResponses.native.native.impressionTrackers[0]); expect(result.native.clickTrackers[0]).to.equal(bidResponses.native.native.clickTrackers[0]); From 72e0e970df29f4775b0e2fa39bc727a88575c8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20DEYM=C3=88S?= <47388595+MaxSmileWanted@users.noreply.github.com> Date: Mon, 11 Mar 2019 17:25:24 +0100 Subject: [PATCH 08/33] New bid adapter for SmileWanted (#3601) * New bid adapter for SmileWanted - Smilewanted BidAdapter module and test spec added * Update to correct feedback from PR #3601 - Add comments - Default value for currencyCode - replacing "utils.getTopWindowUrl()" with bidderRequest.refererInfo - Update unit tests * Delete of the commented spec --- modules/smilewantedBidAdapter.js | 113 +++++++++++ modules/smilewantedBidAdapter.md | 31 +++ .../modules/smilewantedBidAdapter_spec.js | 191 ++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 modules/smilewantedBidAdapter.js create mode 100644 modules/smilewantedBidAdapter.md create mode 100644 test/spec/modules/smilewantedBidAdapter_spec.js diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js new file mode 100644 index 00000000000..bf9a75b6650 --- /dev/null +++ b/modules/smilewantedBidAdapter.js @@ -0,0 +1,113 @@ +import * as utils from '../src/utils'; +import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory'; + +export const spec = { + code: 'smilewanted', + aliases: ['smile', 'sw'], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.zoneId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + var payload = { + zoneId: bid.params.zoneId, + currencyCode: config.getConfig('currency.adServerCurrency') || 'EUR', + bidfloor: bid.params.bidfloor || 0.0, + tagId: bid.adUnitCode, + sizes: bid.sizes.map(size => ({ + w: size[0], + h: size[1] + })), + transactionId: bid.transactionId, + timeout: config.getConfig('bidderTimeout'), + bidId: bid.bidId, + prebidVersion: '$prebid.version$' + }; + + if (bidderRequest && bidderRequest.refererInfo) { + payload.pageDomain = bidderRequest.refererInfo.referer || ''; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + var payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: 'https://prebid.smilewanted.com', + data: payloadString, + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + var response = serverResponse.body; + try { + if (response) { + const bidResponse = { + requestId: JSON.parse(bidRequest.data).bidId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + dealId: response.dealId, + currency: response.currency, + netRevenue: response.isNetCpm, + ttl: response.ttl, + adUrl: response.adUrl, + ad: response.ad + }; + + bidResponses.push(bidResponse); + } + } catch (error) { + utils.logError('Error while parsing smilewanted response', error); + } + return bidResponses; + }, + + /** + * User syncs. + * + * @param {*} syncOptions Publisher prebid configuration. + * @param {*} serverResponses A successful response from the server. + * @return {Syncs[]} An array of syncs that should be executed. + */ + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = [] + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + if (serverResponses[0].body.cSyncUrl === 'https://csync.smilewanted.com') { + syncs.push({ + type: 'iframe', + url: serverResponses[0].body.cSyncUrl + }); + } + } + return syncs; + } +} + +registerBidder(spec); diff --git a/modules/smilewantedBidAdapter.md b/modules/smilewantedBidAdapter.md new file mode 100644 index 00000000000..ddc25fe7456 --- /dev/null +++ b/modules/smilewantedBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: SmileWanted Bidder Adapter +Module Type: Bidder Adapter +Maintainer: maxime@smilewanted.com +``` + +# Description + +To use us as a bidder you must have an account and an active "zoneId" on our SmileWanted platform. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "smilewanted", + params: { + zoneId: 1 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js new file mode 100644 index 00000000000..489f393523a --- /dev/null +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -0,0 +1,191 @@ +import { expect } from 'chai'; +import { spec } from 'modules/smilewantedBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; +import * as utils from 'src/utils'; +import { requestBidsHook } from 'modules/consentManagement'; + +// Default params with optional ones +describe('smilewantedBidAdapterTests', function () { + var DEFAULT_PARAMS = [{ + adUnitCode: 'sw_300x250', + bidId: '12345', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smilewanted', + params: { + zoneId: '1234', + bidfloor: 2.50 + }, + requestId: 'request_abcd1234', + transactionId: 'trans_abcd1234' + }]; + + var BID_RESPONSE = { + body: { + cpm: 3, + width: 300, + height: 250, + creativeId: 'crea_sw_1', + currency: 'EUR', + isNetCpm: true, + ttl: 300, + adUrl: 'https://www.smilewanted.com', + ad: '< --- sw script --- >', + cSyncUrl: 'https://csync.smilewanted.com' + } + }; + + it('SmileWanted - Verify build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS); + expect(request[0]).to.have.property('url').and.to.equal('https://prebid.smilewanted.com'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('zoneId').and.to.equal('1234'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(2.50); + expect(requestContent).to.have.property('sizes'); + expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(200); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + }); + + it('SmileWanted - Verify build request with referrer', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, { + refererInfo: { + referer: 'http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('pageDomain').and.to.equal('http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html'); + }); + + describe('gdpr tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('SmileWanted - Verify build request with GDPR', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS, { + gdprConsent: { + consentString: 'BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA', + gdprApplies: true + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr').and.to.equal(true); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA'); + }); + + it('SmileWanted - Verify build request with GDPR without gdprApplies', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS, { + gdprConsent: { + consentString: 'BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.not.have.property('gdpr'); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA'); + }); + }); + + it('SmileWanted - Verify parse response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(3); + expect(bid.adUrl).to.equal('https://www.smilewanted.com'); + expect(bid.ad).to.equal('< --- sw script --- >'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('crea_sw_1'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(DEFAULT_PARAMS[0].bidId); + + expect(function () { + spec.interpretResponse(BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('SmileWanted - Verify bidder code', function () { + expect(spec.code).to.equal('smilewanted'); + }); + + it('SmileWanted - Verify bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(2); + expect(spec.aliases[0]).to.equal('smile'); + expect(spec.aliases[1]).to.equal('sw'); + }); + + it('SmileWanted - Verify if bid request valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + zoneId: 1234 + } + })).to.equal(true); + }); + + it('SmileWanted - Verify if params(zoneId) is not passed', function () { + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + }); + + it('SmileWanted - Verify user sync', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE]); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://csync.smilewanted.com'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false + }, [BID_RESPONSE]); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true + }, []); + expect(syncs).to.have.lengthOf(0); + }); +}); From c173ab247c4ae0d0ed6dddaf730e835ab7f45649 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Mon, 11 Mar 2019 15:09:25 -0400 Subject: [PATCH 09/33] fix lint errors in ozoneBidAdapter unit tests (#3624) --- test/spec/modules/ozoneBidAdapter_spec.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index 995ec93ad99..a910ef391b0 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -73,7 +73,7 @@ var validBidRequestsWithNonBannerMediaTypes = [ bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, ozoneData: {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - mediaTypes: {video:{info:'dummy data'}, native:{info:'dummy data'}}, + mediaTypes: {video: {info: 'dummy data'}, native: {info: 'dummy data'}}, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; @@ -580,7 +580,7 @@ describe('ozone Adapter', function () { expect(result).to.be.empty; }); it('should fail ok if seatbid is not an array', function () { - const result = spec.interpretResponse({'body':{'seatbid':'nothing_here'}}, {}); + const result = spec.interpretResponse({'body': {'seatbid': 'nothing_here'}}, {}); expect(result).to.be.an('array'); expect(result).to.be.empty; }); @@ -596,5 +596,4 @@ describe('ozone Adapter', function () { expect(result).to.be.empty; }); }); - }); From 16e5e1609cdba4598c9c3c15bf99f571e6965566 Mon Sep 17 00:00:00 2001 From: kusapan Date: Tue, 12 Mar 2019 07:25:30 +0900 Subject: [PATCH 10/33] YIELDONE adapter - add buildRequests payload params (#3611) * added UserSync * added UserSync Unit Test * support for multi sizes * register the adapter as supporting video * supporting video * change requestId acquisition method * fix the parameter name of dealID * update test parameters * support instream video * add test for bidRequest * add test for interpretResponse * add test params * add note to documentaion * add payload params * add test * delete tmax param --- modules/yieldoneBidAdapter.js | 2 ++ test/spec/modules/yieldoneBidAdapter_spec.js | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 728665bb204..487cd3a306d 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -21,12 +21,14 @@ export const spec = { const cb = Math.floor(Math.random() * 99999999999); const referrer = encodeURIComponent(utils.getTopWindowUrl()); const bidId = bidRequest.bidId; + const unitCode = bidRequest.adUnitCode; const payload = { v: 'hb1', p: placementId, cb: cb, r: referrer, uid: bidId, + uc: unitCode, t: 'i' }; diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index e064b90f059..e91eeab1369 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -88,6 +88,11 @@ describe('yieldoneBidAdapter', function() { expect(request[0].data.w).to.equal('300'); expect(request[0].data.h).to.equal('250'); }); + + it('adUnitCode should be sent as uc parameters on any requests', function () { + expect(request[0].data.uc).to.equal('adunit-code1'); + expect(request[1].data.uc).to.equal('adunit-code2'); + }); }); describe('interpretResponse', function () { From 0b3929808bb1b3b4270aeb0d43f762dc45e1ac1a Mon Sep 17 00:00:00 2001 From: Benjamin Clot Date: Tue, 12 Mar 2019 00:26:31 +0100 Subject: [PATCH 11/33] Apply matchMedia to the top frame (#3612) * Apply matchMedia to the top frame In case Prebid.js is called from within an iFrame, matchMedia should be applied to window.top, not the containing iFrame. This PR falls back to the legacy behavior in case of unfriendly iFrames. * Spec update --- src/sizeMapping.js | 14 ++++++++++++-- test/spec/sizeMapping_spec.js | 7 +++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/sizeMapping.js b/src/sizeMapping.js index b8278df69c0..04c9773a1af 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -1,5 +1,5 @@ import { config } from './config'; -import {logWarn, isPlainObject, deepAccess, deepClone} from './utils'; +import {logWarn, isPlainObject, deepAccess, deepClone, getWindowTop} from './utils'; import includes from 'core-js/library/fn/array/includes'; let sizeConfig = []; @@ -123,7 +123,17 @@ function evaluateSizeConfig(configs) { typeof config === 'object' && typeof config.mediaQuery === 'string' ) { - if (matchMedia(config.mediaQuery).matches) { + let ruleMatch = false; + + try { + ruleMatch = getWindowTop().matchMedia(config.mediaQuery).matches; + } catch (e) { + logWarn('Unfriendly iFrame blocks sizeConfig from being correctly evaluated'); + + ruleMatch = matchMedia(config.mediaQuery).matches; + } + + if (ruleMatch) { if (Array.isArray(config.sizesSupported)) { results.shouldFilter = true; } diff --git a/test/spec/sizeMapping_spec.js b/test/spec/sizeMapping_spec.js index aca4ccf8f54..f85da4cba0b 100644 --- a/test/spec/sizeMapping_spec.js +++ b/test/spec/sizeMapping_spec.js @@ -60,6 +60,13 @@ describe('sizeMapping', function () { matchMediaOverride = {matches: false}; + sandbox.stub(utils.getWindowTop(), 'matchMedia').callsFake((...args) => { + if (typeof matchMediaOverride === 'function') { + return matchMediaOverride.apply(utils.getWindowTop(), args); + } + return matchMediaOverride; + }); + sandbox.stub(window, 'matchMedia').callsFake((...args) => { if (typeof matchMediaOverride === 'function') { return matchMediaOverride.apply(window, args); From 6c48043a8458869f8d40d4fa991f8a03b7f05e01 Mon Sep 17 00:00:00 2001 From: Jaimin Panchal <7393273+jaiminpanchal27@users.noreply.github.com> Date: Tue, 12 Mar 2019 10:19:48 -0400 Subject: [PATCH 12/33] update auction algorithm logic for long-form (#3625) --- modules/adpod.js | 15 +++++ modules/freeWheelAdserverVideo.js | 18 +----- src/utils.js | 17 ++++++ test/spec/modules/adpod_spec.js | 61 ++++++++++++++++++- .../modules/freeWheelAdserverVideo_spec.js | 32 +++++----- 5 files changed, 111 insertions(+), 32 deletions(-) diff --git a/modules/adpod.js b/modules/adpod.js index 9cb8c3528a5..fe49c10e193 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -413,3 +413,18 @@ export function callPrebidCacheAfterAuction(bids, callback) { } }) } + +/** + * Compare function to be used in sorting long-form bids. This will compare bids on price per second. + * @param {Object} bid + * @param {Object} bid + */ +export function sortByPricePerSecond(a, b) { + if (a.cpm / a.video.durationBucket < b.cpm / b.video.durationBucket) { + return 1; + } + if (a.cpm / a.video.durationBucket > b.cpm / b.video.durationBucket) { + return -1; + } + return 0; +} diff --git a/modules/freeWheelAdserverVideo.js b/modules/freeWheelAdserverVideo.js index 01ed145b00b..c81f3356d71 100644 --- a/modules/freeWheelAdserverVideo.js +++ b/modules/freeWheelAdserverVideo.js @@ -4,10 +4,10 @@ import { registerVideoSupport } from '../src/adServerManager'; import { auctionManager } from '../src/auctionManager'; -import { groupBy, deepAccess, logError } from '../src/utils'; +import { groupBy, deepAccess, logError, compareOn } from '../src/utils'; import { config } from '../src/config'; import { ADPOD } from '../src/mediaTypes'; -import { initAdpodHooks, TARGETING_KEY_PB_CAT_DUR, TARGETING_KEY_CACHE_ID, callPrebidCacheAfterAuction } from './adpod'; +import { initAdpodHooks, TARGETING_KEY_PB_CAT_DUR, TARGETING_KEY_CACHE_ID, callPrebidCacheAfterAuction, sortByPricePerSecond } from './adpod'; import { getHook } from '../src/hook'; export function notifyTranslationModule(fn) { @@ -37,7 +37,7 @@ export function getTargeting({codes, callback} = {}) { let bids = getBidsForAdpod(bidsReceived, adPodAdUnits); bids = (competiveExclusionEnabled || deferCachingEnabled) ? getExclusiveBids(bids) : bids; - bids.sort(compareOn('cpm')); + bids.sort(sortByPricePerSecond); let targeting = {}; if (deferCachingEnabled === false) { @@ -119,18 +119,6 @@ function getAdPodAdUnits(codes) { .filter((adUnit) => (codes.length > 0) ? codes.indexOf(adUnit.code) != -1 : true); } -function compareOn(property) { - return function compare(a, b) { - if (a[property] < b[property]) { - return 1; - } - if (a[property] > b[property]) { - return -1; - } - return 0; - } -} - /** * This function removes bids of same freewheel category. It will be used when competitive exclusion is enabled. * @param {Array[Object]} bidsReceived diff --git a/src/utils.js b/src/utils.js index 5e70b31b0b2..a93e25356bd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1276,3 +1276,20 @@ export function getMinValueFromArray(array) { export function getMaxValueFromArray(array) { return Math.max(...array); } + +/** + * This function will create compare function to sort on object property + * @param {string} property + * @returns {function} compare function to be used in sorting + */ +export function compareOn(property) { + return function compare(a, b) { + if (a[property] < b[property]) { + return 1; + } + if (a[property] > b[property]) { + return -1; + } + return 0; + } +} diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js index b38f7ad545e..a47c77d25ac 100644 --- a/test/spec/modules/adpod_spec.js +++ b/test/spec/modules/adpod_spec.js @@ -4,7 +4,7 @@ import * as videoCache from 'src/videoCache'; import * as auction from 'src/auction'; import { ADPOD } from 'src/mediaTypes'; -import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig } from 'modules/adpod'; +import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig, sortByPricePerSecond } from 'modules/adpod'; let expect = require('chai').expect; @@ -990,4 +990,63 @@ describe('adpod.js', function () { expect(logWarnStub.called).to.equal(false); }) }); + + describe('adpod utils', function() { + it('should sort bids array', function() { + let bids = [{ + cpm: 10.12345, + video: { + durationBucket: 15 + } + }, { + cpm: 15, + video: { + durationBucket: 15 + } + }, { + cpm: 15.00, + video: { + durationBucket: 30 + } + }, { + cpm: 5.45, + video: { + durationBucket: 5 + } + }, { + cpm: 20.1234567, + video: { + durationBucket: 60 + } + }] + bids.sort(sortByPricePerSecond); + let sortedBids = [{ + cpm: 5.45, + video: { + durationBucket: 5 + } + }, { + cpm: 15, + video: { + durationBucket: 15 + } + }, { + cpm: 10.12345, + video: { + durationBucket: 15 + } + }, { + cpm: 15.00, + video: { + durationBucket: 30 + } + }, { + cpm: 20.1234567, + video: { + durationBucket: 60 + } + }] + expect(bids).to.include.deep.ordered.members(sortedBids); + }); + }) }); diff --git a/test/spec/modules/freeWheelAdserverVideo_spec.js b/test/spec/modules/freeWheelAdserverVideo_spec.js index 7f1c8857285..5846774c8b1 100644 --- a/test/spec/modules/freeWheelAdserverVideo_spec.js +++ b/test/spec/modules/freeWheelAdserverVideo_spec.js @@ -86,7 +86,7 @@ describe('freeWheel adserver module', function() { }); expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['midroll_1'].length).to.equal(2); + expect(targeting['midroll_1'].length).to.equal(3); }); it('should return targeting for passed adunit code', function() { @@ -128,7 +128,7 @@ describe('freeWheel adserver module', function() { }); expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['midroll_1'].length).to.equal(2); + expect(targeting['midroll_1'].length).to.equal(3); }); it('should return unique category bids when competitive exclusion is enabled', function() { @@ -139,10 +139,10 @@ describe('freeWheel adserver module', function() { } }); amStub.returns([ - createBid(10, 'preroll_1', 30, '10.00_airline_30s', '123', 'airline'), - createBid(15, 'preroll_1', 30, '15.00_airline_30s', '123', 'airline'), - createBid(15, 'midroll_1', 60, '15.00_travel_60s', '123', 'travel'), - createBid(10, 'preroll_1', 30, '10.00_airline_30s', '123', 'airline') + createBid(10, 'preroll_1', 30, '10.00_395_30s', '123', '395'), + createBid(15, 'preroll_1', 30, '15.00_395_30s', '123', '395'), + createBid(15, 'midroll_1', 60, '15.00_406_60s', '123', '406'), + createBid(10, 'preroll_1', 30, '10.00_395_30s', '123', '395') ]); let targeting; getTargeting({ @@ -157,9 +157,9 @@ describe('freeWheel adserver module', function() { it('should only select bids less than adpod duration', function() { amStub.returns([ - createBid(10, 'preroll_1', 90, '10.00_airline_90s', '123', 'airline'), - createBid(15, 'preroll_1', 90, '15.00_airline_90s', '123', 'airline'), - createBid(15, 'midroll_1', 90, '15.00_travel_90s', '123', 'travel') + createBid(10, 'preroll_1', 90, '10.00_395_90s', '123', '395'), + createBid(15, 'preroll_1', 90, '15.00_395_90s', '123', '395'), + createBid(15, 'midroll_1', 90, '15.00_406_90s', '123', '406') ]); let targeting; getTargeting({ @@ -194,11 +194,11 @@ describe('freeWheel adserver module', function() { function getBidsReceived() { return [ - createBid(10, 'preroll_1', 15, '10.00_airline_15s', '123', 'airline'), - createBid(15, 'preroll_1', 15, '15.00_airline_15s', '123', 'airline'), - createBid(15, 'midroll_1', 30, '15.00_travel_30s', '123', 'travel'), - createBid(5, 'midroll_1', 5, '5.00_travel_5s', '123', 'travel'), - createBid(20, 'midroll_1', 60, '20.00_travel_60s', '123', 'travel'), + createBid(10, 'preroll_1', 15, '10.00_395_15s', '123', '395'), + createBid(15, 'preroll_1', 15, '15.00_395_15s', '123', '395'), + createBid(15, 'midroll_1', 30, '15.00_406_30s', '123', '406'), + createBid(5, 'midroll_1', 5, '5.00_406_5s', '123', '406'), + createBid(20, 'midroll_1', 60, '20.00_406_60s', '123', '406'), ] } @@ -225,8 +225,8 @@ function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, 'appnexus': { 'buyerMemberId': 9325 }, - 'vastUrl': 'http://nym1-ib.adnxs.com/ab?ro=1&referrer=http%3A%2F%2Fprebid.org%2Fexamples%2Fvideo%2FjwPlayerPrebid.html&e=wqT_3QKQCKAQBAAAAwDWAAUBCOC2reIFENXVz86_iKrdKRiyjp7_7P7s0GQqNgkAAAECCBRAEQEHNAAAFEAZAAAA4HoUFEAhERIAKREJADERG6gw6dGnBjjtSEDtSEgCUMuBwC5YnPFbYABozbp1eIHdBIABAYoBA1VTRJIBAQbwUJgBAaABAagBAbABALgBA8ABBMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU0ODQ0MjQ2NCk7dWYoJ3InLCA5NzUxNzc3MTYeAPQAAZIC8QEhOXpPdkVBaTItTHdLRU11QndDNFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUTZkR25CbGdBWUVwb0FIQ0FBWGdBZ0FHMEFvZ0JBSkFCQVpnQkFhQUJBYWdCQTdBQkFMa0I4NjFxcEFBQUZFREJBZk90YXFRQUFCUkF5UUhWSVlsRnN5SDRQOWtCQUFBQUFBQUE4RF9nQVFEMUFRQUFBQUNZQWdDZ0FnQzFBZ0FBQUFDOUFnQUFBQURBQWdESUFnRGdBZ0RvQWdENEFnQ0FBd0dRQXdDWUF3R29BN2I0dkFxNkF3bE9XVTB5T2pRd016SGdBODBGmgJhIU53M1VaUWkyLvQAKG5QRmJJQVFvQURFCY1cQUFVUURvSlRsbE5Nam8wTURNeFFNMEZTBZwYQUFBUEFfVREMDEFBQVcdDPBMwgI_aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc2hvdy12aWRlby13aXRoLWEtZGZwLXZpZGVvLXRhZy5odG1s2AIA4AKtmEjqAjRGSgAgZXhhbXBsZXMvBUUkL2p3UGxheWVyUAlseGh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-4ElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw3-8E6YBACiBAsxMC4xLjEyLjE4MKgEjq4IsgQSCAEQAhiABSDoAigBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNOWU0yOjQwMzHaBAIIAeAEAPAEYTYgiAUBmAUAoAX_EQEUAcAFAMkFaXAU8D_SBQkJCQx4AADYBQHgBQHwBcOVC_oFBAgAEACQBgGYBgC4BgDBBgklJPA_yAYA2gYWChAJEDQAAAAAAAAAAAAAEAAYAA..&s=539bcaeb9ce05a13a8c4a6cab3c000194a8e8f53', - 'vastImpUrl': 'http://nym1-ib.adnxs.com/vast_track/v2?info=ZQAAAAMArgAFAQlgW0tcAAAAABHV6tP5Q6i6KRlgW0tcAAAAACDLgcAuKAAw7Ug47UhA0-hISLuv1AFQ6dGnBljDlQtiAkZSaAFwAXgAgAEBiAEBkAGABZgB6AKgAQCoAcuBwC4.&s=61db1767c8c362ef1a58d2c5587dd6a9b1015aeb&event_type=1', + 'vastUrl': 'http://some-vast-url.com', + 'vastImpUrl': 'http://some-vast-imp-url.com', 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', 'responseTimestamp': 1548442460888, 'requestTimestamp': 1548442460827, From 1be14882870c9e2bb08131353d37824a846c18ad Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Tue, 12 Mar 2019 16:49:44 -0400 Subject: [PATCH 13/33] update circleci test to not auto-fix lint errors (#3623) --- .circleci/config.yml | 2 +- gulpfile.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fbf7e77e10f..16bdd5b317e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,4 +47,4 @@ jobs: # run tests! - run: name: BrowserStack testing - command: gulp test --browserstack + command: gulp test --browserstack --nolintfix diff --git a/gulpfile.js b/gulpfile.js index 8775b651b9f..d3b29aa66a7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -83,7 +83,7 @@ function lint(done) { return file.eslint != null && file.eslint.fixed; } return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js'], {base: './'}) - .pipe(eslint({fix: true})) + .pipe(gulpif(argv.nolintfix, eslint(), eslint({fix: true}))) .pipe(eslint.format('stylish')) .pipe(eslint.failAfterError()) .pipe(gulpif(isFixed, gulp.dest('./'))); From c8f928c3898bf7335beed337c5aa6232fd582ec6 Mon Sep 17 00:00:00 2001 From: Jaimin Panchal Date: Tue, 12 Mar 2019 16:56:58 -0400 Subject: [PATCH 14/33] Prebid 2.6.0 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cd78a3036b9..b33581bd2c0 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.6.0-pre", + "version": "2.6.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 18b04f3849e58d902859b41ffe2911745ae58148 Mon Sep 17 00:00:00 2001 From: Jaimin Panchal Date: Tue, 12 Mar 2019 17:04:17 -0400 Subject: [PATCH 15/33] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b33581bd2c0..82f92b5045f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.6.0", + "version": "2.7.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From b4870e7d9650cc2d4480247d1ff08889f650034a Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Thu, 14 Mar 2019 07:23:54 -0700 Subject: [PATCH 16/33] Added a consistent prefix in logWarn message in PubMatic adapter (#3633) --- modules/pubmaticBidAdapter.js | 47 ++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index b8da30021cd..9044597c742 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -4,6 +4,7 @@ import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes'; import {config} from '../src/config'; const BIDDER_CODE = 'pubmatic'; +const LOG_WARN_PREFIX = 'PubMatic: '; const ENDPOINT = '//hbopenbid.pubmatic.com/translator?source=prebid-client'; const USYNCURL = '//ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p='; const DEFAULT_CURRENCY = 'USD'; @@ -175,7 +176,7 @@ function _getDomainFromURL(url) { function _parseSlotParam(paramName, paramValue) { if (!utils.isStr(paramValue)) { - paramValue && utils.logWarn('PubMatic: Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); + paramValue && utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); return UNDEFINED; } @@ -221,7 +222,7 @@ function _parseAdSlot(bid) { splits = slot.split('@'); if (splits.length != 2) { if (!(sizesArrayExists)) { - utils.logWarn('AdSlot Error: adSlot not in required format'); + utils.logWarn(LOG_WARN_PREFIX + 'AdSlot Error: adSlot not in required format'); return; } } @@ -229,7 +230,7 @@ function _parseAdSlot(bid) { if (splits.length > 1) { // i.e size is specified in adslot, so consider that and ignore sizes array splits = splits[1].split('x'); if (splits.length != 2) { - utils.logWarn('AdSlot Error: adSlot not in required format'); + utils.logWarn(LOG_WARN_PREFIX + 'AdSlot Error: adSlot not in required format'); return; } bid.params.width = parseInt(splits[0]); @@ -273,7 +274,7 @@ function _handleCustomParams(params, conf) { if (utils.isStr(value)) { conf[key] = value; } else { - utils.logWarn('PubMatic: Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); + utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); } } } @@ -311,25 +312,25 @@ function _checkParamDataType(key, value, datatype) { switch (datatype) { case DATA_TYPES.BOOLEAN: if (!utils.isBoolean(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; case DATA_TYPES.NUMBER: if (!utils.isNumber(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; case DATA_TYPES.STRING: if (!utils.isStr(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; case DATA_TYPES.ARRAY: if (!utils.isArray(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; @@ -356,7 +357,7 @@ function _createNativeRequest(params) { } }; } else { - utils.logWarn(BIDDER_CODE + ' Error: Title Length is required for native ad: ' + JSON.stringify(params)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Title Length is required for native ad: ' + JSON.stringify(params)); } break; case NATIVE_ASSET_KEY.IMAGE: @@ -376,7 +377,7 @@ function _createNativeRequest(params) { }; } else { // Log Warn - utils.logWarn(BIDDER_CODE + ' Error: Image sizes is required for native ad: ' + JSON.stringify(params)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Image sizes is required for native ad: ' + JSON.stringify(params)); } break; case NATIVE_ASSET_KEY.ICON: @@ -392,7 +393,7 @@ function _createNativeRequest(params) { }; } else { // Log Warn - utils.logWarn(BIDDER_CODE + ' Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); }; break; case NATIVE_ASSET_KEY.SPONSOREDBY: @@ -650,7 +651,7 @@ function _createImpressionObject(bid, conf) { impObj.banner = bannerObj; } if (isInvalidNativeRequest && impObj.hasOwnProperty('native')) { - utils.logWarn(BIDDER_CODE + ': Call to OpenBid will not be sent for native ad unit as it does not contain required valid native params.' + JSON.stringify(bid) + ' Refer:' + PREBID_NATIVE_HELP_LINK); + utils.logWarn(LOG_WARN_PREFIX + 'Call to OpenBid will not be sent for native ad unit as it does not contain required valid native params.' + JSON.stringify(bid) + ' Refer:' + PREBID_NATIVE_HELP_LINK); return; } return impObj; @@ -717,7 +718,7 @@ function _parseNativeResponse(bid, newBid) { try { adm = JSON.parse(bid.adm.replace(/\\/g, '')); } catch (ex) { - utils.logWarn(BIDDER_CODE + ' Error: Cannot parse native reponse for ad response: ' + newBid.adm); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); return; } if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { @@ -784,17 +785,17 @@ export const spec = { isBidRequestValid: bid => { if (bid && bid.params) { if (!utils.isStr(bid.params.publisherId)) { - utils.logWarn(BIDDER_CODE + ' Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } if (!utils.isStr(bid.params.adSlot)) { - utils.logWarn(BIDDER_CODE + ': adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } // video ad validation if (bid.params.hasOwnProperty('video')) { if (!bid.params.video.hasOwnProperty('mimes') || !utils.isArray(bid.params.video.mimes) || bid.params.video.mimes.length === 0) { - utils.logWarn(BIDDER_CODE + ': For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); return false; } } @@ -826,12 +827,12 @@ export const spec = { _parseAdSlot(bid); if (bid.params.hasOwnProperty('video')) { if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex)) { - utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); return; } } else { if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex && bid.params.width && bid.params.height)) { - utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); return; } } @@ -841,7 +842,7 @@ export const spec = { if (bidCurrency === '') { bidCurrency = bid.params.currency || undefined; } else if (bid.params.hasOwnProperty('currency') && bidCurrency !== bid.params.currency) { - utils.logWarn(BIDDER_CODE + ': Currency specifier ignored. Only one currency permitted.'); + utils.logWarn(LOG_WARN_PREFIX + 'Currency specifier ignored. Only one currency permitted.'); } bid.params.currency = bidCurrency; // check if dctr is added to more than 1 adunit @@ -910,13 +911,13 @@ export const spec = { key_val: dctr.trim() } } else { - utils.logWarn(BIDDER_CODE + ': Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); + utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); } if (dctrArr.length > 1) { - utils.logWarn(BIDDER_CODE + ': dctr value found in more than 1 adunits. Value from 1st adunit will be picked. Ignoring values from subsequent adunits'); + utils.logWarn(LOG_WARN_PREFIX + 'dctr value found in more than 1 adunits. Value from 1st adunit will be picked. Ignoring values from subsequent adunits'); } } else { - utils.logWarn(BIDDER_CODE + ': dctr value not found in 1st adunit, ignoring values from subsequent adunits'); + utils.logWarn(LOG_WARN_PREFIX + 'dctr value not found in 1st adunit, ignoring values from subsequent adunits'); } } @@ -1004,7 +1005,7 @@ export const spec = { url: syncurl }]; } else { - utils.logWarn('PubMatic: Please enable iframe based user sync.'); + utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.'); } }, From 9e0f00c0cb0ad4c3ddafcbd705d83685034b1528 Mon Sep 17 00:00:00 2001 From: Justin Grimes Date: Fri, 15 Mar 2019 06:19:57 -0700 Subject: [PATCH 17/33] Consumable Bid Adapter: Pass GDPR and Prebid version info. Pixel syncs. (#3578) --- modules/consumableBidAdapter.js | 33 ++++++++++--------- .../spec/modules/consumableBidAdapter_spec.js | 14 +++++++- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 9374be62452..d462acaee59 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -28,10 +28,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests) { - // Do we need to group by bidder? i.e. to make multiple requests for - // different endpoints. - + buildRequests: function(validBidRequests, bidderRequest) { let ret = { method: 'POST', url: '', @@ -50,14 +47,21 @@ export const spec = { const data = Object.assign({ placements: [], time: Date.now(), - user: {}, url: utils.getTopWindowUrl(), referrer: document.referrer, - enableBotFiltering: true, - includePricingData: true, - parallel: true + source: [{ + 'name': 'prebidjs', + 'version': '$prebid.version$' + }] }, validBidRequests[0].params); + if (bidderRequest && bidderRequest.gdprConsent) { + data.gdpr = { + consent: bidderRequest.gdprConsent.consentString, + applies: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + }; + } + validBidRequests.map(bid => { const placement = Object.assign({ divName: bid.bidId, @@ -123,12 +127,16 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, serverResponses) { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', url: '//sync.serverbid.com/ss/' + siteId + '.html' }]; + } + + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + return serverResponses[0].body.pixels; } else { utils.logWarn(bidder + ': Please enable iframe based user syncing.'); } @@ -192,12 +200,7 @@ function getSize(sizes) { } function retrieveAd(decision, unitId, unitName) { - let oad = decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); - let cb = Math.round(new Date().getTime()); - let ad = '' + oad; - ad += ''; - ad += ''; - ad += '' + let ad = decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); return ad; } diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index 832706b2b95..fc5e1d1b45a 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -43,6 +43,10 @@ const REQUEST = { 'bidderRequestId': '109f2a181342a9', 'auctionId': 'a4713c32-3762-4798-b342-4ab810ca770d' }], + 'gdprConsent': { + 'consentString': 'consent-test', + 'gdprApplies': true + }, 'start': 1487883186070, 'auctionStart': 1487883186069, 'timeout': 3000 @@ -52,6 +56,7 @@ const RESPONSE = { 'headers': null, 'body': { 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'pixels': [{ 'type': 'image', 'url': '//sync.serverbid.com/ss/' }], 'decisions': { '2b0f82502298c9': { 'adId': 2364764, @@ -206,7 +211,7 @@ describe('Consumable BidAdapter', function () { }); describe('interpretResponse validation', function () { it('response should have valid bidderCode', function () { - let bidRequest = spec.buildRequests(REQUEST.bidRequest); + let bidRequest = spec.buildRequests(REQUEST.bidRequest, REQUEST); let bid = createBid(1, bidRequest.bidRequest[0]); expect(bid.bidderCode).to.equal('consumable'); @@ -264,5 +269,12 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); + + it('should return a sync url if pixel syncs are enabled and some are returned from the server', function () { + let syncOptions = {'pixelEnabled': true}; + let opts = spec.getUserSyncs(syncOptions, [RESPONSE]); + + expect(opts.length).to.equal(1); + }); }); }); From 0c5fbabfd64c784a50834d75b63c19583809148e Mon Sep 17 00:00:00 2001 From: Hendrik Iseke <39734979+hiseke@users.noreply.github.com> Date: Fri, 15 Mar 2019 15:20:21 +0100 Subject: [PATCH 18/33] add bidfloor to params object (#3641) * initial orbidder version in personal github repo * use adUnits from orbidder_example.html * replace obsolete functions * forgot to commit the test * check if bidderRequest object is available * try to fix weird safari/ie issue * ebayK: add more params * update orbidderBidAdapter.md * use spec. instead of this. for consistency reasons * add bidfloor parameter to params object --- modules/orbidderBidAdapter.js | 1 + test/spec/modules/orbidderBidAdapter_spec.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index de365fe87de..e316f3ef212 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -18,6 +18,7 @@ export const spec = { return !!(bid.sizes && bid.bidId && bid.params && (bid.params.accountId && (typeof bid.params.accountId === 'string')) && (bid.params.placementId && (typeof bid.params.placementId === 'string')) && + ((typeof bid.params.bidfloor === 'undefined') || (typeof bid.params.bidfloor === 'number')) && ((typeof bid.params.keyValues === 'undefined') || (typeof bid.params.keyValues === 'object'))); }, diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js index 29c6c2c6d9a..0761ed8d31e 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -64,6 +64,21 @@ describe('orbidderBidAdapter', () => { delete bidRequest.params; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); + + it('accepts optional bidfloor', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.bidfloor = 123; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + + bidRequest.params.bidfloor = 1.23; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('doesn\'t accept malformed bidfloor', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.bidfloor = 'another not usable string'; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); }); describe('buildRequests', () => { From 879789ba4e2c8ee988f4c368d3e1e94bc112e6ec Mon Sep 17 00:00:00 2001 From: Finteza Analytics <45741245+finteza@users.noreply.github.com> Date: Fri, 15 Mar 2019 20:37:07 +0200 Subject: [PATCH 19/33] Add finteza analytics adapter (#3555) * Add finteza analytics adapter * Fix PR issues --- modules/fintezaAnalyticsAdapter.js | 406 ++++++++++++++++++ modules/fintezaAnalyticsAdapter.md | 28 ++ .../modules/fintezaAnalyticsAdapter_spec.js | 209 +++++++++ 3 files changed, 643 insertions(+) create mode 100644 modules/fintezaAnalyticsAdapter.js create mode 100644 modules/fintezaAnalyticsAdapter.md create mode 100644 test/spec/modules/fintezaAnalyticsAdapter_spec.js diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js new file mode 100644 index 00000000000..2b5cd0421c2 --- /dev/null +++ b/modules/fintezaAnalyticsAdapter.js @@ -0,0 +1,406 @@ +import { ajax } from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; +import * as utils from '../src/utils'; +import { parse as parseURL } from '../src/url'; + +const CONSTANTS = require('../src/constants.json'); + +const ANALYTICS_TYPE = 'endpoint'; +const FINTEZA_HOST = 'https://content.mql5.com/tr'; +const BID_REQUEST_TRACK = 'Bid Request %BIDDER%'; +const BID_RESPONSE_TRACK = 'Bid Response %BIDDER%'; +const BID_TIMEOUT_TRACK = 'Bid Timeout %BIDDER%'; +const BID_WON_TRACK = 'Bid Won %BIDDER%'; + +const FIRST_VISIT_DATE = '_fz_fvdt'; +const SESSION_ID = '_fz_ssn'; +const SESSION_DURATION = 30 * 60 * 1000; +const SESSION_RAND_PART = 9; +const TRACK_TIME_KEY = '_fz_tr'; + +function getPageInfo() { + const pageInfo = { + domain: window.location.hostname, + } + + if (document.referrer) { + pageInfo.referrerDomain = parseURL(document.referrer).hostname; + } + + return pageInfo; +} + +function initFirstVisit() { + let now; + let visitDate; + let cookies; + + try { + cookies = parseCookies(document.cookie); + } catch (a) { + cookies = {}; + } + + visitDate = cookies[ FIRST_VISIT_DATE ]; + + if (!visitDate) { + now = new Date(); + + visitDate = parseInt(now.getTime() / 1000, 10); + + now.setFullYear(now.getFullYear() + 20); + + try { + document.cookie = FIRST_VISIT_DATE + '=' + visitDate + '; path=/; expires=' + now.toUTCString(); + } catch (e) {} + } + + return visitDate; +} + +function trim(string) { + if (string.trim) { + return string.trim(); + } + return string.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); +} + +function parseCookies(cookie) { + let values = {}; + let arr, item; + let param, value; + let i, j; + + if (!cookie) { + return {}; + } + + arr = cookie.split(';'); + + for (i = 0, j = arr.length; i < j; i++) { + item = arr[ i ]; + if (!item) { + continue; + } + + param = item.split('='); + if (param.length <= 1) { + continue; + } + + value = decodeURIComponent(param[0]); + value = trim(value); + + values[value] = decodeURIComponent(param[1]); + } + + return values; +} + +function getRandAsStr(digits) { + let str = ''; + let rand = 0; + let i; + + digits = digits || 4; + + for (i = 0; i < digits; i++) { + rand = (Math.random() * 10) >>> 0; + str += '' + rand; + } + + return str; +} + +function getSessionBegin(session) { + if (!session || (typeof session !== 'string')) { + return 0; + } + + const len = session.length; + if (len && len <= SESSION_RAND_PART) { + return 0; + } + + const timestamp = session.substring(0, len - SESSION_RAND_PART); + + return parseInt(timestamp, 10); +} + +function initSession() { + const now = new Date(); + const expires = new Date(now.getTime() + SESSION_DURATION); + const timestamp = Math.floor(now.getTime() / 1000); + let begin = 0; + let cookies; + let sessionId; + let sessionDuration; + let isNew = false; + + try { + cookies = parseCookies(document.cookie); + } catch (a) { + cookies = {}; + } + + sessionId = cookies[ SESSION_ID ]; + + if (!sessionId || + !checkSessionByExpires() || + !checkSessionByReferer() || + !checkSessionByDay()) { + sessionId = '' + timestamp + getRandAsStr(SESSION_RAND_PART); + begin = timestamp; + + isNew = true; + } else { + begin = getSessionBegin(sessionId); + } + + if (begin > 0) { + sessionDuration = Math.floor(timestamp - begin); + } else { + sessionDuration = -1; + } + + try { + document.cookie = SESSION_ID + '=' + sessionId + '; path=/; expires=' + expires.toUTCString(); + } catch (e) {} + + return { + isNew: isNew, + id: sessionId, + duration: sessionDuration + }; +} + +function checkSessionByExpires() { + const timestamp = getTrackRequestLastTime(); + const now = new Date().getTime(); + + if (now > timestamp + SESSION_DURATION) { + return false; + } + return true; +} + +function checkSessionByReferer() { + const referrer = fntzAnalyticsAdapter.context.pageInfo.referrerDomain; + const domain = fntzAnalyticsAdapter.context.pageInfo.domain; + + return referrer === '' || domain === referrer; +} + +function checkSessionByDay() { + let last = getTrackRequestLastTime(); + if (last) { + last = new Date(last); + const now = new Date(); + + return last.getUTCDate() === now.getUTCDate() && + last.getUTCMonth() === now.getUTCMonth() && + last.getUTCFullYear() === now.getUTCFullYear(); + } + + return false; +} + +function saveTrackRequestTime() { + const now = new Date().getTime(); + const expires = new Date(now + SESSION_DURATION); + + try { + if (window.localStorage) { + window.localStorage.setItem(TRACK_TIME_KEY, now.toString()); + } else { + document.cookie = TRACK_TIME_KEY + '=' + now + '; path=/; expires=' + expires.toUTCString(); + } + } catch (a) {} +} + +function getTrackRequestLastTime() { + let cookie; + + try { + if (window.localStorage) { + return parseInt( + window.localStorage.getItem(TRACK_TIME_KEY) || 0, + 10, + ); + } + + cookie = parseCookies(document.cookie); + cookie = cookie[ TRACK_TIME_KEY ]; + if (cookie) { + return parseInt(cookie, 10); + } + } catch (e) {} + + return 0; +} + +function getAntiCacheParam() { + const date = new Date(); + const rand = (Math.random() * 99999 + 1) >>> 0; + + return ([ date.getTime(), rand ].join('')); +} + +function replaceBidder(str, bidder) { + let _str = str; + _str = _str.replace(/\%bidder\%/, bidder.toLowerCase()); + _str = _str.replace(/\%BIDDER\%/, bidder.toUpperCase()); + _str = _str.replace(/\%Bidder\%/, bidder.charAt(0).toUpperCase() + bidder.slice(1).toLowerCase()); + + return _str; +} + +function prepareBidRequestedParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidRequestTrack, args.bidderCode)), + ref: encodeURIComponent(window.location.href), + }]; +} + +function prepareBidResponseParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidResponseTrack, args.bidderCode)), + c1_value: args.timeToRespond, + c1_unit: 'ms', + c2_value: args.cpm, + c2_unit: 'usd', + }]; +} + +function prepareBidWonParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidWonTrack, args.bidderCode)), + c1_value: args.cpm, + c1_unit: 'usd', + }]; +} + +function prepareBidTimeoutParams(args) { + return args.map(function(bid) { + return { + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidTimeoutTrack, bid.bidder)), + c1_value: bid.timeout, + c1_unit: 'ms', + }; + }) +} + +function prepareTrackData(evtype, args) { + let prepareParams = null; + + switch (evtype) { + case CONSTANTS.EVENTS.BID_REQUESTED: + prepareParams = prepareBidRequestedParams; + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + prepareParams = prepareBidResponseParams; + break; + case CONSTANTS.EVENTS.BID_WON: + prepareParams = prepareBidWonParams; + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + prepareParams = prepareBidTimeoutParams; + break; + } + + if (!prepareParams) { return null; } + + const data = prepareParams(args); + + if (!data) { return null; } + + const session = initSession(); + + return data.map(d => { + const trackData = Object.assign(d, { + id: fntzAnalyticsAdapter.context.id, + ref: encodeURIComponent(window.location.href), + title: encodeURIComponent(document.title), + scr_res: fntzAnalyticsAdapter.context.screenResolution, + fv_date: fntzAnalyticsAdapter.context.firstVisit, + ac: getAntiCacheParam(), + }) + + if (session.id) { + trackData.ssn = session.id; + } + if (session.isNew) { + session.isNew = false; + trackData.ssn_start = 1; + } + trackData.ssn_dr = session.duration; + + return trackData; + }); +} + +function sendTrackRequest(trackData) { + try { + ajax( + fntzAnalyticsAdapter.context.host, + null, // Callback + trackData, + { + method: 'GET', + contentType: 'application/x-www-form-urlencoded', + // preflight: true, + }, + ); + saveTrackRequestTime(); + } catch (err) { + utils.logError('Error on send data: ', err); + } +} + +const fntzAnalyticsAdapter = Object.assign( + adapter({ + FINTEZA_HOST, + ANALYTICS_TYPE + }), + { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + const trackData = prepareTrackData(eventType, args); + if (!trackData) { return; } + + trackData.forEach(sendTrackRequest); + } + } + } +); + +fntzAnalyticsAdapter.originEnableAnalytics = fntzAnalyticsAdapter.enableAnalytics; + +fntzAnalyticsAdapter.enableAnalytics = function (config) { + if (!config.options.id) { + utils.logError('Client ID (id) option is not defined. Analytics won\'t work'); + return; + } + + fntzAnalyticsAdapter.context = { + host: config.options.host || FINTEZA_HOST, + id: config.options.id, + bidRequestTrack: config.options.bidRequestTrack || BID_REQUEST_TRACK, + bidResponseTrack: config.options.bidResponseTrack || BID_RESPONSE_TRACK, + bidTimeoutTrack: config.options.bidTimeoutTrack || BID_TIMEOUT_TRACK, + bidWonTrack: config.options.bidWonTrack || BID_WON_TRACK, + firstVisit: initFirstVisit(), + screenResolution: `${window.screen.width}x${window.screen.height}`, + pageInfo: getPageInfo(), + }; + + fntzAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: fntzAnalyticsAdapter, + code: 'finteza' +}); + +export default fntzAnalyticsAdapter; diff --git a/modules/fintezaAnalyticsAdapter.md b/modules/fintezaAnalyticsAdapter.md new file mode 100644 index 00000000000..22525f55366 --- /dev/null +++ b/modules/fintezaAnalyticsAdapter.md @@ -0,0 +1,28 @@ +# Overview + +``` +Module Name: Finteza Analytics Adapter +Module Type: Analytics Adapter +Maintainer: renat@finteza.com +``` + +# Description + +The Finteza adapter for integration with Prebid is an analytics tool for publishers who use the Header Bidding technology. The adapter tracks auction opening, offer sending to advertisers, receipt of bids by the publisher and auction winner selection. All tracks are sent to Finteza and enable visual advertiser quality evaluation: how many offers partners accept, what prices they provide, how fast they respond and how often their bids win. + +For more information, visit the [official Finteza website](https://www.finteza.com/). + +# Test Parameters + +``` +{ + provider: 'finteza', + options: { + id: 'xxxxx', // Website ID (required) + bidRequestTrack: 'Bid Request %BIDDER%', + bidResponseTrack: 'Bid Response %BIDDER%', + bidTimeoutTrack: 'Bid Timeout %BIDDER%', + bidWonTrack: 'Bid Won %BIDDER%' + } +} +``` diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..95600e84a9f --- /dev/null +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -0,0 +1,209 @@ +import fntzAnalyticsAdapter from 'modules/fintezaAnalyticsAdapter'; +import includes from 'core-js/library/fn/array/includes'; +import { expect } from 'chai'; +import { parse as parseURL } from 'src/url'; +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('finteza analytics adapter', function () { + const clientId = 'fntz-client-32145'; + + let xhr; + let requests; + + beforeEach(function () { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => { requests.push(request) }; + sinon.stub(events, 'getEvents').returns([]); + sinon.spy(fntzAnalyticsAdapter, 'track'); + + adapterManager.registerAnalyticsAdapter({ + code: 'finteza', + adapter: fntzAnalyticsAdapter + }); + + adapterManager.enableAnalytics({ + provider: 'finteza', + options: { + id: clientId, // Client ID (required) + bidRequestTrack: 'Bid Request %BIDDER%', + bidResponseTrack: 'Bid Response %bidder%', + bidTimeoutTrack: 'Bid Timeout %Bidder%', + bidWonTrack: 'Bid Won %BIDDER%', + } + }); + }); + + afterEach(function () { + xhr.restore(); + events.getEvents.restore(); + fntzAnalyticsAdapter.track.restore(); + fntzAnalyticsAdapter.disableAnalytics(); + }); + + describe('track', () => { + describe('bid request', () => { + it('builds and sends data', function () { + const bidderCode = 'Bidder789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const bidRequest = { + bidderCode: bidderCode, + auctionId: pauctionId, + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: bidderCode, + placementCode: 'container-1', + bidId: '208750227436c1', + bidderRequestId: '1a6fc81528d0f6', + auctionId: pauctionId, + startTime: 1509369418389, + sizes: [[300, 250]], + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }; + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Request ${bidderCode.toUpperCase()}`); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + + describe('bid response', () => { + it('builds and sends data', function () { + const bidderCode = 'Bidder789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const timeToRespond = 443; + const cpm = 0.015; + + const bidResponse = { + bidderCode: bidderCode, + adId: '208750227436c1', + cpm: cpm, + auctionId: pauctionId, + responseTimestamp: 1509369418832, + requestTimestamp: 1509369418389, + bidder: bidderCode, + timeToRespond: timeToRespond, + size: '300x250', + width: 300, + height: 250, + }; + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Response ${bidderCode.toLowerCase()}`); + expect(url.search.c1_value).to.equal(String(timeToRespond)); + expect(url.search.c1_unit).to.equal('ms'); + expect(url.search.c2_value).to.equal(String(cpm)); + expect(url.search.c2_unit).to.equal('usd'); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + + describe('bid won', () => { + it('builds and sends data', function () { + const bidderCode = 'Bidder789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const cpm = 0.015; + + const bidWon = { + bidderCode: bidderCode, + cpm: cpm, + adId: 'adIdData', + ad: 'adContent', + auctionId: pauctionId, + width: 300, + height: 250 + } + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_WON, bidWon); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Won ${bidderCode.toUpperCase()}`); + expect(url.search.c1_value).to.equal(String(cpm)); + expect(url.search.c1_unit).to.equal('usd'); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + + describe('bid timeout', () => { + it('builds and sends data', function () { + const bidderCode = 'biDDer789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const timeout = 2540; + + const bidTimeout = [ + { + bidId: '208750227436c1', + bidder: bidderCode, + auctionId: pauctionId, + timeout: timeout, + } + ]; + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Timeout Bidder789`); + expect(url.search.c1_value).to.equal(String(timeout)); + expect(url.search.c1_unit).to.equal('ms'); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + }); +}); From 8c5f26ea949267c2c19c752f2fcb714d665bf9f7 Mon Sep 17 00:00:00 2001 From: PWyrembak Date: Fri, 15 Mar 2019 22:25:41 +0300 Subject: [PATCH 20/33] Add video support in TrustX Bid Adapter (#3632) * 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 --- modules/trustxBidAdapter.js | 60 +++++- modules/trustxBidAdapter.md | 13 ++ test/spec/modules/trustxBidAdapter_spec.js | 207 +++++++++++++++++++++ 3 files changed, 274 insertions(+), 6 deletions(-) diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index bd5f63e5302..b0593e5c7d7 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -1,9 +1,14 @@ import * as utils from '../src/utils'; import {registerBidder} from '../src/adapters/bidderFactory'; +import { Renderer } from '../src/Renderer'; +import { VIDEO, BANNER } from '../src/mediaTypes'; + const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = '//sofia.trustx.org/hb'; const TIME_TO_LIVE = 360; const ADAPTER_SYNC_URL = '//sofia.trustx.org/push_sync'; +const RENDERER_URL = '//cdn.adnxs.com/renderer/video/ANOutstreamVideo.js'; + const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -17,6 +22,7 @@ const LOG_ERROR_MESS = { }; export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [ BANNER, VIDEO ], /** * Determines whether or not the given bid request is valid. * @@ -113,7 +119,7 @@ export const spec = { * @param {*} bidRequest * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, bidRequest) { + interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; const bidsMap = bidRequest.bidsMap; @@ -128,7 +134,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses); + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses, RendererConst); }); } if (errorMessage) utils.logError(errorMessage); @@ -155,7 +161,7 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { +function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererConst) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); @@ -168,7 +174,7 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { const slot = awaitingBids[sizeId][0]; const bid = slot.bids.shift(); - bidResponses.push({ + const bidResponse = { requestId: bid.bidId, // bid.bidderRequestId, bidderCode: spec.code, cpm: serverBid.price, @@ -178,9 +184,26 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { currency: 'USD', netRevenue: priceType !== 'gross', ttl: TIME_TO_LIVE, - ad: serverBid.adm, dealId: serverBid.dealid - }); + }; + if (serverBid.content_type === 'video') { + bidResponse.vastXml = serverBid.adm; + bidResponse.mediaType = VIDEO; + bidResponse.adResponse = { + content: bidResponse.vastXml + }; + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL + }, RendererConst); + } + } else { + bidResponse.ad = serverBid.adm; + bidResponse.mediaType = BANNER; + } + + bidResponses.push(bidResponse); if (!slot.bids.length) { slot.parents.forEach(({parent, key, uid}) => { @@ -206,4 +229,29 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { } } +function outstreamRender (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse + }); + }); +} + +function createRenderer (bid, rendererParams, RendererConst) { + const rendererInst = RendererConst.install({ + id: rendererParams.id, + url: rendererParams.url, + loaded: false + }); + + try { + rendererInst.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return rendererInst; +} + registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md index ca407b0c5e8..d6b660c6248 100755 --- a/modules/trustxBidAdapter.md +++ b/modules/trustxBidAdapter.md @@ -7,6 +7,7 @@ Maintainer: paul@trustx.org # Description Module that connects to TrustX demand source to fetch bids. +TrustX Bid Adapter supports Banner and Video (instream and outstream). # Test Parameters ``` @@ -35,6 +36,18 @@ Module that connects to TrustX demand source to fetch bids. } } ] + },{ + code: 'test-div', + sizes: [[640, 360]], + mediaTypes: { video: {} }, + bids: [ + { + bidder: "trustx", + params: { + uid: 7697 + } + } + ] } ]; ``` \ No newline at end of file diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 207d3a068ba..de0d1c8a530 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -194,6 +194,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } @@ -251,6 +252,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -264,6 +266,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 2
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -277,6 +280,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 3
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } @@ -404,6 +408,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -417,6 +422,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 2
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -430,6 +436,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 3
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -443,6 +450,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 4
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } @@ -504,6 +512,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -517,6 +526,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 2
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } @@ -526,4 +536,201 @@ describe('TrustXAdapter', function () { expect(result).to.deep.equal(expectedResponse); }); }); + + it('should get correct video bid response', function () { + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '50' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57dfefb80eca', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '51' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e893c787c22dd', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video'}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '57dfefb80eca', + 'cpm': 1.15, + 'creativeId': 50, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should have right renderer in the bid response', function () { + const spySetRenderer = sinon.spy(); + const stubRenderer = { + setRender: spySetRenderer + }; + const spyRendererInstall = sinon.spy(function() { return stubRenderer; }); + const stubRendererConst = { + install: spyRendererInstall + }; + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '50' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e6e65553fc8', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'mediaTypes': { + 'video': { + 'context': 'outstream' + } + } + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '51' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c8fdcb3f269f', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3' + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '52' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '1de036c37685', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'renderer': {} + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, + {'bid': [{'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 52, content_type: 'video', w: 300, h: 250}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': 'e6e65553fc8', + 'cpm': 1.15, + 'creativeId': 50, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': 'c8fdcb3f269f', + 'cpm': 1.00, + 'creativeId': 51, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': '1de036c37685', + 'cpm': 1.20, + 'creativeId': 52, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request, stubRendererConst); + + expect(spySetRenderer.calledTwice).to.equal(true); + expect(spySetRenderer.getCall(0).args[0]).to.be.a('function'); + expect(spySetRenderer.getCall(1).args[0]).to.be.a('function'); + + expect(spyRendererInstall.calledTwice).to.equal(true); + expect(spyRendererInstall.getCall(0).args[0]).to.deep.equal({ + id: 'e6e65553fc8', + url: '//cdn.adnxs.com/renderer/video/ANOutstreamVideo.js', + loaded: false + }); + expect(spyRendererInstall.getCall(1).args[0]).to.deep.equal({ + id: 'c8fdcb3f269f', + url: '//cdn.adnxs.com/renderer/video/ANOutstreamVideo.js', + loaded: false + }); + + expect(result).to.deep.equal(expectedResponse); + }); }); From 7cfb94b00179678be3b66c618c2a5014aff7cff9 Mon Sep 17 00:00:00 2001 From: Fedor Belov Date: Fri, 15 Mar 2019 23:41:39 +0300 Subject: [PATCH 21/33] otm prioritize sizes (#3642) --- modules/otmBidAdapter.js | 43 ++++++++++++++++++++++--- test/spec/modules/otmBidAdapter_spec.js | 38 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/modules/otmBidAdapter.js b/modules/otmBidAdapter.js index 78015d69594..57ac414c7b6 100644 --- a/modules/otmBidAdapter.js +++ b/modules/otmBidAdapter.js @@ -1,5 +1,5 @@ -import {BANNER} from '../src/mediaTypes'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER} from 'src/mediaTypes'; +import {registerBidder} from 'src/adapters/bidderFactory'; export const spec = { code: 'otm', @@ -9,10 +9,11 @@ export const spec = { }, buildRequests: function (bidRequests) { const requests = bidRequests.map(function (bid) { + const size = getMaxPrioritySize(bid.sizes); const params = { tz: getTz(), - w: bid.sizes[0][0], - h: bid.sizes[0][1], + w: size[0], + h: size[1], s: bid.params.tid, bidid: bid.bidId, transactionid: bid.transactionId, @@ -57,4 +58,38 @@ function getTz() { return new Date().getTimezoneOffset(); } +function getMaxPrioritySize(sizes) { + var maxPrioritySize = null; + + const sizesByPriority = [ + [300, 250], + [240, 400], + [728, 90], + [300, 600], + [970, 250], + [300, 50], + [320, 100] + ]; + + const sizeToString = (size) => { + return size[0] + 'x' + size[1]; + }; + + const sizesAsString = sizes.map(sizeToString); + + sizesByPriority.forEach(size => { + if (!maxPrioritySize) { + if (sizesAsString.indexOf(sizeToString(size)) !== -1) { + maxPrioritySize = size; + } + } + }); + + if (maxPrioritySize) { + return maxPrioritySize; + } else { + return sizes[0]; + } +} + registerBidder(spec); diff --git a/test/spec/modules/otmBidAdapter_spec.js b/test/spec/modules/otmBidAdapter_spec.js index 0fe81a512cb..f3a98d43e57 100644 --- a/test/spec/modules/otmBidAdapter_spec.js +++ b/test/spec/modules/otmBidAdapter_spec.js @@ -29,6 +29,44 @@ describe('otmBidAdapterTests', function () { expect(req_data.bidid).to.equal('bid1234'); }); + it('validate_best_size_select', function () { + // when: + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'otm', + params: { + tid: '123', + bidfloor: 20 + }, + sizes: [[300, 500], [300, 600], [240, 400], [300, 50]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + // then: + expect(req_data.w).to.equal(240); + expect(req_data.h).to.equal(400); + + // when: + bidRequestData = [{ + bidId: 'bid1234', + bidder: 'otm', + params: { + tid: '123', + bidfloor: 20 + }, + sizes: [[200, 240], [400, 440]] + }]; + + request = spec.buildRequests(bidRequestData); + req_data = request[0].data; + + // then: + expect(req_data.w).to.equal(200); + expect(req_data.h).to.equal(240); + }); + it('validate_response_params', function () { let bidRequestData = { data: { From 966cff186400e93316788d2de0bd9f6f6d813123 Mon Sep 17 00:00:00 2001 From: adxcgcom <31470944+adxcgcom@users.noreply.github.com> Date: Mon, 18 Mar 2019 14:51:40 +0000 Subject: [PATCH 22/33] adxcgBidAdapter native update (#3647) * adxcgBidAdapter native fix * removed comment --- modules/adxcgBidAdapter.js | 21 ++++++++++++++++++--- test/spec/modules/adxcgBidAdapter_spec.js | 18 +++++++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 73b70fe8e72..23808fa3be7 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -10,6 +10,7 @@ import includes from 'core-js/library/fn/array/includes' * updated to latest prebid repo on 2017.10.20 * updated for gdpr compliance on 2018.05.22 -requires gdpr compliance module * updated to pass aditional auction and impression level parameters. added pass for video targeting parameters + * updated to fix native support for image width/height and icon 2019.03.17 */ const BIDDER_CODE = 'adxcg' @@ -210,8 +211,10 @@ export const spec = { let nativeResponse = serverResponseOneItem.nativeResponse bid['native'] = { - clickUrl: encodeURIComponent(nativeResponse.link.url), - impressionTrackers: nativeResponse.imptrackers + clickUrl: nativeResponse.link.url, + impressionTrackers: nativeResponse.imptrackers, + clickTrackers: nativeResponse.clktrackers, + javascriptTrackers: nativeResponse.jstrackers } nativeResponse.assets.forEach(asset => { @@ -220,7 +223,19 @@ export const spec = { } if (asset.img && asset.img.url) { - bid['native'].image = asset.img.url + let nativeImage = {} + nativeImage.url = asset.img.url + nativeImage.height = asset.img.h + nativeImage.width = asset.img.w + bid['native'].image = nativeImage + } + + if (asset.icon && asset.icon.url) { + let nativeIcon = {} + nativeIcon.url = asset.icon.url + nativeIcon.height = asset.icon.h + nativeIcon.width = asset.icon.w + bid['native'].icon = nativeIcon } if (asset.data && asset.data.label === 'DESC' && asset.data.value) { diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 71fb7bc3776..0277e0ab964 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -235,6 +235,14 @@ describe('AdxcgAdapter', function () { 'label': 'SPONSORED', 'value': 'sponsoredByContent' } + }, { + 'id': 5, + 'required': 0, + 'icon': { + 'url': 'iconContent', + 'w': 400, + 'h': 400 + } }], 'link': { 'url': 'linkContent' @@ -307,7 +315,15 @@ describe('AdxcgAdapter', function () { expect(result[0].native.clickUrl).to.equal('linkContent') expect(result[0].native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']) expect(result[0].native.title).to.equal('titleContent') - expect(result[0].native.image).to.equal('imageContent') + + expect(result[0].native.image.url).to.equal('imageContent') + expect(result[0].native.image.height).to.equal(600) + expect(result[0].native.image.width).to.equal(600) + + expect(result[0].native.icon.url).to.equal('iconContent') + expect(result[0].native.icon.height).to.equal(400) + expect(result[0].native.icon.width).to.equal(400) + expect(result[0].native.body).to.equal('descriptionContent') expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent') }) From f584653a685ec910cea79e0d50c5c2ac31a321d6 Mon Sep 17 00:00:00 2001 From: trchandraprakash <47793448+trchandraprakash@users.noreply.github.com> Date: Mon, 18 Mar 2019 08:04:52 -0700 Subject: [PATCH 23/33] Update Bidder Code (#3646) --- modules/advangelistsBidAdapter.js | 2 +- modules/advangelistsBidAdapter.md | 4 ++-- .../modules/advangelistsBidAdapter_spec.js | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) mode change 100644 => 100755 modules/advangelistsBidAdapter.js mode change 100644 => 100755 modules/advangelistsBidAdapter.md mode change 100644 => 100755 test/spec/modules/advangelistsBidAdapter_spec.js diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js old mode 100644 new mode 100755 index 926be211649..e98de8dd77e --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -7,7 +7,7 @@ import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; const ADAPTER_VERSION = '1.0'; -const BIDDER_CODE = 'avng'; +const BIDDER_CODE = 'advangelists'; export const VIDEO_ENDPOINT = '//nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; export const BANNER_ENDPOINT = '//nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; diff --git a/modules/advangelistsBidAdapter.md b/modules/advangelistsBidAdapter.md old mode 100644 new mode 100755 index 14e2befd48f..1765241eaf3 --- a/modules/advangelistsBidAdapter.md +++ b/modules/advangelistsBidAdapter.md @@ -24,7 +24,7 @@ var displayAdUnit = [ [320, 50] ], bids: [{ - bidder: 'avng', + bidder: 'advangelists', params: { pubid: '0cf8d6d643e13d86a5b6374148a4afac', placement: 1234 @@ -47,7 +47,7 @@ var videoAdUnit = { }, bids: [ { - bidder: 'avng', + bidder: 'advangelists', params: { pubid: '8537f00948fc37cc03c5f0f88e198a76', placement: 1234, diff --git a/test/spec/modules/advangelistsBidAdapter_spec.js b/test/spec/modules/advangelistsBidAdapter_spec.js old mode 100644 new mode 100755 index f7a49ef995f..fbdfc9f30ee --- a/test/spec/modules/advangelistsBidAdapter_spec.js +++ b/test/spec/modules/advangelistsBidAdapter_spec.js @@ -7,9 +7,9 @@ describe('advangelistsBidAdapter', function () { let bidRequestsVid; beforeEach(function () { - bidRequests = [{'bidder': 'avng', 'params': {'pubid': '0cf8d6d643e13d86a5b6374148a4afac', 'placement': 1234}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + bidRequests = [{'bidder': 'advangelists', 'params': {'pubid': '0cf8d6d643e13d86a5b6374148a4afac', 'placement': 1234}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; - bidRequestsVid = [{'bidder': 'avng', 'params': {'pubid': '8537f00948fc37cc03c5f0f88e198a76', 'placement': 1234, 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + bidRequestsVid = [{'bidder': 'advangelists', 'params': {'pubid': '8537f00948fc37cc03c5f0f88e198a76', 'placement': 1234, 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; }); describe('spec.isBidRequestValid', function () { @@ -86,14 +86,14 @@ describe('advangelistsBidAdapter', function () { it('should return valid video bid responses', function () { let _mediaTypes = VIDEO; - const avngbidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; + const advangelistsbidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; const serverResponseVid = {'cur': 'USD', 'id': '25c6ab92aa0e81', 'seatbid': [{'seat': '3', 'bid': [{'crid': '1855', 'h': 480, 'protocol': 2, 'nurl': 'http://nep.advangelists.com/xp/evt?pp=1MO1wiaMhhq7wLRzZZwwwPkJxxKpYEnM5k5MH4qSGm1HR8rp3Nl7vDocvzZzSAvE4pnREL9mQ1kf5PDjk6E8em6DOk7vVrYUH1TYQyqCucd58PFpJNN7h30RXKHHFg3XaLuQ3PKfMuI1qZATBJ6WHcu875y0hqRdiewn0J4JsCYF53M27uwmcV0HnQxARQZZ72mPqrW95U6wgkZljziwKrICM3aBV07TU6YK5R5AyzJRuD6mtrQ2xtHlQ3jXVYKE5bvWFiUQd90t0jOGhPtYBNoOfP7uQ4ZZj4pyucxbr96orHe9PSOn9UpCSWArdx7s8lOfDpwOvbMuyGxynbStDWm38sDgd4bMHnIt762m5VMDNJfiUyX0vWzp05OsufJDVEaWhAM62i40lQZo7mWP4ipoOWLkmlaAzFIMsTcNaHAHiKKqGEOZLkCEhFNM0SLcvgN2HFRULOOIZvusq7TydOKxuXgCS91dLUDxDDDFUK83BFKlMkTxnCzkLbIR1bd9GKcr1TRryOrulyvRWAKAIhEsUzsc5QWFUhmI2dZ1eqnBQJ0c89TaPcnoaP2WipF68UgyiOstf2CBy0M34858tC5PmuQwQYwXscg6zyqDwR0i9MzGH4FkTyU5yeOlPcsA0ht6UcoCdFpHpumDrLUwAaxwGk1Nj8S6YlYYT5wNuTifDGbg22QKXzZBkUARiyVvgPn9nRtXnrd7WmiMYq596rya9RQj7LC0auQW8bHVQLEe49shsZDnAwZTWr4QuYKqgRGZcXteG7RVJe0ryBZezOq11ha9C0Lv0siNVBahOXE35Wzoq4c4BDaGpqvhaKN7pjeWLGlQR04ufWekwxiMWAvjmfgAfexBJ7HfbYNZpq__', 'adid': '61_1855', 'adomain': ['chevrolet.com.ar'], 'price': 2, 'w': 320, 'iurl': 'https://daf37cpxaja7f.cloudfront.net/c61/creative_url_14922301369663_1.png', 'cat': ['IAB2'], 'id': '7f570b40-aca1-4806-8ea8-818ea679c82b_0', 'attr': [], 'impid': '0', 'cid': '61'}]}], 'bidid': '7f570b40-aca1-4806-8ea8-818ea679c82b'} - const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, avngbidreqVid); + const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, advangelistsbidreqVid); delete bidResponseVid['vastUrl']; delete bidResponseVid['ad']; expect(bidResponseVid).to.deep.equal({ requestId: bidRequestsVid[0].bidId, - bidderCode: 'avng', + bidderCode: 'advangelists', creativeId: serverResponseVid.seatbid[0].bid[0].crid, cpm: serverResponseVid.seatbid[0].bid[0].price, width: serverResponseVid.seatbid[0].bid[0].w, @@ -106,10 +106,10 @@ describe('advangelistsBidAdapter', function () { }); it('should return valid banner bid responses', function () { - const avngbidreq = {bids: {}}; + const advangelistsbidreq = {bids: {}}; bidRequests.forEach(bid => { let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); - avngbidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, + advangelistsbidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] @@ -117,11 +117,11 @@ describe('advangelistsBidAdapter', function () { }); const serverResponse = {'id': '2aa73f571eaf29', 'seatbid': [{'bid': [{'id': '2c5e8a1a84522d', 'impid': '2c5e8a1a84522d', 'price': 0.81, 'adid': 'abcde-12345', 'nurl': '', 'adm': '
', 'adomain': ['advertiserdomain.com'], 'iurl': '', 'cid': 'campaign1', 'crid': 'abcde-12345', 'w': 300, 'h': 250}], 'seat': '19513bcfca8006'}], 'bidid': '19513bcfca8006', 'cur': 'USD', 'w': 300, 'h': 250}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, avngbidreq); + const bidResponse = spec.interpretResponse({ body: serverResponse }, advangelistsbidreq); expect(bidResponse).to.deep.equal({ requestId: bidRequests[0].bidId, ad: serverResponse.seatbid[0].bid[0].adm, - bidderCode: 'avng', + bidderCode: 'advangelists', creativeId: serverResponse.seatbid[0].bid[0].crid, cpm: serverResponse.seatbid[0].bid[0].price, width: serverResponse.seatbid[0].bid[0].w, From 70d28081623555d0af881ea3727bc072a3858778 Mon Sep 17 00:00:00 2001 From: Kelvin Chappell Date: Mon, 18 Mar 2019 20:45:55 +0000 Subject: [PATCH 24/33] Add brand ID to OpenX bid responses (#3630) --- modules/openxBidAdapter.js | 5 +++++ test/spec/modules/openxBidAdapter_spec.js | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index e465e44b25b..4671f054042 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -116,6 +116,11 @@ function createBannerBidResponses(oxResponseObj, {bids, startTime}) { } bidResponse.ts = adUnit.ts; + bidResponse.meta = {}; + if (adUnit.brand_id) { + bidResponse.meta.brandId = adUnit.brand_id; + } + bidResponses.push(bidResponse); registerBeacon(BANNER, adUnit, startTime); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 6ef04ddb9d9..8e12a3bb7e4 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1190,6 +1190,10 @@ describe('OpenxAdapter', function () { expect(bid.ts).to.equal(adUnitOverride.ts); }); + it('should return a brand ID', function () { + expect(bid.meta.brandId).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.brand_id); + }); + it('should register a beacon', function () { resetBoPixel(); spec.interpretResponse({body: bidResponse}, bidRequest); From 4fd7c474399ab86712c822bbe843e616b6fa6031 Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Mon, 18 Mar 2019 17:14:49 -0400 Subject: [PATCH 25/33] Sonobi - Analytics Adapter (#3615) * Initial release of Sonobi Analytics adapter. * Removed withCredentials on the sendData requests. Improved unit tests * unit tests working. * remove unneeded array.from --- modules/sonobiAnalyticsAdapter.js | 277 ++++++++++++++++++ modules/sonobiAnalyticsAdapter.md | 24 ++ .../modules/sonobiAnalyticsAdapter_spec.js | 89 ++++++ 3 files changed, 390 insertions(+) create mode 100644 modules/sonobiAnalyticsAdapter.js create mode 100644 modules/sonobiAnalyticsAdapter.md create mode 100644 test/spec/modules/sonobiAnalyticsAdapter_spec.js diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js new file mode 100644 index 00000000000..ab110cb4d01 --- /dev/null +++ b/modules/sonobiAnalyticsAdapter.js @@ -0,0 +1,277 @@ +import adapter from '../src/AnalyticsAdapter'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager'; +import {ajaxBuilder} from '../src/ajax'; + +const utils = require('../src/utils'); +let ajax = ajaxBuilder(0); + +const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; +const analyticsType = 'endpoint'; +const QUEUE_TIMEOUT_DEFAULT = 200; +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_ADJUSTMENT, + BIDDER_DONE, + BID_WON, + BID_RESPONSE, + BID_TIMEOUT + } +} = CONSTANTS; + +let initOptions = {}; +let auctionCache = {}; +let auctionTtl = 60 * 60 * 1000; + +function deleteOldAuctions() { + for (let auctionId in auctionCache) { + let auction = auctionCache[auctionId]; + if (Date.now() - auction.start > auctionTtl) { + delete auctionCache[auctionId]; + } + } +} + +function buildAuctionEntity(args) { + return { + 'id': args.auctionId, + 'start': args.timestamp, + 'timeout': args.timeout, + 'adUnits': {}, + 'stats': {}, + 'queue': [], + 'qTimeout': false + }; +} +function buildAdUnit(data) { + return `/${initOptions.pubId}/${initOptions.siteId}/${data.adUnitCode.toLowerCase()}`; +} +function getLatency(data) { + if (!data.responseTimestamp) { + return -1; + } else { + return data.responseTimestamp - data.requestTimestamp; + } +} +function getBid(data) { + if (data.cpm) { + return Math.round(data.cpm * 100); + } else { + return 0; + } +} +function buildItem(data, response, phase = 1) { + let size = data.width ? {width: data.width, height: data.height} : {width: data.sizes[0][0], height: data.sizes[0][1]}; + return { + 'bidid': data.bidId || data.requestId, + 'p': phase, + 'buyerid': data.bidder.toLowerCase(), + 'bid': getBid(data), + 'adunit_code': buildAdUnit(data), + 's': `${size.width}x${size.height}`, + 'latency': getLatency(data), + 'response': response, + 'jsLatency': getLatency(data), + 'buyername': data.bidder.toLowerCase() + }; +} +function sendQueue(auctionId) { + let auction = auctionCache[auctionId]; + let data = auction.queue; + auction.queue = []; + auction.qTimeout = false; + sonobiAdapter.sendData(auction, data); +} +function addToAuctionQueue(auctionId, id) { + let auction = auctionCache[auctionId]; + auction.queue = auction.queue.filter((item) => { + if (item.bidid !== id) { return true; } + return auction.stats[id].data.p !== item.p; + }); + auction.queue.push(utils.deepClone(auction.stats[id].data)); + if (!auction.qTimeout) { + auction.qTimeout = setTimeout(() => { + sendQueue(auctionId); + }, initOptions.delay) + } +} +function updateBidStats(auctionId, id, data) { + let auction = auctionCache[auctionId]; + auction.stats[id].data = {...auction.stats[id].data, ...data}; + addToAuctionQueue(auctionId, id); + logInfo('Updated Bid Stats: ', auction.stats[id]); + return auction.stats[id]; +} + +function handleOtherEvents(eventType, args) { + logInfo('Other Event: ' + eventType, args); +} + +function handlerAuctionInit(args) { + auctionCache[args.auctionId] = buildAuctionEntity(args); + deleteOldAuctions(); + logInfo('Auction Init', args); +} +function handlerBidRequested(args) { + let auction = auctionCache[args.auctionId]; + let data = []; + let phase = 1; + let response = 1; + args.bids.forEach(function (bidRequest) { + auction = auctionCache[bidRequest.auctionId] + let built = buildItem(bidRequest, response, phase); + auction.stats[built.bidid] = {id: built.bidid, adUnitCode: bidRequest.adUnitCode, data: built}; + addToAuctionQueue(args.auctionId, built.bidid); + }) + + logInfo('Bids Requested ', data); +} + +function handlerBidAdjustment(args) { + logInfo('Bid Adjustment', args); +} +function handlerBidderDone(args) { + logInfo('Bidder Done', args); +} + +function handlerAuctionEnd(args) { + let winners = {}; + args.bidsReceived.forEach((bid) => { + if (!winners[bid.adUnitCode]) { + winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; + } else if (winners[bid.adUnitCode].cpm < bid.cpm) { + winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; + } + }) + args.adUnitCodes.forEach((adUnitCode) => { + if (winners[adUnitCode]) { + let bidId = winners[adUnitCode].bidId; + updateBidStats(args.auctionId, bidId, {response: 4}); + } + }) + logInfo('Auction End', args); + logInfo('Auction Cache', auctionCache[args.auctionId].stats); +} +function handlerBidWon(args) { + let {auctionId, requestId} = args; + let res = updateBidStats(auctionId, requestId, {p: 3, response: 6}); + logInfo('Bid Won ', args); + logInfo('Bid Update Result: ', res); +} +function handlerBidResponse(args) { + let {auctionId, requestId, cpm, size, timeToRespond} = args; + updateBidStats(auctionId, requestId, {bid: cpm, s: size, jsLatency: timeToRespond, latency: timeToRespond, p: 2, response: 9}); + + logInfo('Bid Response ', args); +} +function handlerBidTimeout(args) { + let {auctionId, bidId} = args; + logInfo('Bid Timeout ', args); + updateBidStats(auctionId, bidId, {p: 2, response: 0, latency: args.timeout, jsLatency: args.timeout}); +} +let sonobiAdapter = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType}), { + track({eventType, args}) { + switch (eventType) { + case AUCTION_INIT: + handlerAuctionInit(args); + break; + case BID_REQUESTED: + handlerBidRequested(args); + break; + case BID_ADJUSTMENT: + handlerBidAdjustment(args); + break; + case BIDDER_DONE: + handlerBidderDone(args); + break; + case AUCTION_END: + handlerAuctionEnd(args); + break; + case BID_WON: + handlerBidWon(args); + break; + case BID_RESPONSE: + handlerBidResponse(args); + break; + case BID_TIMEOUT: + handlerBidTimeout(args); + break; + default: + handleOtherEvents(eventType, args); + break; + } + }, + +}); + +sonobiAdapter.originEnableAnalytics = sonobiAdapter.enableAnalytics; + +sonobiAdapter.enableAnalytics = function (config) { + if (this.initConfig(config)) { + logInfo('Analytics adapter enabled', initOptions); + sonobiAdapter.originEnableAnalytics(config); + } +}; + +sonobiAdapter.initConfig = function (config) { + let isCorrectConfig = true; + initOptions = {}; + initOptions.options = utils.deepClone(config.options); + + initOptions.pubId = initOptions.options.pubId || null; + initOptions.siteId = initOptions.options.siteId || null; + initOptions.delay = initOptions.options.delay || QUEUE_TIMEOUT_DEFAULT; + if (!initOptions.pubId) { + logError('"options.pubId" is empty'); + isCorrectConfig = false; + } + if (!initOptions.siteId) { + logError('"options.siteId" is empty'); + isCorrectConfig = false; + } + + initOptions.server = DEFAULT_EVENT_URL; + initOptions.host = initOptions.options.host || window.location.hostname; + this.initOptions = initOptions; + return isCorrectConfig; +}; + +sonobiAdapter.getOptions = function () { + return initOptions; +}; + +sonobiAdapter.sendData = function (auction, data) { + let url = 'https://' + initOptions.server + '?pageviewid=' + auction.id + '&corscred=1&pubId=' + initOptions.pubId + '&siteId=' + initOptions.siteId; + ajax( + url, + function () { logInfo('Auction [' + auction.id + '] sent ', data); }, + JSON.stringify(data), + { + method: 'POST', + // withCredentials: true, + contentType: 'text/plain' + } + ); +} + +function logInfo(message, meta) { + utils.logInfo(buildLogMessage(message), meta); +} + +function logError(message) { + utils.logError(buildLogMessage(message)); +} + +function buildLogMessage(message) { + return 'Sonobi Prebid Analytics: ' + message; +} + +adapterManager.registerAnalyticsAdapter({ + adapter: sonobiAdapter, + code: 'sonobi' +}); + +export default sonobiAdapter; diff --git a/modules/sonobiAnalyticsAdapter.md b/modules/sonobiAnalyticsAdapter.md new file mode 100644 index 00000000000..1ef46bac833 --- /dev/null +++ b/modules/sonobiAnalyticsAdapter.md @@ -0,0 +1,24 @@ +# Overview + +``` +Module Name: Sonobi Analytics Adapter +Module Type: Analytics Adapter +Maintainer: apex@sonobi.com +``` + +# Description + +Module that connects to Sonobi's Analytics service + +# Test Parameters +``` + + pbjs.enableAnalytics({ + provider: 'sonobi', + options: { + pubId: 'ffBB352', + siteId: 57463, + delay: 300 + } + }); +``` diff --git a/test/spec/modules/sonobiAnalyticsAdapter_spec.js b/test/spec/modules/sonobiAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..389dfee34f9 --- /dev/null +++ b/test/spec/modules/sonobiAnalyticsAdapter_spec.js @@ -0,0 +1,89 @@ +import sonobiAnalytics from 'modules/sonobiAnalyticsAdapter'; +import {expect} from 'chai'; +let events = require('src/events'); +let adapterManager = require('src/adapterManager').default; +let constants = require('src/constants.json'); + +describe('Sonobi Prebid Analytic', function () { + let xhr; + let requests = []; + var clock; + + describe('enableAnalytics', function () { + beforeEach(function () { + requests = []; + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + sinon.stub(events, 'getEvents').returns([]); + clock = sinon.useFakeTimers(Date.now()); + }); + + afterEach(function () { + xhr.restore(); + events.getEvents.restore(); + clock.restore(); + }); + + after(function () { + sonobiAnalytics.disableAnalytics(); + }); + + it('should catch all events', function (done) { + const initOptions = { + pubId: 'A3B254F', + siteId: '1234', + delay: 100 + }; + + sonobiAnalytics.enableAnalytics(initOptions) + + const bid = { + bidderCode: 'sonobi_test_bid', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '1234', + auctionId: '13', + responseTimestamp: 1496410856397, + requestTimestamp: 1496410856295, + cpm: 1.13, + bidder: 'sonobi', + adUnitCode: 'dom-sample-id', + timeToRespond: 100, + placementCode: 'placementtest' + }; + + // Step 1: Initialize adapter + adapterManager.enableAnalytics({ + provider: 'sonobi', + options: initOptions + }); + + // Step 2: Send init auction event + events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: '13', timestamp: Date.now()}); + + expect(sonobiAnalytics.initOptions).to.have.property('pubId', 'A3B254F'); + expect(sonobiAnalytics.initOptions).to.have.property('siteId', '1234'); + expect(sonobiAnalytics.initOptions).to.have.property('delay', 100); + // Step 3: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, { bids: [bid], auctionId: '13' }); + + // Step 4: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bid); + + // Step 5: Send bid won event + events.emit(constants.EVENTS.BID_WON, bid); + + // Step 6: Send bid timeout event + events.emit(constants.EVENTS.BID_TIMEOUT, {auctionId: '13'}); + + // Step 7: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {auctionId: '13', bidsReceived: [bid]}); + + clock.tick(5000); + expect(requests).to.have.length(1); + expect(JSON.parse(requests[0].requestBody)).to.have.length(3) + done(); + }); + }); +}); From 63c1d366ce24bab9b2e14780d0005551826ab3f9 Mon Sep 17 00:00:00 2001 From: Isaac Dettman Date: Tue, 19 Mar 2019 10:06:14 -0700 Subject: [PATCH 26/33] add 'hb_cache_host' and 'hb_cache_path' targeting for video bids using cache (#3652) --- src/auction.js | 114 +++++++++++++++---------------- src/constants.json | 4 +- test/spec/auctionmanager_spec.js | 16 +++++ 3 files changed, 76 insertions(+), 58 deletions(-) diff --git a/src/auction.js b/src/auction.js index bf3f1bb1b71..10594dc3f32 100644 --- a/src/auction.js +++ b/src/auction.js @@ -48,7 +48,8 @@ * @property {function(): void} callBids - sends requests to all adapters for bids */ -import { uniques, flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest } from './utils'; +import { uniques, flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue } from './utils'; +import { parse as parseURL } from './url'; import { getPriceBucketString } from './cpmBucketManager'; import { getNativeTargeting } from './native'; import { getCacheUrl, store } from './videoCache'; @@ -505,6 +506,19 @@ function setupBidTargeting(bidObject, bidderRequest) { } export function getStandardBidderSettings(mediaType) { + // factory for key value objs + function createKeyVal(key, value) { + return { + key, + val: (typeof value === 'function') + ? function (bidResponse) { + return value(bidResponse); + } + : function (bidResponse) { + return getValue(bidResponse, value); + } + }; + } // Use the config value 'mediaTypeGranularity' if it has been set for mediaType, else use 'priceGranularity' const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); const granularity = (typeof mediaType === 'string' && mediaTypeGranularity) ? ((typeof mediaTypeGranularity === 'string') ? mediaTypeGranularity : 'custom') : config.getConfig('priceGranularity'); @@ -514,68 +528,54 @@ export function getStandardBidderSettings(mediaType) { bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = {}; } if (!bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { + const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS; + bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = [ - { - key: CONSTANTS.TARGETING_KEYS.BIDDER, - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: CONSTANTS.TARGETING_KEYS.AD_ID, - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, - val: function (bidResponse) { - if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { - return bidResponse.pbAg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { - return bidResponse.pbDg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { - return bidResponse.pbLg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { - return bidResponse.pbMg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { - return bidResponse.pbHg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { - return bidResponse.pbCg; - } - } - }, { - key: CONSTANTS.TARGETING_KEYS.SIZE, - val: function (bidResponse) { - return bidResponse.size; - } - }, { - key: CONSTANTS.TARGETING_KEYS.DEAL, - val: function (bidResponse) { - return bidResponse.dealId; - } - }, - { - key: CONSTANTS.TARGETING_KEYS.SOURCE, - val: function (bidResponse) { - return bidResponse.source; + createKeyVal(TARGETING_KEYS.BIDDER, 'bidderCode'), + createKeyVal(TARGETING_KEYS.AD_ID, 'adId'), + createKeyVal(TARGETING_KEYS.PRICE_BUCKET, function(bidResponse) { + if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { + return bidResponse.pbAg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { + return bidResponse.pbDg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { + return bidResponse.pbLg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { + return bidResponse.pbMg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { + return bidResponse.pbHg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { + return bidResponse.pbCg; } - }, - { - key: CONSTANTS.TARGETING_KEYS.FORMAT, - val: function (bidResponse) { - return bidResponse.mediaType; - } - }, + }), + createKeyVal(TARGETING_KEYS.SIZE, 'size'), + createKeyVal(TARGETING_KEYS.DEAL, 'dealId'), + createKeyVal(TARGETING_KEYS.SOURCE, 'source'), + createKeyVal(TARGETING_KEYS.FORMAT, 'mediaType'), ] if (mediaType === 'video') { - [CONSTANTS.TARGETING_KEYS.UUID, CONSTANTS.TARGETING_KEYS.CACHE_ID].forEach(item => { - bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].push({ - key: item, - val: function val(bidResponse) { - return bidResponse.videoCacheKey; - } - }) + // Adding hb_uuid + hb_cache_id + [TARGETING_KEYS.UUID, TARGETING_KEYS.CACHE_ID].forEach(targetingKey => { + bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].push(createKeyVal(targetingKey, 'videoCacheKey')); }); + + // Adding hb_cache_host + hb_cache_path + if (config.getConfig('cache.url')) { + const urlInfo = parseURL(config.getConfig('cache.url')); + + bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].push(createKeyVal(TARGETING_KEYS.CACHE_HOST, function(bidResponse) { + return utils.deepAccess(bidResponse, `adserverTargeting.${TARGETING_KEYS.CACHE_HOST}`) + ? bidResponse.adserverTargeting[TARGETING_KEYS.CACHE_HOST] + : urlInfo.hostname; + })); + + bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].push(createKeyVal(TARGETING_KEYS.CACHE_PATH, function(bidResponse) { + return utils.deepAccess(bidResponse, `adserverTargeting.${TARGETING_KEYS.CACHE_PATH}`) + ? bidResponse.adserverTargeting[TARGETING_KEYS.CACHE_PATH] + : urlInfo.pathname; + })); + } } } return bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]; diff --git a/src/constants.json b/src/constants.json index 1a78e376150..b3a0e4a9ce6 100644 --- a/src/constants.json +++ b/src/constants.json @@ -64,7 +64,9 @@ "SOURCE": "hb_source", "FORMAT": "hb_format", "UUID": "hb_uuid", - "CACHE_ID": "hb_cache_id" + "CACHE_ID": "hb_cache_id", + "CACHE_HOST": "hb_cache_host", + "CACHE_PATH": "hb_cache_path" }, "NATIVE_KEYS": { "title": "hb_native_title", diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index aab1da11653..eb1310e523c 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -142,6 +142,8 @@ describe('auctionmanager.js', function () { if (bid.mediaType === 'video') { expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey; expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey; + expected[ CONSTANTS.TARGETING_KEYS.CACHE_HOST ] = 'prebid.adnxs.com'; + expected[ CONSTANTS.TARGETING_KEYS.CACHE_PATH ] = '/pbc/v1/cache'; } if (!keys) { return expected; @@ -167,6 +169,11 @@ describe('auctionmanager.js', function () { }); it('No bidder level configuration defined - default for video', function () { + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }); $$PREBID_GLOBAL$$.bidderSettings = {}; let videoBid = utils.deepClone(bid); videoBid.mediaType = 'video'; @@ -229,6 +236,11 @@ describe('auctionmanager.js', function () { }); it('Custom configuration for all bidders with video bid', function () { + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }); let videoBid = utils.deepClone(bid); videoBid.mediaType = 'video'; videoBid.videoCacheKey = 'abc123def'; @@ -288,6 +300,10 @@ describe('auctionmanager.js', function () { }; let expected = getDefaultExpected(videoBid); + // Since we are effectively overwriting the bidderSettings above... + // we are not including the new host / path logic. So we will expect them to be gone. + delete expected['hb_cache_host']; + delete expected['hb_cache_path']; let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); assert.deepEqual(response, expected); }); From 9e0f11f4679ac7085766bbc3492b3bb07e2b4986 Mon Sep 17 00:00:00 2001 From: Michael Callari Date: Tue, 19 Mar 2019 13:13:00 -0400 Subject: [PATCH 27/33] Adding Optimera/AppNexus workaround documentation #3597 (#3598) --- modules/optimeraBidAdapter.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/modules/optimeraBidAdapter.md b/modules/optimeraBidAdapter.md index 84df1d1ad07..909c0a46cd6 100644 --- a/modules/optimeraBidAdapter.md +++ b/modules/optimeraBidAdapter.md @@ -34,3 +34,23 @@ Module that adds ad placement visibility scores for DFP. }] }]; ``` + +# AppNexus Issue +There is an issue where the plugin sometimes doesn't return impressions with AppNexus. + +There is an open issue here: [#3597](https://github.com/prebid/Prebid.js/issues/3597) + +## Configuration Workaround + +Optimera's configuration requires the use of size 0,0 which, in some instances, causes the AppNexus ad server to respond to ad requests but not fill impressions. AppNexus and vendors using the AppNexus ad server should monitor 3rd party numbers to ensure there is no decline in fill rate. + +Configuration Example: + +``` +code: ‘leaderboard', +mediaTypes: { + banner: { + sizes: [[970, 250],[970, 90],[970, 66],[728, 90],[320, 50],[320, 100],[300, 250],[0, 0]], + } +} +``` From b5b27aa0ad6aba4b23e6e99bd5f9044bcc9af2eb Mon Sep 17 00:00:00 2001 From: Kelvin Chappell Date: Tue, 19 Mar 2019 17:13:40 +0000 Subject: [PATCH 28/33] Add buyer data to Pubmatic bid responses (#3619) --- modules/pubmaticBidAdapter.js | 11 +++++++++++ test/spec/modules/pubmaticBidAdapter_spec.js | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 9044597c742..d80d6c5c810 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -977,6 +977,17 @@ export const spec = { newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; } + newBid.meta = {}; + if (bid.ext && bid.ext.dspid) { + newBid.meta.networkId = bid.ext.dspid; + } + if (bid.ext && bid.ext.advid) { + newBid.meta.buyerId = bid.ext.advid; + } + if (bid.adomain && bid.adomain.length > 0) { + newBid.meta.clickUrl = bid.adomain[0]; + } + bidResponses.push(newBid); }); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 912a6696bdd..0042ef5211e 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -281,10 +281,13 @@ describe('PubMatic adapter', function () { 'impid': '22bddb28db77d', 'price': 1.3, 'adm': 'image3.pubmatic.com Layer based creative', + 'adomain': ['blackrock.com'], 'h': 250, 'w': 300, 'ext': { - 'deal_channel': 6 + 'deal_channel': 6, + 'advid': 976, + 'dspid': 123 } }] }, { @@ -293,10 +296,13 @@ describe('PubMatic adapter', function () { 'impid': '22bddb28db77e', 'price': 1.7, 'adm': 'image3.pubmatic.com Layer based creative', + 'adomain': ['hivehome.com'], 'h': 250, 'w': 300, 'ext': { - 'deal_channel': 5 + 'deal_channel': 5, + 'advid': 832, + 'dspid': 422 } }] }] @@ -1323,6 +1329,9 @@ describe('PubMatic adapter', function () { expect(response[0].currency).to.equal('USD'); expect(response[0].netRevenue).to.equal(false); expect(response[0].ttl).to.equal(300); + expect(response[0].meta.networkId).to.equal(123); + expect(response[0].meta.buyerId).to.equal(976); + expect(response[0].meta.clickUrl).to.equal('blackrock.com'); expect(response[0].referrer).to.include(data.site.ref); expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); @@ -1339,6 +1348,9 @@ describe('PubMatic adapter', function () { expect(response[1].currency).to.equal('USD'); expect(response[1].netRevenue).to.equal(false); expect(response[1].ttl).to.equal(300); + expect(response[1].meta.networkId).to.equal(422); + expect(response[1].meta.buyerId).to.equal(832); + expect(response[1].meta.clickUrl).to.equal('hivehome.com'); expect(response[1].referrer).to.include(data.site.ref); expect(response[1].ad).to.equal(bidResponses.body.seatbid[1].bid[0].adm); }); From 90cefb8a5257fb51a7ee6e7cf76b4540929fa5d1 Mon Sep 17 00:00:00 2001 From: Isaac Dettman Date: Tue, 19 Mar 2019 10:44:10 -0700 Subject: [PATCH 29/33] Revert "add 'hb_cache_host' and 'hb_cache_path' targeting for video bids using cache (#3652)" (#3653) This reverts commit 63c1d366ce24bab9b2e14780d0005551826ab3f9. --- src/auction.js | 114 +++++++++++++++---------------- src/constants.json | 4 +- test/spec/auctionmanager_spec.js | 16 ----- 3 files changed, 58 insertions(+), 76 deletions(-) diff --git a/src/auction.js b/src/auction.js index 10594dc3f32..bf3f1bb1b71 100644 --- a/src/auction.js +++ b/src/auction.js @@ -48,8 +48,7 @@ * @property {function(): void} callBids - sends requests to all adapters for bids */ -import { uniques, flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue } from './utils'; -import { parse as parseURL } from './url'; +import { uniques, flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest } from './utils'; import { getPriceBucketString } from './cpmBucketManager'; import { getNativeTargeting } from './native'; import { getCacheUrl, store } from './videoCache'; @@ -506,19 +505,6 @@ function setupBidTargeting(bidObject, bidderRequest) { } export function getStandardBidderSettings(mediaType) { - // factory for key value objs - function createKeyVal(key, value) { - return { - key, - val: (typeof value === 'function') - ? function (bidResponse) { - return value(bidResponse); - } - : function (bidResponse) { - return getValue(bidResponse, value); - } - }; - } // Use the config value 'mediaTypeGranularity' if it has been set for mediaType, else use 'priceGranularity' const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); const granularity = (typeof mediaType === 'string' && mediaTypeGranularity) ? ((typeof mediaTypeGranularity === 'string') ? mediaTypeGranularity : 'custom') : config.getConfig('priceGranularity'); @@ -528,54 +514,68 @@ export function getStandardBidderSettings(mediaType) { bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = {}; } if (!bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { - const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS; - bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = [ - createKeyVal(TARGETING_KEYS.BIDDER, 'bidderCode'), - createKeyVal(TARGETING_KEYS.AD_ID, 'adId'), - createKeyVal(TARGETING_KEYS.PRICE_BUCKET, function(bidResponse) { - if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { - return bidResponse.pbAg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { - return bidResponse.pbDg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { - return bidResponse.pbLg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { - return bidResponse.pbMg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { - return bidResponse.pbHg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { - return bidResponse.pbCg; + { + key: CONSTANTS.TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: CONSTANTS.TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { + return bidResponse.pbAg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { + return bidResponse.pbDg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { + return bidResponse.pbLg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { + return bidResponse.pbMg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { + return bidResponse.pbHg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { + return bidResponse.pbCg; + } + } + }, { + key: CONSTANTS.TARGETING_KEYS.SIZE, + val: function (bidResponse) { + return bidResponse.size; + } + }, { + key: CONSTANTS.TARGETING_KEYS.DEAL, + val: function (bidResponse) { + return bidResponse.dealId; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.SOURCE, + val: function (bidResponse) { + return bidResponse.source; } - }), - createKeyVal(TARGETING_KEYS.SIZE, 'size'), - createKeyVal(TARGETING_KEYS.DEAL, 'dealId'), - createKeyVal(TARGETING_KEYS.SOURCE, 'source'), - createKeyVal(TARGETING_KEYS.FORMAT, 'mediaType'), + }, + { + key: CONSTANTS.TARGETING_KEYS.FORMAT, + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, ] if (mediaType === 'video') { - // Adding hb_uuid + hb_cache_id - [TARGETING_KEYS.UUID, TARGETING_KEYS.CACHE_ID].forEach(targetingKey => { - bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].push(createKeyVal(targetingKey, 'videoCacheKey')); + [CONSTANTS.TARGETING_KEYS.UUID, CONSTANTS.TARGETING_KEYS.CACHE_ID].forEach(item => { + bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].push({ + key: item, + val: function val(bidResponse) { + return bidResponse.videoCacheKey; + } + }) }); - - // Adding hb_cache_host + hb_cache_path - if (config.getConfig('cache.url')) { - const urlInfo = parseURL(config.getConfig('cache.url')); - - bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].push(createKeyVal(TARGETING_KEYS.CACHE_HOST, function(bidResponse) { - return utils.deepAccess(bidResponse, `adserverTargeting.${TARGETING_KEYS.CACHE_HOST}`) - ? bidResponse.adserverTargeting[TARGETING_KEYS.CACHE_HOST] - : urlInfo.hostname; - })); - - bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].push(createKeyVal(TARGETING_KEYS.CACHE_PATH, function(bidResponse) { - return utils.deepAccess(bidResponse, `adserverTargeting.${TARGETING_KEYS.CACHE_PATH}`) - ? bidResponse.adserverTargeting[TARGETING_KEYS.CACHE_PATH] - : urlInfo.pathname; - })); - } } } return bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]; diff --git a/src/constants.json b/src/constants.json index b3a0e4a9ce6..1a78e376150 100644 --- a/src/constants.json +++ b/src/constants.json @@ -64,9 +64,7 @@ "SOURCE": "hb_source", "FORMAT": "hb_format", "UUID": "hb_uuid", - "CACHE_ID": "hb_cache_id", - "CACHE_HOST": "hb_cache_host", - "CACHE_PATH": "hb_cache_path" + "CACHE_ID": "hb_cache_id" }, "NATIVE_KEYS": { "title": "hb_native_title", diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index eb1310e523c..aab1da11653 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -142,8 +142,6 @@ describe('auctionmanager.js', function () { if (bid.mediaType === 'video') { expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey; expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey; - expected[ CONSTANTS.TARGETING_KEYS.CACHE_HOST ] = 'prebid.adnxs.com'; - expected[ CONSTANTS.TARGETING_KEYS.CACHE_PATH ] = '/pbc/v1/cache'; } if (!keys) { return expected; @@ -169,11 +167,6 @@ describe('auctionmanager.js', function () { }); it('No bidder level configuration defined - default for video', function () { - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache' - } - }); $$PREBID_GLOBAL$$.bidderSettings = {}; let videoBid = utils.deepClone(bid); videoBid.mediaType = 'video'; @@ -236,11 +229,6 @@ describe('auctionmanager.js', function () { }); it('Custom configuration for all bidders with video bid', function () { - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache' - } - }); let videoBid = utils.deepClone(bid); videoBid.mediaType = 'video'; videoBid.videoCacheKey = 'abc123def'; @@ -300,10 +288,6 @@ describe('auctionmanager.js', function () { }; let expected = getDefaultExpected(videoBid); - // Since we are effectively overwriting the bidderSettings above... - // we are not including the new host / path logic. So we will expect them to be gone. - delete expected['hb_cache_host']; - delete expected['hb_cache_path']; let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); assert.deepEqual(response, expected); }); From c0fdf028761906bb66940aedaffdf701a112c112 Mon Sep 17 00:00:00 2001 From: evanmsmrtb Date: Tue, 19 Mar 2019 12:45:44 -0500 Subject: [PATCH 30/33] modules: Implement SmartRTB adapter and spec. (#3575) * modules: Implement SmartRTB adapter and spec. * Fix for-loop syntax to support IE; refactor getDomain out of exported set. --- modules/smartrtbBidAdapter.js | 111 +++++++++++++++++++ modules/smartrtbBidAdapter.md | 34 ++++++ test/spec/modules/smartrtbBidAdapter_spec.js | 109 ++++++++++++++++++ 3 files changed, 254 insertions(+) create mode 100644 modules/smartrtbBidAdapter.js create mode 100644 modules/smartrtbBidAdapter.md create mode 100644 test/spec/modules/smartrtbBidAdapter_spec.js diff --git a/modules/smartrtbBidAdapter.js b/modules/smartrtbBidAdapter.js new file mode 100644 index 00000000000..561ce58e016 --- /dev/null +++ b/modules/smartrtbBidAdapter.js @@ -0,0 +1,111 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'smartrtb'; + +function getDomain () { + if (!utils.inIframe()) { + return window.location.hostname + } + let origins = window.document.location.ancestorOrigins + if (origins && origins.length > 0) { + return origins[origins.length - 1] + } +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['smrtb'], + isBidRequestValid: function(bid) { + return (bid.params.pubId !== null && + bid.params.medId !== null && + bid.params.zoneId !== null); + }, + buildRequests: function(validBidRequests, bidderRequest) { + let stack = (bidderRequest.refererInfo && + bidderRequest.refererInfo.stack ? bidderRequest.refererInfo + : []) + + const payload = { + start_time: utils.timestamp(), + tmax: 120, + language: window.navigator.userLanguage || window.navigator.language, + site: { + domain: getDomain(), + iframe: !bidderRequest.refererInfo.reachedTop, + url: stack && stack.length > 0 ? [stack.length - 1] : null, + https: (window.location.protocol === 'https:'), + referrer: bidderRequest.refererInfo.referer + }, + imps: [] + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr = { + applies: bidderRequest.gdprConsent.gdprApplies, + consent: bidderRequest.gdprConsent.consentString + }; + } + + for (let x = 0; x < validBidRequests.length; x++) { + let req = validBidRequests[x] + + payload.imps.push({ + pub_id: req.params.pubId, + med_id: req.params.medId, + zone_id: req.params.zoneId, + bid_id: req.bidId, + imp_id: req.transactionId, + sizes: req.sizes, + force_bid: req.params.forceBid + }); + } + + return { + method: 'POST', + url: '//pubs.smrtb.com/json/publisher/prebid', + data: JSON.stringify(payload) + }; + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + let res = serverResponse.body; + if (!res.bids || !res.bids.length) { + return [] + } + + for (let x = 0; x < serverResponse.body.bids.length; x++) { + let bid = serverResponse.body.bids[x] + + bidResponses.push({ + requestId: bid.bid_id, + cpm: bid.cpm, + width: bid.w, + height: bid.h, + ad: bid.html, + ttl: 120, + creativeId: bid.crid, + netRevenue: true, + currency: 'USD' + }) + } + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = [] + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: '//ads.smrtb.com/sync' + }); + } else if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: '//ads.smrtb.com/sync' + }); + } + return syncs; + } +}; + +registerBidder(spec); diff --git a/modules/smartrtbBidAdapter.md b/modules/smartrtbBidAdapter.md new file mode 100644 index 00000000000..f043e277d0d --- /dev/null +++ b/modules/smartrtbBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Smart RTB (smrtb.com) Bidder Adapter +Module Type: Bidder Adapter +Maintainer: evanm@smrtb.com +``` + +# Description + +Prebid adapter for Smart RTB. Requires approval and account setup. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "smartrtb", + params: { + pubId: 123, + medId: "m_00a95d003340dbb2fcb8ee668a84fa", + zoneId: "z_261b6c7e7d4d4985393b293cc903d1", + force_bid: true + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/smartrtbBidAdapter_spec.js new file mode 100644 index 00000000000..8f1fb9efc1e --- /dev/null +++ b/test/spec/modules/smartrtbBidAdapter_spec.js @@ -0,0 +1,109 @@ +import { expect } from 'chai' +import { spec, _getPlatform } from 'modules/smartrtbBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +describe('SmartRTBBidAdapter', function () { + const adapter = newBidder(spec) + + let bidRequest = { + bidId: '123', + transactionId: '456', + sizes: [[ 300, 250 ]], + params: { + pubId: 123, + medId: 'm_00a95d003340dbb2fcb8ee668a84fa', + zoneId: 'z_261b6c7e7d4d4985393b293cc903d1' + } + } + + describe('codes', function () { + it('should return a bidder code of smartrtb', function () { + expect(spec.code).to.equal('smartrtb') + }) + it('should alias smrtb', function () { + expect(spec.aliases.length > 0 && spec.aliases[0] === 'smrtb').to.be.true + }) + }) + + describe('isBidRequestValid', function () { + it('should return true if all params present', function () { + expect(spec.isBidRequestValid(bidRequest)).to.be.true + }) + + it('should return false if any parameter missing', function () { + expect(spec.isBidRequestValid(Object.assign(bidRequest, { params: { pubId: null } }))).to.be.false + expect(spec.isBidRequestValid(Object.assign(bidRequest, { params: { medId: null } }))).to.be.false + expect(spec.isBidRequestValid(Object.assign(bidRequest, { params: { zoneId: null } }))).to.be.false + }) + }) + + describe('buildRequests', function () { + let req = spec.buildRequests([ bidRequest ], { refererInfo: { } }) + let rdata + + it('should return request object', function () { + expect(req).to.not.be.null + }) + + it('should build request data', function () { + expect(req.data).to.not.be.null + }) + + it('should include one request', function () { + rdata = JSON.parse(req.data) + expect(rdata.imps.length).to.equal(1) + }) + + it('should include all publisher params', function () { + let r = rdata.imps[0] + expect(r.pub_id !== null && r.med_id !== null && r.zone_id !== null).to.be.true + }) + }) + + describe('interpretResponse', function () { + it('should form compliant bid object response', function () { + let res = { + body: { + bids: [{ + bid_id: '123', + cpm: 1.23, + w: 300, + h: 250, + html: 'deadbeef', + crid: 'crid' + }] + } + } + + let ir = spec.interpretResponse(res, bidRequest) + + expect(ir.length).to.equal(1) + + let en = ir[0] + + expect(en.requestId != null && + en.cpm != null && typeof en.cpm === 'number' && + en.width != null && typeof en.width === 'number' && + en.height != null && typeof en.height === 'number' && + en.ad != null && + en.creativeId != null + ).to.be.true + }) + }) + + describe('getUserSyncs', function () { + it('should return iframe sync', function () { + let sync = spec.getUserSyncs({ iframeEnabled: true }) + expect(sync.length).to.equal(1) + expect(sync[0].type === 'iframe') + expect(typeof sync[0].url === 'string') + }) + + it('should return pixel sync', function () { + let sync = spec.getUserSyncs({ pixelEnabled: true }) + expect(sync.length).to.equal(1) + expect(sync[0].type === 'image') + expect(typeof sync[0].url === 'string') + }) + }) +}) From 2163303ece53fcec429a8f284642dd88a39a48b1 Mon Sep 17 00:00:00 2001 From: kusapan Date: Wed, 20 Mar 2019 02:57:44 +0900 Subject: [PATCH 31/33] add tmax to BidRequest params (#3626) --- modules/yieldoneBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 487cd3a306d..7abb94736df 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -22,6 +22,7 @@ export const spec = { const referrer = encodeURIComponent(utils.getTopWindowUrl()); const bidId = bidRequest.bidId; const unitCode = bidRequest.adUnitCode; + const timeout = config.getConfig('bidderTimeout'); const payload = { v: 'hb1', p: placementId, @@ -29,6 +30,7 @@ export const spec = { r: referrer, uid: bidId, uc: unitCode, + tmax: timeout, t: 'i' }; From 633b9b966ba0b5545937edb53d467f0bd468043f Mon Sep 17 00:00:00 2001 From: Bret Gorsline Date: Tue, 19 Mar 2019 14:30:45 -0400 Subject: [PATCH 32/33] Prebid 2.7.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82f92b5045f..84eb70695e8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.7.0-pre", + "version": "2.7.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 6cd91f980cd64686cc7a76edd6c9f1cd39d13020 Mon Sep 17 00:00:00 2001 From: Bret Gorsline Date: Tue, 19 Mar 2019 14:49:25 -0400 Subject: [PATCH 33/33] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84eb70695e8..d73d9014d99 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.7.0", + "version": "2.8.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": {