diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js new file mode 100644 index 00000000000..27aa4e2c8a6 --- /dev/null +++ b/modules/deepintentBidAdapter.js @@ -0,0 +1,142 @@ +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER} from '../src/mediaTypes'; +import * as utils from '../src/utils'; +const BIDDER_CODE = 'deepintent'; +const BIDDER_ENDPOINT = 'https://prebid.deepintent.com/prebid'; +const USER_SYNC_URL = 'https://beacon.deepintent.com/usersync.html'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + aliases: [], + + // tagId is mandatory param + isBidRequestValid: bid => { + let valid = false; + if (bid && bid.params && bid.params.tagId) { + if (typeof bid.params.tagId === 'string' || bid.params.tagId instanceof String) { + valid = true; + } + } + return valid; + }, + interpretResponse: function(bidResponse, request) { + let responses = []; + if (bidResponse && bidResponse.body) { + let bids = bidResponse.body.seatbid && bidResponse.body.seatbid[0] ? bidResponse.body.seatbid[0].bid : []; + responses = bids.map(bid => formatResponse(bid)) + } + return responses; + }, + buildRequests: function (validBidRequests, bidderRequest) { + const openRtbBidRequest = { + id: utils.generateUUID(), + at: 1, + imp: validBidRequests.map(bid => buildImpression(bid)), + site: buildSite(bidderRequest), + device: buildDevice(), + source: { + fd: 0, + ext: { + type: 2 + } + } + }; + + return { + method: 'POST', + url: BIDDER_ENDPOINT, + data: JSON.stringify(openRtbBidRequest), + options: { + contentType: 'application/json' + } + }; + }, + /** + * Register User Sync. + */ + getUserSyncs: syncOptions => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_URL + }]; + } + } + +}; + +function formatResponse(bid) { + return { + requestId: bid && bid.impid ? bid.impid : undefined, + cpm: bid && bid.price ? bid.price : 0.0, + width: bid && bid.w ? bid.w : 0, + height: bid && bid.h ? bid.h : 0, + ad: bid && bid.adm ? bid.adm : '', + creativeId: bid && bid.crid ? bid.crid : undefined, + netRevenue: false, + currency: bid && bid.cur ? bid.cur : 'USD', + ttl: 300, + dealId: bid && bid.dealId ? bid.dealId : undefined + } +} + +function buildImpression(bid) { + return { + id: bid.bidId, + tagid: bid.params.tagId || '', + secure: window.location.protocol === 'https' ? 1 : 0, + banner: buildBanner(bid), + ext: bid.params.custom ? bid.params.custom : {} + }; +} + +function buildBanner(bid) { + if (utils.deepAccess(bid, 'mediaTypes.banner')) { + // Get Sizes from MediaTypes Object, Will always take first size, will be overrided by params for exact w,h + if (utils.deepAccess(bid, 'mediaTypes.banner.sizes') && !bid.params.height && !bid.params.width) { + let sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); + if (utils.isArray(sizes) && sizes.length > 0) { + return { + h: sizes[0][1], + w: sizes[0][0] + } + } + } else { + return { + h: bid.params.height, + w: bid.params.width + } + } + } +} + +function buildSite(bidderRequest) { + let site = {}; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + site.page = bidderRequest.refererInfo.referer; + site.domain = getDomain(bidderRequest.refererInfo.referer); + } + return site; +} + +function getDomain(referer) { + if (referer) { + let domainA = document.createElement('a'); + domainA.href = referer; + return domainA.hostname; + } +} + +function buildDevice() { + return { + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack === '1') ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language + } +} + +registerBidder(spec); diff --git a/modules/deepintentBidAdapter.md b/modules/deepintentBidAdapter.md new file mode 100644 index 00000000000..7f5afbd233a --- /dev/null +++ b/modules/deepintentBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: Deepintent Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@deepintent.com +``` + +# Description + +Deepintent currently supports the BANNER type ads through prebid js + +Module that connects to Deepintent's demand sources. + +# Banner Test Request +``` + var adUnits = [ + { + code: 'di_adUnit1', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size, only first one will be picked up since multiple ad sizes are not supported yet + } + } + bids: [ + { + bidder: 'deepintent', + params: { + tagId: '1300', // Required parameter + w: 300, // Width and Height here will override sizes in mediatype + h: 250, + custom: { // Custom parameters in form of key value pairs + user_min_age: 18 + } + } + } + ] + } + ]; +``` + +###Recommended User Sync Configuration + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + enabledBidders: ['deepintent'], + syncDelay: 3000 + }}); + + +``` diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js new file mode 100644 index 00000000000..353a815eb8c --- /dev/null +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -0,0 +1,162 @@ +import {expect} from 'chai'; +import {spec} from 'modules/deepintentBidAdapter'; +import * as utils from '../../../src/utils'; + +describe('Deepintent adapter', function () { + let request; + let bannerResponse; + + beforeEach(function () { + request = [ + { + bidder: 'deepintent', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + tagId: '100013', + w: 728, + h: 90, + custom: { + user_gender: 'female', + user_max_age: 25 + } + } + } + ]; + bannerResponse = { + 'body': { + 'id': '303e1fae-9677-41e2-9a92-15a23445363f', + 'seatbid': [{ + 'bid': [{ + 'id': '11447bb1-a266-470d-b0d7-8810f5b1b75f', + 'impid': 'a7e92b9b-d9db-4de8-9c3f-f90737335445', + 'price': 0.6, + 'adid': '10001', + 'adm': "\r\n", + 'adomain': ['deepintent.com'], + 'cid': '103389', + 'crid': '13665', + 'w': 300, + 'h': 250, + 'dealId': 'dee_12312stdszzsx' + }], + 'seat': '10000' + }], + 'bidid': '0b08b09f-aaa1-4c14-b1c8-7debb1a7c1cd' + } + } + }); + + describe('validations', function () { + it('validBid : tagId is passed', function () { + let bid = { + bidder: 'deepintent', + params: { + tagId: '1232' + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('invalidBid : tagId is not passed', function () { + let bid = { + bidder: 'deepintent', + params: { + h: 200, + w: 300 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + it('invalidBid : tagId is not a string', function () { + let bid = { + bidder: 'deepintent', + params: { + tagId: 12345 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('request check', function () { + it('unmutaable bid request check', function () { + let oRequest = utils.deepClone(request), + bidRequest = spec.buildRequests(request); + expect(request).to.deep.equal(oRequest); + }); + it('bidder connection check', function () { + let bRequest = spec.buildRequests(request); + expect(bRequest.url).to.equal('https://prebid.deepintent.com/prebid'); + expect(bRequest.method).to.equal('POST'); + expect(bRequest.options.contentType).to.equal('application/json'); + }); + it('bid request check : Device', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.device.ua).to.be.a('string'); + expect(data.device.js).to.equal(1); + expect(data.device.dnt).to.be.a('number'); + expect(data.device.h).to.be.a('number'); + expect(data.device.w).to.be.a('number'); + }); + it('bid request check : Impression', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.at).to.equal(1); // auction type + expect(data.imp[0].id).to.equal(request[0].bidId); + expect(data.imp[0].tagid).to.equal('100013'); + }); + it('bid request check : ad size', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].banner).to.be.a('object'); + expect(data.imp[0].banner.w).to.equal(300); + expect(data.imp[0].banner.h).to.equal(250); + }); + it('bid request check : custom params', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].ext).to.be.a('object'); + expect(data.imp[0].ext.user_gender).to.equal('female'); + expect(data.imp[0].ext.user_max_age).to.equal(25); + }); + it('bid request check: source params', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.source.fd).to.equal(0); + expect(data.source.ext.type).to.equal(2); + }) + }); + describe('user sync check', function () { + it('user sync url check', function () { + let syncOptions = { + iframeEnabled: true + }; + let userSync = spec.getUserSyncs(syncOptions); + expect(userSync).to.be.an('array').with.length.above(0); + expect(userSync[0].type).to.equal('iframe'); + expect(userSync[0].url).to.equal('https://beacon.deepintent.com/usersync.html'); + }); + }); + describe('response check', function () { + it('bid response check: valid bid response', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + let bResponse = spec.interpretResponse(bannerResponse, request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + }) +});