diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js new file mode 100644 index 00000000000..081638c0adb --- /dev/null +++ b/modules/tpmnBidAdapter.js @@ -0,0 +1,120 @@ +/* eslint-disable no-tabs */ +import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { BANNER } from '../src/mediaTypes'; +import {parse as parseUrl} from '../src/url'; + +export const ADAPTER_VERSION = '1'; +const SUPPORTED_AD_TYPES = [BANNER]; + +const BIDDER_CODE = 'tpmn'; +const URL = 'https://ad.tpmn.co.kr/prebidhb.tpmn'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + /** + *Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return 'params' in bid && + 'inventoryId' in bid.params && + 'publisherId' in bid.params && + !isNaN(Number(bid.params.inventoryId)) && + bid.params.inventoryId > 0 && + (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes + }, + + /** + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ + buildRequests: (bidRequests, bidderRequest) => { + if (bidRequests.length === 0) { + return []; + } + const bids = bidRequests.map(bidToRequest); + const bidderApiUrl = URL; + const payload = { + 'bids': [...bids], + 'site': createSite(bidderRequest.refererInfo) + }; + return [{ + method: 'POST', + url: bidderApiUrl, + data: payload + }]; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {serverResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, serverRequest) { + if (!Array.isArray(serverResponse.body)) { + return []; + } + // server response body is an array of bid results + const bidResults = serverResponse.body; + // our server directly returns the format needed by prebid.js so no more + // transformation is needed here. + return bidResults; + } +}; + +registerBidder(spec); + +/** + * Creates site description object + */ +function createSite(refInfo) { + let url = parseUrl(refInfo.referer); + let site = { + 'domain': url.hostname, + 'page': url.protocol + '://' + url.hostname + url.pathname + }; + if (self === top && document.referrer) { + site.ref = document.referrer; + } + let keywords = document.getElementsByTagName('meta')['keywords']; + if (keywords && keywords.content) { + site.keywords = keywords.content; + } + return site; +} + +function parseSize(size) { + let sizeObj = {} + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + return sizeObj; +} + +function parseSizes(sizes) { + if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map(size => parseSize(size)); + } + return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) +} + +function getBannerSizes(bidRequest) { + return parseSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes); +} + +function bidToRequest(bid) { + const bidObj = {}; + bidObj.sizes = getBannerSizes(bid); + + bidObj.inventoryId = bid.params.inventoryId; + bidObj.publisherId = bid.params.publisherId; + bidObj.bidId = bid.bidId; + bidObj.adUnitCode = bid.adUnitCode; + bidObj.auctionId = bid.auctionId; + + return bidObj; +} diff --git a/modules/tpmnBidAdapter.md b/modules/tpmnBidAdapter.md new file mode 100644 index 00000000000..8387528bb0f --- /dev/null +++ b/modules/tpmnBidAdapter.md @@ -0,0 +1,37 @@ +# Overview + +``` +Module Name: TPMN Bid Adapter +Module Type: Bidder Adapter +Maintainer: develop@tpmn.co.kr +``` + +# Description + +Connects to TPMN exchange for bids. + +NOTE: +- TPMN bid adapter only supports Banner at the moment. +- Multi-currency is not supported. + +# Sample Ad Unit Config +``` + var adUnits = [{ + // Banner adUnit + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [320, 50]], // banner size + } + }, + bids: [ + { + bidder: 'tpmn', + params: { + inventoryId: '1', + publisherId: 'TPMN' + } + } + ] + }]; +``` \ No newline at end of file diff --git a/test/spec/tpmnBidAdapter_spec.js b/test/spec/tpmnBidAdapter_spec.js new file mode 100644 index 00000000000..8bff8bf465a --- /dev/null +++ b/test/spec/tpmnBidAdapter_spec.js @@ -0,0 +1,144 @@ +/* eslint-disable no-tabs */ +import { expect } from 'chai'; +import { spec } from 'modules/tpmnBidAdapter'; + +describe('tpmnAdapterTests', function() { + describe('isBidRequestValid', function() { + let bid = { + adUnitCode: 'temp-unitcode', + bidder: 'tpmn', + params: { + inventoryId: '1', + publisherId: 'TPMN' + }, + bidId: '29092404798c9', + bidderRequestId: 'a01', + auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + it('should return true if a bid is valid banner bid request', function() { + expect(spec.isBidRequestValid(bid)).to.be.equal(true); + }); + + it('should return false where requried param is missing', function() { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.be.equal(false); + }); + + it('should return false when required param values have invalid type', function() { + let bid = Object.assign({}, bid); + bid.params = { + 'inventoryId': null, + 'publisherId': null + }; + expect(spec.isBidRequestValid(bid)).to.be.equal(false); + }); + }); + + describe('buildRequests', function() { + it('should return an empty list if there are no bid requests', function() { + const emptyBidRequests = []; + const bidderRequest = {}; + const request = spec.buildRequests(emptyBidRequests, bidderRequest); + expect(request).to.be.an('array').that.is.empty; + }); + it('should generate a POST server request with bidder API url, data', function() { + const bid = { + adUnitCode: 'temp-unitcode', + bidder: 'tpmn', + params: { + inventoryId: '1', + publisherId: 'TPMN' + }, + bidId: '29092404798c9', + bidderRequestId: 'a01', + auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + const tempBidRequests = [bid]; + const tempBidderRequest = {refererInfo: { + referer: 'test', + site: { + domain: 'localhost', + page: 'http://localhost/test' + } + }}; + const builtRequest = spec.buildRequests(tempBidRequests, tempBidderRequest); + + expect(builtRequest).to.have.lengthOf(1); + expect(builtRequest[0].method).to.equal('POST'); + expect(builtRequest[0].url).to.match(/ad.tpmn.co.kr\/prebidhb.tpmn/); + const apiRequest = builtRequest[0].data; + expect(apiRequest.site).to.deep.equal({ + domain: 'localhost', + page: 'http://localhost/test' + }); + expect(apiRequest.bids).to.have.lengthOf('1'); + expect(apiRequest.bids[0].inventoryId).to.equal('1'); + expect(apiRequest.bids[0].publisherId).to.equal('TPMN'); + expect(apiRequest.bids[0].bidId).to.equal('29092404798c9'); + expect(apiRequest.bids[0].adUnitCode).to.equal('temp-unitcode'); + expect(apiRequest.bids[0].auctionId).to.equal('da1d7a33-0260-4e83-a621-14674116f3f9'); + expect(apiRequest.bids[0].sizes).to.have.lengthOf('1'); + expect(apiRequest.bids[0].sizes[0]).to.deep.equal({ + width: 300, + height: 250 + }); + }); + }); + + describe('interpretResponse', function() { + const bid = { + adUnitCode: 'temp-unitcode', + bidder: 'tpmn', + params: { + inventoryId: '1', + publisherId: 'TPMN' + }, + bidId: '29092404798c9', + bidderRequestId: 'a01', + auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + const tempBidRequests = [bid]; + + it('should return an empty aray to indicate no valid bids', function() { + const emptyServerResponse = {}; + const bidResponses = spec.interpretResponse(emptyServerResponse, tempBidRequests); + expect(bidResponses).is.an('array').that.is.empty; + }); + it('should return an empty array to indicate no valid bids', function() { + const mockBidResult = { + requestId: '9cf19229-34f6-4d06-bc1d-0e44e8d616c8', + cpm: 10.0, + creativeId: '1', + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + ttl: 1800, + ad: '
TPMN HeaderBidding!
', + adType: 'banner' + }; + const testServerResponse = { + headers: [], + body: [mockBidResult] + }; + const bidResponses = spec.interpretResponse(testServerResponse, tempBidRequests); + expect(bidResponses).deep.equal([mockBidResult]); + }); + }); +});