Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added MobFox Adapter #1312

Merged
merged 11 commits into from
Jul 26, 2017
1 change: 1 addition & 0 deletions adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"trion",
"prebidServer",
"adsupply",
"mobfox",
{
"appnexus": {
"alias": "brealtime"
Expand Down
167 changes: 167 additions & 0 deletions src/adapters/mobfox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
const bidfactory = require('../bidfactory.js');
const bidmanager = require('../bidmanager.js');
const adloader = require('../adloader');
const ajax = require('../ajax.js');
const CONSTANTS = require('../constants.json');
const utils = require('../utils.js');

const mobfoxAdapter = function () {
const BIDDER_CODE = 'mobfox';
const BID_REQUEST_BASE_URL = "https://my.mobfox.com/request.php";

// request
function buildQueryStringFromParams(params) {
for (let key in params) {
if (params.hasOwnProperty(key)) {
if (params[key] === undefined)
delete params[key];
else
params[key] = encodeURIComponent(params[key]);
}
}

return utils._map(Object.keys(params), key => `${key}=${params[key]}`)
.join('&');
}

function buildBidRequest(bid) {
let bidParams = bid.params;

let requestParams = {
// -------------------- Mandatory Parameters ------------------
rt: bidParams.rt || "api-fetchip"
, r_type: bidParams.r_type || "banner"
, r_resp: bidParams.r_resp || "json" // string | vast20
// , i: bidParams.i || undefined // string | 69.197.148.18
, s: bidParams.s // string | 80187188f458cfde788d961b6882fd53
, u: bidParams.u || window.navigator.userAgent // string

// ------------------- Global Parameters ----------------------
, adspace_width: bidParams.adspace_width || bid.sizes[0][0] // integer | 320
, adspace_height: bidParams.adspace_height || bid.sizes[0][1]// integer | 48
, r_floor: bidParams.r_floor || undefined // 0.8

, o_andadvid: bidParams.o_andadvid || undefined // "c6292267-56ad-4326-965d-deef6fcd5er9"
, longitude: bidParams.longitude || undefined // 12.12
, latitude: bidParams.latitude || undefined // 280.12
, demo_age: bidParams.demo_age || undefined // 1978

// ------------------- banner / interstitial ----------------------
, adspace_strict: bidParams.adspace_strict || undefined

// ------------------- interstitial / video ----------------------
, imp_instl: bidParams.imp_instl || undefined // integer | 1

// ------------------- mraid ----------------------
, c_mraid: bidParams.c_mraid || undefined // integer | 1

// ------------------- video ----------------------
, v_dur_min: bidParams.v_dur_min || undefined // integer | 0
, v_dur_max: bidParams.v_dur_max || undefined // integer | 999
, v_autoplay: bidParams.v_autoplay || undefined // integer | 1
, v_startmute: bidParams.v_startmute || undefined // integer | 0
, v_rewarded: bidParams.v_rewarded || undefined // integer | 0
, v_api: bidParams.v_api || undefined // string | vpaid20
, n_ver: bidParams.n_ver || undefined //
, n_adunit: bidParams.n_adunit || undefined //
, n_layout: bidParams.n_layout || undefined //
, n_context: bidParams.n_context || undefined //
, n_plcmttype: bidParams.n_plcmttype || undefined //
, n_img_icon_req: bidParams.n_img_icon_req || undefined // boolean0
, n_img_icon_size: bidParams.n_img_icon_size || undefined // string80
, n_img_large_req: bidParams.n_img_large_req || undefined // boolean0
, n_img_large_w: bidParams.n_img_large_w || undefined // integer1200
, n_img_large_h: bidParams.n_img_large_h || undefined // integer627
, n_title_req: bidParams.n_title_req || undefined // boolean0
, n_title_len: bidParams.n_title_len || undefined // string25
, n_desc_req: bidParams.n_desc_req || undefined // boolean0
, n_desc_len: bidParams.n_desc_len || undefined // string140
, n_rating_req: bidParams.n_rating_req || undefined //
};

return requestParams;
}

function sendBidRequest(bid) {
let requestParams = buildBidRequest(bid);
let queryString = buildQueryStringFromParams(requestParams);

ajax.ajax(`${BID_REQUEST_BASE_URL}?${queryString}`, {
success(resp, xhr) {
if (xhr.getResponseHeader("Content-Type") == "application/json")
resp = JSON.parse(resp);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrap all JSON.parse in try catch block

onBidResponse({
data: resp,
xhr: xhr
}, bid);
},
error(err) {
if (xhr.getResponseHeader("Content-Type") == "application/json")
err = JSON.parse(err);
onBidResponseError(bid, [err]);
}
});
}

//response
function onBidResponseError(bid, err) {
utils.logError("Bid Response Error", bid, ...err);
let bidResponse = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bid);
bidResponse.bidderCode = BIDDER_CODE;
bidmanager.addBidResponse(bid.placementCode, bidResponse);
}

function onBidResponse(bidderResponse, bid) {
// transform the response to a valid prebid response
let bidResponse = transformResponse(bidderResponse, bid);
bidmanager.addBidResponse(bid.placementCode, bidResponse);
}

function transformResponse(bidderResponse, bid) {

let responseBody = bidderResponse.data;

// Validate Request
let err = responseBody.error;
if (err) {
return onBidResponseError(bid, [err]);
}

let htmlString = responseBody.request && responseBody.request.htmlString;
if (!htmlString) {
return onBidResponseError(bid, [`htmlString is missing`, responseBody]);
}

let cpm, cpmHeader = bidderResponse.xhr.getResponseHeader("X-Pricing-CPM");
try {
cpm = Number(cpmHeader);
} catch (e) {
return onBidResponseError(bid, ["Invalid CPM value:", cpmHeader])
}

// Validations passed - Got bid
let bidResponse = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bid);
bidResponse.bidderCode = BIDDER_CODE;

bidResponse.ad = htmlString;
bidResponse.cpm = cpm;

bidResponse.width = bid.sizes[0][0];
bidResponse.height = bid.sizes[0][1];

return bidResponse;

}

// prebid api
function callBids(params) {
let bids = params.bids || [];
bids.forEach(sendBidRequest);
}

return {
callBids: callBids
};
};

