diff --git a/adapters.json b/adapters.json
index 6242bd55c1f..6c34196f121 100644
--- a/adapters.json
+++ b/adapters.json
@@ -37,6 +37,7 @@
"underdogmedia",
"memeglobal",
"centro",
+ "sharethrough",
"roxot",
"vertoz",
"widespace",
diff --git a/src/adapters/analytics/sharethrough_analytics.js b/src/adapters/analytics/sharethrough_analytics.js
new file mode 100644
index 00000000000..6b4f9297346
--- /dev/null
+++ b/src/adapters/analytics/sharethrough_analytics.js
@@ -0,0 +1,63 @@
+import adapter from 'AnalyticsAdapter';
+const utils = require('../../utils');
+
+const emptyUrl = '';
+const analyticsType = 'endpoint';
+const STR_BIDDER_CODE = "sharethrough";
+const STR_VERSION = "0.1.0";
+
+export default utils.extend(adapter(
+ {
+ emptyUrl,
+ analyticsType
+ }
+),
+ {
+ STR_BEACON_HOST: document.location.protocol + "//b.sharethrough.com/butler?",
+ placementCodeSet: {},
+
+ track({ eventType, args }) {
+ if(eventType === 'bidRequested' && args.bidderCode === 'sharethrough') {
+ var bids = args.bids;
+ var keys = Object.keys(bids);
+ for(var i = 0; i < keys.length; i++) {
+ this.placementCodeSet[bids[keys[i]].placementCode] = args.bids[keys[i]];
+ }
+ }
+
+ if(eventType === 'bidWon') {
+ this.bidWon(args);
+ }
+ },
+
+ bidWon(args) {
+ const curBidderCode = args.bidderCode;
+
+ if(curBidderCode !== STR_BIDDER_CODE && (args.adUnitCode in this.placementCodeSet)) {
+ let strBid = this.placementCodeSet[args.adUnitCode];
+ this.fireLoseBeacon(curBidderCode, args.cpm, strBid.adserverRequestId, "headerBidLose");
+ }
+ },
+
+ fireLoseBeacon(winningBidderCode, winningCPM, arid, type) {
+ let loseBeaconUrl = this.STR_BEACON_HOST;
+ loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, "winnerBidderCode", winningBidderCode);
+ loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, "winnerCpm", winningCPM);
+ loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, "arid", arid);
+ loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, "type", type);
+ loseBeaconUrl = this.appendEnvFields(loseBeaconUrl);
+
+ this.fireBeacon(loseBeaconUrl);
+ },
+ appendEnvFields(url) {
+ url = utils.tryAppendQueryString(url, 'hbVersion', '$prebid.version$');
+ url = utils.tryAppendQueryString(url, 'strVersion', STR_VERSION);
+ url = utils.tryAppendQueryString(url, 'hbSource', 'prebid');
+
+ return url;
+ },
+ fireBeacon(theUrl) {
+ const img = new Image();
+ img.src = theUrl;
+ }
+});
diff --git a/src/adapters/sharethrough.js b/src/adapters/sharethrough.js
new file mode 100644
index 00000000000..2732d714479
--- /dev/null
+++ b/src/adapters/sharethrough.js
@@ -0,0 +1,127 @@
+var utils = require('../utils.js');
+var bidmanager = require('../bidmanager.js');
+var bidfactory = require('../bidfactory.js');
+
+const STR_BIDDER_CODE = "sharethrough";
+const STR_VERSION = "0.1.0"; //Need to check analytics too for version
+
+var SharethroughAdapter = function SharethroughAdapter() {
+
+ const str = {};
+ str.STR_BTLR_HOST = document.location.protocol + "//btlr.sharethrough.com";
+ str.STR_BEACON_HOST = document.location.protocol + "//b.sharethrough.com/butler?";
+ str.placementCodeSet = {};
+
+ function _callBids(params) {
+ const bids = params.bids;
+
+ addEventListener("message", _receiveMessage, false);
+
+ // cycle through bids
+ for (let i = 0; i < bids.length; i += 1) {
+ const bidRequest = bids[i];
+ str.placementCodeSet[bidRequest.placementCode] = bidRequest;
+ const scriptUrl = _buildSharethroughCall(bidRequest);
+ str.loadIFrame(scriptUrl);
+ }
+ }
+
+ function _buildSharethroughCall(bid) {
+ const testPkey = 'test';
+ const pkey = utils.getBidIdParameter('pkey', bid.params);
+
+ let host = str.STR_BTLR_HOST;
+
+ let url = host + "/header-bid/v1?";
+ url = utils.tryAppendQueryString(url, 'bidId', bid.bidId);
+
+ if(pkey !== testPkey) {
+ url = utils.tryAppendQueryString(url, 'placement_key', pkey);
+ url = utils.tryAppendQueryString(url, 'ijson', '$$PREBID_GLOBAL$$.strcallback');
+ url = appendEnvFields(url);
+ } else {
+ url = url.substring(0, url.length - 1);
+ }
+
+ return url;
+ }
+
+ str.loadIFrame = function(url) {
+ const iframe = document.createElement("iframe");
+ iframe.src = url;
+ iframe.style.cssText = 'display:none;';
+
+ document.body.appendChild(iframe);
+ };
+
+ function _receiveMessage(event) {
+ if(event.origin === str.STR_BTLR_HOST) {
+ try {
+ $$PREBID_GLOBAL$$.strcallback(JSON.parse(event.data).response);
+ } catch(e) {
+ console.log(e);
+ }
+ }
+ }
+
+ $$PREBID_GLOBAL$$.strcallback = function(bidResponse) {
+ const bidId = bidResponse.bidId;
+ const bidObj = utils.getBidRequest(bidId);
+ try {
+ const bid = bidfactory.createBid(1, bidObj);
+ bid.bidderCode = STR_BIDDER_CODE;
+ bid.cpm = bidResponse.creatives[0].cpm;
+ const size = bidObj.sizes[0];
+ bid.width = size[0];
+ bid.height = size[1];
+ bid.adserverRequestId = bidResponse.adserverRequestId;
+ str.placementCodeSet[bidObj.placementCode].adserverRequestId = bidResponse.adserverRequestId;
+
+ bid.pkey = utils.getBidIdParameter('pkey', bidObj.params);
+
+ const windowLocation = `str_response_${bidId}`;
+ const bidJsonString = JSON.stringify(bidResponse);
+ bid.ad = `
+
+
+
+ `;
+ bidmanager.addBidResponse(bidObj.placementCode, bid);
+ } catch (e) {
+ _handleInvalidBid(bidObj);
+ }
+ };
+
+ function _handleInvalidBid(bidObj) {
+ const bid = bidfactory.createBid(2, bidObj);
+ bidmanager.addBidResponse(bidObj.placementCode, bid);
+ }
+
+ function appendEnvFields(url) {
+ url = utils.tryAppendQueryString(url, 'hbVersion', '$prebid.version$');
+ url = utils.tryAppendQueryString(url, 'strVersion', STR_VERSION);
+ url = utils.tryAppendQueryString(url, 'hbSource', 'prebid');
+
+ return url;
+ }
+
+ return {
+ callBids: _callBids,
+ str : str,
+ };
+};
+
+module.exports = SharethroughAdapter;
+
diff --git a/test/spec/adapters/sharethrough_spec.js b/test/spec/adapters/sharethrough_spec.js
new file mode 100644
index 00000000000..83e207e0199
--- /dev/null
+++ b/test/spec/adapters/sharethrough_spec.js
@@ -0,0 +1,178 @@
+import { expect } from 'chai';
+import Adapter from '../../../src/adapters/sharethrough';
+import bidManager from '../../../src/bidmanager';
+
+describe('sharethrough adapter', () => {
+
+ let adapter;
+ let sandbox;
+ let bidsRequestedOriginal;
+
+ const bidderRequest = {
+ bidderCode: 'sharethrough',
+ bids: [
+ {
+ bidder: 'sharethrough',
+ bidId: 'bidId1',
+ sizes: [[600,300]],
+ placementCode: 'foo',
+ params: {
+ pkey: 'aaaa1111'
+ }
+ },
+ {
+ bidder: 'sharethrough',
+ bidId: 'bidId2',
+ sizes: [[700,400]],
+ placementCode: 'bar',
+ params: {
+ pkey: 'bbbb2222'
+ }
+ }
+ ]
+ };
+
+ beforeEach(() => {
+ adapter = new Adapter();
+ sandbox = sinon.sandbox.create();
+ bidsRequestedOriginal = pbjs._bidsRequested;
+ pbjs._bidsRequested = [];
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+
+ pbjs._bidsRequested = bidsRequestedOriginal;
+ });
+
+ describe('callBids', () => {
+
+ let firstBidUrl;
+ let secondBidUrl;
+
+ beforeEach(() => {
+ sandbox.spy(adapter.str, 'loadIFrame');
+ });
+
+ it('should call loadIFrame on the adloader for each bid', () => {
+
+ adapter.callBids(bidderRequest);
+
+ firstBidUrl = adapter.str.loadIFrame.firstCall.args[0];
+ secondBidUrl = adapter.str.loadIFrame.secondCall.args[0];
+
+ sinon.assert.calledTwice(adapter.str.loadIFrame);
+
+ expect(firstBidUrl).to.contain(adapter.str.STR_BTLR_HOST + '/header-bid/v1?bidId=bidId1&placement_key=aaaa1111&ijson=pbjs.strcallback&hbVersion=%24prebid.version%24&strVersion=0.1.0&hbSource=prebid&');
+ expect(secondBidUrl).to.contain(adapter.str.STR_BTLR_HOST + '/header-bid/v1?bidId=bidId2&placement_key=bbbb2222&ijson=pbjs.strcallback&hbVersion=%24prebid.version%24&strVersion=0.1.0&hbSource=prebid&');
+ });
+
+ });
+
+ describe('strcallback', () => {
+
+ it('should exist and be a function', () => {
+ let shit = sandbox.stub(pbjs, 'strcallback');
+ expect(pbjs.strcallback).to.exist.and.to.be.a('function');
+ });
+
+ });
+
+ describe('bid requests', () => {
+
+ let firstBid;
+ let secondBid;
+
+ beforeEach(() => {
+ sandbox.stub(bidManager, 'addBidResponse');
+
+ pbjs._bidsRequested.push(bidderRequest);
+ adapter.str.placementCodeSet['foo'] = {};
+ adapter.str.placementCodeSet['bar'] = {};
+ // respond
+
+ let bidderReponse1 = {
+ "adserverRequestId": "40b6afd5-6134-4fbb-850a-bb8972a46994",
+ "bidId": "bidId1",
+ "creatives": [
+ {
+ "cpm": 12.34,
+ "auctionWinId": "b2882d5e-bf8b-44da-a91c-0c11287b8051",
+ "version": 1
+ }
+ ],
+ "stxUserId": ""
+ };
+
+ let bidderReponse2 = {
+ "adserverRequestId": "40b6afd5-6134-4fbb-850a-bb8972a46994",
+ "bidId": "bidId2",
+ "creatives": [
+ {
+ "cpm": 12.35,
+ "auctionWinId": "b2882d5e-bf8b-44da-a91c-0c11287b8051",
+ "version": 1
+ }
+ ],
+ "stxUserId": ""
+ };
+
+ pbjs.strcallback(bidderReponse1);
+ pbjs.strcallback(bidderReponse2);
+
+ firstBid = bidManager.addBidResponse.firstCall.args[1];
+ secondBid = bidManager.addBidResponse.secondCall.args[1];
+ });
+
+ it('should add a bid object for each bid', () => {
+ sinon.assert.calledTwice(bidManager.addBidResponse);
+ });
+
+ it('should pass the correct placement code as first param', () => {
+ let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0];
+ let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0];
+
+ expect(firstPlacementCode).to.eql('foo');
+ expect(secondPlacementCode).to.eql('bar');
+ });
+
+ it('should include the bid request bidId as the adId', () => {
+ expect(firstBid).to.have.property('adId', 'bidId1');
+ expect(secondBid).to.have.property('adId', 'bidId2');
+ });
+
+ it('should have a good statusCode', () => {
+ expect(firstBid.getStatusCode()).to.eql(1);
+ expect(secondBid.getStatusCode()).to.eql(1);
+ });
+
+ it('should add the CPM to the bid object', () => {
+ expect(firstBid).to.have.property('cpm', 12.34);
+ expect(secondBid).to.have.property('cpm', 12.35);
+ });
+
+ it('should add the bidder code to the bid object', () => {
+ expect(firstBid).to.have.property('bidderCode', 'sharethrough');
+ expect(secondBid).to.have.property('bidderCode', 'sharethrough');
+ });
+
+ it('should include the ad on the bid object', () => {
+ expect(firstBid).to.have.property('ad');
+ expect(secondBid).to.have.property('ad');
+ });
+
+ it('should include the size on the bid object', () => {
+ expect(firstBid).to.have.property('width', 600);
+ expect(firstBid).to.have.property('height', 300);
+ expect(secondBid).to.have.property('width', 700);
+ expect(secondBid).to.have.property('height', 400);
+ });
+
+ it('should include the pkey', () => {
+ expect(firstBid).to.have.property('pkey', 'aaaa1111');
+ expect(secondBid).to.have.property('pkey', 'bbbb2222');
+ });
+
+ });
+
+});
diff --git a/test/spec/unit/adapters/analytics/sharethrough_analytics_spec.js b/test/spec/unit/adapters/analytics/sharethrough_analytics_spec.js
new file mode 100644
index 00000000000..81a46205d88
--- /dev/null
+++ b/test/spec/unit/adapters/analytics/sharethrough_analytics_spec.js
@@ -0,0 +1,99 @@
+import sharethroughAnalytics from 'src/adapters/analytics/sharethrough_analytics';
+import { expect } from 'chai';
+
+describe('sharethrough analytics adapter', () => {
+ let sandbox;
+
+ beforeEach(() =>{
+ sandbox = sinon.sandbox.create();
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ describe('track', () => {
+
+ describe('when event type is bidRequested', () => {
+
+ beforeEach(() => {
+ let eventType = 'bidRequested';
+ let args = {"bidderCode" : "sharethrough", "bids":{"0" : {"placementCode" : "fake placement Code"}}};
+ sharethroughAnalytics.track({eventType, args})
+ });
+
+ it('placementCodeSet contains a value', () => {
+ expect(sharethroughAnalytics.placementCodeSet["fake placement Code"] == undefined).to.equal(false)
+ });
+ });
+
+ });
+
+ describe('bid won handler', () => {
+
+ let fireLoseBeaconStub;
+
+ beforeEach(() => {
+ fireLoseBeaconStub = sandbox.stub(sharethroughAnalytics, 'fireLoseBeacon');
+ });
+
+ describe('when bidderCode is not sharethrough and sharethrough is in bid', () => {
+ beforeEach(() => {
+ sharethroughAnalytics.placementCodeSet["div-gpt-ad-1460505748561-0"] = {"adserverRequestId" : "0eca470d-fcac-48e6-845a-c86483ccaa0c"}
+ var args = {
+ "bidderCode": "someoneelse",
+ "width": 600,
+ "height": 300,
+ "statusMessage": "Bid available",
+ "adId": "23fbe93a90c924",
+ "cpm": 3.984986853301525,
+ "adserverRequestId": "0eca470d-fcac-48e6-845a-c86483ccaa0c",
+ "winId": "1c404469-f7bb-4e50-b6f6-a8eaf0808999",
+ "pkey": "xKcxTTHyndFyVx7T8GKSzxPE",
+ "ad": "",
+ "requestId": "dd2420bd-cdc2-4c66-8479-f3499ece73da",
+ "responseTimestamp": 1473983655565,
+ "requestTimestamp": 1473983655458,
+ "bidder": "sharethrough",
+ "adUnitCode": "div-gpt-ad-1460505748561-0",
+ "timeToRespond": 107,
+ "pbLg": "3.50",
+ "pbMg": "3.90",
+ "pbHg": "3.98",
+ "pbAg": "3.95",
+ "pbDg": "3.95",
+ "size": "600x300",
+ "adserverTargeting": {
+ "hb_bidder": "sharethrough",
+ "hb_adid": "23fbe93a90c924",
+ "hb_pb": "3.90",
+ "hb_size": "600x300"
+ }
+ };
+
+ sharethroughAnalytics.bidWon(args);
+
+ });
+
+ it('should fire lose beacon', () => {
+ sinon.assert.calledOnce(fireLoseBeaconStub);
+ });
+
+ });
+
+ });
+
+ describe('lose beacon is fired', () => {
+
+ beforeEach(() => {
+ sandbox.stub(sharethroughAnalytics, 'fireBeacon');
+ sharethroughAnalytics.fireLoseBeacon('someoneelse', 10.0, 'arid', 'losebeacontype');
+ });
+
+ it('should call correct url', () => {
+ let winUrl = sharethroughAnalytics.fireBeacon.firstCall.args[0];
+ expect(winUrl).to.contain(sharethroughAnalytics.STR_BEACON_HOST + 'winnerBidderCode=someoneelse&winnerCpm=10&arid=arid&type=losebeacontype&hbVersion=%24prebid.version%24&strVersion=0.1.0&hbSource=prebid&');
+ });
+ });
+
+});
\ No newline at end of file