Skip to content

Commit

Permalink
Support for MANTIS Ad Network (prebid#911)
Browse files Browse the repository at this point in the history
* support for mantisadnetwork.com

* style fix
  • Loading branch information
parisholley authored and Will Alexander committed Mar 6, 2017
1 parent 14e89d9 commit bdbe728
Show file tree
Hide file tree
Showing 3 changed files with 390 additions and 0 deletions.
1 change: 1 addition & 0 deletions adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"indexExchange",
"kruxlink",
"komoona",
"mantis",
"openx",
"piximedia",
"pubmatic",
Expand Down
223 changes: 223 additions & 0 deletions src/adapters/mantis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
var bidfactory = require('../bidfactory.js');
var bidmanager = require('../bidmanager.js');
var adloader = require('../adloader');
var constants = require('../constants.json');

module.exports = function () {
function inIframe() {
try {
return window.self !== window.top && !window.mantis_link;
} catch (e) {
return true;
}
}

function isDesktop(ignoreTouch) {
var scope = function (win) {
var width = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth;
var supportsTouch = !ignoreTouch && ('ontouchstart' in window || navigator.msMaxTouchPoints);

return !supportsTouch && (!width || width >= (window.mantis_breakpoint || 768));
};

if (inIframe()) {
try {
return scope(window.top);
} catch (ex) {
}
}

return scope(window);
}

function isSendable(val) {
if (val === null || val === undefined) {
return false;
}

if (typeof val === 'string') {
return !(!val || /^\s*$/.test(val));
}

if (typeof val === 'number') {
return !isNaN(val);
}

return true;
}

function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}

function isAmp() {
return typeof window.context === "object" && (window.context.tagName === "AMP-AD" || window.context.tagName === "AMP-EMBED");
}

function isSecure() {
return document.location.protocol === "https:";
}

function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}

function jsonp(callback) {
if (!window.mantis_jsonp) {
window.mantis_jsonp = [];
}

window.mantis_jsonp.push(callback);

return 'mantis_jsonp[' + (window.mantis_jsonp.length - 1) + ']';
}

function jsonToQuery(data, chain, form) {
if (!data) {
return null;
}

var parts = form || [];

for (var key in data) {
var queryKey = key;

if (chain) {
queryKey = chain + '[' + key + ']';
}

var val = data[key];

if (isArray(val)) {
for (var index = 0; index < val.length; index++) {
var akey = queryKey + '[' + index + ']';
var aval = val[index];

if (isObject(aval)) {
jsonToQuery(aval, akey, parts);
} else if (isSendable(aval)) {
parts.push(akey + '=' + encodeURIComponent(aval));
}
}
} else if (isObject(val)) {
jsonToQuery(val, queryKey, parts);
} else if (isSendable(val)) {
parts.push(queryKey + '=' + encodeURIComponent(val));
}
}

return parts.join('&');
}


function buildMantisUrl(path, data, domain) {
var params = {
referrer: document.referrer,
tz: new Date().getTimezoneOffset(),
buster: new Date().getTime(),
secure: isSecure()
};

if (!inIframe() || isAmp()) {
params.mobile = !isAmp() && isDesktop(true) ? 'false' : 'true';
}

if (window.mantis_uuid) {
params.uuid = window.mantis_uuid;
} else if (window.localStorage) {
var localUuid = window.localStorage.getItem('mantis:uuid');

if (localUuid) {
params.uuid = localUuid;
}
}

if (!inIframe()) {
try {
params.title = window.top.document.title;
params.referrer = window.top.document.referrer;
params.url = window.top.document.location.href;
} catch (ex) {

}
} else {
params.iframe = true;
}

if (isAmp()) {
if (!params.url && window.context.canonicalUrl) {
params.url = window.context.canonicalUrl;
}

if (!params.url && window.context.location) {
params.url = window.context.location.href;
}

if (!params.referrer && window.context.referrer) {
params.referrer = window.context.referrer;
}
}

Object.keys(data || {}).forEach(function (key) {
params[key] = data[key];
});

var query = jsonToQuery(params);

return (window.mantis_domain === undefined ? domain || 'https://mantodea.mantisadnetwork.com' : window.mantis_domain) + path + '?' + query;
}

var Prebid = function (bidfactory, bidmanager, adloader, constants) {
return {
callBids: function (params) {
var property = null;

params.bids.some(function (bid) {
if (bid.params.property) {
property = bid.params.property;

return true;
}
});

var url = {
jsonp: jsonp(function (resp) {
params.bids.forEach(function (bid) {
var ad = resp.ads[bid.bidId];

var bidObject;

if (ad) {
bidObject = bidfactory.createBid(constants.STATUS.GOOD);
bidObject.bidderCode = 'mantis';
bidObject.cpm = ad.cpm;
bidObject.ad = ad.html;
bidObject.width = ad.width;
bidObject.height = ad.height;
} else {
bidObject = bidfactory.createBid(constants.STATUS.NO_BID);
bidObject.bidderCode = 'mantis';
}

bidmanager.addBidResponse(bid.placementCode, bidObject);
});
}),
property: property,
bids: params.bids.map(function (bid) {
return {
bidId: bid.bidId,
sizes: bid.sizes.map(function (size) {
return {width: size[0], height: size[1]};
})
};
}),
version: 1
};

adloader.loadScript(buildMantisUrl('/website/prebid', url));
}
};
};

