prebid/Prebid.js

View on GitHub
modules/fintezaAnalyticsAdapter.js

Summary

Maintainability
C
1 day
Test Coverage
import { parseUrl, logError } from '../src/utils.js';
import { ajax } from '../src/ajax.js';
import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import {getStorageManager} from '../src/storageManager.js';
import { EVENTS } from '../src/constants.js';
import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js';

const MODULE_CODE = 'finteza';
const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE});

const ANALYTICS_TYPE = 'endpoint';
const FINTEZA_HOST = 'https://content.mql5.com/tr';
const BID_REQUEST_TRACK = 'Bid Request %BIDDER%';
const BID_RESPONSE_PRICE_TRACK = 'Bid Response Price %BIDDER%';
const BID_RESPONSE_TIME_TRACK = 'Bid Response Time %BIDDER%';
const BID_TIMEOUT_TRACK = 'Bid Timeout %BIDDER%';
const BID_WON_TRACK = 'Bid Won %BIDDER%';

const FIRST_VISIT_DATE = '_fz_fvdt';
const SESSION_ID = '_fz_ssn';
const SESSION_DURATION = 30 * 60 * 1000;
const SESSION_RAND_PART = 9;
const TRACK_TIME_KEY = '_fz_tr';
const UNIQ_ID_KEY = '_fz_uniq';

function getPageInfo() {
  const pageInfo = {
    domain: window.location.hostname,
  }

  if (document.referrer) {
    pageInfo.referrerDomain = parseUrl(document.referrer).hostname;
  }

  return pageInfo;
}

function getUniqId() {
  let isUniqFromLS;
  let uniq = storage.getCookie(UNIQ_ID_KEY);
  if (!uniq) {
    try {
      if (storage.hasLocalStorage()) {
        uniq = storage.getDataFromLocalStorage(UNIQ_ID_KEY) || '';
        isUniqFromLS = true;
      }
    } catch (b) {}
  }

  if (uniq && isNaN(uniq)) {
    uniq = null;
  }

  if (uniq && isUniqFromLS) {
    let expires = new Date();
    expires.setFullYear(expires.getFullYear() + 10);

    try {
      storage.setCookie(UNIQ_ID_KEY, uniq, expires.toUTCString());
    } catch (e) {}
  }

  return uniq;
}

function initFirstVisit() {
  let now;
  let visitDate;
  let cookies;

  try {
    // TODO: commented out because of rule violations
    cookies = {} // parseCookies(document.cookie);
  } catch (a) {
    cookies = {};
  }

  visitDate = cookies[ FIRST_VISIT_DATE ];

  if (!visitDate) {
    now = new Date();

    visitDate = parseInt(now.getTime() / 1000, 10);

    now.setFullYear(now.getFullYear() + 20);

    try {
      storage.setCookie(FIRST_VISIT_DATE, visitDate, now.toUTCString());
    } catch (e) {}
  }

  return visitDate;
}
// TODO: commented out because of rule violations
/*
function trim(string) {
  if (string.trim) {
    return string.trim();
  }
  return string.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}

function parseCookies(cookie) {
  let values = {};
  let arr, item;
  let param, value;
  let i, j;

  if (!cookie || !storage.cookiesAreEnabled()) {
    return {};
  }

  arr = cookie.split(';');

  for (i = 0, j = arr.length; i < j; i++) {
    item = arr[ i ];
    if (!item) {
      continue;
    }

    param = item.split('=');
    if (param.length <= 1) {
      continue;
    }

    value = decodeURIComponent(param[0]);
    value = trim(value);

    values[value] = decodeURIComponent(param[1]);
  }

  return values;
}
*/

function getRandAsStr(digits) {
  let str = '';
  let rand = 0;
  let i;

  digits = digits || 4;

  for (i = 0; i < digits; i++) {
    rand = (Math.random() * 10) >>> 0;
    str += '' + rand;
  }

  return str;
}

function getSessionBegin(session) {
  if (!session || (typeof session !== 'string')) {
    return 0;
  }

  const len = session.length;
  if (len && len <= SESSION_RAND_PART) {
    return 0;
  }

  const timestamp = session.substring(0, len - SESSION_RAND_PART);

  return parseInt(timestamp, 10);
}

