diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 0c9028133e0..22569f04cc3 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -6,7 +6,7 @@ import { BANNER, NATIVE } from '../src/mediaTypes'; const BIDDER_CODE = 'improvedigital'; export const spec = { - version: '5.1.0', + version: '5.2.0', code: BIDDER_CODE, aliases: ['id'], supportedMediaTypes: [BANNER, NATIVE], @@ -43,6 +43,10 @@ export const spec = { requestParameters.gdpr = bidderRequest.gdprConsent.consentString; } + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + requestParameters.referrer = bidderRequest.refererInfo.referer; + } + let requestObj = idClient.createRequest( normalizedBids, // requestObject requestParameters @@ -75,6 +79,8 @@ export const spec = { if (bidObject.native) { // Native bid.native = getNormalizedNativeAd(bidObject.native); + // Expose raw oRTB response to the client to allow parsing assets not directly supported by Prebid + bid.ortbNative = bidObject.native; if (bidObject.nurl) { bid.native.impressionTrackers.unshift(bidObject.nurl); } @@ -169,11 +175,13 @@ function getNormalizedBidRequest(bid) { publisherId = utils.getBidIdParameter('publisherId', bid.params) || null; placementKey = utils.getBidIdParameter('placementKey', bid.params) || null; } - let keyValues = utils.getBidIdParameter('keyValues', bid.params) || null; - let singleSizeFilter = utils.getBidIdParameter('size', bid.params) || null; - let bidId = utils.getBidIdParameter('bidId', bid); - let transactionId = utils.getBidIdParameter('transactionId', bid); + const keyValues = utils.getBidIdParameter('keyValues', bid.params) || null; + const singleSizeFilter = utils.getBidIdParameter('size', bid.params) || null; + const bidId = utils.getBidIdParameter('bidId', bid); + const transactionId = utils.getBidIdParameter('transactionId', bid); const currency = config.getConfig('currency.adServerCurrency'); + const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + const bidFloorCur = utils.getBidIdParameter('bidFloorCur', bid.params); let normalizedBidRequest = {}; if (placementId) { @@ -211,6 +219,10 @@ function getNormalizedBidRequest(bid) { if (currency) { normalizedBidRequest.currency = currency; } + if (bidFloor) { + normalizedBidRequest.bidFloor = bidFloor; + normalizedBidRequest.bidFloorCur = bidFloorCur ? bidFloorCur.toUpperCase() : 'USD'; + } return normalizedBidRequest; } @@ -231,6 +243,33 @@ function getNormalizedNativeAd(rawNative) { case 2: native.body = asset.data.value; break; + case 3: + native.rating = asset.data.value; + break; + case 4: + native.likes = asset.data.value; + break; + case 5: + native.downloads = asset.data.value; + break; + case 6: + native.price = asset.data.value; + break; + case 7: + native.salePrice = asset.data.value; + break; + case 8: + native.phone = asset.data.value; + break; + case 9: + native.address = asset.data.value; + break; + case 10: + native.body2 = asset.data.value; + break; + case 11: + native.displayUrl = asset.data.value; + break; case 12: native.cta = asset.data.value; break; @@ -255,26 +294,42 @@ function getNormalizedNativeAd(rawNative) { } }); // Trackers - native.impressionTrackers = rawNative.imptrackers || []; - native.javascriptTrackers = rawNative.jstracker; + if (rawNative.eventtrackers) { + native.impressionTrackers = []; + rawNative.eventtrackers.forEach(tracker => { + // Only handle impression event. Viewability events are not supported yet. + if (tracker.event !== 1) return; + switch (tracker.method) { + case 1: // img + native.impressionTrackers.push(tracker.url); + break; + case 2: // js + // javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. + native.javascriptTrackers = ``; + break; + } + }); + } else { + native.impressionTrackers = rawNative.imptrackers || []; + native.javascriptTrackers = rawNative.jstracker; + } if (rawNative.link) { native.clickUrl = rawNative.link.url; native.clickTrackers = rawNative.link.clicktrackers; } + if (rawNative.privacy) { + native.privacyLink = rawNative.privacy; + } return native; } registerBidder(spec); export function ImproveDigitalAdServerJSClient(endPoint) { this.CONSTANTS = { - HTTP_SECURITY: { - STANDARD: 0, - SECURE: 1 - }, AD_SERVER_BASE_URL: 'ice.360yield.com', END_POINT: endPoint || 'hb', AD_SERVER_URL_PARAM: 'jsonp=', - CLIENT_VERSION: 'JS-6.0.0', + CLIENT_VERSION: 'JS-6.2.0', MAX_URL_LENGTH: 2083, ERROR_CODES: { MISSING_PLACEMENT_PARAMS: 2, @@ -300,6 +355,7 @@ export function ImproveDigitalAdServerJSClient(endPoint) { } requestParameters.returnObjType = requestParameters.returnObjType || this.CONSTANTS.RETURN_OBJ_TYPE.DEFAULT; + requestParameters.adServerBaseUrl = 'https://' + (requestParameters.adServerBaseUrl || this.CONSTANTS.AD_SERVER_BASE_URL); let impressionObjects = []; let impressionObject; @@ -325,7 +381,7 @@ export function ImproveDigitalAdServerJSClient(endPoint) { } let errors = null; - let baseUrl = `${(requestParameters.secure === 1 ? 'https' : 'http')}://${this.CONSTANTS.AD_SERVER_BASE_URL}/${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; + let baseUrl = `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; let bidRequestObject = { bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) @@ -386,12 +442,11 @@ export function ImproveDigitalAdServerJSClient(endPoint) { case this.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT: return { method: 'GET', - url: `//${this.CONSTANTS.AD_SERVER_BASE_URL}/${this.CONSTANTS.END_POINT}`, + url: `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}`, data: `${this.CONSTANTS.AD_SERVER_URL_PARAM}${encodeURIComponent(JSON.stringify(bidRequestObject))}` }; default: - const baseUrl = `${(requestParameters.secure === 1 ? 'https' : 'http')}://` + - `${this.CONSTANTS.AD_SERVER_BASE_URL}/` + + const baseUrl = `${requestParameters.adServerBaseUrl}/` + `${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; return { url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) @@ -401,6 +456,7 @@ export function ImproveDigitalAdServerJSClient(endPoint) { this.createBasicBidRequestObject = function(requestParameters, extraRequestParameters) { let impressionBidRequestObject = {}; + impressionBidRequestObject.secure = 1; if (requestParameters.requestId) { impressionBidRequestObject.id = requestParameters.requestId; } else { @@ -418,9 +474,6 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (requestParameters.callback) { impressionBidRequestObject.callback = requestParameters.callback; } - if ('secure' in requestParameters) { - impressionBidRequestObject.secure = requestParameters.secure; - } if (requestParameters.libVersion) { impressionBidRequestObject.version = requestParameters.libVersion + '-' + this.CONSTANTS.CLIENT_VERSION; } @@ -455,6 +508,12 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (placementObject.currency) { impressionObject.currency = placementObject.currency.toUpperCase(); } + if (placementObject.bidFloor) { + impressionObject.bidfloor = placementObject.bidFloor; + } + if (placementObject.bidFloorCur) { + impressionObject.bidfloorcur = placementObject.bidFloorCur.toUpperCase(); + } if (placementObject.placementId) { impressionObject.pid = placementObject.placementId; } diff --git a/modules/improvedigitalBidAdapter.md b/modules/improvedigitalBidAdapter.md index d70b624171f..15602d11038 100644 --- a/modules/improvedigitalBidAdapter.md +++ b/modules/improvedigitalBidAdapter.md @@ -2,7 +2,7 @@ **Module Name**: Improve Digital Bidder Adapter **Module Type**: Bidder Adapter -**Maintainer**: hb@improvedigital.com +**Maintainer**: hb@azerion.com # Description diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 6c78afca6b2..8b8f6c4bf4c 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -7,7 +7,7 @@ describe('Improve Digital Adapter Tests', function () { let idClient = new ImproveDigitalAdServerJSClient('hb'); const METHOD = 'GET'; - const URL = '//ice.360yield.com/hb'; + const URL = 'https://ice.360yield.com/hb'; const PARAM_PREFIX = 'jsonp='; const simpleBidRequest = { @@ -33,11 +33,17 @@ describe('Improve Digital Adapter Tests', function () { } }; - const bidderRequest = { - 'gdprConsent': { - 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', - 'vendorData': {}, - 'gdprApplies': true + const bidderRequestGdpr = { + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {}, + gdprApplies: true + }, + }; + + const bidderRequestReferrer = { + refererInfo: { + referer: 'https://blah.com/test.html', }, }; @@ -151,13 +157,42 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub.restore(); }); + it('should add bid floor', function () { + const bidRequest = Object.assign({}, simpleBidRequest); + let request = spec.buildRequests([bidRequest])[0]; + let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + // Floor price currency shouldn't be populated without a floor price + expect(params.bid_request.imp[0].bidfloorcur).to.not.exist; + + // Default floor price currency + bidRequest.params.bidFloor = 0.05; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); + expect(params.bid_request.imp[0].bidfloorcur).to.equal('USD'); + + // Floor price currency + bidRequest.params.bidFloorCur = 'eUR'; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); + expect(params.bid_request.imp[0].bidfloorcur).to.equal('EUR'); + }); + it('should add GDPR consent string', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const request = spec.buildRequests([bidRequest], bidderRequestGdpr)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.gdpr).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); }); + it('should add referrer', function () { + const bidRequest = Object.assign({}, simpleBidRequest); + const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.referrer).to.equal('https://blah.com/test.html'); + }); + it('should return 2 requests', function () { const requests = spec.buildRequests([ simpleBidRequest, @@ -310,13 +345,61 @@ describe('Improve Digital Adapter Tests', function () { { data: { type: 3, - value: 'Should get ignored' + value: '4' // rating + } + }, + { + data: { + type: 4, + value: '10105' // likes + } + }, + { + data: { + type: 5, + value: '150000' // downloads + } + }, + { + data: { + type: 6, + value: '3.99' // price + } + }, + { + data: { + type: 7, + value: '4.49' // salePrice + } + }, + { + data: { + type: 8, + value: '(123) 456-7890' // phone + } + }, + { + data: { + type: 9, + value: '123 Main Street, Anywhere USA' // address + } + }, + { + data: { + type: 10, + value: 'body2' + } + }, + { + data: { + type: 11, + value: 'https://myurl.com' // displayUrl } }, { data: { type: 12, - value: 'Do it' + value: 'Do it' // cta } }, { @@ -334,6 +417,7 @@ describe('Improve Digital Adapter Tests', function () { h: 30, w: 40 } + }, { img: { @@ -354,7 +438,8 @@ describe('Improve Digital Adapter Tests', function () { 'http://imptrack1.com', 'http://imptrack2.com' ], - jstracker: '' + jstracker: '', + privacy: 'https://www.myprivacyurl.com' } } ], @@ -362,6 +447,19 @@ describe('Improve Digital Adapter Tests', function () { } }; + const nativeEventtrackers = [ + { + event: 1, + method: 1, + url: 'http://www.mytracker.com/imptracker' + }, + { + event: 1, + method: 2, + url: 'http://www.mytracker.com/tracker.js' + } + ]; + describe('interpretResponse', function () { let expectedBid = [ { @@ -411,8 +509,17 @@ describe('Improve Digital Adapter Tests', function () { native: { title: 'Native title', body: 'Native body', + body2: 'body2', cta: 'Do it', sponsoredBy: 'Improve Digital', + rating: '4', + likes: '10105', + downloads: '150000', + price: '3.99', + salePrice: '4.49', + phone: '(123) 456-7890', + address: '123 Main Street, Anywhere USA', + displayUrl: 'https://myurl.com', icon: { url: 'http://blah.com/icon.jpg', height: 30, @@ -430,7 +537,8 @@ describe('Improve Digital Adapter Tests', function () { 'http://imptrack1.com', 'http://imptrack2.com' ], - javascriptTrackers: '' + javascriptTrackers: '', + privacyLink: 'https://www.myprivacyurl.com' } } ]; @@ -532,8 +640,23 @@ describe('Improve Digital Adapter Tests', function () { // Native ads it('should return a well-formed native ad bid', function () { - const bids = spec.interpretResponse(serverResponseNative); + let bids = spec.interpretResponse(serverResponseNative); + expect(bids[0].ortbNative).to.deep.equal(serverResponseNative.body.bid[0].native); + delete bids[0].ortbNative; expect(bids).to.deep.equal(expectedBidNative); + + // eventtrackers + const response = JSON.parse(JSON.stringify(serverResponseNative)); + const expectedBids = JSON.parse(JSON.stringify(expectedBidNative)); + response.body.bid[0].native.eventtrackers = nativeEventtrackers; + expectedBids[0].native.impressionTrackers = [ + 'http://ice.360yield.com/imp_pixel?ic=wVm', + 'http://www.mytracker.com/imptracker' + ]; + expectedBids[0].native.javascriptTrackers = ''; + bids = spec.interpretResponse(response); + delete bids[0].ortbNative; + expect(bids).to.deep.equal(expectedBids); }); });