return new Prebid(bidfactory, bidmanager, adloader, constants);
};
166 changes: 166 additions & 0 deletions test/spec/adapters/mantis_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
'use strict';

describe('mantis adapter tests', function () {
const expect = require('chai').expect;
const adapter = require('src/adapters/mantis.js');
const bidmanager = require('src/bidmanager');
const adloader = require('src/adloader');
const constants = require('src/constants.json');

var mantis, sandbox;

beforeEach(() => {
mantis = new adapter();
sandbox = sinon.sandbox.create();
});


afterEach(() => {
sandbox.restore();

delete window.context;
delete window.mantis_link;
delete window.mantis_breakpoint;
delete window.mantis_uuid;
});

var callBidExample = {
bidderCode: 'mantis',
bids: [
{
bidId: 'bidId1',
bidder: 'mantis',
placementCode: 'foo',
sizes: [[728, 90]],
params: {
property: '1234'
}
},
{
bidId: 'bidId2',
bidder: 'mantis',
placementCode: 'bar',
sizes: [[300, 600], [300, 250]],
params: {
property: '1234'
}
}
]
};

describe('callBids', () => {
it('should create appropriate bid responses', () => {
sandbox.stub(bidmanager, 'addBidResponse');
sandbox.stub(adloader, 'loadScript', function (url) {
var jsonp = eval(decodeURIComponent(url.match(/jsonp=(.*)&property/)[1]));

jsonp({
ads: {
bidId1: {
cpm: 1,
html: '<script></script>',
width: 300,
height: 600
}
}
});
});

mantis.callBids(callBidExample);

sinon.assert.calledTwice(bidmanager.addBidResponse);

expect(bidmanager.addBidResponse.firstCall.args[0]).to.eql('foo');

var bid1 = bidmanager.addBidResponse.firstCall.args[1];
expect(bid1.getStatusCode()).to.eql(constants.STATUS.GOOD);
expect(bid1.bidderCode).to.eql('mantis');
expect(bid1.cpm).to.eql(1);
expect(bid1.ad).to.eql('<script></script>');
expect(bid1.width).to.eql(300);
expect(bid1.height).to.eql(600);

expect(bidmanager.addBidResponse.secondCall.args[0]).to.eql('bar');

var bid2 = bidmanager.addBidResponse.secondCall.args[1];
expect(bid2.getStatusCode()).to.eql(constants.STATUS.NO_BID);
expect(bid2.bidderCode).to.eql('mantis');
});

it('should load script with relevant bid data', () => {
sandbox.stub(adloader, 'loadScript');

mantis.callBids(callBidExample);

sinon.assert.calledOnce(adloader.loadScript);

var serverCall = adloader.loadScript.firstCall.args[0];

expect(serverCall).to.match(/buster=[0-9]+&/);
expect(serverCall).to.match(/tz=[0-9]+&/);
expect(serverCall).to.match(/secure=(true|false)&/);
expect(serverCall).to.string('property=1234&');
expect(serverCall).to.string('bids[0][bidId]=bidId1&');
expect(serverCall).to.string('bids[0][sizes][0][width]=728&');
expect(serverCall).to.string('bids[0][sizes][0][height]=90&');
expect(serverCall).to.string('bids[1][bidId]=bidId2&');
expect(serverCall).to.string('bids[1][sizes][0][width]=300&');
expect(serverCall).to.string('bids[1][sizes][0][height]=600&');
expect(serverCall).to.string('bids[1][sizes][1][width]=300&');
expect(serverCall).to.string('bids[1][sizes][1][height]=250&');
expect(serverCall).to.string('version=1');
});

/* tests below are to just adhere to code coverage requirements, but it is already tested in our own libraries/deployment process */
it('should send uuid from window if set', () => {
sandbox.stub(adloader, 'loadScript');

window.mantis_uuid = '4321';

mantis.callBids(callBidExample);

sinon.assert.calledOnce(adloader.loadScript);

var serverCall = adloader.loadScript.firstCall.args[0];

expect(serverCall).to.string('uuid=4321&');
});

it('should send mobile = true if breakpoint is hit', () => {
sandbox.stub(adloader, 'loadScript');

window.mantis_link = true; // causes iframe detection to not work
window.mantis_breakpoint = 100000000; // force everything to be mobile

mantis.callBids(callBidExample);

sinon.assert.calledOnce(adloader.loadScript);

var serverCall = adloader.loadScript.firstCall.args[0];

expect(serverCall).to.string('mobile=true&');
});

it('should send different params if amp is detected', () => {
sandbox.stub(adloader, 'loadScript');

window.context = {
tagName: "AMP-AD",
location: {
href: 'bar',
referrer: 'baz'
}
};

mantis.callBids(callBidExample);

sinon.assert.calledOnce(adloader.loadScript);

var serverCall = adloader.loadScript.firstCall.args[0];

expect(serverCall).to.string('mobile=true&');
expect(serverCall).to.string('url=bar&');
});
});
});

0 comments on commit bdbe728

Please sign in to comment.