Source: index.js

import { initAnalyticsProxy } from "./analyticsProxy.js";
import { initClearbitReveal } from "./clearbitReveal.js";
import { initLogRocket } from "./logrocket.js";
import { initMntn } from "./mntn.js";
import { initRedditPixel } from "./reddit.js";
import { initSegment } from "./segment.js";
import { initSixSense } from "./sixSense.js";
import { sendSegmentEvents } from "./sixSenseCallbacks.js";
import { initUtmMediumHash } from "./utmMediumHash.js";
import { initVwo } from "./vwo.js";

export { track } from "./track.js";
export { identify } from "./identify.js";
export { personalizePage } from "./personalizePage.js";
export { fetchOutreachAccountCustomFields } from "./fetchOutreachAccountCustomField.js";
export { INDUSTRY_LOOKUP, EMPLOYEE_SIZE_LOOKUP } from "./sixSenseAttributes.js";
export { mntnConversion } from "./mntn.js";

// Helper function to throw when undefined arguments are given when a paramter is required
function requiredParam(argName) {
  throw new Error(`${argName} is required`);
}

// Helper function to evaluate a flag
// A flag can either be a boolean or a function returning a boolean
function evaluateFlag(obj, prop) {
  const value = obj[prop];

  // If the value is undefined or null, treat it as false
  if (value === undefined || value === null) {
    return false;
  }

  // If the value is a boolean, return it.
  if (typeof value === "boolean") {
    return value;
  }

  // If it is a function, evaluate it.
  if (typeof value === "function") {
    try {
      return !!value();
    } catch (err) {
      // Evaluating the flag should not stop other code from executing if it throws, so swallow the error.
      // Print it out for debugging purposes.
      console.error(
        `[lr-analytics] evaluating ${prop} as a function threw an error`,
        err,
      );
      return false;
    }
  }

  throw new Error(`flag ${value} is neither a boolean nor a function`);
}

/**
 * Functions to be used with the 6sense company API response
 */
export const sixSenseCallbacks = {
  sendSegmentEvents,
};

export const __helper = {
  evaluateFlag,
};

/**
 * Initializes analytics tools and trackers.
 *
 * The options object given to this function is a a blueprint for what trackers to enable.
 *
 *
 * @example
 * window.lr_analytics.init({
 *   analyticsProxy : { enabled: true },
 *   segment: {
 *     enabled: true,
 *     writeKey: 'abc',
 *     load: true,
 *   },
 *   sixSense: {
 *     enabled: true,
 *     apiKey: 'xyz',
 *   }
 * });
 *
 * @param {Object} options - Options object specifying what analytics/trackers to set up.
 * @param {Object} [options.analyticsProxy] - Options for setting up the analytics proxy.
 * @param {(boolean|function)} options.analyticsProxy.enabled - Enable the analytics proxy
 * @param {string[]} [options.analyticsProxy.hostnames] - Additional hostnames to proxy
 * @param {string[]} [options.analyticsProxy.wordsToMask] - Additional words to mask
 * @param {Object} [options.segment] - Options for seting up segment snippet
 * @param {(boolean|function)} options.segment.enabled - Enable segment
 * @param {string} [options.segment.writeKey] - The segment api key
 * @param {boolean} [options.segment.load] - Makes the `window.analytics.load()` after executing the snippet. Defaults to false.
 * @param {Object} [options.sixSense] -- Options for setting up 6sense & 6signal snippet
 * @param {(boolean|function)} options.sixSense.enabled - Enable 6sense
 * @param {string} options.sixSense.apiKey - 6sense api key
 * @param {boolean} [options.sixSense.qa] - Send data to the QA/test endpoint instead of production
 * @param {Object} [options.sixSense.companyDetails] - Options relating to the 6sense company details API
 * @param {(boolean|function)} options.sixSense.companyDetails.enabled - Enable the 6sense company details API
 * @param {string} options.sixSense.companyDetails.apiKey - The company details API api key
 * @param {SixSenseCompanyApiCallback} options.sixSense.companyDetails.callback - function to run after we receive a response from the 6sense company details API
 * @param {Object} [options.vwo] - Options for setting up the vwo snippet
 * @param {(boolean|function)} options.vwo.enabled - Enable vwo
 * @param {number} options.vwo.accountId - VWO account ID
 * @param {Object} [options.redditPixel] - Options for setting up the reddit pixel snippet
 * @param {(boolean|function)} options.redditPixel.enabled - Enable the redit pixel
 * @param {string} options.redditPixel.accountId - Reddit adds advertiser id prefixed by
 * @param {Object} [options.clearbitReveal] - Options for setting up Clearbit reveal
 * @param {(boolean|function)} options.clearbitReveal.enabled - Enable clearbit reveal
 * @param {string} options.clearbitReveal.apiKey - Public api key for clearbit reveal. starts with pk_
 * @param {function} options.clearbitReveal.callback - Function to be called with result of clearbit reveal
 * @param {Object} [options.utmMediumHash] - Options for utm_medium URL hash
 * @param {(boolean|function)} options.utmMediumHash.enabled - Enable putting the utm_medium query param in the hash
 * @param {Object} [options.logrocket] - Options for setting up the logrocket snippet
 * @param {(boolean|function)} options.logrocket.enabled - Enable LogRocket
 * @param {boolean} options.logrocket.callInit - Makes the `LogRocket.init()` call once the script has been loaded.
 * @param {string} options.logrocket.appSlug - LogRocket appslug. Will be passed to `LogRocket.init()` if it is called
 * @param {Object} options.logrocket.initOptions - Options to pass as-is to `LogRocket.Init()`
 * @param {Object} [options.mntn] - Options for setting up the MNTN snippet
 * @param {(boolean|function)} options.mntn.enabled - Enable MNTN
 */
