Source: Offline/offline.js

/**
 * @class
 * @name Offline
 * @memberof ATInternet.Tracker.Plugins
 * @type {function}
 * @param parent {object} Instance of the Tag used
 * @description
 * Plugin to save hits that could not be sent, following a loss of Internet connection, to the user’s browser.
 * <br />To do this, the plugin uses the localStorage feature available in recent browsers.
 * The status of the Internet connection (whether the browser is online or not) is determined by the browser’s onLine property available in HTML5.
 * @public
 */
window['ATInternet']['Tracker']['Plugins']['Offline'] = function (parent) {
    "use strict";

    var _this = this;
    var _config = {};

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

    /**
     * Check if local storage is both supported and available.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @return {boolean}
     * @private
     */
    var _isLocalStorageAvailable = function () {
        try {
            var storage = window['localStorage'],
                x = '__storage_test__';
            storage.setItem(x, x);
            storage.removeItem(x);
            return true;
        }
        catch (e) {
            /* @if debug */
            parent.debug('Offline:localStorage:Error', 'ERROR', 'Local storage may be unsupported/unavailable', {except: e});
            /* @endif */
            return false;
        }
    };

    /**
     * Get stored data from local storage.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @return {object}
     * @private
     */
    var _getStorageData = function () {
        var st = localStorage.getItem('ATOffline');
        var obj = {
            hits: [],
            length: 0
        };
        if (st) {
            var stObj = ATInternet.Utils.jsonParse(st) || {hits: []};
            obj.hits = stObj.hits;
            obj.length = st.length;
        }
        return obj;
    };

    /**
     * Set data into local storage.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @param value {object} Object containing array of hits
     * @return {object}
     * @private
     */
    var _setStorageData = function (value) {
        try {
            localStorage.setItem('ATOffline', ATInternet.Utils.jsonSerialize(value));
        }
        catch (e) {
            /* @if debug */
            parent.debug('Offline:localStorage:setItem:Error', 'ERROR', 'Cannot set value in local storage', {
                except: e,
                value: ATInternet.Utils.jsonSerialize(value)
            });
            /* @endif */
        }
    };

    /**
     * Get estimated storage data length (max bytes).
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @return {number}
     * @private
     */
    var _getStorageDataLength = function () {
        /* @if debug */
        parent.debug('Offline:offline:getLength', 'DEBUG', 'method started');
        /* @endif */
        return _getStorageData().length * 4;
    };

    /**
     * Remove hits from local storage.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @private
     */
    var _removeHitsFromStorage = function () {
        var value = {hits: []};
        _setStorageData(value);
        /* @if debug */
        parent.debug('Offline:offline:remove', 'DEBUG', 'method ended');
        /* @endif */
    };

    /**
     * Get hits array from local storage.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @return {Array}
     * @private
     */
    var _getHitsFromStorage = function () {
        /* @if debug */
        parent.debug('Offline:offline:get', 'DEBUG', 'method started');
        /* @endif */
        return _getStorageData().hits;
    };

    /**
     * Fire single hit from hits array.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @param hits {Array} List of hits to send
     * @private
     */
    var _sendSingleHit = function (hits) {
        if (hits.length > 0) {
            // Get URL
            var url = hits.shift();
            // Update localStorage
            var storageHits = _getHitsFromStorage();
            if (storageHits && storageHits[0] === url) {
                storageHits.shift();
                var value = {hits: storageHits};
                _setStorageData(value);
            }
            // Update local object
            hits = storageHits;
            // Add action on trigger
            parent.onTrigger('Tracker:Hit:Sent:Ok', function () {
                _sendSingleHit(hits);
            }, true);
            // Send URL
            parent.sendUrl(url);
        }
        else {
            _sendHitsFromStorage(false);
        }
    };

    /**
     * Fire all hits from local storage.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @param store {boolean} Store new hits during dispatch if necessary
     * @private
     */
    var _sendHitsFromStorage = function (store) {
        var hits = _getHitsFromStorage();
        if (hits.length > 0) {
            _sendSingleHit(hits);
            if (store) {
                _storeHits();
            }
        }
    };

    /**
     * Store a new hit in local storage.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @param hit {string} Hit value
     * @private
     */
    var _storeSingleHit = function (hit) {
        var obj = _getStorageData();
        var storageLength = obj.length || '{"hits":[]}'.length;
        var hitLength = ATInternet.Utils.jsonSerialize(hit).length;
        if (((storageLength + hitLength) * 4) > (_config.storageCapacity * 1024 * 1024)) {
            obj.hits.shift();
        }
        obj.hits.push(hit);
        var value = {hits: obj.hits};
        _setStorageData(value);
    };

    /**
     * Store all new hits into local storage.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @private
     */
    var _storeHits = function () {
        var _process = function (h) {
            if (h) {
                var str = h.split(/&ref=\.*/i);
                var params = '&cn=offline&olt=' + new Date().getTime();
                h = str[0] + params + '&ref=' + (str[1] || '');
            }
            return h;
        };
        parent.builder.sendUrl = function (hit) {
            _storeSingleHit(_process(hit));
        };
    };

    /**
     * Initialise global process.
     * @memberof ATInternet.Tracker.Plugins.Offline#
     * @function
     * @private
     */
    var _init = function () {
        var isLocalStorage = _isLocalStorageAvailable();
        var isOnline = navigator.onLine;
        if (isLocalStorage && (typeof isOnline !== 'undefined')) {
            var storageMode = _config.storageMode;
            if (storageMode === 'required') {
                if (isOnline) {
                    _sendHitsFromStorage(true);
                }
                else {
                    _storeHits();
                }
            }
            else if (storageMode === 'always') {
                _storeHits();
            }
        }
        parent.emit('Offline:Ready', {
            lvl: 'INFO',
            details: {
                isLocalStorageAvailable: isLocalStorage,
                storageMode: _config.storageMode,
                isOnline: isOnline
            }
        });
    };

    /**
     * [Object added by plugin {@link ATInternet.Tracker.Plugins.Offline Offline}] Tags to manage local storage.
     * @name offline
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @type {object}
     * @property {function} getLength Tag helper, see details here {@link ATInternet.Tracker.Tag#offline.getLength}
     * @property {function} remove Tag helper, see details here {@link ATInternet.Tracker.Tag#offline.remove}
     * @property {function} get Tag helper, see details here {@link ATInternet.Tracker.Tag#offline.get}
     * @property {function} send Tag helper, see details here {@link ATInternet.Tracker.Tag#offline.send}
     * @public
     */
    parent['offline'] = {};

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.Offline Offline}] Get estimated storage data length.
     * @alias offline.getLength
     * @memberof! ATInternet.Tracker.Tag#
     * @function
     * @returns {number}
     * @example
     * <pre><code class="javascript">var len = tag.offline.getLength();
     * </code></pre>
     * @public
     */
    parent['offline']['getLength'] = _getStorageDataLength;

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.Offline Offline}] Remove hits from local storage.
     * @alias offline.remove
     * @memberof! ATInternet.Tracker.Tag#
     * @function
     * @example
     * <pre><code class="javascript">tag.offline.remove();
     * </code></pre>
     * @public
     */
    parent['offline']['remove'] = _removeHitsFromStorage;

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.Offline Offline}] Get hits array from local storage.
     * @alias offline.get
     * @memberof! ATInternet.Tracker.Tag#
     * @function
     * @returns {Array}
     * @example
     * <pre><code class="javascript">var hitArr = tag.offline.get();
     * </code></pre>
     * @public
     */
    parent['offline']['get'] = _getHitsFromStorage;

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.Offline Offline}] Send hits from local storage.
     * @alias offline.send
     * @memberof! ATInternet.Tracker.Tag#
     * @function
     * @example
     * <pre><code class="javascript">tag.offline.send();
     * </code></pre>
     * @public
     */
    parent['offline']['send'] = function () {
        _sendHitsFromStorage(false);
        /* @if debug */
        parent.debug('Offline:offline:send', 'DEBUG', 'method ended');
        /* @endif */
    };

    // Initialises global process.
    _init();

    // For unit tests on private elements !!!
    /* @if test */
    _this._isLocalStorageAvailable = _isLocalStorageAvailable;
    _this._getStorageData = _getStorageData;
    _this._setStorageData = _setStorageData;
    _this._getStorageDataLength = _getStorageDataLength;
    _this._removeHitsFromStorage = _removeHitsFromStorage;
    _this._getHitsFromStorage = _getHitsFromStorage;
    _this._sendSingleHit = _sendSingleHit;
    _this._sendHitsFromStorage = _sendHitsFromStorage;
    _this._storeSingleHit = _storeSingleHit;
    _this._storeHits = _storeHits;
    _this._init = _init;
    /* @endif */

};
window['ATInternet']['Tracker']['addPlugin']('Offline');