repos
/ analytics-django master

analytics-django

mirror archived upstream

Self-hostable website analytics on Django: a straightforward collector API, dashboards, a world map, and PDF reports.

analyticsdjangodockerhandcodedpythonself-hostedsqliteviteweb-analytics

5.8 KB · 190 lines · JavaScript Raw History
  1/**
  2 * colector.js
  3 *
  4 * Our basic collector script that is added to all pages we want to collect.
  5 * This sends back the following basic events:
  6 *
  7 * - session_start
  8 * - page_view
  9 * - click
 10 * - scroll
 11 *
 12 * This will also send back custom events that are triggered by pushing data to
 13 * the collectorQueue with an event name and then event data that are key-value
 14 * pairs.
 15 */
 16
 17(function() {
 18  // get the collector cookie as user_id, if it doesn't exist, create it
 19  // set a collector cookie with a random value and set it to expire in a year
 20  function set_cookie(name, value, expires) {
 21    var d = new Date();
 22    d.setTime(d.getTime() + (expires * 24 * 60 * 60 * 1000));
 23    expires = "expires=" + d.toUTCString();
 24    document.cookie = name + "=" + value + "; " + expires + "; path=/";
 25  }
 26
 27  function get_cookie(name) {
 28    name = name + "=";
 29    var ca = document.cookie.split(";");
 30    for (var i = 0; i < ca.length; i++) {
 31      var c = ca[i];
 32      while (c.charAt(0) == " ") {
 33        c = c.substring(1);
 34      }
 35      if (c.indexOf(name) == 0) {
 36        return c.substring(name.length, c.length);
 37      }
 38    }
 39    return "";
 40  }
 41
 42  var collectorUserId = get_cookie("collectoruserid");
 43  if (collectorUserId === "") {
 44    collectorUserId = Math.floor(Math.random() * 1000000000);
 45    set_cookie("collectoruserid", collectorUserId, 365);
 46    window.collectorQueue.push({
 47      collector_id: window.collectorId,
 48      event: "session_start",
 49      data: {
 50        user_id: collectorUserId,
 51        url: window.location.pathname,
 52        title: document.title,
 53        referrer: document.referrer,
 54        screen_width: window.screen.width,
 55        screen_height: window.screen.height,
 56        user_agent: "userAgent" in navigator ? navigator.userAgent : "",
 57        platform: "userAgentData" in navigator ? navigator.userAgentData.platform : "",
 58        device: "userAgentData" in navigator ? navigator.userAgentData.mobile ? "Mobile" : "Desktop" : "",
 59        browser: "userAgentData" in navigator ? navigator.userAgentData.brands[navigator.userAgentData.brands.length - 1].brand : "",
 60      },
 61    });
 62  }
 63
 64  window.collectorQueue = {
 65    data: window.collectorQueue || [],
 66    post: function() {
 67      for (var i = 0; i < this.data.length; i++) {
 68        var data = this.data[i];
 69        if (!data.collectorId) {
 70          data.collectorId = window.collectorId;
 71        }
 72        if (!data.user_id) {
 73          data.user_id = collectorUserId;
 74        }
 75        fetch(window.collectorServer + "/collect/", {
 76          method: "POST",
 77          headers: {
 78            "Content-Type": "application/json",
 79          },
 80          body: JSON.stringify(data),
 81        });
 82      }
 83      this.data = [];
 84    },
 85    push: function(data) {
 86      this.data.push(data);
 87      this.post();
 88    },
 89  };
 90
 91  // parse querystring into an object
 92  function parse_querystring(querystring) {
 93    var query = {};
 94    var pairs = querystring.split("&");
 95    for (var i = 0; i < pairs.length; i++) {
 96      var pair = pairs[i].split("=");
 97      query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
 98    }
 99    return query;
100  }
101
102  const query = parse_querystring(window.location.search.substring(1));
103
104  // send a page view event
105  window.collectorQueue.push({
106    collector_id: window.collectorId,
107    event: "page_view",
108    data: {
109      user_id: collectorUserId,
110      url: window.location.pathname,
111      title: document.title,
112      referrer: document.referrer,
113      utm_source: query.utm_source,
114      utm_medium: query.utm_medium,
115      utm_campaign: query.utm_campaign,
116    },
117  });
118
119  // send click and auxclick events
120  document.addEventListener("click", function (event) {
121    window.collectorQueue.push({
122      collector_id: window.collectorId,
123      event: "click",
124      data: {
125        user_id: collectorUserId,
126        url: window.location.pathname,
127        title: document.title,
128        x: event.clientX,
129        y: event.clientY,
130        target: event.target.tagName,
131        text: event.target.textContent,
132      },
133    });
134  });
135
136  // send scroll events, but only one per second
137  var last_scroll_event = 0;
138  window.addEventListener("scroll", function () {
139    if (new Date().getTime() - last_scroll_event > 1000) {
140      window.collectorQueue.push({
141        collector_id: window.collectorId,
142        event: "scroll",
143        data: {
144          user_id: collectorUserId,
145          url: window.location.pathname,
146          title: document.title,
147        },
148      });
149      last_scroll_event = new Date().getTime();
150    }
151  });
152
153  // send page_leave events
154  // Track only *visible* time so idle/background tabs don't inflate the metric.
155  // Accumulate elapsed time in chunks bounded by visibilitychange, then flush
156  // on pagehide (more reliable than beforeunload on Safari/mobile).
157  var visible_since = document.visibilityState === "visible" ? new Date().getTime() : null;
158  var visible_accumulated = 0;
159  var page_leave_sent = false;
160
161  document.addEventListener("visibilitychange", function () {
162    var now = new Date().getTime();
163    if (document.visibilityState === "hidden" && visible_since !== null) {
164      visible_accumulated += now - visible_since;
165      visible_since = null;
166    } else if (document.visibilityState === "visible" && visible_since === null) {
167      visible_since = now;
168    }
169  });
170
171  function send_page_leave() {
172    if (page_leave_sent) return;
173    page_leave_sent = true;
174    var now = new Date().getTime();
175    var time_on_page = visible_accumulated + (visible_since !== null ? now - visible_since : 0);
176    window.collectorQueue.push({
177      collector_id: window.collectorId,
178      event: "page_leave",
179      data: {
180        user_id: collectorUserId,
181        url: window.location.pathname,
182        title: document.title,
183        time_on_page: time_on_page,
184      },
185    });
186  }
187
188  window.addEventListener("pagehide", send_page_leave);
189})();