Source: Tracker/utils.js

/**
 * @class
 * @classdesc Utility methods.
 * @name Utils
 * @public
 */
var Utils = function () {

    var self = this;

    var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;
    var whitespace = "[\\x20\\t\\r\\n\\f]";
    var rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g");

    /**
     * Serialize any element.
     * @memberof Utils#
     * @param obj {Object} Object to serialize
     * @returns {String}
     * @private
     */
    function serialJSON(obj) {
        var t = typeof (obj);
        if (t !== 'object' || obj === null) {
            if (t === 'string') {
                obj = '"' + obj + '"';
            }
            return String(obj);
        } else {
            var n, v, json = [],
                arr = (obj && obj.constructor === Array);
            for (n in obj) {
                if (obj.hasOwnProperty(n)) {
                    v = obj[n];
                    t = typeof (v);
                    if (t !== 'function' && t !== 'undefined') {
                        if (t === 'string') {
                            v = '"' + v.replace(/[^\\]"/g, '\\"') + '"';
                        } else if (t === 'object' && v !== null) {
                            v = serialJSON(v);
                        }
                        json.push((arr ? '' : '"' + n + '":') + String(v));
                    }
                }
            }
            return (arr ? '[' : '{') + String(json) + (arr ? ']' : '}');
        }
    }

    /**
     * Trim text.
     * @memberof Utils#
     * @param text {String} Text to trim
     * @returns {String}
     * @private
     */
    function trim(text) {
        return text === null ?
            '' :
            ( text + '' ).replace(rtrim, '');
    }

    /**
     * Parse any string.
     * @memberof Utils#
     * @param data {string} String to parse
     * @returns {*}
     * @private
     */
    function parseJSON(data) {
        var requireNonComma,
            depth = null,
            str = trim(data + '');
        // Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
        // after removing valid tokens
        return str && !trim(str.replace(rvalidtokens, function (token, comma, open, close) {
            // Force termination if we see a misplaced comma
            if (requireNonComma && comma) {
                depth = 0;
            }
            // Perform no more replacements after returning to outermost depth
            if (depth === 0) {
                return token;
            }
            // Commas must not follow "[", "{", or ","
            requireNonComma = open || comma;
            // Determine new depth
            // array/object open ("[" or "{"): depth += true - false (increment)
            // array/object close ("]" or "}"): depth += false - true (decrement)
            // other cases ("," or primitive): depth += true - true (numeric cast)
            depth += !close - !open;
            // Remove this token
            return '';
        })) ?
            ( Function('return ' + str) )() :
            null;
    }

    /**
     * Check if local storage is both supported and available
     * @name isLocalStorageAvailable
     * @memberof Utils#
     * @return {boolean}
     * @public
     */
    self.isLocalStorageAvailable = function () {
        try {
            var storage = localStorage,
                x = '__storage_test__';
            storage.setItem(x, x);
            storage.removeItem(x);
            return true;
        }
        catch (e) {
            return false;
        }
    };

    /**
     * Get a stored value from name
     * @name getStorageData
     * @memberof Utils#
     * @function
     * @param name {String} Name of the stored data
     * @return {*} Return null if the stored data does not exist
     * @private
     */
    function getStorageData(name) {
        var storedData = null;
        if (self.isLocalStorageAvailable()) {
            storedData = localStorage.getItem(name)
        }
        if (!storedData) {
            var cookies = document.cookie;
            var regExp = new RegExp('(?:^| )' + name + '=([^;]+)');
            var result = regExp.exec(cookies) || null;
            if (result) {
                storedData = result[1];
            }
        }
        return decodeURIComponent(storedData);
    }

    /**
     * Test value from OPT-OUT stored object
     * @memberof Utils#
     * @function
     * @return {Boolean}
     * @private
     */
    function isValFromOptedOutStorage() {
        var sItem = getStorageData('atoptedout');
        if (sItem) {
            var item = self.jsonParse(sItem) || self.jsonParse(self.Base64.decode(sItem));
            if (item) {
                return !!item.val;
            }
        }
        return false;
    }

    /**
     * Base64 encode / decode
     * *http://www.webtoolkit.info/
     * @memberof Utils#
     * @Object
     * @public
     */
    self.Base64 = {

        // private property
        _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

        // public method for encoding
        encode: function (input) {
            var output = "";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i = 0;

            input = self.Base64._utf8_encode(input);

            while (i < input.length) {

                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);

                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;

                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                }

                output = output +
                    this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                    this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

            }

            return output;
        },

        // public method for decoding
        decode: function (input) {
            var output = "";
            var chr1, chr2, chr3;
            var enc1, enc2, enc3, enc4;
            var i = 0;

            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

            while (i < input.length) {

                enc1 = this._keyStr.indexOf(input.charAt(i++));
                enc2 = this._keyStr.indexOf(input.charAt(i++));
                enc3 = this._keyStr.indexOf(input.charAt(i++));
                enc4 = this._keyStr.indexOf(input.charAt(i++));

                chr1 = (enc1 << 2) | (enc2 >> 4);
                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                chr3 = ((enc3 & 3) << 6) | enc4;

                output = output + String.fromCharCode(chr1);

                if (enc3 != 64) {
                    output = output + String.fromCharCode(chr2);
                }
                if (enc4 != 64) {
                    output = output + String.fromCharCode(chr3);
                }

            }

            output = self.Base64._utf8_decode(output);

            return output;

        },

        // private method for UTF-8 encoding
        _utf8_encode: function (string) {
            string = string.replace(/\r\n/g, "\n");
            var utftext = "";

            for (var n = 0; n < string.length; n++) {

                var c = string.charCodeAt(n);

                if (c < 128) {
                    utftext += String.fromCharCode(c);
                }
                else if ((c > 127) && (c < 2048)) {
                    utftext += String.fromCharCode((c >> 6) | 192);
                    utftext += String.fromCharCode((c & 63) | 128);
                }
                else {
                    utftext += String.fromCharCode((c >> 12) | 224);
                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                    utftext += String.fromCharCode((c & 63) | 128);
                }

            }

            return utftext;
        },

        // private method for UTF-8 decoding
        _utf8_decode: function (utftext) {
            var string = "";
            var i = 0;
            var c, c2, c3;
            c = c2 = c3 = 0;

            while (i < utftext.length) {

                c = utftext.charCodeAt(i);

                if (c < 128) {
                    string += String.fromCharCode(c);
                    i++;
                }
                else if ((c > 191) && (c < 224)) {
                    c2 = utftext.charCodeAt(i + 1);
                    string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                    i += 2;
                }
                else {
                    c2 = utftext.charCodeAt(i + 1);
                    c3 = utftext.charCodeAt(i + 2);
                    string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                    i += 3;
                }

            }

            return string;
        }

    };

    /**
     * Load a script.
     * @name loadScript
     * @memberof Utils#
     * @function
     * @param scriptObj {object} Object containing script properties (url)
     * @param callback {function} Function to call on success or on error
     * @public
     */
    self.loadScript = function (scriptObj, callback) {
        var newScript;
        callback = callback || function () {
        };

        var errorCallback = function (event) {
            newScript.onload = newScript.onreadystatechange = newScript.onerror = null;
            callback({msg: 'script not loaded', event: event}); // first param not null means an error has occured
        };
        var onloadCallback = function (event) {
            event = event || window.event;
            // Check for different events in IEs
            if (event.type === "load" || (/loaded|complete/.test(newScript.readyState) && (!document.documentMode || document.documentMode < 9))) {
                newScript.onload = newScript.onreadystatechange = newScript.onerror = null;
                callback(null, event);
            }
        };
        newScript = document.createElement("script");
        newScript.type = "text/javascript";
        newScript.src = scriptObj.url;
        newScript.async = false;
        newScript.defer = false;
        newScript.onload = newScript.onreadystatechange = onloadCallback;
        newScript.onerror = errorCallback;
        var head = document.head || document.getElementsByTagName("head")[0];
        head.insertBefore(newScript, head.lastChild);
    };

    //On ignore volontairement le deuxième paramètre qui nous est réservé :)
    /**
     * Copy an object which doesnt' contain functions/objects.
     * @name cloneSimpleObject
     * @memberof Utils#
     * @inner
     * @function
     * @param inst {object} Item that you want to clone
     * @param delUndefined {boolean} If true, undefined properties are not cloned
     * @returns newInst {object} clone of the instance
     * @public
     */
    self.cloneSimpleObject = function (inst, delUndefined) {
        /*Si l'instance source n'est pas un objet ou qu'elle ne vaut rien c'est une feuille donc on la retourne*/
        if (typeof (inst) !== 'object' || inst === null || inst instanceof Date) {
            return inst;
        }
        /*On appelle le constructeur de l'instance source pour créer une nouvelle instance de la même classe*/
        var newInst = new inst.constructor;
        /*On parcourt les propriétés de l'objet et on les recopie dans la nouvelle instance*/
        for (var i in inst) {
            if (inst.hasOwnProperty(i)) {
                //On gère le cas où il a été demandé (delUndefined) de ne pas copier les propriétés undefined
                if (i !== undefined && (!delUndefined || inst[i] !== undefined)) {
                    newInst[i] = self.cloneSimpleObject(inst[i]);
                }
            }
        }
        /*On retourne la nouvelle instance*/
        return newInst;
    };

    /**
     * Serialize any element. Use JSON.stringify if possible.
     * @name jsonSerialize
     * @memberof Utils#
     * @function
     * @param obj {Object} Javascript object that you want to serialize to JSON
     * @returns {Object|String} Serialized JSON
     * @public
     */
    self.jsonSerialize = function (obj) {
        try {
            if (typeof JSON !== 'undefined' && JSON.stringify) {
                return JSON.stringify(obj);
            } else {
                return serialJSON(obj);
            }
        }
        catch (e) {
            return null;
        }
    };

    /**
     * Parse a string. Use JSON.parse if possible.
     * @name jsonParse
     * @memberof Utils#
     * @function
     * @param str {String} JSON string that you want to parse to Javascript object
     * @returns {Object}
     * @public
     */
    self.jsonParse = function (str) {
        try {
            if (typeof JSON !== 'undefined' && JSON.parse) {
                return JSON.parse(str + '');
            } else {
                return parseJSON(str);
            }
        }
        catch (e) {
            return null;
        }
    };

    /**
     * Search an element in an array. Use Array.indexOf if possible.
     * @name arrayIndexOf
     * @memberof Utils#
     * @function
     * @param arr {Array}
     * @param elemToSearch {*}
     * @returns {number}
     * @public
     */
    self.arrayIndexOf = function (arr, elemToSearch) {
        if (Array.indexOf) {
            return arr.indexOf(elemToSearch);
        } else {
            return (function (searchElement) {
                "use strict";
                if (this == null) {
                    throw new TypeError();
                }
                var t = Object(this);
                var len = t.length >>> 0;
                if (len === 0) {
                    return -1;
                }
                var n = 0;
                if (arguments.length > 1) {
                    n = Number(arguments[1]);
                    if (n != n) { // shortcut for verifying if it's NaN
                        n = 0;
                    }
                    else if (n != 0 && n != Infinity && n != -Infinity) {
                        n = (n > 0 || -1) * Math.floor(Math.abs(n));
                    }
                }
                if (n >= len) {
                    return -1;
                }
                var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
                for (; k < len; k++) {
                    if (k in t && t[k] === searchElement) {
                        return k;
                    }
                }
                return -1;
            }).apply(arr, [elemToSearch]);
        }
    };

    /**
     * Generate UUID.
     * @name uuid
     * @memberof Utils#
     * @function
     * @returns {function}
     * @public
     */
    self.uuid = function () {

        /**
         * Generate GUID with alphanumeric characters (v4 format).
         * @name _v4
         * @memberof Utils#
         * @function
         * @returns {string}
         * @public
         */
        function _v4() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }

        /**
         * Generate GUID with numeric characters.
         * @name _num
         * @memberof Utils#
         * @function
         * @param len {number} Length of the generated GUID
         * @returns {string}
         * @public
         */
        function _num(len) {
            var d = new Date();
            var format = function (a) {
                a = a - Math.floor(a / 100) * 100;
                if (a < 10) {
                    return '0' + a;
                } else {
                    return String(a);
                }
            };
            var rand = function (n) {
                return Math.floor((Math.random() * 9 + 1) * Math.pow(10, n - 1));
            };
            return format(d.getHours()) + '' + format(d.getMinutes()) + '' + format(d.getSeconds()) + '' + rand(len - 6);
        }

        return {
            v4: _v4,
            num: _num
        };
    };

    /**
     * Get all keys from object.
     * @name getObjectKeys
     * @memberof Utils#
     * @function
     * @param object {object}
     * @returns {Array}
     * @public
     */
    self.getObjectKeys = function (object) {
        var keys = [];
        for (var elem in object) {
            if (object.hasOwnProperty(elem)) {
                keys.push(elem);
            }
        }
        return keys;
    };

    /**
     * Complete the first level of an object with another.
     * @name completeFstLevelObj
     * @memberof Utils#
     * @function
     * @param target {object}
     * @param source {object}
     * @param overload {boolean} If true, properties of the target will be overloaded by source ones if they exist
     * @returns {object}
     * @public
     */
    self.completeFstLevelObj = function (target, source, overload) {
        if (target) {
            if (source) {
                for (var key in source) {
                    if (source.hasOwnProperty(key)) {
                        if (!target[key] || overload) {
                            target[key] = source[key];
                        }
                    }
                }
            }
        }
        else {
            target = source;
        }
        return target;
    };

    /**
     * Check if we are in Safari previewing case.
     * @name isPreview
     * @memberof Utils#
     * @function
     * @returns {boolean}
     * @public
     */
    self.isPreview = function () {
        return (window.navigator && window.navigator.loadPurpose === 'preview');
    };

    /**
     * Check if we are in Chrome or IE prerendering case.
     * @name isPrerender
     * @memberof Utils#
     * @function
     * @param callback {function}
     * @returns {boolean}
     * @public
     */
    self.isPrerender = function (callback) {
        var visibilityChangeEvent;
        var isPrerender = false;
        //Prefixes: Chrome, IE
        var prefixes = ['webkit', 'ms'];
        if (document.visibilityState === 'prerender') {
            //Opera 12.10 and Firefox 18 and later support
            visibilityChangeEvent = 'visibilitychange';
        }
        else {
            for (var i = 0; i < prefixes.length; i++) {
                if (document[prefixes[i] + 'VisibilityState'] === 'prerender') {
                    visibilityChangeEvent = prefixes[i] + 'visibilitychange';
                }
            }
        }
        if (typeof visibilityChangeEvent !== 'undefined') {
            var _manageCallback = function (event) {
                callback(event);
                self.removeEvtListener(document, visibilityChangeEvent, _manageCallback);
            };
            self.addEvtListener(document, visibilityChangeEvent, _manageCallback);
            isPrerender = true;
        }
        return isPrerender;
    };

    /**
     * Add an event listener to an object.
     * @name addEvtListener
     * @memberof Utils#
     * @function
     * @param obj {object} DOM Element on which you want to add the event listener
     * @param event {string} Event you need to listen
     * @param callback {function} When event is triggered, it takes one parameter which is the event object
     * @public
     */
    var _addEvtListener = self.addEvtListener = function (obj, event, callback) {
        if (obj.addEventListener) {
            obj.addEventListener(event, callback, false);
        } else if (obj.attachEvent) {
            obj.attachEvent('on' + event, callback);
        }
    };

    /**
     * Remove an event listener from an object.
     * @name removeEvtListener
     * @memberof Utils#
     * @function
     * @param obj {object} DOM Element on which you want to add the event listener
     * @param event {string} Event you need to listen
     * @param callback {function}
     * @public
     */
    var _removeEvtListener = self.removeEvtListener = function (obj, event, callback) {
        if (obj.removeEventListener) {
            obj.removeEventListener(event, callback, false);
        } else if (obj.detachEvent) {
            obj.detachEvent('on' + event, callback);
        }
    };

    /**
     * Make a unique number with a given string.
     * @name hashcode
     * @memberof Utils#
     * @function
     * @param str
     * @returns {number}
     * @public
     */
    self.hashcode = function (str) {
        var hash = 0;
        if (str.length === 0) return hash;
        for (var i = 0; i < str.length; i++) {
            var character = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + character;
            hash |= 0;
        }
        return hash;
    };

    /**
     * Force document location.
     * @name setLocation
     * @memberof Utils#
     * @function
     * @param contextObj {object} Redirection's url and target (properties location & target)
     * @public
     */
    self.setLocation = function (contextObj) {
        var loc = contextObj['location'];
        var obj = window[contextObj['target']] || window;
        if (loc) {
            obj.location.href = loc;
        }
    };

    /****************************** Callback management *************************/

    /**
     * Dispatch event for callbacks
     * @name dispatchCallbackEvent
     * @memberof Utils#
     * @function
     * @param name {string} Callback's name
     * @public
     */
    self.dispatchCallbackEvent = function (name) {
        // Create the event.
        var event;
        if (typeof window.Event === "function") {
            event = new Event('ATCallbackEvent');
        }
        else {
            try {
                event = document.createEvent('Event');
                // Define that the event name is 'ATCallbackEvent'.
                // Deprecated.
                event.initEvent && event.initEvent('ATCallbackEvent', true, true);
            }
            catch (e) {
            }
        }
        if (event && (typeof document.dispatchEvent === "function")) {
            event.name = name;
            document.dispatchEvent(event);
        }
    };

    /**
     * Add callback event
     * @name addCallbackEvent
     * @memberof Utils#
     * @function
     * @param func {function} function to execute
     * @public
     */
    self.addCallbackEvent = function (func) {
        // Listen to the event.
        _addEvtListener(document, 'ATCallbackEvent', func);
    };

    /****************************** Custom Event management *************************/

    /**
     * Polyfill the CustomEvent() constructor functionality in Internet Explorer 9 and higher
     * @memberof Utils#
     * @function
     * @private
     */
    (function () {
        if (typeof window.CustomEvent === 'function') {
            window.ATCustomEvent = window.CustomEvent;
            return false;
        }

        function ATCustomEvent(evt, params) {
            params = params || {bubbles: false, cancelable: false, detail: undefined};
            var event;
            try {
                event = document.createEvent('CustomEvent');
                event.initCustomEvent(evt, params.bubbles, params.cancelable, params.detail);
            }
            catch (e) {
            }
            return event;
        }

        if (typeof window.Event === "function") {
            ATCustomEvent.prototype = window.Event.prototype;
        }
        window.ATCustomEvent = ATCustomEvent;
    })();

    /**
     * Add custom event
     * @name addEvent
     * @memberof Utils#
     * @param eventName {string} event name
     * @param detailName {string} detail name
     * @param detailID {number} detail uuid
     * @param func {function} function to execute
     * @function
     * @public
     */
    self.addEvent = function (eventName, detailName, detailID, func) {
        self[eventName] = new ATCustomEvent(eventName, {
            detail: {
                name: detailName,
                id: detailID
            }
        });
        // Listen to the event.
        _addEvtListener(document, eventName, func);
    };

    /**
     * Remove custom event
     * @name removeEvent
     * @memberof Utils#
     * @param eventName {string} event name
     * @param func {function} function to execute
     * @function
     * @public
     */
    self.removeEvent = function (eventName, func) {
        // Remove the event listener.
        _removeEvtListener(document, eventName, func);
    };

    /**
     * Dispatch custom event
     * @name dispatchEvent
     * @memberof Utils#
     * @param eventName {string} event name
     * @param detailName {string} detail name
     * @function
     * @public
     */
    self.dispatchEvent = function (eventName, detailName) {
        // Init the event.
        self[eventName] = self[eventName] || new ATCustomEvent(eventName, {
            detail: {
                name: detailName,
                id: -1
            }
        });
        try {
            document.dispatchEvent(self[eventName]);
        }
        catch (e) {
        }
    };

    /****************************** OPT-OUT management *************************/

    /**
     * Add OPT-OUT event
     * @name addOptOutEvent
     * @memberof Utils#
     * @param eventID {number} uuid
     * @param func {function} function to associate
     * @function
     * @public
     */
    self.addOptOutEvent = function (eventID, func) {
        self.addEvent('ATOptOutEvent', 'clientsideuserid', eventID, func);
    };

    /**
     * Remove OPT-OUT event
     * @name removeOptOutEvent
     * @memberof Utils#
     * @param func {function} function to remove
     * @function
     * @public
     */
    self.removeOptOutEvent = function (func) {
        self.removeEvent('ATOptOutEvent', func);
    };

    /**
     * Dispatch event for OPT-OUT
     * @name dispatchOptOutEvent
     * @memberof Utils#
     * @param active {boolean} Activate or deactivate OPT-OUT
     * @function
     * @private
     */
    self.dispatchOptOutEvent = function (active) {
        // Init OPT-OUT mode.
        self.optedOut = active;
        self.dispatchEvent('ATOptOutEvent', 'clientsideuserid');
    };

    /**
     * Activate OPT-OUT mode adding OPT-OUT value in hit and storage for idclient parameter
     * @name userOptedOut
     * @memberof Utils#
     * @function
     * @public
     */
    self.userOptedOut = function () {
        self.dispatchOptOutEvent(true);
    };

    /**
     * Deactivate OPT-OUT mode
     * @name userOptedIn
     * @memberof Utils#
     * @function
     * @public
     */
    self.userOptedIn = function () {
        self.dispatchOptOutEvent(false);
    };

    /**
     * Get OPTOUT activation status
     * @name isOptedOut
     * @memberof Utils#
     * @function
     * @public
     */
    self.isOptedOut = function () {
        if (self.optedOut === null) {
            self.optedOut = isValFromOptedOutStorage();
        }
        return !!self.optedOut;
    };

    /**
     * OPT-OUT value
     * @name optedOut
     * @memberof Utils#
     * @private
     */
    self.optedOut = null;

    /****************************** Consent Page management *************************/

    /**
     * Specify whether consent has been obtained preventing or allowing data to be stored in cookies or local storage
     * @name consentReceived
     * @memberof Utils#
     * @param active {boolean} false to prevent data to be stored, true to allow data to be stored
     * @function
     * @public
     */
    self.consentReceived = function (active) {
        self.consent = !!active;
    };

    /**
     * Consent page option value
     * @name consent
     * @memberof Utils#
     * @private
     */
    self.consent = true;

    /*******************************************************************************/

    // For unit tests on private elements !!!
    /* @if test */
    self.serialJSON = serialJSON;
    self.trim = trim;
    self.parseJSON = parseJSON;
    self.getStorageData = getStorageData;
    self.isValFromOptedOutStorage = isValFromOptedOutStorage;
    /* @endif */
};

/**
 * Module with utility methods.
 * @name ATInternet.Utils
 * @memberof ATInternet
 * @type {Utils}
 * @public
 * @see {@link Utils}
 */
window['ATInternet']['Utils'] = new Utils();