Skip to content

Commit

Permalink
Merge pull request prebid#9 from pm-harshad-mane/PubMatic_One
Browse files Browse the repository at this point in the history
PubMatic one
  • Loading branch information
PubMatic-OpenWrap committed Dec 12, 2017
2 parents fdad392 + b6e7805 commit 80c4d2f
Show file tree
Hide file tree
Showing 2 changed files with 395 additions and 0 deletions.
276 changes: 276 additions & 0 deletions modules/pubmaticBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
const constants = require('src/constants.json');

const BIDDER_CODE = 'pubmatic';
const ENDPOINT = '//openbid.pubmatic.com/translator?source=prebid-client';
const USYNCURL = '//ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=';
const CURRENCY = 'USD';
const AUCTION_TYPE = 2; //todo ?? is auction type correct ? second price auction
//todo: now what is significance of value ?
const CUSTOM_PARAMS = {
'kadpageurl': 'kadpageurl',
'gender': 'gender', // User gender
'yob': 'yob', // User year of birth
'dctr': 'dctr', // Custom Targeting //todo : remove ????
'lat': 'lat', // User location - Latitude
'lon': 'lon', // User Location - Longitude
'wiid': 'wiid', // OpenWrap Wrapper Impression ID
'profId': 'profId', // OpenWrap Legacy: Profile ID
'verId': 'verId' // OpenWrap Legacy: version ID
};

let publisherId = 0;

function _processPmZoneId(zoneId) {
if (utils.isStr(zoneId)) {
return zoneId.split(',').slice(0, 50).join();
} else {
utils.logWarn('PubMatic: Ignoring param key: pmzoneid, expects string-value, found ' + typeof zoneId);
return undefined;
}
}

function _processFloor(floor){
if (utils.isStr(floor)) {
return parseFloat(floor) || undefined;
} else {
utils.logWarn('PubMatic: Ignoring param key: kadfloor, expects string-value, found ' + typeof floor);
return undefined;
}
}

function _cleanSlot(slotName) {
if (utils.isStr(slotName)) {
return slotName.replace(/^\s+/g, '').replace(/\s+$/g, '');
}
return '';
}

function _parseAdSlot(bid){

bid.params.adUnit = '';
bid.params.adUnitIndex = '0';
bid.params.width = 0;
bid.params.height = 0;

bid.params.adSlot = _cleanSlot(bid.params.adSlot);

var slot = bid.params.adSlot;
var splits = slot.split(':');

slot = splits[0];
if(splits.length == 2){
bid.params.adUnitIndex = splits[1];
}
splits = slot.split('@');
if(splits.length != 2){
return;
}
bid.params.adUnit = splits[0];
splits = splits[1].split('x');
if(splits.length != 2){
return;
}
bid.params.width = parseInt(splits[0]);
bid.params.height = parseInt(splits[1]);
}

function _initConf() {
var conf = {};
var currTime = new Date();
conf.sec = window.location.protocol === 'https:' ? 1 : 0;
conf.wp = 'PreBid';//todo : do we need to send this ?
conf.wv = constants.REPO_AND_VERSION;
try {
conf.pageURL = window.top.location.href;
conf.hostname = window.top.location.hostname;
conf.refurl = window.top.document.referrer;
} catch (e) {
conf.pageURL = window.location.href;
conf.hostname = window.location.hostname;
conf.refurl = window.document.referrer;
}
return conf;
}

