Source: ClientSideUserId/clientsideuserid.js

/**
 * @class
 * @name ClientSideUserId
 * @memberof ATInternet.Tracker.Plugins
 * @type {function}
 * @param tag {object} Instance of the Tag used
 * @description
 * Plugin to enable a visitor to be tracked by creating a user ID in cases where writing a third-party cookie is not possible.
 * <br />For example, this might be the case on an Intranet site,
 * or when the Safari browser is used (default settings),
 * or when a visitor browses using an Apple device like iPhone, iPad or iPod.
 * It is also possible to configure the plugin to force the use of a random or custom ID.
 * This ID can be found in the idclient variable, present in all hits when the plugin is active.
 * @public
 */
ATInternet.Tracker.Plugins.ClientSideUserId = function (tag) {

    'use strict';

    var _config = {};
    var _isITP = false;
    var _isClient = false;
    var _user = null;
    var _eventID = -1;
    var _debug = {
        level: 'DEBUG',
        messageEnd: 'method ended'
    };

    // Set specific plugin configuration.
    // If global configuration already exists, set only undefined properties.
    tag.configPlugin('ClientSideUserId', dfltPluginCfg || {}, function (newConf) {
        _config = newConf;
    });

    /**
     * Check if general configuration implies ClientSideUserId mode activation.
     * <br />Note : konqueror|Lunascape|midori|OmniWeb browsers are managed like Safari
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @return {boolean}
     * @private
     */
    var _isClientConfiguration = function () {
        var isClient = false;
        if (_config.clientSideMode === 'required') {
            var userAgent = '';
            if (window.navigator) {
                userAgent = window.navigator.userAgent;
            }
            if ((/Safari/.test(userAgent) && !/Chrome/.test(userAgent))
                || /iPhone|iPod|iPad/.test(userAgent)) {
                isClient = true;
            }
        } else if (_config.clientSideMode === 'always') {
            isClient = true;
        }
        return isClient;
    };

    /**
     * Check if OPT-OUT mode is activated or not.
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @private
     */
    var _setOptoutSettings = function () {
        var fromStorage = false;
        if (ATInternet.Utils.optedOut === null) {
            if (tag.storage.get(_config.userIdStorageName, true) === _config.optOut) {
                ATInternet.Utils.optedOut = true;
                fromStorage = true;
            } else {
                ATInternet.Utils.optedOut = false;
            }
        } else if (ATInternet.Utils.optedOut === false) {
            if (tag.getParam(_config.userIdHitName) === _config.optOut) {
                tag.delParam(_config.userIdHitName);
            }
            if (tag.storage.get(_config.userIdStorageName, true) === _config.optOut) {
                tag.storage.del(_config.userIdStorageName);
            }
        }
        _user.optout.isOptedout = ATInternet.Utils.optedOut;
        _user.optout.fromStorage = fromStorage;
    };

    /**
     * Check if user id cookie has to be set on server side (CDDC).
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * return {bool}
     * @private
     */
    var _isServerSideUserId = function () {
        var cddcDomain = _config.baseDomain;
        if (!cddcDomain) {
            var cookieDomain = tag.getConfig('cookieDomain');
            if (cookieDomain) {
                cddcDomain = cookieDomain;
                if (cddcDomain.charAt(0) === '.') {
                    cddcDomain = cddcDomain.substring(1, cddcDomain.length);
                }
            }
        }
        var collectDomain = tag.builder.getCollectDomain();
        var currentDomain = tag.utils.getHostName();
        return !!(cddcDomain && collectDomain && currentDomain && collectDomain.indexOf(cddcDomain) !== -1 && currentDomain.indexOf(cddcDomain) !== -1);
    };

    /**
     * Check if current configuration is ITP compatible.
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @return {boolean}
     * @private
     */
    var _isITPCompatible = function () {
        var isITP = false;
        var configHttp = tag.getConfig('forceHttp');
        if (!configHttp && _config.itpCompliant) {
            // Optout status or userid from customer context have priority
            if (typeof _user.contextUserId === 'undefined' && !_user.optout.isOptedout) {
                // Server side has to be checked on:
                // -> 'never' mode
                // --> on all devices/browser
                // -> 'always' mode:
                // -> 'required' mode:
                // --> on devices/browser other than Apple/Safari
                // --> on devices/browser like Apple/Safari with no userid cookie from client side
                switch (_config.clientSideMode) {
                    case 'never':
                        isITP = _isServerSideUserId();
                        break;
                    case 'always':
                    case 'required':
                        if (!_isClient || (_user.storageUserId === null)) {
                            isITP = _isServerSideUserId();
                        }
                        break;
                }
            }
        }
        return isITP;
    };

    /**
     * Init user object
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @private
     */
    var _initUser = function () {
        _user = {
            contextUserId: undefined,
            storageUserId: null,
            finalUserId: null,
            isFromTrackerContext: false,
            forceStorage: false,
            optout: {
                isOptedout: false,
                fromStorage: false
            }
        };
    };

    /**
     * Init global context.
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @private
     */
    var _initContext = function () {
        // Init user object
        _initUser();
        // Process OPT-OUT first
        _setOptoutSettings();
        // Get current user IDs
        _user.contextUserId = tag.getContext('userIdentifier');
        _user.storageUserId = tag.storage.get('atuserid', true);
        // Init context parameters
        _isClient = _isClientConfiguration();
        // Check if user id has to be set from server side (CDDC)
        _isITP = _isITPCompatible();
    };

    /**
     * Process user object
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @private
     */
    var _processUser = function () {
        _user.isFromTrackerContext = false;
        _user.forceStorage = false;
        if (_user.optout.isOptedout) {
            _user.finalUserId = _config.optOut;
            _user.isFromTrackerContext = !_user.optout.fromStorage;
            _user.forceStorage = true;
        } else if (tag.getConfig('disableCookie') || tag.getConfig('disableStorage')) {
            _user.finalUserId = tag.getParam(_config.userIdHitName); // Consent-NO
            _user.isFromTrackerContext = true;
        } else if (typeof _user.contextUserId !== 'undefined') {
            _user.finalUserId = _user.contextUserId;
            _user.isFromTrackerContext = true;
        } else if (_user.storageUserId !== null) {
            _user.finalUserId = _user.storageUserId;
        } else {
            _user.finalUserId = ATInternet.Utils.uuid().v4();
        }
    };

    /**
     * Create or update stored data value for identifier.
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @private
     */
    var _setStorageValue = function () {
        if (_config.userIdExpirationMode === 'relative' || (_config.userIdExpirationMode === 'fixed' && _user.storageUserId === null) || _user.isFromTrackerContext) {
            var expiration_date = new Date();
            expiration_date.setTime(expiration_date.getTime() + (_config.userIdCookieDuration * 24 * 60 * 60 * 1000));
            tag.storage.set(_config.userIdStorageName, _user.finalUserId, {
                end: expiration_date,
                path: '/'
            }, _user.forceStorage);
            if (ATInternet.Utils.consent && !_user.isFromTrackerContext && _user.finalUserId !== tag.storage.get(_config.userIdStorageName, true)) {
                tag.setParam(_config.userIdHitName, _user.finalUserId + '-NO', {
                    multihit: true,
                    permanent: true,
                    hitType: ['all']
                });
            }
        }
    };

    /**
     * Set user ID in buffer and storage.
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @private
     */
    var _setUserId = function () {
        tag.setParam(_config.userIdHitName, _user.finalUserId, {multihit: true, permanent: true, hitType: ['all']});
        _setStorageValue();
    };

    /**
     * Get context and stored values according to priorities or generate a new GUID.
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @private
     */
    var _run = function () {
        _initContext();
        if (!_isITP && (_isClient || _user.optout.isOptedout || (typeof _user.contextUserId !== 'undefined'))) {
            tag.setConfig('userIdOrigin', 'client');
            _processUser();
            _setUserId();
        } else {
            tag.setConfig('userIdOrigin', 'server');
        }
    };

    /**
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @description
     * React to OPT-OUT actions.
     * @param event {object}
     * @private
     */
    var _processEvent = function (event) {
        if (event) {
            var detail = event.detail;
            if (detail && (detail.name === 'clientsideuserid') && (detail.id === _eventID)) {
                _run();
            }
        }
    };

    /**
     * Launch plugin when all dependencies are loaded.
     * @memberof ATInternet.Tracker.Plugins.ClientSideUserId#
     * @function
     * @private
     */
    var _init = function () {
        var dependencies = ['Storage', 'Utils'];
        tag.plugins.waitForDependencies(dependencies, function () {
            var uuid = ATInternet.Utils.uuid();
            _eventID = parseInt(uuid.num(8));
            // Attach event in order to process OPT-OUT actions
            ATInternet.Utils.removeOptOutEvent(_processEvent);
            ATInternet.Utils.addOptOutEvent(_eventID, _processEvent);
            _run();
        });
    };

    // Initialise global process.
    _init();

    /**
     * [Object added by plugin {@link ATInternet.Tracker.Plugins.ClientSideUserId ClientSideUserId}] Tags to manage a user ID on client side mode.
     * @name clientSideUserId
     * @inner
     * @type {object}
     * @memberof ATInternet.Tracker.Tag
     * @property {function} set Tag helper, see details here {@link ATInternet.Tracker.Tag#clientSideUserId.set}
     * @public
     */
    tag.clientSideUserId = {};

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.ClientSideUserId ClientSideUserId}]
     * Add a user ID to all hits "&idclient=" and store it in "atuserid".
     * @alias clientSideUserId.set
     * @memberof! ATInternet.Tracker.Tag#
     * @function
     * @param id {string|number} User ID
     * @example
     * <pre><code class="javascript">tag.clientSideUserId.set('abc123');
     * </code></pre>
     * @public
     */
    tag.clientSideUserId.set = function (id) {
        if (!_user.optout.isOptedout) {
            _user.finalUserId = id;
            _user.isFromTrackerContext = true;
            _user.forceStorage = false;
            _setUserId();
        }
        /* @if debug */
        tag.debug('ClientSideUserId:clientSideUserId:set', _debug.level, _debug.messageEnd, {id: id});
        /* @endif */
    };

    /**
     *[Helper added by plugin {@link ATInternet.Tracker.Plugins.ClientSideUserId ClientSideUserId}] Force idclient storage (useful for consent page mode).
     * @alias clientSideUserId.store
     * @memberof! ATInternet.Tracker.Tag#
     * @function
     * @example
     * <pre><code class="javascript">tag.clientSideUserId.store();</code></pre>
     * @public
     */
    tag.clientSideUserId.store = function () {
        _user.finalUserId = (tag.getParam(_config.userIdHitName) || _user.finalUserId);
        if (_user.finalUserId !== null &&
            _user.finalUserId !== ATInternet.Utils.privacy.CONSENTNO &&
            _user.finalUserId !== _user.storageUserId) {
            _user.isFromTrackerContext = true;
            _user.forceStorage = true;
            _setStorageValue();
        }
        /* @if debug */
        tag.debug('ClientSideUserId:clientSideUserId:store', _debug.level, _debug.messageEnd, {id: _user.finalUserId});
        /* @endif */
    };

    /**
     *[Helper added by plugin {@link ATInternet.Tracker.Plugins.ClientSideUserId ClientSideUserId}] Get user ID.
     * @alias clientSideUserId.get
     * @memberof! ATInternet.Tracker.Tag#
     * @function
     * @example
     * <pre><code class="javascript">tag.clientSideUserId.get();</code></pre>
     * @return {string|null}
     * @public
     */
    tag.clientSideUserId.get = function () {
        /* @if debug */
        tag.debug('ClientSideUserId:clientSideUserId:get', _debug.level, _debug.messageEnd, {id: _user.finalUserId});
        /* @endif */
        _user.finalUserId = (tag.getParam(_config.userIdHitName) || _user.finalUserId);
        return _user.finalUserId;
    };

    /**
     *[Helper added by plugin {@link ATInternet.Tracker.Plugins.ClientSideUserId ClientSideUserId}] Clear user ID.
     * @alias clientSideUserId.clear
     * @memberof! ATInternet.Tracker.Tag#
     * @function
     * @example
     * <pre><code class="javascript">tag.clientSideUserId.clear();</code></pre>
     * @public
     */
    tag.clientSideUserId.clear = function () {
        /* @if debug */
        tag.debug('ClientSideUserId:clientSideUserId:clear', _debug.level, _debug.messageEnd, {id: _user.finalUserId});
        /* @endif */
        _initUser();
        tag.delParam(_config.userIdHitName);
        tag.storage.del(_config.userIdStorageName);
    };

    // For unit tests on private elements !!!
    /* @if test */
    var _this = this;
    _this._isClientConfiguration = _isClientConfiguration;
    _this._setOptoutSettings = _setOptoutSettings;
    _this._isServerSideUserId = _isServerSideUserId;
    _this._isITPCompatible = _isITPCompatible;
    _this._initUser = _initUser;
    _this._initContext = _initContext;
    _this._processUser = _processUser;
    _this._setStorageValue = _setStorageValue;
    _this._setUserId = _setUserId;
    _this._run = _run;
    _this._processEvent = _processEvent;
    _this._init = _init;
    _this._userIdCookieDuration = _config.userIdCookieDuration;
    _this._userIdExpirationMode = _config.userIdExpirationMode;
    _this._optOut = _config.optOut;
    _this._userIdStorageName = _config.userIdStorageName;
    _this._userIdFromContext = _user.contextUserId;
    _this._userIdFromStorage = _user.storageUserId;
    _this.getThemAll = function () {
        _this._userIdCookieDuration = _config.userIdCookieDuration;
        _this._userIdExpirationMode = _config.userIdExpirationMode;
        _this._optOut = _config.optOut;
        _this._userIdStorageName = _config.userIdStorageName;
        _this._userIdFromContext = _user.contextUserId;
        _this._userIdFromStorage = _user.storageUserId;
    };
    /* @endif */
};
ATInternet.Tracker.addPlugin('ClientSideUserId');