GithubHelp home page GithubHelp logo

Comments (5)

adriaanvanrossum avatar adriaanvanrossum commented on June 6, 2024

Thank you for this!

I just updated our code (not deployed yet). Would you mind testing this?

/* Simple Analytics - Privacy friendly analytics (docs.simpleanalytics.com/script; 2020-01-20; 69b5) */
!function(c){if(c){function u(e){i&&i.warn&&i.warn("Simple Analytics: "+e)}function e(e,t){return e&&e.getAttribute("data-"+t)}var n,r,t="https:",a=t+"//",d=a+"queue.simpleanalyticscdn.com/v2/post",i=c.console,o="doNotTrack",h="pageviews",l="events",s=c.navigator,p=s.userAgent,f=c.location,m=c.document,v=f.hostname,g="Not sending requests ",y="localhost",w=c.addEventListener,E="pushState",T=c.dispatchEvent,b=Date.now(),q=0,S=m.querySelector('script[src="'+a+'scripts.simpleanalyticscdn.com/latest.js"]'),x=e(S,"mode"),D="true"===e(S,"skip-dnt"),M=e(S,"sa-global")||"sa";if(!D&&o in s&&"1"===s[o])return u(g+"when "+o+" is enabled");if(v===y||"file:"===f.protocol)return u(g+"from "+y);if(!p||-1<p.search(/(bot|spider|crawl)/gi))return u(g+"because bot detected");try{function B(e){var t=f.search.match(new RegExp("[?&]("+e+")=([^?&]+)","gi")),n=t?t.map(function(e){return e.split("=")[1]}):[];if(n&&n[0])return n[0]}var O;try{O=Intl.DateTimeFormat().resolvedOptions().timeZone}catch(Q){}var R="(utm_)?",$={version:2,hostname:v,https:f.protocol===t,timezone:O,width:c.innerWidth,source:{source:B(R+"source|source|ref"),medium:B(R+"medium"),campaign:B(R+"campaign"),referrer:(m.referrer||"").replace(/^https?:\/\/((m|l|w{2,3}([0-9]+)?)\.)?([^?#]+)(.*)$/,"$4").replace(/^([^/]+)\/$/,"$1")||undefined},pageviews:[]},k=0,A=null;c.addEventListener("visibilitychange",function(){m.hidden?A=Date.now():k+=Date.now()-A},!1);function C(e){return Math.round((Date.now()-(e||0))/1e3)}var H,I="sendBeacon",L=JSON.stringify,N=I in s&&!1===/ip(hone|ad)(.*)os\s([1-9]|1[0-2])_/i.test(p);N&&w("unload",function(){var e=$[h][$[h].length-1];e.duration=C(b+k),k=0;var t=Math.max(0,q,z());t&&(e.scrolled=t),$.time=C(),s[I](d,L($))},!1);var _="scroll",F=m.body,U=m.documentElement,z=function(){var e="Height",t=_+e,n="offset"+e,r="client"+e,a=Math.max(F[t],F[n],U[r],U[t],U[n]);return Math.min(100,5*Math.round(100*(U.scrollTop+U[r])/a/5))};w("load",function(){q=z(),w(_,function(){q<z()&&(q=z())},!1)});function J(e,t,n){var r=$[h],a=r?r.length:0,i=a?r[a-1]:null;if(e===l){var o=""+t;if(i?i[l]?i[l].push(o):i[l]=[o]:N&&u("Couldn't save event '"+o+"'"),N)return;delete $[h],$[l]=[o]}else if(a&&(i.duration=C(b+k),i.scrolled=q),r.push(t),N)return b=Date.now(),k=0,void(q=c.setTimeout(z,500));var s=new XMLHttpRequest;s.open("POST",d,!0),n&&delete $.source,$.time=C(),s.setRequestHeader("Content-Type","text/plain; charset=UTF-8"),s.send(L($)),delete $[h],delete $[l]}function P(e){var t=f.pathname;if("hash"===x&&f.hash&&(t+=f.hash.split("?")[0]),H!==t){var n={path:H=t,added:C()};try{var r=c.performance,a="navigation",i=r&&r.getEntriesByType&&r.getEntriesByType(a)[0]&&r.getEntriesByType(a)[0].type?-1<["reload","back_forward"].indexOf(r.getEntriesByType(a)[0].type):r&&r[a]&&-1<[1,2].indexOf(r[a].type);n.unique=!e&&!i&&(!m.referrer||m.referrer.split("/")[2]!==v)}catch(o){n.error=o.message}J(h,n,e)}}var W=c.history;if((W?W.pushState:null)&&Event&&T){W.pushState=(r=W[n=E],function(){var e=r.apply(this,arguments),t=new Event(n);return t.arguments=arguments,T(t),e}),w(E,function(){P(1)},!1),w("popstate",function(){P(1)},!1)}"hash"===x&&"onhashchange"in c&&w("hashchange",function(){P(1)},!1),P();function X(e){J(l,e)}c[M]||(c[M]=X);var Z=c[M],j=Z&&Z.q?Z.q:[];for(var G in c[M]=X,j)J(l,j[G])}catch(Q){u(Q.message);var K=d+"image.gif";Q.message&&(K=K+"?error="+encodeURIComponent(Q.message)),(new Image).src=K}}}(window);

You might need to replace this URL with the URL + path where the script is hosted:

scripts.simpleanalyticscdn.com/latest.js

for it to pickup the hash mode setting.

from scripts.

adriaanvanrossum avatar adriaanvanrossum commented on June 6, 2024

I created a version for you with the mode set to hash:

Custom script with mode set to `hash`
(function(window, version, script, endpoint) {
  if (!window) return;

  // Set urls outside try block because they are needed in the catch block
  var https = "https:";
  var protocol = https + "//";
  var apiUrl = protocol + endpoint;
  var con = window.console;
  var doNotTrack = "doNotTrack";
  var pageviews = "pageviews";
  var events = "events";
  var slash = "/";
  var nav = window.navigator;
  var userAgent = nav.userAgent;
  var loc = window.location;
  var doc = window.document;
  var hostname = loc.hostname;
  var notSending = "Not sending requests ";
  var localhost = "localhost";
  var thousand = 1000;
  var addEventListenerFunc = window.addEventListener;

  /** if spa **/
  var pushState = "pushState";
  var dis = window.dispatchEvent;
  /** endif **/

  /** if duration **/
  var duration = "duration";
  var start = Date.now();
  /** endif **/

  /** if scroll **/
  var scrolled = 0;
  /** endif **/

  // A simple log function so the user knows why a request is not being send
  var warn = function(message) {
    if (con && con.warn) con.warn("Simple Analytics: " + message);
  };

  var scriptElement = doc.querySelector(
    'script[src="' + protocol + script + '"]'
  );
  var attr = function(scriptElement, attribute) {
    return scriptElement && scriptElement.getAttribute("data-" + attribute);
  };

  var mode = "hash"; // attr(scriptElement, "mode");
  var skipDNT = attr(scriptElement, "skip-dnt") === "true";
  var functionName = attr(scriptElement, "sa-global") || "sa";

  // Don't track when Do Not Track is set to true
  if (!skipDNT && doNotTrack in nav && nav[doNotTrack] === "1")
    return warn(notSending + "when " + doNotTrack + " is enabled");

  // Don't track when localhost
  if (hostname === localhost || loc.protocol === "file:")
    return warn(notSending + "from " + localhost);

  // We do advanced bot detection in our API, but this line filters already most bots
  if (!userAgent || userAgent.search(/(bot|spider|crawl)/gi) > -1)
    return warn(notSending + "because bot detected");

  try {
    var getParams = function(regex) {
      // From the search we grab the utm_source and ref and save only that
      var matches = loc.search.match(
        new RegExp("[?&](" + regex + ")=([^?&]+)", "gi")
      );
      var match = matches
        ? matches.map(function(m) {
            return m.split("=")[1];
          })
        : [];
      if (match && match[0]) return match[0];
    };

    // This code could error on not having resolvedOptions in the Android Webview, that's why we use try...catch
    var timezone;
    try {
      timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
    } catch (e) {
      /* Do nothing */
    }

    // We don't want to end up with sensitive data so we clean the referrer URL
    var utmRegexPrefix = "(utm_)?";
    var payload = {
      version: version,
      hostname: hostname,
      https: loc.protocol === https,
      timezone: timezone,
      width: window.innerWidth,
      source: {
        source: getParams(utmRegexPrefix + "source|source|ref"),
        medium: getParams(utmRegexPrefix + "medium"),
        campaign: getParams(utmRegexPrefix + "campaign"),
        referrer:
          (doc.referrer || "")
            .replace(
              /^https?:\/\/((m|l|w{2,3}([0-9]+)?)\.)?([^?#]+)(.*)$/,
              "$4"
            )
            .replace(/^([^/]+)\/$/, "$1") || undefined
      },
      pageviews: []
    };

    // We don't put msHidden in if duration block, because it's used outside of that functionality
    var msHidden = 0;

    /** if duration **/
    var hiddenStart = null;
    window.addEventListener(
      "visibilitychange",
      function() {
        if (doc.hidden) hiddenStart = Date.now();
        else msHidden += Date.now() - hiddenStart;
      },
      false
    );
    /** endif **/

    var seconds = function(since) {
      return Math.round((Date.now() - (since || 0)) / thousand);
    };

    var sendBeacon = "sendBeacon";
    var stringify = JSON.stringify;
    var lastSendPath;

    // Safari on iOS < 13 has some issues with the Beacon API
    var useSendBeacon =
      sendBeacon in nav &&
      /ip(hone|ad)(.*)os\s([1-9]|1[0-2])_/i.test(userAgent) === false;

    if (useSendBeacon)
      addEventListenerFunc(
        "unload",
        function() {
          var last = payload[pageviews][payload[pageviews].length - 1];

          /** if duration **/
          last[duration] = seconds(start + msHidden);
          msHidden = 0;
          /** endif **/

          /** if scroll **/
          var currentScroll = Math.max(0, scrolled, position());
          if (currentScroll) last.scrolled = currentScroll;
          /** endif **/

          payload.time = seconds();

          nav[sendBeacon](apiUrl, stringify(payload));
        },
        false
      );

    /** if scroll **/
    var scroll = "scroll";
    var body = doc.body;
    var documentElement = doc.documentElement;
    var position = function() {
      var Height = "Height";
      var scrollHeight = scroll + Height;
      var offsetHeight = "offset" + Height;
      var clientHeight = "client" + Height;
      var height = Math.max(
        body[scrollHeight],
        body[offsetHeight],
        documentElement[clientHeight],
        documentElement[scrollHeight],
        documentElement[offsetHeight]
      );
      return Math.min(
        100,
        Math.round(
          (100 * (documentElement.scrollTop + documentElement[clientHeight])) /
            height /
            5
        ) * 5
      );
    };

    addEventListenerFunc("load", function() {
      scrolled = position();
      addEventListenerFunc(
        scroll,
        function() {
          if (scrolled < position()) scrolled = position();
        },
        false
      );
    });
    /** endif **/

    var post = function(type, data, isPushState) {
      var payloadPageviews = payload[pageviews];
      var payloadPageviewsLength = payloadPageviews
        ? payloadPageviews.length
        : 0;
      var payloadPageviewLast = payloadPageviewsLength
        ? payloadPageviews[payloadPageviewsLength - 1]
        : null;

      if (type === events) {
        /** if events **/
        var event = "" + data;
        if (payloadPageviewLast) {
          if (payloadPageviewLast[events])
            payloadPageviewLast[events].push(event);
          else payloadPageviewLast[events] = [event];
        } else if (useSendBeacon) {
          warn("Couldn't save event '" + event + "'");
        }

        if (useSendBeacon) return;
        else {
          delete payload[pageviews];
          payload[events] = [event];
        }
        /** endif **/
      } else {
        // Continue when type is pageviews
        if (payloadPageviewsLength) {
          /** if duration **/
          payloadPageviewLast.duration = seconds(start + msHidden);
          /** endif **/

          /** if scroll **/
          payloadPageviewLast.scrolled = scrolled;
          /** endif **/
        }
        payloadPageviews.push(data);

        if (useSendBeacon) {
          /** if duration **/
          start = Date.now();
          msHidden = 0;
          /** endif **/

          /** if scroll **/
          scrolled = window.setTimeout(position, 500);
          /** endif **/

          return;
        }
      }

      var request = new XMLHttpRequest();
      request.open("POST", apiUrl, true);

      if (isPushState) {
        delete payload.source;
      }

      payload.time = seconds();

      // We use content type text/plain here because we don't want to send an
      // pre-flight OPTIONS request
      request.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
      request.send(stringify(payload));

      delete payload[pageviews];
      delete payload[events];
    };

    var pageview = function(isPushState) {
      // Obfuscate personal data in URL by dropping the search and hash
      var path = loc.pathname;

      /** if hash **/
      // Add hash to path when script is put in to hash mode
      if (mode === "hash" && loc.hash) path += loc.hash.split("?")[0];
      /** endif **/

      // Don't send the last path again (this could happen when pushState is used to change the path hash or search)
      if (lastSendPath === path) return;

      lastSendPath = path;

      var data = {
        path: path,
        added: seconds()
      };

      /** if uniques **/
      // We put new code always in a try block to prevent huge issues
      try {
        var perf = window.performance;
        var navigation = "navigation";
        // Check if back, forward or reload buttons are being use in modern browsers
        var back =
          perf &&
          perf.getEntriesByType &&
          perf.getEntriesByType(navigation)[0] &&
          perf.getEntriesByType(navigation)[0].type
            ? ["reload", "back_forward"].indexOf(
                perf.getEntriesByType(navigation)[0].type
              ) > -1
            : // Check if back, forward or reload buttons are being use in older browsers
              // 1: TYPE_RELOAD, 2: TYPE_BACK_FORWARD
              perf &&
              perf[navigation] &&
              [1, 2].indexOf(perf[navigation].type) > -1;

        // We set unique variable based on pushstate or back navigation, if no match we check the referrer
        data.unique =
          isPushState || back
            ? false
            : doc.referrer
            ? doc.referrer.split(slash)[2] !== hostname
            : true;
      } catch (error) {
        data.error = error.message;
      }
      /** endif **/

      post(pageviews, data, isPushState);
    };

    /** if spa **/
    var his = window.history;
    var hisPushState = his ? his.pushState : null;
    if (hisPushState && Event && dis) {
      var stateListener = function(type) {
        var orig = his[type];
        return function() {
          var rv = orig.apply(this, arguments);
          var event = new Event(type);
          event.arguments = arguments;
          dis(event);
          return rv;
        };
      };
      his.pushState = stateListener(pushState);
      addEventListenerFunc(
        pushState,
        function() {
          pageview(1);
        },
        false
      );
      addEventListenerFunc(
        "popstate",
        function() {
          pageview(1);
        },
        false
      );
    }
    /** endif **/

    /** if hash **/
    // When in hash mode, we record a pageview based on the onhashchange function
    if (mode === "hash" && "onhashchange" in window) {
      addEventListenerFunc(
        "hashchange",
        function() {
          pageview(1);
        },
        false
      );
    }
    /** endif **/

    pageview();

    /** if events **/
    var defaultEventFunc = function(event) {
      post(events, event);
    };

    // Set default function if user didn't define a function
    if (!window[functionName]) window[functionName] = defaultEventFunc;

    var eventFunc = window[functionName];

    // Read queue of the user defined function
    var queue = eventFunc && eventFunc.q ? eventFunc.q : [];

    // Overwrite user defined function
    window[functionName] = defaultEventFunc;

    // Post events from the queue of the user defined function
    for (var event in queue) post(events, queue[event]);
    /** endif **/
  } catch (e) {
    warn(e.message);
    var url = apiUrl + "image.gif";
    if (e.message) url = url + "?error=" + encodeURIComponent(e.message);
    new Image().src = url;
  }
})(window, 2, "scripts.simpleanalyticscdn.com/latest.js", "queue.simpleanalyticscdn.com/v2/post");

You could copy paste this in your terminal to see if it works. Or in somewhere in your frontend code. Would love to know if this fixes your problem!

from scripts.

Bathtor avatar Bathtor commented on June 6, 2024

Ah, I wasn't clear: I already fixed the problem in my code and redeployed (using exactly the same approach I suggested above). I simply opened the issue, so that other people wouldn't run into the same problem in the future.

So I can't really test your fix against my code anymore, since it would work correctly whether or not you actually fixed it on your side.

from scripts.

adriaanvanrossum avatar adriaanvanrossum commented on June 6, 2024

Ah, then extra thanks for letting us know! Future customers will not have this issue anymore ;)

from scripts.

adriaanvanrossum avatar adriaanvanrossum commented on June 6, 2024

This is now fixed!

from scripts.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.