function _handleCustomParams(params, conf) {
// istanbul ignore else
if (!conf.kadpageurl) {
conf.kadpageurl = conf.pageURL;
}

var key, value, entry;
for (key in CUSTOM_PARAMS) {
// istanbul ignore else
if (CUSTOM_PARAMS.hasOwnProperty(key)) {
value = params[key];
// istanbul ignore else
if (value) {
entry = CUSTOM_PARAMS[key];

if (typeof entry === 'object') {
value = entry.m(value, conf);
key = entry.n;
} else {
key = CUSTOM_PARAMS[key];
}

if (utils.isStr(value)) {
conf[key] = value;
} else {
utils.logWarn('PubMatic: Ignoring param key: ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value);
}
}
}
}
return conf;
}

function _createOrtbTemplate(conf){
return {
id : '' + new Date().getTime(),
at: AUCTION_TYPE,
cur: [CURRENCY],
imp: [],
site: {
domain: conf.hostname,
page: conf.pageURL,
publisher: {}
},
device: {
ua: navigator.userAgent,
js: 1,
dnt: (navigator.doNotTrack == "yes" || navigator.doNotTrack == "1" || navigator.msDoNotTrack == "1") ? 1 : 0,
h: screen.height,
w: screen.width,
language: navigator.language
},
user: {},
ext: {}
};
}

function _createImpressionObject(bid, conf){
return {
id: bid.bidId,
tagid: bid.params.adUnit,
bidfloor: _processFloor(bid.params.kadfloor),
secure: conf.sec,
banner: {
pos: 0,
w: bid.params.width,
h: bid.params.height,
topframe: 1, //todo: may need to change for postbid : check with open bid
},
ext: {
pmZoneId: _processPmZoneId(bid.params.pmzoneid)
}
};
}

export const spec = {
code: BIDDER_CODE,

/**
* Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid
*
* @param {BidRequest} bid The bid params to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: bid => {
return !!(bid && bid.params && bid.params.publisherId && bid.params.adSlot);
},

/**
* Make a server request from the list of BidRequests.
*
* @param {validBidRequests[]} - an array of bids
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: validBidRequests => {
var conf = _initConf();
var payload = _createOrtbTemplate(conf);
validBidRequests.forEach(bid => {
_parseAdSlot(bid);
if(! (bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex && bid.params.width && bid.params.height)){
utils.logWarn('PubMatic: Skipping the non-standard adslot:', bid.params.adSlot, bid);
return;
}
conf.pubId = conf.pubId || bid.params.publisherId;
conf = _handleCustomParams(bid.params, conf);
conf.transactionId = bid.transactionId;
payload.imp.push(_createImpressionObject(bid, conf));
});

if(payload.imp.length == 0){
return;
}

payload.site.publisher.id = conf.pubId;
publisherId = conf.pubId;
payload.ext.wrapper = {};
payload.ext.wrapper.profile = conf.profId || undefined;
payload.ext.wrapper.version = conf.verId || undefined;
payload.ext.wrapper.wiid = conf.wiid || undefined;
payload.ext.wrapper.wv = conf.wv || undefined;
payload.ext.wrapper.transactionId = conf.transactionId;
payload.user.gender = conf.gender || undefined;
payload.user.lat = conf.lat || undefined;
payload.user.lon = conf.lon || undefined;
payload.user.yob = conf.yob || undefined;
payload.site.page = conf.kadpageurl || payload.site.page;
return {
method: 'POST',
url: ENDPOINT,
data: JSON.stringify(payload)
};
},

/**
* Unpack the response from the server into a list of bids.
*
* @param {*} response A successful response from the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: (response, request) => {
const bidResponses = [];
try {
if (response.body && response.body.seatbid && response.body.seatbid[0] && response.body.seatbid[0].bid) {
response.body.seatbid[0].bid.forEach(bid => {
let newBid = {
requestId: bid.impid,
cpm: bid.price, // Can we round to min precision ?
width: bid.w,
height: bid.h,
creativeId: bid.crid || bid.id,
dealId: bid.dealid,
currency: CURRENCY,
netRevenue: true,
ttl: 300,
referrer: utils.getTopWindowUrl(),
ad: bid.adm
};
bidResponses.push(newBid);
});
}
} catch (error) {
utils.logError(error);
}
return bidResponses;
},

/**
* Register User Sync.
*/
getUserSyncs: syncOptions => {
if (syncOptions.iframeEnabled) {
return [{
type: 'iframe',
url: USYNCURL + publisherId
}];
}
}
};

registerBidder(spec);
119 changes: 119 additions & 0 deletions test/spec/modules/pubmaticBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import {expect} from 'chai';
import {spec} from 'modules/pubmaticBidAdapter';
const constants = require('src/constants.json');

describe('PubMatic adapter', () => {
let bidRequests;

beforeEach(() => {
bidRequests = [
{
bidder: 'pubmatic',
params: {
publisherId: '301',
adSlot: '/15671365/DMDemo@300x250:0',
kadfloor: '1.2',
pmzoneid: 'aabc, ddef',
kadpageurl: 'www.publisher.com',
yob: '1986',
gender: 'M',
lat: '12.3',
lon: '23.7',
wiid: '1234567890',
profId: '100',
verId: '200'
},
placementCode: '/19968336/header-bid-tag-1',
sizes: [[300, 250], [300, 600]],
bidId: '23acc48ad47af5',
requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99',
bidderRequestId: '1c56ad30b9b8ca8',
transactionId: '92489f71-1bf2-49a0-adf9-000cea934729'
}
];
});

describe('implementation', () => {

describe('Bid validations', () => {

it('valid bid case', () => {
let validBid = {
bidder: 'pubmatic',
params: {
publisherId: '301',
adSlot: '/15671365/DMDemo@300x250:0'
}
},
isValid = spec.isBidRequestValid(validBid);
expect(isValid).to.equal(true);
});

it('invalid bid case: publisherId not passed', () => {
let validBid = {
bidder: 'pubmatic',
params: {
adSlot: '/15671365/DMDemo@300x250:0'
}
},
isValid = spec.isBidRequestValid(validBid);
expect(isValid).to.equal(false);
});

it('invalid bid case: adSlot not passed', () => {
let validBid = {
bidder: 'pubmatic',
params: {
publisherId: '301'
}
},
isValid = spec.isBidRequestValid(validBid);
expect(isValid).to.equal(false);
});
});

describe('Request formation', () => {
it('Endpoint checking', () => {
let request = spec.buildRequests(bidRequests);
expect(request.url).to.equal('//openbid.pubmatic.com/translator?source=prebid-client');
expect(request.method).to.equal('POST');
});

it('Request params check', () => {
let request = spec.buildRequests(bidRequests);
let data = JSON.parse(request.data);
expect(data.at).to.equal(2); //auction type
expect(data.cur[0]).to.equal("USD"); //currency
expect(data.site.domain).to.be.a('string'); // domain should be set
expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL
expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id
expect(data.user.yob).to.equal(bidRequests[0].params.yob); // YOB
expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender
expect(data.user.lat).to.equal(bidRequests[0].params.lat); // Latitude
expect(data.user.lon).to.equal(bidRequests[0].params.lon); // Lognitude
expect(data.ext.wrapper.wv).to.equal(constants.REPO_AND_VERSION); // Wrapper Version
expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId
expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID
expect(data.ext.wrapper.profile).to.equal(bidRequests[0].params.profId); // OpenWrap: Wrapper Profile ID
expect(data.ext.wrapper.version).to.equal(bidRequests[0].params.verId); // OpenWrap: Wrapper Profile Version ID

expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id
expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor
expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid
expect(data.imp[0].banner.w).to.equal(300); // width
expect(data.imp[0].banner.h).to.equal(250); // height
expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid); // pmzoneid
});

it('invalid adslot', () => {
bidRequests[0].params.adSlot = '/15671365/DMDemo';
let request = spec.buildRequests(bidRequests);
expect(request).to.equal(undefined);
});
});

});



});

0 comments on commit 80c4d2f

Please sign in to comment.