function initSession() {
  const now = new Date();
  const expires = new Date(now.getTime() + SESSION_DURATION);
  const timestamp = Math.floor(now.getTime() / 1000);
  let begin = 0;
  let cookies;
  let sessionId;
  let sessionDuration;
  let isNew = false;

  try {
    // TODO: commented out because of rule violations
    cookies = {} // parseCookies(document.cookie);
  } catch (a) {
    cookies = {};
  }

  sessionId = cookies[ SESSION_ID ];

  if (!sessionId ||
      !checkSessionByExpires() ||
      !checkSessionByReferer() ||
      !checkSessionByDay()) {
    sessionId = '' + timestamp + getRandAsStr(SESSION_RAND_PART); // lgtm [js/insecure-randomness]
    begin = timestamp;

    isNew = true;
  } else {
    begin = getSessionBegin(sessionId);
  }

  if (begin > 0) {
    sessionDuration = Math.floor(timestamp - begin);
  } else {
    sessionDuration = -1;
  }

  try {
    storage.setCookie(SESSION_ID, sessionId, expires.toUTCString());
  } catch (e) {}

  return {
    isNew: isNew,
    id: sessionId,
    duration: sessionDuration
  };
}

function checkSessionByExpires() {
  const timestamp = getTrackRequestLastTime();
  const now = new Date().getTime();

  if (now > timestamp + SESSION_DURATION) {
    return false;
  }
  return true;
}

function checkSessionByReferer() {
  const referrer = fntzAnalyticsAdapter.context.pageInfo.referrerDomain;
  const domain = fntzAnalyticsAdapter.context.pageInfo.domain;

  return referrer === '' || domain === referrer;
}

function checkSessionByDay() {
  let last = getTrackRequestLastTime();
  if (last) {
    last = new Date(last);
    const now = new Date();

    return last.getUTCDate() === now.getUTCDate() &&
      last.getUTCMonth() === now.getUTCMonth() &&
      last.getUTCFullYear() === now.getUTCFullYear();
  }

  return false;
}

function saveTrackRequestTime() {
  const now = new Date().getTime();
  const expires = new Date(now + SESSION_DURATION);

  try {
    if (storage.hasLocalStorage()) {
      storage.setDataInLocalStorage(TRACK_TIME_KEY, now.toString());
    } else {
      storage.setCookie(TRACK_TIME_KEY, now.toString(), expires.toUTCString());
    }
  } catch (a) {}
}

function getTrackRequestLastTime() {
  let cookie;

  try {
    if (storage.hasLocalStorage()) {
      return parseInt(
        storage.getDataFromLocalStorage(TRACK_TIME_KEY) || 0,
        10,
      );
    }

    // TODO: commented out because of rule violations
    cookie = {} // parseCookies(document.cookie);
    cookie = cookie[ TRACK_TIME_KEY ];
    if (cookie) {
      return parseInt(cookie, 10);
    }
  } catch (e) {}

  return 0;
}

function getAntiCacheParam() {
  const date = new Date();
  const rand = (Math.random() * 99999 + 1) >>> 0;

  return ([ date.getTime(), rand ].join(''));
}

function replaceBidder(str, bidder) {
  let _str = str;
  _str = _str.replace(/\%bidder\%/, bidder.toLowerCase());
  _str = _str.replace(/\%BIDDER\%/, bidder.toUpperCase());
  _str = _str.replace(/\%Bidder\%/, bidder.charAt(0).toUpperCase() + bidder.slice(1).toLowerCase());

  return _str;
}

function prepareBidRequestedParams(args) {
  return [{
    event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidRequestTrack, args.bidderCode)),
    ref: encodeURIComponent(window.location.href),
  }];
}

function prepareBidResponseParams(args) {
  return [{
    event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidResponsePriceTrack, args.bidderCode)),
    value: args.cpm,
    unit: 'usd'
  }, {
    event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidResponseTimeTrack, args.bidderCode)),
    value: args.timeToRespond,
    unit: 'ms'
  }];
}