module.exports = mobfoxAdapter;
173 changes: 173 additions & 0 deletions test/spec/adapters/mobfox_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
describe('mobfox adapter tests', function () {
const expect = require('chai').expect;
const utils = require('src/utils');
const adapter = require('src/adapters/mobfox');
const bidmanager = require('src/bidmanager');
const adloader = require('src/adloader');
const CONSTANTS = require('src/constants.json');
const ajax = require('src/ajax.js');
let mockResponses = {
banner: {
"request": {
"type": "textAd",
"htmlString": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title><\/title><style>body{margin:0;padding:0}#mobfoxCover{background:0 0;margin:0;padding:0;border:none;position:absolute;left:0;top:0;z-index:100}<\/style><\/head><body><div id=\"mobfoxCover\"><\/div><script type=\"text\/javascript\">function checkRedirect(e){return function(){if(state===REDIRECT){state=REDUNDANT;var t=window.document.querySelector(\"iframe\").contentDocument.querySelector(\"html\").innerHTML.toLowerCase();if(!(t.indexOf(\"<script\")<0&&t.indexOf(\"<iframe\")<0)){var o=new XMLHttpRequest,d={creativeId:creativeId,advertiserId:advertiserId,hParam:hParam,dspId:dspId,networkId:networkId,autoPilotInventoryConfId:autoPilotInventoryConfId,stackItemId:stackItemId,adSpaceId:adSpaceId,cId:cId,adomain:adomain,geo:geo,event:e,ua:window.navigator.userAgent,adId:adId,site:window.location.href,md5Hash:md5Hash,snapshot:btoa(unescape(encodeURIComponent(t)))};o.open(\"POST\",\"http:\/\/my.mobfox.com\/fraud-integration\",!1),o.setRequestHeader(\"Content-type\",\"application\/json\"),o.send(JSON.stringify(d))}}}}function init(){window.onbeforeunload=checkRedirect(\"onbeforeunload\"),window.addEventListener(\"beforeunload\",checkRedirect(\"beforeunload\")),window.addEventListener(\"unload\",checkRedirect(\"unload\")),document.addEventListener(\"visibilitychange\",function(){\"hidden\"===document.visibilityState&&checkRedirect(\"visibilityState\")});var e=document.createElement(\"iframe\");document.body.appendChild(e),e.width=\"320\",e.height=\"50\";var t=document.querySelector(\"#mobfoxCover\");t.style.width=e.width+\"px\",t.style.height=e.height+\"px\",e.style.margin=\"0px\",e.style.padding=\"0px\",e.style.border=\"none\",e.scrolling=\"no\",e.style.overflow=\"hidden\",e.sandbox=\"allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation allow-same-origin\";var o=atob(markupB64);setTimeout(function(){state=NORMAL},200),setTimeout(function(){var e=document.querySelector(\"#mobfoxCover\");document.body.removeChild(e)},200);var d=\"srcdoc\"in e,n=o;o.indexOf(\"<body>\")<0&&(n=\"<html><body style='margin:0'>\"+o+\"<\/body><\/html>\"),d?e.srcdoc=n:(e.contentWindow.document.open(),e.contentWindow.document.write(n),e.contentWindow.document.close())}var markupB64=\"PGEgaHJlZj0iaHR0cDovL3Rva3lvLW15Lm1vYmZveC5jb20vZXhjaGFuZ2UuY2xpY2sucGhwP2g9ZGI1ZjZkOTJiMDk1OGI0ZDFlNjU4ZjZlNWRkNWY0MmUiIHRhcmdldD0iX2JsYW5rIj48aW1nIHNyYz0iaHR0cHM6Ly9jcmVhdGl2ZWNkbi5tb2Jmb3guY29tL2U4ZTkxNWYzMmJhOTVkM2JmMzY4YTM5N2EyMzQ4NzVmLmdpZiIgYm9yZGVyPSIwIi8+PC9hPjxicj48aW1nIHN0eWxlPSJwb3NpdGlvbjphYnNvbHV0ZTsgbGVmdDogLTEwMDAwcHg7IiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBzcmM9Imh0dHA6Ly90b2t5by1teS5tb2Jmb3guY29tL2V4Y2hhbmdlLnBpeGVsLnBocD9oPWRiNWY2ZDkyYjA5NThiNGQxZTY1OGY2ZTVkZDVmNDJlIi8+PHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPmRvY3VtZW50LndyaXRlKCc8aW1nIHN0eWxlPSJwb3NpdGlvbjphYnNvbHV0ZTsgbGVmdDogLTEwMDAwcHg7IiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBzcmM9Imh0dHA6Ly90b2t5by1teS5tb2Jmb3guY29tL2V4Y2hhbmdlLnBpeGVsLnBocD9oPWRiNWY2ZDkyYjA5NThiNGQxZTY1OGY2ZTVkZDVmNDJlJnRlc3Q9MSIvPicpOzwvc2NyaXB0Pg==\",INITIAL=0,REDIRECT=1,REDUNDANT=2,NORMAL=3,state=INITIAL,creativeId=\"\",advertiserId=\"\",hParam=\"db5f6d92b0958b4d1e658f6e5dd5f42e\",dspId=\"\",networkId=\"\",autoPilotInventoryConfId=\"\",stackItemId=\"392746\",serverHost=\"184.172.209.50\",adSpaceId=\"\",adId=\"\",cId=\"\",adomain=\"\",geo=\"US\",md5Hash=\"f3bd183c0b19faf12c76e75461cb8cac\";document.addEventListener(\"DOMContentLoaded\",function(e){state=REDIRECT}),setTimeout(init,1)<\/script><\/body><\/html>",
"clicktype": "safari",
"clickurl": "http://tokyo-my.mobfox.com/exchange.click.php?h=db5f6d92b0958b4d1e658f6e5dd5f42e",
"urltype": "link",
"refresh": "30",
"scale": "no",
"skippreflight": "yes"
}
}
};

let mockRequestsParams = {
banner: {
rt: "api",
r_type: "banner",
i: "69.197.148.18",
s: "fe96717d9875b9da4339ea5367eff1ec",
u: "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4",
adspace_strict: 0,

// o_iosadvid: "1976F519-26D0-4428-9891-3133253A453F",
// r_floor: "0.2",
// longitude: "12.12",
// latitude: "280.12",
// demo_gender: "male",
// demo_age: "1982",
// demo_keywords: "sports",
// adspace_width: 320,
// adspace_height: 50
}
};

before(() => sinon.stub(document.body, 'appendChild'));
after(() => document.body.appendChild.restore());

let xhrMock = {
getResponseHeader: getResponseHeaderMock
};

function getResponseHeaderMock(header) {
switch (header) {
case "Content-Type":
return "application/json";
case "X-Pricing-CPM":
return "1";
}
}

function createMobfoxErrorStub() {
return sinon.stub(ajax, 'ajax', (url, callbacks) => {
callbacks.success(
JSON.stringify({error: "No Ad Available"}),
xhrMock
);
});
}


function createMobfoxSuccessStub() {
return sinon.stub(ajax, 'ajax', (url, callbacks) => {
callbacks.success(
JSON.stringify(mockResponses.banner)
, xhrMock
);
});
}

describe('test mobfox error response', function () {
let stubAddBidResponse, stubAjax;
before(function () {
stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse');
stubAjax = createMobfoxErrorStub()
});

after(function () {
stubAddBidResponse.restore();
stubAjax.restore();
});

it('should add empty bid responses if no bids returned', function () {

let bidderRequest = {
bidderCode: 'mobfox',
bids: [
{
bidId: 'bidId1',
bidder: 'mobfox',
params: {},
sizes: [[300, 250]],
placementCode: 'test-gpt-div-1234'
}
]
};

// empty ads in bidresponse
let requestParams = utils.cloneJson(mockRequestsParams.banner);
requestParams.adspace_width = 1231564; // should return an error
bidderRequest.bids[0].params = requestParams;
pbjs._bidsRequested.push(bidderRequest);
// adapter needs to be called, in order for the stub to register.
adapter().callBids(bidderRequest);

let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0];
let bidResponse1 = stubAddBidResponse.getCall(0).args[1];
expect(bidPlacementCode1).to.equal('test-gpt-div-1234');
expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID);
expect(bidResponse1.bidderCode).to.equal('mobfox');

});
});


