mk9-prebid
Version:
Header Bidding Management Library
278 lines (253 loc) • 7.75 kB
JavaScript
import adapter from '../src/AnalyticsAdapter.js';
import CONSTANTS from '../src/constants.json';
import adapterManager from '../src/adapterManager.js';
import {ajaxBuilder} from '../src/ajax.js';
const utils = require('../src/utils.js');
let ajax = ajaxBuilder(0);
const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker';
const analyticsType = 'endpoint';
const QUEUE_TIMEOUT_DEFAULT = 200;
const {
EVENTS: {
AUCTION_INIT,
AUCTION_END,
BID_REQUESTED,
BID_ADJUSTMENT,
BIDDER_DONE,
BID_WON,
BID_RESPONSE,
BID_TIMEOUT
}
} = CONSTANTS;
let initOptions = {};
let auctionCache = {};
let auctionTtl = 60 * 60 * 1000;
function deleteOldAuctions() {
for (let auctionId in auctionCache) {
let auction = auctionCache[auctionId];
if (Date.now() - auction.start > auctionTtl) {
delete auctionCache[auctionId];
}
}
}
function buildAuctionEntity(args) {
return {
'id': args.auctionId,
'start': args.timestamp,
'timeout': args.timeout,
'adUnits': {},
'stats': {},
'queue': [],
'qTimeout': false
};
}
function buildAdUnit(data) {
return `/${initOptions.pubId}/${initOptions.siteId}/${data.adUnitCode.toLowerCase()}`;
}
function getLatency(data) {
if (!data.responseTimestamp) {
return -1;
} else {
return data.responseTimestamp - data.requestTimestamp;
}
}
function getBid(data) {
if (data.cpm) {
return Math.round(data.cpm * 100);
} else {
return 0;
}
}
function buildItem(data, response, phase = 1) {
let size = data.width ? {width: data.width, height: data.height} : {width: data.sizes[0][0], height: data.sizes[0][1]};
return {
'bidid': data.bidId || data.requestId,
'p': phase,
'buyerid': data.bidder.toLowerCase(),
'bid': getBid(data),
'adunit_code': buildAdUnit(data),
's': `${size.width}x${size.height}`,
'latency': getLatency(data),
'response': response,
'jsLatency': getLatency(data),
'buyername': data.bidder.toLowerCase()
};
}
function sendQueue(auctionId) {
let auction = auctionCache[auctionId];
let data = auction.queue;
auction.queue = [];
auction.qTimeout = false;
sonobiAdapter.sendData(auction, data);
}
function addToAuctionQueue(auctionId, id) {
let auction = auctionCache[auctionId];
auction.queue = auction.queue.filter((item) => {
if (item.bidid !== id) { return true; }
return auction.stats[id].data.p !== item.p;
});
auction.queue.push(utils.deepClone(auction.stats[id].data));
if (!auction.qTimeout) {
auction.qTimeout = setTimeout(() => {
sendQueue(auctionId);
}, initOptions.delay)
}
}
function updateBidStats(auctionId, id, data) {
let auction = auctionCache[auctionId];
auction.stats[id].data = {...auction.stats[id].data, ...data};
addToAuctionQueue(auctionId, id);
logInfo('Updated Bid Stats: ', auction.stats[id]);
return auction.stats[id];
}
function handleOtherEvents(eventType, args) {
logInfo('Other Event: ' + eventType, args);
}
function handlerAuctionInit(args) {
auctionCache[args.auctionId] = buildAuctionEntity(args);
deleteOldAuctions();
logInfo('Auction Init', args);
}
function handlerBidRequested(args) {
let auction = auctionCache[args.auctionId];
let data = [];
let phase = 1;
let response = 1;
args.bids.forEach(function (bidRequest) {
auction = auctionCache[bidRequest.auctionId]
let built = buildItem(bidRequest, response, phase);
auction.stats[built.bidid] = {id: built.bidid, adUnitCode: bidRequest.adUnitCode, data: built};
addToAuctionQueue(args.auctionId, built.bidid);
})
logInfo('Bids Requested ', data);
}
function handlerBidAdjustment(args) {
logInfo('Bid Adjustment', args);
}
function handlerBidderDone(args) {
logInfo('Bidder Done', args);
}
function handlerAuctionEnd(args) {
let winners = {};
args.bidsReceived.forEach((bid) => {
if (!winners[bid.adUnitCode]) {
winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm};
} else if (winners[bid.adUnitCode].cpm < bid.cpm) {
winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm};
}
})
args.adUnitCodes.forEach((adUnitCode) => {
if (winners[adUnitCode]) {
let bidId = winners[adUnitCode].bidId;
updateBidStats(args.auctionId, bidId, {response: 4});
}
})
logInfo('Auction End', args);
logInfo('Auction Cache', auctionCache[args.auctionId].stats);
}
function handlerBidWon(args) {
let {auctionId, requestId} = args;
let res = updateBidStats(auctionId, requestId, {p: 3, response: 6});
logInfo('Bid Won ', args);
logInfo('Bid Update Result: ', res);
}
function handlerBidResponse(args) {
let {auctionId, requestId, cpm, size, timeToRespond} = args;
updateBidStats(auctionId, requestId, {bid: cpm, s: size, jsLatency: timeToRespond, latency: timeToRespond, p: 2, response: 9});
logInfo('Bid Response ', args);
}
function handlerBidTimeout(args) {
let {auctionId, bidId} = args;
logInfo('Bid Timeout ', args);
updateBidStats(auctionId, bidId, {p: 2, response: 0, latency: args.timeout, jsLatency: args.timeout});
}
let sonobiAdapter = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType}), {
track({eventType, args}) {
switch (eventType) {
case AUCTION_INIT:
handlerAuctionInit(args);
break;
case BID_REQUESTED:
handlerBidRequested(args);
break;
case BID_ADJUSTMENT:
handlerBidAdjustment(args);
break;
case BIDDER_DONE:
handlerBidderDone(args);
break;
case AUCTION_END:
handlerAuctionEnd(args);
break;
case BID_WON:
handlerBidWon(args);
break;
case BID_RESPONSE:
handlerBidResponse(args);
break;
case BID_TIMEOUT:
handlerBidTimeout(args);
break;
default:
handleOtherEvents(eventType, args);
break;
}
},
});
sonobiAdapter.originEnableAnalytics = sonobiAdapter.enableAnalytics;
sonobiAdapter.enableAnalytics = function (config) {
if (this.initConfig(config)) {
logInfo('Analytics adapter enabled', initOptions);
sonobiAdapter.originEnableAnalytics(config);
}
};
sonobiAdapter.initConfig = function (config) {
let isCorrectConfig = true;
initOptions = {};
initOptions.options = utils.deepClone(config.options);
initOptions.pubId = initOptions.options.pubId || null;
initOptions.siteId = initOptions.options.siteId || null;
initOptions.delay = initOptions.options.delay || QUEUE_TIMEOUT_DEFAULT;
if (!initOptions.pubId) {
logError('"options.pubId" is empty');
isCorrectConfig = false;
}
if (!initOptions.siteId) {
logError('"options.siteId" is empty');
isCorrectConfig = false;
}
initOptions.server = DEFAULT_EVENT_URL;
initOptions.host = initOptions.options.host || window.location.hostname;
this.initOptions = initOptions;
return isCorrectConfig;
};
sonobiAdapter.getOptions = function () {
return initOptions;
};
sonobiAdapter.sendData = function (auction, data) {
let url = 'https://' + initOptions.server + '?pageviewid=' + auction.id + '&corscred=1&pubId=' + initOptions.pubId + '&siteId=' + initOptions.siteId;
ajax(
url,
function () { logInfo('Auction [' + auction.id + '] sent ', data); },
JSON.stringify(data),
{
method: 'POST',
// withCredentials: true,
contentType: 'text/plain'
}
);
}
function logInfo(message, meta) {
utils.logInfo(buildLogMessage(message), meta);
}
function logError(message) {
utils.logError(buildLogMessage(message));
}
function buildLogMessage(message) {
return 'Sonobi Prebid Analytics: ' + message;
}
adapterManager.registerAnalyticsAdapter({
adapter: sonobiAdapter,
code: 'sonobi'
});
export default sonobiAdapter;