mk9-prebid
Version:
Header Bidding Management Library
431 lines (383 loc) • 11.4 kB
JavaScript
import adapter from '../src/AnalyticsAdapter.js';
import CONSTANTS from '../src/constants.json';
import adapterManager from '../src/adapterManager.js';
import { getRefererInfo } from '../src/refererDetection.js';
import * as utils from '../src/utils.js';
import { ajax } from '../src/ajax.js';
import { getStorageManager } from '../src/storageManager.js';
const storageObj = getStorageManager();
const ANALYTICS_VERSION = '1.0.0';
const DEFAULT_QUEUE_TIMEOUT = 4000;
const DEFAULT_HOST = 'tag.staq.com';
let staqAdapterRefWin;
const STAQ_EVENTS = {
AUCTION_INIT: 'auctionInit',
BID_REQUEST: 'bidRequested',
BID_RESPONSE: 'bidResponse',
BID_WON: 'bidWon',
AUCTION_END: 'auctionEnd',
TIMEOUT: 'adapterTimedOut'
}
function buildRequestTemplate(connId) {
const url = staqAdapterRefWin.referer;
const ref = staqAdapterRefWin.referer;
const topLocation = staqAdapterRefWin.referer;
return {
ver: ANALYTICS_VERSION,
domain: topLocation.hostname,
path: topLocation.pathname,
userAgent: navigator.userAgent,
connId: connId,
env: {
screen: {
w: window.screen.width,
h: window.screen.height
},
lang: navigator.language
},
src: getUmtSource(url, ref)
}
}
let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), {
track({ eventType, args }) {
if (!analyticsAdapter.context) {
return;
}
let handler = null;
switch (eventType) {
case CONSTANTS.EVENTS.AUCTION_INIT:
if (analyticsAdapter.context.queue) {
analyticsAdapter.context.queue.init();
}
handler = trackAuctionInit;
break;
case CONSTANTS.EVENTS.BID_REQUESTED:
handler = trackBidRequest;
break;
case CONSTANTS.EVENTS.BID_RESPONSE:
handler = trackBidResponse;
break;
case CONSTANTS.EVENTS.BID_WON:
handler = trackBidWon;
break;
case CONSTANTS.EVENTS.BID_TIMEOUT:
handler = trackBidTimeout;
break;
case CONSTANTS.EVENTS.AUCTION_END:
handler = trackAuctionEnd;
break;
}
if (handler) {
let events = handler(args);
if (analyticsAdapter.context.queue) {
analyticsAdapter.context.queue.push(events);
if (eventType === CONSTANTS.EVENTS.BID_WON) {
analyticsAdapter.context.queue.updateWithWins(events);
}
}
if (eventType === CONSTANTS.EVENTS.AUCTION_END) {
sendAll();
}
}
}
});
analyticsAdapter.context = {};
analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics;
analyticsAdapter.enableAnalytics = (config) => {
utils.logInfo('Enabling STAQ Adapter');
staqAdapterRefWin = getRefererInfo(window);
if (!config.options.connId) {
utils.logError('ConnId is not defined. STAQ Analytics won\'t work');
return;
}
if (!config.options.url) {
utils.logError('URL is not defined. STAQ Analytics won\'t work');
return;
}
analyticsAdapter.context = {
host: config.options.host || DEFAULT_HOST,
url: config.options.url,
connectionId: config.options.connId,
requestTemplate: buildRequestTemplate(config.options.connId),
queue: new ExpiringQueue(sendAll, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT)
};
analyticsAdapter.originEnableAnalytics(config);
};
adapterManager.registerAnalyticsAdapter({
adapter: analyticsAdapter,
code: 'staq'
});
export default analyticsAdapter;
function sendAll() {
let events = analyticsAdapter.context.queue.popAll();
if (events.length !== 0) {
let req = analyticsAdapter.context.requestTemplate;
req.auctionId = analyticsAdapter.context.auctionId;
req.events = events
analyticsAdapter.ajaxCall(JSON.stringify(req));
}
}
analyticsAdapter.ajaxCall = function ajaxCall(data) {
utils.logInfo('SENDING DATA: ' + data);
ajax(`https://${analyticsAdapter.context.url}/prebid/${analyticsAdapter.context.connectionId}`, () => {}, data, { contentType: 'text/plain' });
};
function trackAuctionInit(args) {
analyticsAdapter.context.auctionTimeStart = Date.now();
analyticsAdapter.context.auctionId = args.auctionId;
const event = createHbEvent(args.auctionId, undefined, STAQ_EVENTS.AUCTION_INIT);
return [event];
}
function trackBidRequest(args) {
return args.bids.map(bid =>
createHbEvent(args.auctionId, args.bidderCode, STAQ_EVENTS.BID_REQUEST, bid.adUnitCode));
}
function trackBidResponse(args) {
const event = createHbEvent(args.auctionId, args.bidderCode, STAQ_EVENTS.BID_RESPONSE,
args.adUnitCode, args.cpm, args.timeToRespond / 1000, false, args);
return [event];
}
function trackBidWon(args) {
const event = createHbEvent(args.auctionId, args.bidderCode, STAQ_EVENTS.BID_WON, args.adUnitCode, args.cpm, undefined, true, args);
return [event];
}
function trackAuctionEnd(args) {
const event = createHbEvent(args.auctionId, undefined, STAQ_EVENTS.AUCTION_END, undefined,
undefined, (Date.now() - analyticsAdapter.context.auctionTimeStart) / 1000);
return [event];
}
function trackBidTimeout(args) {
return args.map(arg => createHbEvent(arg.auctionId, arg.bidderCode, STAQ_EVENTS.TIMEOUT));
}
function createHbEvent(auctionId, adapter, event, adUnitCode = undefined, value = 0, time = 0, bidWon = undefined, eventArgs) {
let ev = { event: event };
if (adapter) {
ev.adapter = adapter;
ev.bidderName = adapter;
}
if (adUnitCode) {
ev.adUnitCode = adUnitCode;
}
if (value) {
ev.cpm = value;
}
if (time) {
ev.timeToRespond = time;
}
if (typeof bidWon !== 'undefined') {
ev.bidWon = bidWon;
} else if (event === 'bidResponse') {
ev.bidWon = false;
}
ev.auctionId = auctionId;
if (eventArgs) {
if (STAQ_EVENTS.BID_RESPONSE == event || STAQ_EVENTS.BID_WON == event) {
ev.width = eventArgs.width;
ev.height = eventArgs.height;
ev.adId = eventArgs.adId;
}
}
return ev;
}
const UTM_TAGS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
'utm_c1', 'utm_c2', 'utm_c3', 'utm_c4', 'utm_c5'
];
const STAQ_PREBID_KEY = 'staq_analytics';
const DIRECT = '(direct)';
const REFERRAL = '(referral)';
const ORGANIC = '(organic)';
export let storage = {
getItem: (name) => {
return storageObj.getDataFromLocalStorage(name);
},
setItem: (name, value) => {
storageObj.setDataInLocalStorage(name, value);
}
};
export function getUmtSource(pageUrl, referrer) {
let prevUtm = getPreviousTrafficSource();
let currUtm = getCurrentTrafficSource(pageUrl, referrer);
let [updated, actual] = chooseActualUtm(prevUtm, currUtm);
if (updated) {
storeUtm(actual);
}
return actual;
function getPreviousTrafficSource() {
let val = storage.getItem(STAQ_PREBID_KEY);
if (!val) {
return getDirect();
}
return JSON.parse(val);
}
function getCurrentTrafficSource(pageUrl, referrer) {
var source = getUTM(pageUrl);
if (source) {
return source;
}
if (referrer) {
let se = getSearchEngine(referrer);
if (se) {
return asUtm(se, ORGANIC, ORGANIC);
}
let parsedUrl = utils.parseUrl(pageUrl);
let [refHost, refPath] = getReferrer(referrer);
if (refHost && refHost !== parsedUrl.hostname) {
return asUtm(refHost, REFERRAL, REFERRAL, '', refPath);
}
}
return getDirect();
}
function getSearchEngine(pageUrl) {
let engines = {
'google': /^https?\:\/\/(?:www\.)?(?:google\.(?:com?\.)?(?:com|cat|[a-z]{2})|g.cn)\//i,
'yandex': /^https?\:\/\/(?:www\.)?ya(?:ndex\.(?:com|net)?\.?(?:asia|mobi|org|[a-z]{2})?|\.ru)\//i,
'bing': /^https?\:\/\/(?:www\.)?bing\.com\//i,
'duckduckgo': /^https?\:\/\/(?:www\.)?duckduckgo\.com\//i,
'ask': /^https?\:\/\/(?:www\.)?ask\.com\//i,
'yahoo': /^https?\:\/\/(?:[-a-z]+\.)?(?:search\.)?yahoo\.com\//i
};
for (let engine in engines) {
if (engines.hasOwnProperty(engine) && engines[engine].test(pageUrl)) {
return engine;
}
}
}
function getReferrer(referrer) {
let ref = utils.parseUrl(referrer);
return [ref.hostname, ref.pathname];
}
function getUTM(pageUrl) {
let urlParameters = utils.parseUrl(pageUrl).search;
if (!urlParameters['utm_campaign'] || !urlParameters['utm_source']) {
return;
}
let utmArgs = [];
utils._each(UTM_TAGS, (utmTagName) => {
let utmValue = urlParameters[utmTagName] || '';
utmArgs.push(utmValue);
});
return asUtm.apply(this, utmArgs);
}
function getDirect() {
return asUtm(DIRECT, DIRECT, DIRECT);
}
function storeUtm(utm) {
let val = JSON.stringify(utm);
storage.setItem(STAQ_PREBID_KEY, val);
}
function asUtm(source, medium, campaign, term = '', content = '', c1 = '', c2 = '', c3 = '', c4 = '', c5 = '') {
let result = {
source: source,
medium: medium,
campaign: campaign
};
if (term) {
result.term = term;
}
if (content) {
result.content = content;
}
if (c1) {
result.c1 = c1;
}
if (c2) {
result.c2 = c2;
}
if (c3) {
result.c3 = c3;
}
if (c4) {
result.c4 = c4;
}
if (c5) {
result.c5 = c5;
}
return result;
}
function chooseActualUtm(prev, curr) {
if (ord(prev) < ord(curr)) {
return [true, curr];
}
if (ord(prev) > ord(curr)) {
return [false, prev];
} else {
if (prev.campaign === REFERRAL && prev.content !== curr.content) {
return [true, curr];
} else if (prev.campaign === ORGANIC && prev.source !== curr.source) {
return [true, curr];
} else if (isCampaignTraffic(prev) && (prev.campaign !== curr.campaign || prev.source !== curr.source)) {
return [true, curr];
}
}
return [false, prev];
}
function ord(utm) {
switch (utm.campaign) {
case DIRECT:
return 0;
case ORGANIC:
return 1;
case REFERRAL:
return 2;
default:
return 3;
}
}
function isCampaignTraffic(utm) {
return [DIRECT, REFERRAL, ORGANIC].indexOf(utm.campaign) === -1;
}
}
/**
* Expiring queue implementation. Fires callback on elapsed timeout since last last update or creation.
* @param callback
* @param ttl
* @constructor
*/
export function ExpiringQueue(callback, ttl) {
let queue = [];
let timeoutId;
this.push = (event) => {
if (event instanceof Array) {
queue.push.apply(queue, event);
} else {
queue.push(event);
}
reset();
};
this.updateWithWins = (winEvents) => {
winEvents.forEach(winEvent => {
queue.forEach(prevEvent => {
if (prevEvent.event === 'bidResponse' &&
prevEvent.auctionId == winEvent.auctionId &&
prevEvent.adUnitCode == winEvent.adUnitCode &&
prevEvent.adId == winEvent.adId &&
prevEvent.adapter == winEvent.adapter) {
prevEvent.bidWon = true;
}
});
});
}
this.popAll = () => {
let result = queue;
queue = [];
reset();
return result;
};
/**
* For test/debug purposes only
* @return {Array}
*/
this.peekAll = () => {
return queue;
};
this.init = reset;
function reset() {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
if (queue.length) {
callback();
}
}, ttl);
}
}