export function init({
  analyticsProxy = {},
  segment = {},
  sixSense = {},
  vwo = {},
  redditPixel = {},
  clearbitReveal = {},
  utmMediumHash = {},
  logrocket = {},
  mntn = {},
}) {
  if (evaluateFlag(analyticsProxy, "enabled")) {
    const { hostnames = [], wordsToMask = [] } = analyticsProxy;

    const defaultHostnames = [
      "cdn.segment.com",
      "cdn.heapanalytics.com",
      "marketo.net",
      "api.segment.io",
      "heapanalytics.com",
      "visualwebsiteoptimizer.com",
      "mktoresp.com",
      "intercom.io",
      "mountain.com",
    ];

    const defaultWordsToMask = [
      "visitWebPage",
      "clickLink",
      "associateLead",
      "analytics",
      "heap",
      ".gif",
    ];
    initAnalyticsProxy({
      hostnames: [...defaultHostnames, ...hostnames],
      wordsToMask: [...defaultWordsToMask, ...wordsToMask],
    });
  }

  if (evaluateFlag(segment, "enabled")) {
    // It is valid to have an undefined write key. When this happens, the
    // segment snippet will be executed, but the segment library won't be
    // loaded
    const { writeKey, load = false } = segment;

    initSegment({ writeKey, load });
  }

  if (evaluateFlag(sixSense, "enabled")) {
    const {
      apiKey = requiredParam("sixSense.apiKey"),
      companyDetails = {},
      qa = false,
    } = sixSense;
    let options = { apiKey, qa };

    if (evaluateFlag(companyDetails, "enabled")) {
      const {
        apiKey: cdApiKey = requiredParam("sixSense.companyDetails.apiKey"),
        callback = requiredParam("sixSense.companyDetails.callback"),
      } = companyDetails;
      options = { ...options, companyDetails: { apiKey: cdApiKey, callback } };
    }

    initSixSense(options);
  }

  if (evaluateFlag(logrocket, "enabled")) {
    const { callInit = false, appSlug = "", initOptions = {} } = logrocket;
    initLogRocket({ callInit, appSlug, initOptions });
  }

  if (evaluateFlag(vwo, "enabled")) {
    const { accountId = requiredParam("vwo.account_id") } = vwo;

    initVwo({ accountId });
  }

  if (evaluateFlag(redditPixel, "enabled")) {
    const { accountId = requiredParam("redditPixel.account_id") } = redditPixel;

    initRedditPixel({ accountId });
  }

  if (evaluateFlag(clearbitReveal, "enabled")) {
    const { apiKey, callback } = clearbitReveal;

    initClearbitReveal({ apiKey, callback });
  }

  if (evaluateFlag(utmMediumHash, "enabled")) {
    initUtmMediumHash();
  }

  if (evaluateFlag(mntn, "enabled")) {
    initMntn();
  }
}

/**
 * The expected shape of a function that accepts the result of the 6sense company details API call.
 * @callback SixSenseCompanyApiCallback
 * @param {string} sixSenseResponse - The result of the 6sense company API call. Can be parsed using `JSON.parse`
 */