describe('test mobfox success response', function () {

let stubAddBidResponse, stubAjax;
before(function () {
stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse');
stubAjax = createMobfoxSuccessStub()
});

after(function () {
stubAddBidResponse.restore();
stubAjax.restore();
});

it('should add a bid response', function () {

let bidderRequest = {
bidderCode: 'mobfox',
bids: [
{
bidId: 'bidId1',
bidder: 'mobfox',
params: {},
sizes: [[300, 250]],
placementCode: 'test-gpt-div-1234'
}
]
};


let requestParams = utils.cloneJson(mockRequestsParams.banner);
bidderRequest.bids[0].params = requestParams;
pbjs._bidsRequested.push(bidderRequest);
// adapter needs to be called, in order for the stub to register.
adapter().callBids(bidderRequest);

let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0];
let bidResponse1 = stubAddBidResponse.getCall(0).args[1];
expect(bidPlacementCode1).to.equal('test-gpt-div-1234');
expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD);
expect(bidResponse1.bidderCode).to.equal('mobfox');

expect(bidResponse1.cpm).to.equal(1);
expect(bidResponse1.width).to.equal(bidderRequest.bids[0].sizes[0][0]);
expect(bidResponse1.height).to.equal(bidderRequest.bids[0].sizes[0][1]);

});
});

});