diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js new file mode 100644 index 00000000000..5114444be3c --- /dev/null +++ b/modules/tripleliftBidAdapter.js @@ -0,0 +1,140 @@ +import { BANNER } from 'src/mediaTypes'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +const BIDDER_CODE = 'triplelift'; +const STR_ENDPOINT = document.location.protocol + '//tlx.3lift.com/header/auction?'; +let gdprApplies = true; +let consentString = null; + +export const tripleliftAdapterSpec = { + + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function(bid) { + return (typeof bid.params.inventoryCode !== 'undefined'); + }, + + buildRequests: function(bidRequests, bidderRequest) { + let tlCall = STR_ENDPOINT; + let referrer = utils.getTopWindowUrl(); + let data = _buildPostBody(bidRequests); + + tlCall = utils.tryAppendQueryString(tlCall, 'lib', 'prebid'); + tlCall = utils.tryAppendQueryString(tlCall, 'v', '$prebid.version$'); + tlCall = utils.tryAppendQueryString(tlCall, 'fe', _isFlashEnabled().toString()); + tlCall = utils.tryAppendQueryString(tlCall, 'referrer', referrer); + + if (bidderRequest && bidderRequest.timeout) { + tlCall = utils.tryAppendQueryString(tlCall, 'tmax', bidderRequest.timeout); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { + gdprApplies = bidderRequest.gdprConsent.gdprApplies; + tlCall = utils.tryAppendQueryString(tlCall, 'gdpr', gdprApplies.toString()); + } + if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { + consentString = bidderRequest.gdprConsent.consentString; + tlCall = utils.tryAppendQueryString(tlCall, 'cmp_cs', consentString); + } + } + + if (tlCall.lastIndexOf('&') === tlCall.length - 1) { + tlCall = tlCall.substring(0, tlCall.length - 1); + } + utils.logMessage('tlCall request built: ' + tlCall); + + return { + method: 'POST', + url: tlCall, + data, + bidderRequest + }; + }, + + interpretResponse: function(serverResponse, {bidderRequest}) { + let bids = serverResponse.body.bids || []; + return bids.map(function(bid) { + return _buildResponseObject(bidderRequest, bid); + }); + }, + + getUserSyncs: function(syncOptions) { + let ibCall = '//ib.3lift.com/sync?'; + if (consentString !== null) { + ibCall = utils.tryAppendQueryString(ibCall, 'gdpr', gdprApplies); + ibCall = utils.tryAppendQueryString(ibCall, 'cmp_cs', consentString); + } + + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: ibCall + }]; + } + } +} + +function _buildPostBody(bidRequests) { + let data = {}; + data.imp = bidRequests.map(function(bid, index) { + return { + id: index, + tagid: bid.params.inventoryCode, + floor: bid.params.floor, + banner: { + format: _sizes(bid.sizes) + } + } + }); + + return data; +} + +function _sizes(sizeArray) { + return sizeArray.map(function(size) { + return { + w: size[0], + h: size[1] + }; + }); +} + +function _buildResponseObject(bidderRequest, bid) { + let bidResponse = {}; + let width = bid.width || 1; + let height = bid.height || 1; + let dealId = bid.deal_id || ''; + let creativeId = bid.imp_id; + + if (bid.cpm != 0 && bid.ad) { + bidResponse = { + requestId: bidderRequest.bids[creativeId].bidId, + cpm: bid.cpm, + width: width, + height: height, + netRevenue: true, + ad: bid.ad, + creativeId: creativeId, + dealId: dealId, + currency: 'USD', + ttl: 33, + }; + }; + return bidResponse; +} + +function _isFlashEnabled() { + let flash; + try { + flash = Boolean(new ActiveXObject('ShockwaveFlash.ShockwaveFlash')); + } catch (e) { + flash = navigator.mimeTypes && + navigator.mimeTypes['application/x-shockwave-flash'] !== undefined && + navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin ? 1 : 0 + } + return flash ? 1 : 0; +} + +registerBidder(tripleliftAdapterSpec); diff --git a/modules/tripleliftBidAdapter.md b/modules/tripleliftBidAdapter.md new file mode 100644 index 00000000000..ad153cdece7 --- /dev/null +++ b/modules/tripleliftBidAdapter.md @@ -0,0 +1,62 @@ +# Overview + +``` +Module Name: Triplelift Bid Adapter +Module Type: Bidder Adapter +Maintainer: bzellman@triplelift.com +``` + +# Description + +Connects to Triplelift Exchange for bids. +Triplelift bid adapter supports Banner format only. + +# Test Parameters +``` +var adUnits = [{ + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 600], [300, 250], [320, 90]], + } + }, + bids: [ + { + bidder: 'triplelift', + params: { + inventoryCode: 'forbes_main', + floor: 1.009 + } + }] +}, { + code: 'banner-div-2', + mediaTypes: { + banner: { + sizes: [[300, 300]], + } + }, + bids: [ + { + bidder: 'triplelift', + params: { + inventoryCode: 'foodgawker', + floor: 0.00 + } + }] +}, { + code: 'banner-div-3', + mediaTypes: { + banner: { + sizes: [[300, 600], [300, 250]], + } + }, + bids: [ + { + bidder: 'triplelift', + params: { + inventoryCode: 'forbes_main', + floor: 0 + } + }] +}]; +``` diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js new file mode 100644 index 00000000000..3d5b7d2dcba --- /dev/null +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -0,0 +1,207 @@ +import { expect } from 'chai'; +import { tripleliftAdapterSpec } from 'modules/tripleliftBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { deepClone } from 'src/utils'; + +const ENDPOINT = document.location.protocol + '//tlx.3lift.com/header/auction?'; + +describe('triplelift adapter', () => { + const adapter = newBidder(tripleliftAdapterSpec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + bidder: 'triplelift', + params: { + inventoryCode: '12345', + floor: 1.0, + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true for valid bid request', () => { + expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + inventoryCode: 'another_inv_code', + floor: 0.05 + }; + expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + floor: 1.0 + }; + expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + bidder: 'triplelift', + params: { + inventoryCode: '12345', + floor: 1.0, + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + } + ]; + + it('exists and is an object', () => { + const request = tripleliftAdapterSpec.buildRequests(bidRequests); + expect(request).to.exist.and.to.be.a('object'); + }); + + it('should be a post request and populate the payload', () => { + const request = tripleliftAdapterSpec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.exist; + expect(payload.imp[0].tagid).to.equal('12345'); + expect(payload.imp[0].floor).to.equal(1.0); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('should return a query string for TL call', () => { + const request = tripleliftAdapterSpec.buildRequests(bidRequests); + const url = request.url; + expect(url).to.exist; + expect(url).to.be.a('string'); + expect(url).to.match(/(?:tlx.3lift.com\/header\/auction)/) + expect(url).to.match(/(?:lib=prebid)/) + expect(url).to.match(/(?:prebid.version)/) + expect(url).to.match(/(?:fe=)/) + expect(url).to.match(/(?:referrer)/) + }) + }); + + describe('interpretResponse', () => { + let response = { + body: { + bids: [ + { + imp_id: 0, + cpm: 1.062, + width: 300, + height: 250, + ad: 'ad-markup', + iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' + } + ] + } + }; + let bidderRequest = { + bidderCode: 'triplelift', + auctionId: 'a7ebcd1d-66ff-4b5c-a82c-6a21a6ee5a18', + bidderRequestId: '5c55612f99bc11', + bids: [ + { + imp_id: 0, + cpm: 1.062, + width: 300, + height: 250, + ad: 'ad-markup', + iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' + } + ], + gdprConsent: { + consentString: 'BOONm0NOONma-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVOQ6gEaY', + gdprApplies: true + } + }; + + it('should get correct bid response', () => { + let expectedResponse = [ + { + requestId: '3db3773286ee59', + cpm: 1.062, + width: 300, + height: 250, + netRevenue: true, + ad: 'ad-markup', + creativeId: 29681110, + dealId: '', + currency: 'USD', + ttl: 33, + } + ]; + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result).to.have.length(1); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('should return multile responses to support SRA', () => { + let response = { + body: { + bids: [ + { + imp_id: 0, + cpm: 1.062, + width: 300, + height: 250, + ad: 'ad-markup', + iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' + }, + { + imp_id: 0, + cpm: 1.9, + width: 300, + height: 600, + ad: 'ad-markup-2', + iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' + } + ] + } + }; + let bidderRequest = { + bidderCode: 'triplelift', + auctionId: 'a7ebcd1d-66ff-4b5c-a82c-6a21a6ee5a18', + bidderRequestId: '5c55612f99bc11', + bids: [ + { + imp_id: 0, + cpm: 1.062, + width: 300, + height: 600, + ad: 'ad-markup', + iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' + }, + { + imp_id: 0, + cpm: 1.9, + width: 300, + height: 250, + ad: 'ad-markup-2', + iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' + } + ], + gdprConsent: { + consentString: 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY', + gdprApplies: true + } + }; + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result).to.have.length(2); + }); + }); +});