diff --git a/modules/bidfluenceBidAdapter.js b/modules/bidfluenceBidAdapter.js new file mode 100644 index 00000000000..655e52e2d6d --- /dev/null +++ b/modules/bidfluenceBidAdapter.js @@ -0,0 +1,116 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'bidfluence'; + +function stdTimezoneOffset(t) { + const jan = new Date(t.getFullYear(), 0, 1); + const jul = new Date(t.getFullYear(), 6, 1); + return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); +} +function dst(t) { + return t.getTimezoneOffset() < stdTimezoneOffset(t); +} +function getBdfTz(d) { + let tz = d.getTimezoneOffset(); + if (dst(d)) { + tz += 60; + } + return tz.toString(); +} +function getUTCDate() { + var m = new Date(); + var dateString = m.getUTCFullYear() + '/' + + ('0' + (m.getUTCMonth() + 1)).slice(-2) + '/' + + ('0' + m.getUTCDate()).slice(-2) + ' ' + + ('0' + m.getUTCHours()).slice(-2) + ':' + + ('0' + m.getUTCMinutes()).slice(-2) + ':' + + ('0' + m.getUTCSeconds()).slice(-2); + + return dateString; +} + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function (bid) { + return !!bid.params.placementId || !!bid.params.publisherId; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const params = bidRequest.params; + const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + const width = sizes.split('x')[0]; + const height = sizes.split('x')[1]; + const refInfo = bidderRequest.refererInfo; + const gdpr = bidderRequest.gdprConsent; + const body = document.getElementsByTagName('body')[0]; + const vpW = Math.max(window.innerWidth || body.clientWidth || 0) + 2; + const vpH = Math.max(window.innerHeight || body.clientHeight || 0) + 2; + const sr = screen.height > screen.width ? screen.height + 'x' + screen.width + 'x' + screen.colorDepth : screen.width + 'x' + screen.height + 'x' + screen.colorDepth; + + const payload = { + bid: bidRequest.bidId, + v: '1.0', + azr: true, + ck: utils.cookiesAreEnabled(), + tid: params.placementId, + pid: params.publisherId, + rp: params.reservePrice || 0, + re: refInfo ? refInfo.referer : '', + st: refInfo ? refInfo.stack : [], + tz: getBdfTz(new Date()), + sr: sr, + tm: bidderRequest.timeout, + vp: vpW + 'x' + vpH, + sdt: getUTCDate(), + w: width, + h: height, + gdpr: gdpr ? gdpr.gdprApplies : false, + gdprc: gdpr ? gdpr.consentString : '' + }; + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: `//${payload.pid}.bidfluence.com/Hb`, + data: payloadString, + options: { contentType: 'text/plain' } + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const cpm = response.Cpm || 0; + + if (cpm > 0) { + const bidResponse = { + requestId: response.BidId, + cpm: cpm, + width: response.Width, + height: response.Height, + creativeId: response.CreativeId, + ad: response.Ad, + currency: 'USD', + netRevenue: true, + ttl: 360 + }; + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + + getUserSyncs: function (serverResponses) { + if (serverResponses.userSyncs) { + const syncs = serverResponses.UserSyncs.map((sync) => { + return { + type: sync.Type === 'ifr' ? 'iframe' : 'image', + url: sync.Url + }; + }); + return syncs; + } + } +}; +registerBidder(spec); diff --git a/modules/bidfluenceBidAdapter.md b/modules/bidfluenceBidAdapter.md new file mode 100644 index 00000000000..34dbb3d3a1c --- /dev/null +++ b/modules/bidfluenceBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Bidfluence Adapter +Module Type: Bidder Adapter +Maintainer: integrations@bidfluence.com +prebid_1_0_supported : true +gdpr_supported: true +``` + +# Description + +Bidfluence adapter for prebid. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-prebid', + sizes: [[300, 250]], + bids: [{ + bidder: 'bidfluence', + params: { + placementId: '1000', + publisherId: '1000' + } + }] + } +] +``` diff --git a/test/spec/modules/bidfluenceBidAdapter_spec.js b/test/spec/modules/bidfluenceBidAdapter_spec.js new file mode 100644 index 00000000000..71471c6650b --- /dev/null +++ b/test/spec/modules/bidfluenceBidAdapter_spec.js @@ -0,0 +1,111 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bidfluenceBidAdapter'; + +const BIDDER_CODE = 'bidfluence'; +const PLACEMENT_ID = '1000'; +const PUB_ID = '1000'; +const CONSENT_STRING = 'DUXDSDFSFWRRR8345F=='; + +const validBidRequests = [{ + 'bidder': BIDDER_CODE, + 'params': { + 'placementId': PLACEMENT_ID, + 'publisherId': PUB_ID, + 'reservePrice': 0 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '2b1f23307fb8ef', + 'bidderRequestId': '10edf38ec1a719', + 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304' +}]; + +const bidderRequest = { + 'bidderCode': 'bidfluence', + 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304', + 'bidderRequestId': '10edf38ec1a719', + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'test', + 'stack': ['test'] + }, + 'timeout': 1000, + 'gdprConsent': { + 'gdprApplies': true, + 'consentString': CONSENT_STRING, + 'vendorData': '' + } +}; + +bidderRequest.bids = validBidRequests; + +describe('Bidfluence Adapter test', () => { + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBidRequests[0])).to.equal(true); + }); + it('should return the right bidder code', function () { + expect(spec.code).to.eql(BIDDER_CODE); + }); + }); + + describe('buildRequests', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + it('sends bid request to our endpoint via POST', function () { + expect(request[0].method).to.equal('POST'); + }); + + const payload = JSON.parse(request[0].data); + + expect(payload.bid).to.equal(validBidRequests[0].bidId); + expect(payload.azr).to.equal(true); + expect(payload.ck).to.not.be.undefined; + expect(payload.tid).to.equal(PLACEMENT_ID); + expect(payload.pid).to.equal(PUB_ID); + expect(payload.rp).to.be.a('number'); + expect(payload.re).to.not.be.undefined; + expect(payload.st).to.not.be.undefined; + expect(payload.tz).to.not.be.undefined; + expect(payload.sr).to.not.be.undefined; + expect(payload.vp).to.not.be.undefined; + expect(payload.sdt).to.not.be.undefined; + expect(payload.w).to.equal('300'); + expect(payload.h).to.equal('250'); + + it('sends gdpr info if exists', function () { + expect(payload.gdpr).to.equal(true); + expect(payload.gdprc).to.equal(CONSENT_STRING); + }); + }); + + describe('interpretResponse', function () { + const response = { + body: { + 'CreativeId': '1000', + 'Cpm': 0.50, + 'Ad': '
', + 'Height': 250, + 'Width': 300 + } + }; + + it('should get correct bid response', function () { + const expectedResponse = [{ + requestId: response.body.BidId, + cpm: response.body.Cpm, + width: response.body.Width, + height: response.body.Height, + creativeId: response.body.CreativeId, + ad: response.body.Ad, + currency: 'USD', + netRevenue: true, + ttl: 360 + }]; + + let result = spec.interpretResponse(response, { 'bidderRequest': validBidRequests[0] }); + expect(result).to.deep.equal(expectedResponse); + }); + }); +});