prebid/Prebid.js

View on GitHub
modules/pubwiseAnalyticsAdapter.js

Summary

Maintainability
C
1 day
Test Coverage
import { getParameterByName, logInfo, generateUUID, debugTurnedOn } from '../src/utils.js';
import {ajax} from '../src/ajax.js';
import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import { EVENTS } from '../src/constants.js';
import {getStorageManager} from '../src/storageManager.js';
import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js';
const MODULE_CODE = 'pubwise';
const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE});

/****
 * PubWise.io Analytics
 * Contact: support@pubwise.io
 * Developer: Stephen Johnston
 *
 * For testing:
 *
 pbjs.enableAnalytics({
  provider: 'pubwise',
  options: {
    site: 'b1ccf317-a6fc-428d-ba69-0c9c208aa61c'
  }
 });

Changes in 4.0 Version
4.0.1 - Initial Version for Prebid 4.x, adds activationId, adds additiona testing, removes prebid global in favor of a prebid.version const
4.0.2 - Updates to include dedicated default site to keep everything from getting rate limited

*/

const analyticsType = 'endpoint';
const analyticsName = 'PubWise:';
const prebidVersion = '$prebid.version$';
let pubwiseVersion = '4.0.1';
let configOptions = {site: '', endpoint: 'https://api.pubwise.io/api/v5/event/add/', debug: null};
let pwAnalyticsEnabled = false;
let utmKeys = {utm_source: '', utm_medium: '', utm_campaign: '', utm_term: '', utm_content: ''};
let sessionData = {sessionId: '', activationId: ''};
let pwNamespace = 'pubwise';
let pwEvents = [];
let metaData = {};
let auctionEnded = false;
let sessTimeout = 60 * 30 * 1000; // 30 minutes, G Analytics default session length
let sessName = 'sess_id';
let sessTimeoutName = 'sess_timeout';

function enrichWithSessionInfo(dataBag) {
  try {
    // eslint-disable-next-line
    // console.log(sessionData);
    dataBag['session_id'] = sessionData.sessionId;
    dataBag['activation_id'] = sessionData.activationId;
  } catch (e) {
    dataBag['error_sess'] = 1;
  }

  return dataBag;
}

function enrichWithMetrics(dataBag) {
  try {
    if (window.PREBID_TIMEOUT) {
      dataBag['target_timeout'] = window.PREBID_TIMEOUT;
    } else {
      dataBag['target_timeout'] = 'NA';
    }
    dataBag['pw_version'] = pubwiseVersion;
    dataBag['pbjs_version'] = prebidVersion;
    dataBag['debug'] = configOptions.debug;
  } catch (e) {
    dataBag['error_metric'] = 1;
  }

  return dataBag;
}

function enrichWithUTM(dataBag) {
  let newUtm = false;
  try {
    for (let prop in utmKeys) {
      utmKeys[prop] = getParameterByName(prop);
      if (utmKeys[prop]) {
        newUtm = true;
        dataBag[prop] = utmKeys[prop];
      }
    }

    if (newUtm === false) {
      for (let prop in utmKeys) {
        let itemValue = storage.getDataFromLocalStorage(setNamespace(prop));
        if (itemValue !== null && typeof itemValue !== 'undefined' && itemValue.length !== 0) {
          dataBag[prop] = itemValue;
        }
      }
    } else {
      for (let prop in utmKeys) {
        storage.setDataInLocalStorage(setNamespace(prop), utmKeys[prop]);
      }
    }
  } catch (e) {
    pwInfo(`Error`, e);
    dataBag['error_utm'] = 1;
  }
  return dataBag;
}

function expireUtmData() {
  pwInfo(`Session Expiring UTM Data`);
  for (let prop in utmKeys) {
    storage.removeDataFromLocalStorage(setNamespace(prop));
  }
}

function enrichWithCustomSegments(dataBag) {
  // c_script_type: '', c_slot1: '', c_slot2: '', c_slot3: '', c_slot4: ''
  if (configOptions.custom) {
    if (configOptions.custom.c_script_type) {
      dataBag['c_script_type'] = configOptions.custom.c_script_type;
    }

    if (configOptions.custom.c_host) {
      dataBag['c_host'] = configOptions.custom.c_host;
    }

    if (configOptions.custom.c_slot1) {
      dataBag['c_slot1'] = configOptions.custom.c_slot1;
    }

    if (configOptions.custom.c_slot2) {
      dataBag['c_slot2'] = configOptions.custom.c_slot2;
    }

    if (configOptions.custom.c_slot3) {
      dataBag['c_slot3'] = configOptions.custom.c_slot3;
    }

    if (configOptions.custom.c_slot4) {
      dataBag['c_slot4'] = configOptions.custom.c_slot4;
    }
  }

  return dataBag;
}

function setNamespace(itemText) {
  return pwNamespace.concat('_' + itemText);
}

function localStorageSessTimeoutName() {
  return setNamespace(sessTimeoutName);
}

function localStorageSessName() {
  return setNamespace(sessName);
}

function extendUserSessionTimeout() {
  storage.setDataInLocalStorage(localStorageSessTimeoutName(), Date.now().toString());
}

function userSessionID() {
  return storage.getDataFromLocalStorage(localStorageSessName()) || '';
}

function sessionExpired() {
  let sessLastTime = storage.getDataFromLocalStorage(localStorageSessTimeoutName());
  return (Date.now() - parseInt(sessLastTime)) > sessTimeout;
}

function flushEvents() {
  if (pwEvents.length > 0) {
    let dataBag = {metaData: metaData, eventList: pwEvents.splice(0)}; // put all the events together with the metadata and send
    ajax(configOptions.endpoint, (result) => pwInfo(`Result`, result), JSON.stringify(dataBag));
  }
}

