Skip to content

Commit

Permalink
New Cox adapter (#1228)
Browse files Browse the repository at this point in the history
* Cox adapter (new)

* Unit test for cox adapter (new)

* Added cox entry

* Styling for ESLint

* Styling for ESLint
  • Loading branch information
reynold-cox authored and Nate Cozi committed Jun 13, 2017
1 parent 961c826 commit 89c6300
Show file tree
Hide file tree
Showing 3 changed files with 377 additions and 0 deletions.
1 change: 1 addition & 0 deletions adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"trion",
"prebidServer",
"adsupply",
"cox",
{
"appnexus": {
"alias": "brealtime"
Expand Down
255 changes: 255 additions & 0 deletions src/adapters/cox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
var bidfactory = require('../bidfactory.js');
var bidmanager = require('../bidmanager.js');
var adLoader = require('../adloader.js');

var CoxAdapter = function CoxAdapter() {
var adZoneAttributeKeys = ['id', 'size', 'thirdPartyClickUrl'],
otherKeys = ['siteId', 'wrapper', 'referrerUrl'],
placementMap = {},
W = window;

var COX_BIDDER_CODE = 'cox';

function _callBids(params) {
var env = '';

// Create global cdsTag and CMT object (for the latter, only if needed )
W.cdsTag = {};
if (!W.CMT) W.CMT = _getCoxLite();

// Populate the tag with the info from prebid
var bids = params.bids || [],
tag = W.cdsTag,
i,
j;
for (i = 0; i < bids.length; i++) {
var bid = bids[i],
cfg = bid.params || {};

if (cfg.id) {
tag.zones = tag.zones || {};
var zone = {};

for (j = 0; j < adZoneAttributeKeys.length; j++) {
if (cfg[adZoneAttributeKeys[j]]) zone[adZoneAttributeKeys[j]] = cfg[adZoneAttributeKeys[j]];
}
for (j = 0; j < otherKeys.length; j++) {
if (cfg[otherKeys[j]]) tag[otherKeys[j]] = cfg[otherKeys[j]];
}
var adZoneKey = 'as' + cfg.id;
tag.zones[adZoneKey] = zone;

// Check for an environment setting
if (cfg.env) env = cfg.env;

// Update the placement map
var xy = (cfg.size || '0x0').split('x');
placementMap[adZoneKey] = {
p: bid.placementCode,
w: xy[0],
h: xy[1]
};
}
}
if (tag.zones && Object.keys(tag.zones).length > 0) {
tag.__callback__ = function (r) {
tag.response = r;
_notify();
};
adLoader.loadScript(W.CMT.Service.buildSrc(tag, env));
}
}

function _notify() {
// Will execute in the context of a bid
// function finalizeAd(price) {
// this.ad = W.CMT.Service.setAuctionPrice(this.ad, price);
// return this;
// }

for (var adZoneKey in placementMap) {
var bid = W.CMT.Service.getBidTrue(adZoneKey),
bidObj,
data = placementMap[adZoneKey];

if (bid > 0) {
bidObj = bidfactory.createBid(1);
bidObj.cpm = bid;
bidObj.ad = W.CMT.Service.getAd(adZoneKey);
bidObj.width = data.w;
bidObj.height = data.h;
// bidObj.floor = W.CMT.Service.getSecondPrice(adZoneKey);
// bidObj.finalizeAd = finalizeAd;
} else {
bidObj = bidfactory.createBid(2);
}
bidObj.bidderCode = COX_BIDDER_CODE;
bidmanager.addBidResponse(data.p, bidObj);
}
}

function _getCoxLite() {
var CMT = {};

CMT.Util = (function () {
return {

getRand: function getRand() {
return Math.round(Math.random() * 100000000);
},

encodeUriObject: function encodeUriObject(obj) {
return encodeURIComponent(JSON.stringify(obj));
},

extractUrlInfo: function extractUrlInfo() {
function f2(callback) {
try {
if (!W.location.ancestorOrigins) return;
for (var i = 0, len = W.location.ancestorOrigins.length; len > i; i++) {
callback.call(null, W.location.ancestorOrigins[i], i);
}
} catch (ignore) { }
return [];
}

function f1(callback) {
var oneWindow,
infoArray = [];
do {
try {
oneWindow = oneWindow ? oneWindow.parent : W;
callback.call(null, oneWindow, infoArray);
} catch (t) {
infoArray.push({
referrer: null,
location: null,
isTop: !1
});
return infoArray;
}
} while (oneWindow !== W.top);
return infoArray;
}
var allInfo = f1(function (oneWindow, infoArray) {
try {
infoArray.push({ referrer: oneWindow.document.referrer || null, location: oneWindow.location.href || null, isTop: oneWindow === W.top });
} catch (e) {
infoArray.push({ referrer: null, location: null, isTop: oneWindow === W.top });
}
});
f2(function (n, r) {
allInfo[r].ancestor = n;
});
for (var t = '', e = !1, i = allInfo.length - 1, l = allInfo.length - 1; l >= 0; l--) {
t = allInfo[l].location;
if (!t && l > 0) {
t = allInfo[l - 1].referrer;
if (!t) t = allInfo[l - 1].ancestor;
if (t) {
e = W.location.ancestorOrigins ? !0 : l === allInfo.length - 1 && allInfo[allInfo.length - 1].isTop;
break;
}
}
} return { url: t, isTop: e, depth: i };
},

srTestCapabilities: function srTestCapabilities() {
var plugins = navigator.plugins,
flashVer = -1,
sf = 'Shockwave Flash';

if (plugins && plugins.length > 0) {
if (plugins[sf + ' 2.0'] || plugins[sf]) {
var swVer2 = plugins[sf + ' 2.0'] ? ' 2.0' : '';
var flashDescription = plugins[sf + swVer2].description;
flashVer = flashDescription.split(' ')[2].split('.')[0];
}
}
if (flashVer > 4) return 15; else return 7;
}

};
}());

// Ad calling functionality
CMT.Service = (function () {
// Closure variables shared by the service functions
var U = CMT.Util;

return {

buildSrc: function buildSrc(tag, env) {
var src = (document.location.protocol === 'https:' ? 'https://' : 'http://') + (!env || env === 'PRD' ? '' : env === 'PPE' ? 'ppe-' : env === 'STG' ? 'staging-' : '') + 'ad.afy11.net/ad' + '?mode=11' + '&ct=' + U.srTestCapabilities() + '&nif=0' + '&sf=0' + '&sfd=0' + '&ynw=0' + '&rand=' + U.getRand() + '&hb=1' + '&rk1=' + U.getRand() + '&rk2=' + new Date().valueOf() / 1000;

// Make sure we don't have a response object...
delete tag.response;

// Extracted url info...
var urlInfo = U.extractUrlInfo();
tag.pageUrl = urlInfo.url;
tag.puTop = urlInfo.isTop;

// Attach the serialized tag to our string
src += '&ab=' + U.encodeUriObject(tag);

return src;
},

getAd: function (zoneKey) {
if (!zoneKey) return;

return this._getData(zoneKey, 'ad') + (this._getResponse().tpCookieSync || ''); // ...also append cookie sync if present
},

// getSecondPrice: function getSecondPrice(zoneKey) {
// if (zoneKey.substring(0, 2) !== 'as') zoneKey = 'as' + zoneKey;
// var bid = this.getBidTrue(zoneKey),
// floor = this._getData(zoneKey, 'floor');

// // If no floor, just set it to 80% of the bid
// if (!floor) floor = bid * 0.80;

// // Adjust the floor if it's too high...it needs to always be lower
// if (floor >= bid) {
// floor = floor * 0.80; // Take off 20% to account for possible non-adjusted 2nd highest bid

// // If it's still too high, just take 80% to 90% of the bid
// if (floor >= bid) floor = bid * ((Math.random() * 10) + 80) / 100;
// }
// return Math.round(floor * 100) / 100;
// },

// setAuctionPrice: function setAuctionPrice(ad, bid) {
// return ad ? ad.replace('${AUCTION_PRICE}', bid) : ad;
// },

getBidTrue: function getBidTrue(zoneKey) {
return Math.round(this._getData(zoneKey, 'price') * 100) / 100;
},

_getData: function (zoneKey, field) {
var response = this._getResponse(),
zoneResponseData = response.zones ? response.zones[zoneKey] : {};

return (zoneResponseData || {})[field] || null;
},

_getResponse: function () {
var tag = W.cdsTag;
return (tag && tag.response) ? tag.response : {};
},
};
}());

return CMT;
}

// Export the callBids function, so that prebid.js can execute this function
// when the page asks to send out bid requests.
return {
callBids: _callBids,
};
};

module.exports = CoxAdapter;
121 changes: 121 additions & 0 deletions test/spec/adapters/cox_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import Adapter from 'src/adapters/cox';
import bidManager from 'src/bidmanager';
import adLoader from 'src/adloader';
import utils from 'src/utils';
import {expect} from 'chai';

describe('CoxAdapter', () => {
let adapter;
let loadScriptStub;
let addBidResponseSpy;

let emitScript = (script) => {
let node = document.createElement('script');
node.type = 'text/javascript';
node.appendChild(document.createTextNode(script));
document.getElementsByTagName('head')[0].appendChild(node);
};

beforeEach(() => {
adapter = new Adapter();
addBidResponseSpy = sinon.spy(bidManager, 'addBidResponse');
});

afterEach(() => {
loadScriptStub.restore();
addBidResponseSpy.restore();
});

describe('response handling', () => {
const normalResponse = 'cdsTag.__callback__({"zones":{"as2000005991707":{"ad" : "<h1>FOO<\/h1>","uid" : "","price" : 1.51,"floor" : 0,}},"tpCookieSync":"<h1>FOOKIE<\/h1>"})';
const zeroPriceResponse = 'cdsTag.__callback__({"zones":{"as2000005991707":{"ad" : "<h1>DEFAULT FOO<\/h1>","uid" : "","price" : 0,"floor" : 0,}},"tpCookieSync":"<h1>FOOKIE<\/h1>"})';
const incompleteResponse = 'cdsTag.__callback__({"zones":{},"tpCookieSync":"<h1>FOOKIE<\/h1>"})';

const oneBidConfig = {
bidderCode: 'cox',
bids: [{
bidder: 'cox',
placementCode: 'FOO456789',
sizes: [300, 250],
params: { size: '300x250', id: 2000005991707, siteId: 2000100948180, env: 'PROD' },
}]
};

// ===== 1
it('should provide a correctly populated Bid given a valid response', () => {
loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(normalResponse); })

adapter.callBids(oneBidConfig);

let bid = addBidResponseSpy.args[0][1];
expect(bid.cpm).to.equal(1.51);
expect(bid.ad).to.be.a('string');
expect(bid.bidderCode).to.equal('cox');
});

// ===== 2
it('should provide an empty Bid given a zero-price response', () => {
loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(zeroPriceResponse); })

adapter.callBids(oneBidConfig);

let bid = addBidResponseSpy.args[0][1];
expect(bid.cpm).to.not.be.ok
expect(bid.ad).to.not.be.ok;
});

// ===== 3
it('should provide an empty Bid given an incomplete response', () => {
loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(incompleteResponse); })

adapter.callBids(oneBidConfig);

let bid = addBidResponseSpy.args[0][1];
expect(bid.cpm).to.not.be.ok
expect(bid.ad).to.not.be.ok;
});

// ===== 4
it('should not provide a Bid given no response', () => {
loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(''); });

adapter.callBids(oneBidConfig);

expect(addBidResponseSpy.callCount).to.equal(0);
});
});

describe('request generation', () => {
const missingBidsConfig = {
bidderCode: 'cox',
bids: null,
};
const missingParamsConfig = {
bidderCode: 'cox',
bids: [{
bidder: 'cox',
placementCode: 'FOO456789',
sizes: [300, 250],
params: null,
}]
};

// ===== 5
it('should not make an ad call given missing bids in config', () => {
loadScriptStub = sinon.stub(adLoader, 'loadScript');

adapter.callBids(missingBidsConfig);

expect(loadScriptStub.callCount).to.equal(0);
});

// ===== 6
it('should not make an ad call given missing params in config', () => {
loadScriptStub = sinon.stub(adLoader, 'loadScript');

adapter.callBids(missingParamsConfig);

expect(loadScriptStub.callCount).to.equal(0);
});
});
});

0 comments on commit 89c6300

Please sign in to comment.