Skip to content

Commit

Permalink
Add UT endpoint adapter (prebid#507)
Browse files Browse the repository at this point in the history
* Add UT endpoint adapter

Make one request to /ut for all adslots on page

* Build minimal request object

* Correctly format size object

* Implement inital response handler function

* Create bid based on response status

* Test response handling function

* Extend base adapter to allow for alias bidding

* Handle nobid response

* Check for required parameters

* Validate bid parameters

* Store bid reference in request-response map

* Configure XHR options

* Add NO_BID to each placement for response error

* Update adapter name

* Update endpoint to /prebid and to use protocol-relative scheme
  • Loading branch information
matthewlane authored and olafbuitelaar committed Aug 13, 2016
1 parent 575f17e commit 7a011fa
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 96 deletions.
1 change: 1 addition & 0 deletions adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"admedia",
"aol",
"appnexus",
"appnexusAst",
"fan",
"indexExchange",
"kruxlink",
Expand Down
11 changes: 3 additions & 8 deletions loaders/adapterLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,8 @@ function insertAdapters() {
}

return inserts.map(name => {
if (name === 'appnexusAst') {
return `import { AppnexusAst } from './adapters/appnexusAst';
exports.registerBidAdapter(new AppnexusAst('appnexus'), 'appnexus');\n`;
} else {
return `var ${adapterName(name)} = require('./adapters/${name}.js');
exports.registerBidAdapter(new ${adapterName(name)}${useCreateNew(name)}(), '${name}');\n`;
}
return `var ${adapterName(name)} = require('./adapters/${name}.js');
exports.registerBidAdapter(new ${adapterName(name)}${useCreateNew(name)}(), '${name}');\n`;
})
.concat(aliases.map(adapter => {
const name = Object.keys(adapter)[0];
Expand All @@ -80,7 +75,7 @@ function adapterName(adapter) {
* @returns {string}
*/
function useCreateNew(adapter) {
return adapter === 'appnexus' ? '.createNew' : '';
return ['appnexus', 'appnexusAst'].includes(adapter) ? '.createNew' : '';
}

/**
Expand Down
225 changes: 139 additions & 86 deletions src/adapters/appnexusAst.js
Original file line number Diff line number Diff line change
@@ -1,104 +1,157 @@
import { BaseAdapter } from './baseAdapter';
const utils = require('../utils');
const adloader = require('../adloader.js');
const bidmanager = require('../bidmanager');
const bidfactory = require('../bidfactory');
const CONSTANTS = require('../constants.json');

const AST_URL = 'https://acdn.adnxs.com/ast/alpha/ast.js';

export class AppnexusAst extends BaseAdapter {
constructor(code) {
super(code);
this._bidRequests = null;
}

callBids(params) {
window.apntag = window.apntag || {};
window.apntag.anq = window.apntag.anq || [];
this._bidRequests = params.bids;
adloader.loadScript(AST_URL, () => {
this._requestAds(this.code);
}, true);
}

_requestAds(code) {
if (utils.debugTurnedOn()) {
window.apntag.debug = true;
}

window.apntag.clearRequest();

for (let bidRequest of this._bidRequests) {
const astTag = this._buildTag(bidRequest);
const requestTag = window.apntag.defineTag(astTag);
const placementCode = bidRequest.placementCode;

//successful bid
/*jshint -W083 */
requestTag.on('adAvailable', function(ad) {
const bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD);
bid.code = code;
bid.bidderCode = code;
bid.creative_id = ad.creativeId;
bid.cpm = ad.cpm;
bid.ad = ad.banner.content;
try {
const url = ad.banner.trackers[0].impression_urls[0];
const tracker = utils.createTrackPixelHtml(url);
bid.ad += tracker;
} catch (e) {
utils.logError('Error appending tracking pixel', 'appnexusAst.js:_requestAds', e);
}

bid.width = ad.banner.width;
bid.height = ad.banner.height;
bidmanager.addBidResponse(placementCode, bid);
import Adapter from 'src/adapters/adapter';
import bidfactory from 'src/bidfactory';
import bidmanager from 'src/bidmanager';
import * as utils from 'src/utils';
import { ajax } from 'src/ajax';
import { STATUS } from 'src/constants';

const ENDPOINT = '//ib.adnxs.com/ut/v2/prebid';

/**
* Bidder adapter for /ut endpoint. Given the list of all ad unit tag IDs,
* sends out a bid request. When a bid response is back, registers the bid
* to Prebid.js. This adapter supports alias bidding.
*/
function AppnexusAstAdapter() {

let baseAdapter = Adapter.createNew('appnexusAst');
let adUnitCodes = {};

/* Prebid executes this function when the page asks to send out bid requests */
baseAdapter.callBids = function(bidRequest) {
const bids = bidRequest.bids || [];
const tags = bids
.filter(bid => valid(bid))
.map(bid => {
// map request id to bid object to retrieve adUnit code in callback
adUnitCodes[bid.bidId] = bid;

let tag = {};
tag.sizes = getSizes(bid.sizes);
tag.primary_size = tag.sizes[0];
tag.uuid = bid.bidId;
tag.id = Number.parseInt(bid.params.placementId);
tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false;
tag.prebid = true;
tag.disable_psa = true;

return tag;
});

//no bid
requestTag.on('adNoBid', function() {
const bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID);
bid.code = code;
bid.bidderCode = code;
bidmanager.addBidResponse(placementCode, bid);
if (!utils.isEmpty(tags)) {
const payload = JSON.stringify({tags: [...tags]});
ajax(ENDPOINT, handleResponse, payload, {
contentType: 'text/plain',
preflight: false,
});
}
};

window.apntag.loadTags();
}
/* Notify Prebid of bid responses so bids can get in the auction */
function handleResponse(response) {
let parsed;

_buildTag(bid) {
let tag = {};
const uuid = utils.getUniqueIdentifierStr();
try {
parsed = JSON.parse(response);
} catch (error) {
utils.logError(error);
}

//clone bid.params to tag
const jsonBid = JSON.stringify(bid.params);
tag = JSON.parse(jsonBid);
if (!parsed || parsed.error) {
utils.logError(`Bad response for ${baseAdapter.getBidderCode()} adapter`);

//append member if available. Should not use multiple member IDs.
utils._each(tag, function(value, key) {
if (key === 'member') {
window.apntag.setPageOpts({
member: value
// signal this response is complete
Object.keys(adUnitCodes)
.map(bidId => adUnitCodes[bidId].placementCode)
.forEach(placementCode => {
bidmanager.addBidResponse(placementCode, createBid(STATUS.NO_BID));
});
return;
}

parsed.tags.forEach(tag => {
const cpm = tag.ads && tag.ads[0].cpm;
const type = tag.ads && tag.ads[0].ad_type;

if (type !== 'banner') {
utils.logError(`${type} ad type not supported`);
}

if (key === 'keywords') {
window.apntag.setPageOpts({
keywords: value
});
let bid;
if (cpm !== 0 && type === 'banner') {
bid = createBid(STATUS.GOOD, tag);
} else {
bid = createBid(STATUS.NO_BID);
}

if (!utils.isEmpty(bid)) {
bidmanager.addBidResponse(adUnitCodes[tag.uuid].placementCode, bid);
}
});
}

tag.targetId = uuid;
tag.prebid = true;
if (!tag.sizes) {
tag.sizes = bid.sizes;
/* Check that a bid has required paramters */
function valid(bid) {
if (bid.params.placementId || bid.params.memberId && bid.params.invCode) {
return bid;
} else {
utils.logError('bid requires placementId or (memberId and invCode) params');
}
}

tag.tagId = Number.parseInt(bid.params.placementId);
tag.disablePsa = true;
return tag;
/* Turn bid request sizes into ut-compatible format */
function getSizes(requestSizes) {
let sizes = [];
let sizeObj = {};

if (utils.isArray(requestSizes) && requestSizes.length === 2 &&
!utils.isArray(requestSizes[0])) {
sizeObj.width = parseInt(requestSizes[0], 10);
sizeObj.height = parseInt(requestSizes[1], 10);
sizes.push(sizeObj);
} else if (typeof requestSizes === 'object') {
for (let i = 0; i < requestSizes.length; i++) {
let size = requestSizes[i];
sizeObj = {};
sizeObj.width = parseInt(size[0], 10);
sizeObj.height = parseInt(size[1], 10);
sizes.push(sizeObj);
}
}

return sizes;
}

/* Create and return a bid object based on status and tag */
function createBid(status, tag) {
let bid = bidfactory.createBid(status);
bid.code = baseAdapter.getBidderCode();
bid.bidderCode = baseAdapter.getBidderCode();

if (status === STATUS.GOOD) {
bid.cpm = tag.ads[0].cpm;
bid.creative_id = tag.ads[0].creativeId;
bid.width = tag.ads[0].rtb.banner.width;
bid.height = tag.ads[0].rtb.banner.height;
bid.ad = tag.ads[0].rtb.banner.content;
try {
const url = tag.ads[0].rtb.trackers[0].impression_urls[0];
const tracker = utils.createTrackPixelHtml(url);
bid.ad += tracker;
} catch (error) {
utils.logError('Error appending tracking pixel', error);
}
}

return bid;
}

return {
createNew: exports.createNew,
callBids: baseAdapter.callBids,
setBidderCode: baseAdapter.setBidderCode,
};

}

exports.createNew = () => new AppnexusAstAdapter();
7 changes: 5 additions & 2 deletions src/ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ export const ajax = function ajax(url, callback, data, options = {}) {
if (options.withCredentials) {
x.withCredentials = true;
} else {
x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
x.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
if (options.preflight !== false) {
x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
}
x.setRequestHeader('Content-Type',
options.contentType || 'application/json;charset=UTF-8');
}

//x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
Expand Down
Loading

0 comments on commit 7a011fa

Please sign in to comment.