diff --git a/modules/rxrtbBidAdapter.js b/modules/rxrtbBidAdapter.js new file mode 100644 index 00000000000..cf03e41c208 --- /dev/null +++ b/modules/rxrtbBidAdapter.js @@ -0,0 +1,140 @@ +import * as utils from 'src/utils'; +import {BANNER} from 'src/mediaTypes'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {config} from 'src/config'; + +const BIDDER_CODE = 'rxrtb'; +const DEFAULT_HOST = 'bid.rxrtb.bid'; +const AUCTION_TYPE = 2; +const RESPONSE_TTL = 900; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bidRequest) { + return 'params' in bidRequest && bidRequest.params.source !== undefined && bidRequest.params.id !== undefined && Number.isInteger(bidRequest.params.id) && bidRequest.params.token !== undefined; + }, + buildRequests: function (validBidRequests) { + var requests = []; + for (let i = 0; i < validBidRequests.length; i++) { + let prebidReq = makePrebidRequest(validBidRequests[i]); + if (prebidReq) { + requests.push(prebidReq); + } + } + + return requests; + }, + interpretResponse: function (serverResponse, bidRequest) { + let rtbResp = serverResponse.body; + if ((!rtbResp) || (!rtbResp.seatbid)) { + return []; + } + let bidResponses = []; + for (let i = 0; i < rtbResp.seatbid.length; i++) { + let seatbid = rtbResp.seatbid[i]; + for (let j = 0; j < seatbid.bid.length; j++) { + let bid = seatbid.bid[j]; + let bidResponse = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + mediaType: BANNER, + creativeId: bid.crid, + currency: rtbResp.cur || 'USD', + netRevenue: true, + ttl: bid.exp || RESPONSE_TTL, + ad: bid.adm + }; + bidResponses.push(bidResponse); + } + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + return []; + } +} + +registerBidder(spec); + +function getDomain(url) { + var a = document.createElement('a'); + a.href = url; + + return a.host; +} + +function makePrebidRequest(req) { + let host = req.params.host || DEFAULT_HOST; + let url = window.location.protocol + '//' + host + '/dsp?id=' + req.params.id + '&token=' + req.params.token; + let reqData = makeRtbRequest(req); + return { + method: 'POST', + url: url, + data: JSON.stringify(reqData) + }; +} + +function makeRtbRequest(req) { + let imp = []; + imp.push(makeImp(req)); + return { + 'id': req.auctionId, + 'imp': imp, + 'site': makeSite(req), + 'device': makeDevice(), + 'hb': 1, + 'at': req.params.at || AUCTION_TYPE, + 'cur': ['USD'], + 'badv': req.params.badv || '', + 'bcat': req.params.bcat || '', + }; +} + +function makeImp(req) { + let imp = { + 'id': req.bidId, + 'tagid': req.adUnitCode, + 'banner': makeBanner(req) + }; + + if (req.params.bidfloor && Number.isInteger(req.params.bidfloor)) { + imp.bidfloor = req.params.bidfloor + } + + return imp; +} + +function makeBanner(req) { + let format = []; + let banner = {}; + for (let i = 0; i < req.sizes.length; i++) { + format.push({ + w: req.sizes[i][0], + h: req.sizes[i][1] + }); + } + banner.format = format; + if (req.params.pos && Number.isInteger(req.params.pos)) { + banner.pos = req.params.pos; + } + return banner; +} + +function makeSite(req) { + return { + 'id': req.params.source, + 'domain': getDomain(config.getConfig('publisherDomain')), + 'page': utils.getTopWindowUrl(), + 'ref': utils.getTopWindowReferrer() + }; +} + +function makeDevice() { + return { + 'ua': window.navigator.userAgent || '', + 'ip': 1 + }; +} diff --git a/modules/rxrtbBidAdapter.md b/modules/rxrtbBidAdapter.md new file mode 100644 index 00000000000..e9628bed0dc --- /dev/null +++ b/modules/rxrtbBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: rxrtb Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: contact@picellaltd.com + + +# Description + +Module that connects to rxrtb's demand source + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'test-ad', + sizes: [[728, 98]], + bids: [ + { + bidder: 'rxrtb', + params: { + id: 89, + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + } + } + ] + }, + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/rxrtbBidAdapter_spec.js b/test/spec/modules/rxrtbBidAdapter_spec.js new file mode 100644 index 00000000000..0785c6f144b --- /dev/null +++ b/test/spec/modules/rxrtbBidAdapter_spec.js @@ -0,0 +1,120 @@ +import {expect} from 'chai'; +import {spec} from 'modules/rxrtbBidAdapter'; + +describe('rxrtb adapater', () => { + describe('Test validate req', () => { + it('should accept minimum valid bid', () => { + let bid = { + bidder: 'rxrtb', + params: { + id: 89, + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(true); + }); + + it('should reject missing id', () => { + let bid = { + bidder: 'rxrtb', + params: { + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + + it('should reject id not Integer', () => { + let bid = { + bidder: 'rxrtb', + params: { + id: '123', + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + + it('should reject missing source', () => { + let bid = { + bidder: 'rxrtb', + params: { + id: 89, + token: '658f11a5efbbce2f9be3f1f146fcbc22' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + }); + + describe('Test build request', () => { + it('minimum request', () => { + let bid = { + bidder: 'rxrtb', + sizes: [[728, 90]], + bidId: '4d0a6829338a07', + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '20882439e3238c', + params: { + id: 89, + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + }, + }; + const req = JSON.parse(spec.buildRequests([bid])[0].data); + + expect(req).to.have.property('id'); + expect(req).to.have.property('imp'); + expect(req).to.have.property('device'); + expect(req).to.have.property('site'); + expect(req).to.have.property('hb'); + expect(req.imp[0]).to.have.property('id'); + expect(req.imp[0]).to.have.property('banner'); + expect(req.device).to.have.property('ip'); + expect(req.device).to.have.property('ua'); + expect(req.site).to.have.property('id'); + expect(req.site).to.have.property('domain'); + }); + }); + + describe('Test interpret response', () => { + it('General banner response', () => { + let resp = spec.interpretResponse({ + body: { + id: 'abcd', + seatbid: [{ + bid: [{ + id: 'abcd', + impid: 'banner-bid', + price: 0.3, + w: 728, + h: 98, + adm: 'hello', + crid: 'efgh', + exp: 5 + }] + }] + } + }, null)[0]; + + expect(resp).to.have.property('requestId', 'banner-bid'); + expect(resp).to.have.property('cpm', 0.3); + expect(resp).to.have.property('width', 728); + expect(resp).to.have.property('height', 98); + expect(resp).to.have.property('creativeId', 'efgh'); + expect(resp).to.have.property('ttl', 5); + expect(resp).to.have.property('ad', 'hello'); + }); + }); +});