function isIngestedEvent(eventType) {
  const ingested = [
    EVENTS.AUCTION_INIT,
    EVENTS.BID_REQUESTED,
    EVENTS.BID_RESPONSE,
    EVENTS.BID_WON,
    EVENTS.BID_TIMEOUT,
    EVENTS.AD_RENDER_FAILED,
    EVENTS.TCF2_ENFORCEMENT
  ];
  return ingested.indexOf(eventType) !== -1;
}

function markEnabled() {
  pwInfo(`Enabled`, configOptions);
  pwAnalyticsEnabled = true;
  setInterval(flushEvents, 100);
}

function pwInfo(info, context) {
  logInfo(`${analyticsName} ` + info, context);
}

function filterBidResponse(data) {
  let modified = Object.assign({}, data);
  // clean up some properties we don't track in public version
  if (typeof modified.ad !== 'undefined') {
    modified.ad = '';
  }
  if (typeof modified.adUrl !== 'undefined') {
    modified.adUrl = '';
  }
  if (typeof modified.adserverTargeting !== 'undefined') {
    modified.adserverTargeting = '';
  }
  if (typeof modified.ts !== 'undefined') {
    modified.ts = '';
  }
  // clean up a property to make simpler
  if (typeof modified.statusMessage !== 'undefined' && modified.statusMessage === 'Bid returned empty or error response') {
    modified.statusMessage = 'eoe';
  }
  modified.auctionEnded = auctionEnded;
  return modified;
}

function filterAuctionInit(data) {
  let modified = Object.assign({}, data);

  modified.refererInfo = {};
  // handle clean referrer, we only need one
  if (typeof modified.bidderRequests !== 'undefined' && typeof modified.bidderRequests[0] !== 'undefined' && typeof modified.bidderRequests[0].refererInfo !== 'undefined') {
    // TODO: please do not send internal data structures over the network
    modified.refererInfo = modified.bidderRequests[0].refererInfo.legacy;
  }

  if (typeof modified.adUnitCodes !== 'undefined') {
    delete modified.adUnitCodes;
  }
  if (typeof modified.adUnits !== 'undefined') {
    delete modified.adUnits;
  }
  if (typeof modified.bidderRequests !== 'undefined') {
    delete modified.bidderRequests;
  }
  if (typeof modified.bidsReceived !== 'undefined') {
    delete modified.bidsReceived;
  }
  if (typeof modified.config !== 'undefined') {
    delete modified.config;
  }
  if (typeof modified.noBids !== 'undefined') {
    delete modified.noBids;
  }
  if (typeof modified.winningBids !== 'undefined') {
    delete modified.winningBids;
  }

  return modified;
}

let pubwiseAnalytics = Object.assign(adapter({analyticsType}), {
  // Override AnalyticsAdapter functions by supplying custom methods
  track({eventType, args}) {
    this.handleEvent(eventType, args);
  }
});

pubwiseAnalytics.handleEvent = function(eventType, data) {
  // we log most events, but some are information
  if (isIngestedEvent(eventType)) {
    pwInfo(`Emitting Event ${eventType} ${pwAnalyticsEnabled}`, data);

    // record metadata
    metaData = {
      target_site: configOptions.site,
      debug: configOptions.debug ? 1 : 0,
    };
    metaData = enrichWithSessionInfo(metaData);
    metaData = enrichWithMetrics(metaData);
    metaData = enrichWithUTM(metaData);
    metaData = enrichWithCustomSegments(metaData);

    // add data on init to the metadata container
    if (eventType === EVENTS.AUCTION_INIT) {
      data = filterAuctionInit(data);
    } else if (eventType === EVENTS.BID_RESPONSE) {
      data = filterBidResponse(data);
    }

    // add all ingested events
    pwEvents.push({
      eventType: eventType,
      args: data
    });
  } else {
    pwInfo(`Skipping Event ${eventType} ${pwAnalyticsEnabled}`, data);
  }

  // once the auction ends, or the event is a bid won send events
  if (eventType === EVENTS.AUCTION_END || eventType === EVENTS.BID_WON) {
    flushEvents();
  }
};

pubwiseAnalytics.storeSessionID = function (userSessID) {
  storage.setDataInLocalStorage(localStorageSessName(), userSessID);
  pwInfo(`New Session Generated`, userSessID);
};

// ensure a session exists, if not make one, always store it
pubwiseAnalytics.ensureSession = function () {
  let sessionId = userSessionID();
  if (sessionExpired() === true || sessionId === null || sessionId === '') {
    let generatedId = generateUUID();
    expireUtmData();
    this.storeSessionID(generatedId);
    sessionData.sessionId = generatedId;
  } else if (sessionId != null) {
    sessionData.sessionId = sessionId;
  }
  // eslint-disable-next-line
  // console.log('ensured session');
  extendUserSessionTimeout();
};

pubwiseAnalytics.adapterEnableAnalytics = pubwiseAnalytics.enableAnalytics;

pubwiseAnalytics.enableAnalytics = function (config) {
  configOptions = Object.assign(configOptions, config.options);
  // take the PBJS debug for our debug setting if no PW debug is defined
  if (configOptions.debug === null) {
    configOptions.debug = debugTurnedOn();
  }
  markEnabled();
  sessionData.activationId = generateUUID();
  this.ensureSession();
  pubwiseAnalytics.adapterEnableAnalytics(config);
};

adapterManager.registerAnalyticsAdapter({
  adapter: pubwiseAnalytics,
  code: MODULE_CODE,
  gvlid: 842
});

export default pubwiseAnalytics;