heartwood every commit a ring
6.0 KB raw
/**
 * colector.js
 *
 * Our basic collector script that is added to all pages we want to collect.
 * This sends back the following basic events:
 *
 * - session_start
 * - page_view
 * - click
 * - scroll
 *
 * This will also send back custom events that are triggered by pushing data to
 * the collectorQueue with an event name and then event data that are key-value
 * pairs.
 */

(function() {
  // get the collector cookie as user_id, if it doesn't exist, create it
  // set a collector cookie with a random value and set it to expire in a year
  function set_cookie(name, value, expires) {
    var d = new Date();
    d.setTime(d.getTime() + (expires * 24 * 60 * 60 * 1000));
    expires = "expires=" + d.toUTCString();
    document.cookie = name + "=" + value + "; " + expires + "; path=/";
  }

  function get_cookie(name) {
    name = name + "=";
    var ca = document.cookie.split(";");
    for (var i = 0; i < ca.length; i++) {
      var c = ca[i];
      while (c.charAt(0) == " ") {
        c = c.substring(1);
      }
      if (c.indexOf(name) == 0) {
        return c.substring(name.length, c.length);
      }
    }
    return "";
  }

  var collectorUserId = get_cookie("collectoruserid");
  if (collectorUserId === "") {
    collectorUserId = Math.floor(Math.random() * 1000000000);
    set_cookie("collectoruserid", collectorUserId, 365);
    window.collectorQueue.push({
      collectorId: window.collectorId,
      event: "session_start",
      data: {
        user_id: collectorUserId,
        url: window.location.pathname,
        title: document.title,
        referrer: document.referrer,
        screen_width: window.screen.width,
        screen_height: window.screen.height,
        user_agent: "userAgent" in navigator ? navigator.userAgent : "",
        platform: "userAgentData" in navigator ? navigator.userAgentData.platform : "",
        device: "userAgentData" in navigator ? navigator.userAgentData.mobile ? "Mobile" : "Desktop" : "",
        browser: "userAgentData" in navigator ? navigator.userAgentData.brands[navigator.userAgentData.brands.length - 1].brand : "",
      },
    });
  }

  window.collectorQueue = {
    data: window.collectorQueue || [],
    post: function() {
      for (var i = 0; i < this.data.length; i++) {
        var data = this.data[i];
        if (!data.collectorId) {
          data.collectorId = window.collectorId;
        }
        if (!data.user_id) {
          data.user_id = collectorUserId;
        }
        fetch(window.collectorServer + "/collect/", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(data),
        });
      }
      this.data = [];
    },
    push: function(data) {
      this.data.push(data);
      this.post();
    },
  };

  // parse querystring into an object
  function parse_querystring(querystring) {
    var query = {};
    var pairs = querystring.split("&");
    for (var i = 0; i < pairs.length; i++) {
      var pair = pairs[i].split("=");
      query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
    }
    return query;
  }

  const query = parse_querystring(window.location.search.substring(1));

  // send a page view event. Screen dimensions are included here too (not just
  // session_start) so the dashboard's screen-size breakdown populates for
  // returning visitors whose collectoruserid cookie suppresses session_start.
  window.collectorQueue.push({
    collectorId: window.collectorId,
    event: "page_view",
    data: {
      user_id: collectorUserId,
      url: window.location.pathname,
      title: document.title,
      referrer: document.referrer,
      utm_source: query.utm_source,
      utm_medium: query.utm_medium,
      utm_campaign: query.utm_campaign,
      screen_width: window.screen.width,
      screen_height: window.screen.height,
    },
  });

  // send click and auxclick events
  document.addEventListener("click", function (event) {
    window.collectorQueue.push({
      collectorId: window.collectorId,
      event: "click",
      data: {
        user_id: collectorUserId,
        url: window.location.pathname,
        title: document.title,
        x: event.clientX,
        y: event.clientY,
        target: event.target.tagName,
        text: event.target.textContent,
      },
    });
  });

  // send scroll events, but only one per second
  var last_scroll_event = 0;
  window.addEventListener("scroll", function () {
    if (new Date().getTime() - last_scroll_event > 1000) {
      window.collectorQueue.push({
        collectorId: window.collectorId,
        event: "scroll",
        data: {
          user_id: collectorUserId,
          url: window.location.pathname,
          title: document.title,
        },
      });
      last_scroll_event = new Date().getTime();
    }
  });

  // send page_leave events
  // Track only *visible* time so idle/background tabs don't inflate the metric.
  // Accumulate elapsed time in chunks bounded by visibilitychange, then flush
  // on pagehide (more reliable than beforeunload on Safari/mobile).
  var visible_since = document.visibilityState === "visible" ? new Date().getTime() : null;
  var visible_accumulated = 0;
  var page_leave_sent = false;

  document.addEventListener("visibilitychange", function () {
    var now = new Date().getTime();
    if (document.visibilityState === "hidden" && visible_since !== null) {
      visible_accumulated += now - visible_since;
      visible_since = null;
    } else if (document.visibilityState === "visible" && visible_since === null) {
      visible_since = now;
    }
  });

  function send_page_leave() {
    if (page_leave_sent) return;
    page_leave_sent = true;
    var now = new Date().getTime();
    var time_on_page = visible_accumulated + (visible_since !== null ? now - visible_since : 0);
    window.collectorQueue.push({
      collectorId: window.collectorId,
      event: "page_leave",
      data: {
        user_id: collectorUserId,
        url: window.location.pathname,
        title: document.title,
        time_on_page: time_on_page,
      },
    });
  }

  window.addEventListener("pagehide", send_page_leave);
})();