securitydata-securitytrackjs

TrackJS and GDPR and SPA


I'm using TrackJS in SPA, i have logic related to GDPR, so I can NOT send any data before consent given.

I found onError config option which help with this. But i still have external request to https://usage.trackjs.com/usage.gif. I can disable this by some tricks but it doesn't looks correct way.

So how to prevent usage call?

Example of implementation:

import { TrackJS } from 'trackjs';

let GLOBAL_VARIABLE_IS_CONSENT_GIVEN = false;

TrackJS.install({
  usageURL: '#', // disable usage call
  onError: function() {
    if (!GLOBAL_VARIABLE_IS_CONSENT_GIVEN) {
      return false;
    }

    // other logic

    return true;
  },
});

I also found issue with usage call in SPA. It will not show actual page view result. So it will be good to have support to call usage request manually, it will solve two issues above. Found that it's known limitation: https://docs.trackjs.com/data-management/pageviews/


Solution

  • At the current moment I found only one way to solve it. I wrote helper class which apply monkey patch to prevent sending usage call and error tracking until it allowed. Monkey patch is usually practiced when you need to block loading external scripts, e.g. iubenda which we already use.

    Helper class:

    class TrackJSUtil {
      static URL_PATTERN = /usage\.trackjs\.com/;
    
      /**
       * Keep usage url
       *
       * @private
       * @type {null|string}
       */
      usageUrl = null;
    
      /**
       * Is requests allowed
       *
       * @private
       * @type {boolean}
       */
      isRequestsAllowed = false;
    
      /**
       * Apply monkey patch
       */
      install() {
        if (this.isRequestsAllowed) {
          return;
        }
    
        const setUsageUrl = (value) => {
          this.usageUrl = value;
        };
    
        const ignore = () => this.isRequestsAllowed || this.usageUrl != null;
    
        const createElementOriginal = document.createElement.bind(document);
    
        document.createElement = function createElementForTrackJS(...args) {
          const element = createElementOriginal(...args);
    
          if (ignore() || element.tagName.toUpperCase() !== 'IMG') {
            return element;
          }
    
          Object.defineProperty(element, 'src', {
            get() {
              return this.getAttribute('src');
            },
            set(value) {
              if (TrackJSUtil.URL_PATTERN.test(value)) {
                setUsageUrl(value);
              } else {
                this.setAttribute('src', value);
              }
            },
          });
    
          return element;
        };
      }
    
      /**
       * Callback for onError in TrackJS
       * @return {boolean}
       */
      onTrackJSError() {
        return this.isRequestsAllowed;
      }
    
      /**
       * Call it when requests are allowed
       */
      allow() {
        if (this.isRequestsAllowed) {
          return;
        }
    
        this.isRequestsAllowed = true;
    
        if (this.usageUrl == null) {
          return;
        }
    
        document.createElement('img').src = this.usageUrl;
      }
    }
    

    Usage:

    import { TrackJS } from 'trackjs';
    
    const util = new TrackJSUtil();
    
    if (IS_CONSENT_GIVEN) {
      util.allow()
    } else {
      callItWhenConsentGiven(() => {
        util.allow();
      });
    }
    
    util.install();
    
    TrackJS.install({
      onError: () => util.onTrackJSError(),
    });