Smile-SA/elasticsuite

View on GitHub
src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js

Summary

Maintainability
F
4 days
Test Coverage
/* global document: true */
/* global window: true */
/* global console: true */
/* exported smileTracker */

/**
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer
 * versions in the future.
 *
 * @category  Smile
 * @package   Smile\ElasticsuiteTracker
 * @author    Aurelien FOUCRET <aurelien.foucret@smile.fr>
 * @copyright 2020 Smile
 * @license   Open Software License ("OSL") v. 3.0
 */
const smileTracker = (function () {

    "use strict";

    const guid = (function() {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1);
        }
        return function() {
            return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
                s4() + '-' + s4() + s4() + s4();
        };
    }());

    function getCookie(cookieName) {
        const name = cookieName + "=";
        const ca = document.cookie.split(';');
        for (let i = 0; i < ca.length; i++) {
            let c = ca[i];
            while (c.charAt(0) === ' ') {
                c = c.substring(1);
            }
            if (c.indexOf(name) !== -1) {
                return c.substring(name.length, c.length);
            }
        }
        return null;
    }

    function setCookie(cookieName, cookieValue, expiresAt, path) {
        const expires = "expires=" + expiresAt.toUTCString();
        document.cookie = cookieName + "=" + cookieValue + "; " + expires + "; path=" + path;
    }

    // Retrieve values for a param into URL
    function getQueryStringParameterByName(name) {
        let results = null;

        if (name && name.replace) {
            name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
            const regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
            results = regex.exec(window.location.search);
        }

        return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    }

    // Add page title and page URL to the tracked variables
    function addStandardPageVars() {
        // Website base url tracking (eg. mydomain.com)
        this.addPageVar("site", window.location.hostname);

        // Page URL tracking (eg. home.html)
        this.addPageVar("url", window.location.pathname);

        // Page title tracking
        this.addPageVar("title", document.title);
    }

    // Append GA campaign variable to the tracked variables
    // Following URL variables are used :
    // - utm_source
    // - utm_campaign
    // - utm_medium
    function addCampaignVars() {
        // GA variables to be fetched from URI
        const urlParams = ['utm_source', 'utm_campaign', 'utm_medium', 'utm_term'];

        urlParams.forEach(function (element) {
            const paramName = element;
            const paramValue = getQueryStringParameterByName(paramName);
            if (paramValue) {
                // Append the GA param to the tracker
                this.addPageVar(paramName, paramValue);
            }
        }.bind(this));
    }

    function addReferrerVars() {
        if (document.referrer) {
            let parser = document.createElement('a');
            parser.href = document.referrer;
            this.addPageVar('referrer.domain', parser.hostname);
            this.addPageVar('referrer.page', parser.pathname);
        }
    }

    function addResolutionVars() {
        this.addPageVar('resolution.x', window.screen.availWidth);
        this.addPageVar('resolution.y', window.screen.availHeight);
        return this;
    }

    function addCustomerVars(customerData) {
        getCustomerDataCodeToTrack().forEach(function (customerDataCode) {
            if (customerData.hasOwnProperty('tracking') && customerData.tracking.hasOwnProperty(customerDataCode)) {
                this.addCustomerVar(customerDataCode, customerData.tracking[customerDataCode]);
            }
        }.bind(this));
    }

    function addMetaPageVars() {
        const metaTags = document.getElementsByTagName('meta');
        for (let tagIndex = 0; tagIndex < metaTags.length; tagIndex++) {
            if (metaTags[tagIndex].getAttribute('name')) {
                let components = metaTags[tagIndex].getAttribute('name').split(':');
                if (components.length === 2 && components[0] === 'sct') {
                    let varName = components[1];
                    this.addPageVar(varName, metaTags[tagIndex].getAttribute('content'));
                }
            }
        }
    }

    function getTrackerVars() {
        if (this.trackerVarsAdded === false) {
            addStandardPageVars.bind(this)();
            addReferrerVars.bind(this)();
            addCampaignVars.bind(this)();
            addMetaPageVars.bind(this)();
            addResolutionVars.bind(this)();
            this.trackerVarsAdded = true;
        }

        let urlParams = [];

        for (let currentVar in this.vars) {
            if ({}.hasOwnProperty.call(this.vars, currentVar)) {
                urlParams.push(currentVar + "=" + this.vars[currentVar]);
            }
        }

        return urlParams;
    }

    function getTrackerPostVars() {
        if (this.trackerVarsAdded === false) {
            addStandardPageVars.bind(this)();
            addReferrerVars.bind(this)();
            addCampaignVars.bind(this)();
            addMetaPageVars.bind(this)();
            addResolutionVars.bind(this)();
            this.trackerVarsAdded = true;
        }

        Object.keys(this.vars).forEach(key => {
            this.vars[key] = decodeURIComponent(this.vars[key]);
        });

        return bracketVarsToJson(this.vars);
    }

    function getTrackerUrl() {
        let urlParams = getTrackerVars.bind(this)();
        return this.baseUrl + "?" + urlParams.join('&');
    }

    function getCustomerDataCodeToTrack() {
        return ['age', 'gender', 'zipcode', 'state', 'country'];
    }

    function setTrackerStyle(imgNode) {
        imgNode.setAttribute('style', 'position: absolute; top: 0; left: 0; visibility: hidden;');
    }

    // Send the tag to the remote server
    // Append a transparent pixel to the body
    function sendTag(forceCollect = false) {
        initSession.bind(this)();

        if (this.config && this.config.hasOwnProperty('storeId')) {
            addPageVar.bind(this)('store_id', this.config.storeId);
        }

        if (this.trackerSent === false || forceCollect === true) {
            if (this.endpointUrl) {
                const eventData = {'eventData': getTrackerPostVars.bind(this)()};
                let request = new XMLHttpRequest();
                request.open('POST', this.endpointUrl, true);
                request.setRequestHeader('Content-Type', 'application/json');
                request.send(JSON.stringify(eventData));
            } else {
                const bodyNode = document.getElementsByTagName('body')[0];
                buildTrackingImg.bind(this)(bodyNode, getTrackerUrl.bind(this)());
            }
            this.trackerSent = true;
            this.vars = {};
        }
    }

    function sendTelemetry() {
        initSession.bind(this)();
        initCustomerData.bind(this)();
        getTrackerVars.bind(this);

        const vars = bracketVarsToJson(this.vars);

        if (this.telemetryEnabled && this.telemetryTrackerSent === false) {
            let request = new XMLHttpRequest();
            request.open('POST', this.telemetryUrl, true);
            request.setRequestHeader('Content-Type', 'application/json');
            request.send(JSON.stringify(vars));

            this.telemetryTrackerSent = true;
        }
    }

    function bracketVarsToJson(vars) {
        let result = {};

        for (const i in vars) {
            let a = i.match(/([^\[\]]+)(\[[^\[\]]+[^\]])*?/g),
                p = vars[i];
            let j = a.length;
            while (j--) {
                let q = {};
                q[a[j]] = p;
                p = q;
            }

            let k = Object.keys(p)[0],
                o = result;

            while (k in o) {
                p = p[k];
                o = o[k];
                k = Object.keys(p)[0];
            }

            o[k] = p[k];
        }

        return result;
    }

    function buildTrackingImg(bodyNode, trackingUrl) {
        let imgNode = document.createElement('img');
        imgNode.setAttribute('src', trackingUrl);
        imgNode.setAttribute('alt', '');
        setTrackerStyle(imgNode);
        bodyNode.appendChild(imgNode);
    }

    // Append a variable to the page
    function addVariable(varName, value) {
        this.vars[varName] = encodeURIComponent(value);
        return this;
    }

    function addSessionVar(varName, value) {
        addVariable.bind(this)(transformVarName.bind(this)(varName, 'session'), value);
    }

    function addPageVar(varName, value) {
        addVariable.bind(this)(transformVarName.bind(this)(varName , 'page'), value);
    }

    function addCustomerVar(varName, value) {
        addVariable.bind(this)(transformVarName.bind(this)(varName , 'customer'), value);
    }

    function transformVarName(varName, prefix) {
        return prefix + "[" + varName.replace(/[.]/g, "][") + "]";
    }

    function initSession() {
        if (this.config && this.config.hasOwnProperty('sessionConfig')) {
            const config   = this.config.sessionConfig;
            const expireAt = new Date();
            const path     = config['path'] || '/';

            if (!this.sessionInitialized) {
                if (getCookie(config['visit_cookie_name']) === null) {
                    expireAt.setSeconds(expireAt.getSeconds() + parseInt(config['visit_cookie_lifetime'], 10));
                    setCookie(config['visit_cookie_name'], guid(), expireAt, path);
                } else {
                    expireAt.setSeconds(expireAt.getSeconds() + parseInt(config['visit_cookie_lifetime'], 10));
                    setCookie(config['visit_cookie_name'], getCookie(config['visit_cookie_name']), expireAt, path);
                }

                if (getCookie(config['visitor_cookie_name']) === null) {
                    expireAt.setDate(expireAt.getDate() + parseInt(config['visitor_cookie_lifetime'], 10));
                    setCookie(config['visitor_cookie_name'], guid(), expireAt, path);
                }
                this.sessionInitialized = true;
            }

            addSessionVar.bind(this)('uid', getCookie(config['visit_cookie_name']));
            addSessionVar.bind(this)('vid', getCookie(config['visitor_cookie_name']));
        }
    }

    function initCustomerData() {
        try {
            let mageStorage = localStorage.getItem('mage-cache-storage');
            if (mageStorage !== null) {
                mageStorage = JSON.parse(mageStorage);
                if ((mageStorage.customer !== undefined)) {
                    addCustomerVars.bind(this)(mageStorage.customer);
                }
            }
        } catch (e) {
            // Nothing.
        }
    }

    // Implementation of the tracker
    const SmileTrackerImpl = function() {
        this.vars = {};
        this.trackerSent = false;
        this.telemetryTrackerSent = false;
        this.trackerVarsAdded = false;
        this.sessionInitialized = false;
        this.customerData = {};
    };

    SmileTrackerImpl.prototype.sendTag = function (forceCollect = false) {
        if (document.readyState !== 'loading') {
            sendTag.bind(this)(forceCollect);
            sendTelemetry.bind(this)();
        } else {
            document.addEventListener('DOMContentLoaded', function () {
                sendTag.bind(this)(forceCollect);
                sendTelemetry.bind(this)();
            }.bind(this));
        }
    };

    SmileTrackerImpl.prototype.setConfig = function (config) {
        this.config           = config;
        this.baseUrl          = config.beaconUrl;
        this.endpointUrl      = false;
        if (config.hasOwnProperty('endpointUrl') && (config.endpointUrl.length !== 0)) {
            this.endpointUrl = config.endpointUrl;
        }
        this.telemetryEnabled = config.telemetryEnabled;
        this.telemetryUrl     = config.telemetryUrl;


    };

    SmileTrackerImpl.prototype.addPageVar = function (varName, value) {
        addPageVar.bind(this)(varName, value);
    };

    SmileTrackerImpl.prototype.addCustomerVar = function (varName, value) {
        addCustomerVar.bind(this)(varName, value);
    };

    SmileTrackerImpl.prototype.addSessionVar = function (varName, value) {
        addSessionVar.bind(this)(varName, value);
    };

    return new SmileTrackerImpl();
}());