diff --git a/adapters.json b/adapters.json
index a49141c8345..60f53d9a074 100644
--- a/adapters.json
+++ b/adapters.json
@@ -18,6 +18,7 @@
"indexExchange",
"kruxlink",
"komoona",
+ "mantis",
"openx",
"piximedia",
"pubmatic",
diff --git a/src/adapters/mantis.js b/src/adapters/mantis.js
new file mode 100644
index 00000000000..5b4b17f1638
--- /dev/null
+++ b/src/adapters/mantis.js
@@ -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);
+};
\ No newline at end of file
diff --git a/test/spec/adapters/mantis_spec.js b/test/spec/adapters/mantis_spec.js
new file mode 100644
index 00000000000..094baad8239
--- /dev/null
+++ b/test/spec/adapters/mantis_spec.js
@@ -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: '',
+ 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('');
+ 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&');
+ });
+ });
+});
+