function prepareBidWonParams(args) {
  return [{
    event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidWonTrack, args.bidderCode)),
    value: args.cpm,
    unit: 'usd'
  }];
}

function prepareBidTimeoutParams(args) {
  return args.map(function(bid) {
    return {
      event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidTimeoutTrack, bid.bidder)),
      value: bid.timeout,
      unit: 'ms'
    };
  })
}

function prepareTrackData(evtype, args) {
  let prepareParams = null;

  switch (evtype) {
    case EVENTS.BID_REQUESTED:
      prepareParams = prepareBidRequestedParams;
      break;
    case EVENTS.BID_RESPONSE:
      prepareParams = prepareBidResponseParams;
      break;
    case EVENTS.BID_WON:
      prepareParams = prepareBidWonParams;
      break;
    case EVENTS.BID_TIMEOUT:
      prepareParams = prepareBidTimeoutParams;
      break;
  }

  if (!prepareParams) { return null; }

  const data = prepareParams(args);

  if (!data) { return null; }

  const session = initSession();

  return data.map(d => {
    const trackData = Object.assign(d, {
      id: fntzAnalyticsAdapter.context.id,
      ref: encodeURIComponent(window.location.href),
      title: encodeURIComponent(document.title),
      scr_res: fntzAnalyticsAdapter.context.screenResolution,
      fv_date: fntzAnalyticsAdapter.context.firstVisit,
      ac: getAntiCacheParam(),
    })

    if (fntzAnalyticsAdapter.context.uniqId) {
      trackData.fz_uniq = fntzAnalyticsAdapter.context.uniqId;
    }

    if (session.id) {
      trackData.ssn = session.id;
    }
    if (session.isNew) {
      session.isNew = false;
      trackData.ssn_start = 1;
    }
    trackData.ssn_dr = session.duration;

    return trackData;
  });
}

function sendTrackRequest(trackData) {
  try {
    ajax(
      fntzAnalyticsAdapter.context.host,
      null,
      trackData,
      {
        method: 'GET',
        withCredentials: true,
        contentType: 'application/x-www-form-urlencoded'
      },
    );
    saveTrackRequestTime();
  } catch (err) {
    logError('Error on send data: ', err);
  }
}

const fntzAnalyticsAdapter = Object.assign(
  adapter({
    FINTEZA_HOST,
    ANALYTICS_TYPE
  }),
  {
    track({ eventType, args }) {
      if (typeof args !== 'undefined') {
        const trackData = prepareTrackData(eventType, args);
        if (!trackData) { return; }

        trackData.forEach(sendTrackRequest);
      }
    }
  }
);

fntzAnalyticsAdapter.originEnableAnalytics = fntzAnalyticsAdapter.enableAnalytics;

fntzAnalyticsAdapter.enableAnalytics = function (config) {
  if (!config.options.id) {
    logError('Client ID (id) option is not defined. Analytics won\'t work');
    return;
  }

  fntzAnalyticsAdapter.context = {
    host: config.options.host || FINTEZA_HOST,
    id: config.options.id,
    bidRequestTrack: config.options.bidRequestTrack || BID_REQUEST_TRACK,
    bidResponsePriceTrack: config.options.bidResponsePriceTrack || BID_RESPONSE_PRICE_TRACK,
    bidResponseTimeTrack: config.options.bidResponseTimeTrack || BID_RESPONSE_TIME_TRACK,
    bidTimeoutTrack: config.options.bidTimeoutTrack || BID_TIMEOUT_TRACK,
    bidWonTrack: config.options.bidWonTrack || BID_WON_TRACK,
    firstVisit: initFirstVisit(),
    screenResolution: `${window.screen.width}x${window.screen.height}`,
    uniqId: getUniqId(),
    pageInfo: getPageInfo(),
  };

  fntzAnalyticsAdapter.originEnableAnalytics(config);
};

adapterManager.registerAnalyticsAdapter({
  adapter: fntzAnalyticsAdapter,
  code: MODULE_CODE,
});

export default fntzAnalyticsAdapter;