/**
* @class
* @name Offline
* @memberof ATInternet.Tracker.Plugins
* @type {function}
* @param tag {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
*/
ATInternet.Tracker.Plugins.Offline = function (tag) {
'use strict';
var _config = {};
var _debug = {
level: 'DEBUG',
messageEnd: 'method ended',
messageStart: 'method started'
};
var _triggersAdded = false;
// Set specific plugin configuration.
// If global configuration already exists, set only undefined properties.
tag.configPlugin('Offline', dfltPluginCfg || {}, function (newConf) {
_config = newConf;
});
/**
* Get data from local storage.
* @memberof ATInternet.Tracker.Plugins.Offline#
* @function
* @return {object}
* @private
*/
var _getStorageData = function () {
var storage = localStorage.getItem('ATOffline');
var data = {
hits: [],
length: 0
};
if (storage) {
var storageObject = ATInternet.Utils.jsonParse(storage) || {hits: []};
data.hits = storageObject.hits;
data.length = storage.length;
}
return data;
};
/**
* 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 */
tag.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 */
tag.debug('Offline:offline:getLength', _debug.level, _debug.messageStart);
/* @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 */
tag.debug('Offline:offline:remove', _debug.level, _debug.messageEnd);
/* @endif */
};
/**
* Get hits array from local storage.
* @memberof ATInternet.Tracker.Plugins.Offline#
* @function
* @return {Array}
* @private
*/
var _getHitsFromStorage = function () {
/* @if debug */
tag.debug('Offline:offline:get', _debug.level, _debug.messageStart);
/* @endif */
return _getStorageData().hits;
};
/**
* Add offline parameter to hit in order to store it.
* @memberof ATInternet.Tracker.Plugins.Offline#
* @function
* @param hit {string} Hit value
* @private
*/
var _processSingleHit = function (hit) {
if (hit) {
var str = hit.split(/&ref=\.*/i);
var params = '&cn=offline&olt=' + String(Math.floor(new Date().getTime() / 1000));
hit = str[0] + params + (str[1] ? '&ref=' + str[1] : '');
}
return hit;
};
/**
* Store a new hit in local storage.
* @memberof ATInternet.Tracker.Plugins.Offline#
* @function
* @param hit {string} Hit value
* @private
*/
var _storeSingleHit = function (hit) {
var storageData = _getStorageData();
var storageLength = storageData.length || '{"hits":[]}'.length;
var hitLength = hit.length;
var processHit = true;
// Remove old hits if necessary
if (((storageLength + hitLength) * 4) > (_config.storageCapacity * 1024 * 1024)) {
processHit = false;
var storageHit = storageData.hits.shift();
if (typeof storageHit !== 'undefined') {
processHit = true;
var storageHitLength = storageHit.length;
while (storageHitLength < hitLength) {
storageHit = storageData.hits.shift();
if (typeof storageHit !== 'undefined') {
storageHitLength += storageHit.length;
} else {
processHit = false;
break;
}
}
}
}
if (processHit) {
// Push new hit
storageData.hits.push(hit);
// Update local storage
_setStorageData({hits: storageData.hits});
}
};
/**
* Send a new hit if it exists after the Tracker has been triggered.
* @memberof ATInternet.Tracker.Plugins.Offline#
* @function
* @param hitSent {string} Hit sent
* @param newHit {string} New hit to send
* @param callback {function} Callback to execute
* @param requestMethod {string} Overloading the global method of sending hits (GET|POST)
* @param elementType {string} Element type (mailto, form, redirection)
* @private
*/
function _callback(hitSent, newHit, callback, requestMethod, elementType) {
if (!newHit || (hitSent !== newHit)) {
_sendHitsFromStorage(newHit, callback, requestMethod, elementType);
}
}
/**
* Fire all hits from local storage.
* @memberof ATInternet.Tracker.Plugins.Offline#
* @function
* @param newHit {string} New hit to send
* @param callback {function} Callback to execute
* @param requestMethod {string} Overloading the global method of sending hits (GET|POST)
* @param elementType {string} Element type (mailto, form, redirection)
* @private
*/
function _sendHitsFromStorage(newHit, callback, requestMethod, elementType) {
if (window.navigator && window.navigator.onLine) {
var hits = _getHitsFromStorage();
if (hits.length > 0) {
// Get first hit
var storageHit = hits.shift();
// Update local storage
_setStorageData({hits: hits});
// Process next hit from local storage after triggers
if (!_triggersAdded) {
tag.onTrigger('Tracker:Hit:Sent:Ok', function (event, data) {
_callback(data.details.hit, newHit, callback, requestMethod, elementType);
}, false);
tag.onTrigger('Tracker:Hit:Sent:Error', function (event, data) {
_callback(data.details.hit, newHit, callback, requestMethod, elementType);
}, false);
_triggersAdded = true;
}
// Forcing the use of the 'GET' method for sending to keep the RichMedia scheduling management, in particular
tag.sendUrl(storageHit, null, 'GET', elementType);
} else if (newHit) {
// Send new hit if present
var action = tag.utils.getQueryStringValue('a', newHit);
if (action) {
// Add timeout for Rich Media in order to let enough time to
// possible last storage hit currently being sent
setTimeout(function () {
// Keep the default sending method for non-storage cases
tag.sendUrl(newHit, null, null, null);
}, _config.timeout);
} else {
// Keep the default sending method for non-storage cases
tag.sendUrl(newHit, callback, requestMethod, elementType);
}
}
} else if (newHit) {
// Store new hit if present
_storeSingleHit(_processSingleHit(newHit));
callback && callback();
}
}
/**
* Process all hits.
* @memberof ATInternet.Tracker.Plugins.Offline#
* @function
* @param store {boolean} Store new hits depending on configuration
* @private
*/
var _processHits = function (store) {
// Overload sending method of Tracker
tag.builder.sendUrl = function (hit, callback, requestMethod, elementType) {
// Store current hit if necessary
if (store || (window.navigator && !window.navigator.onLine)) {
_storeSingleHit(_processSingleHit(hit));
callback && callback();
} else {
_sendHitsFromStorage(hit, callback, requestMethod, elementType);
}
};
};
/**
* Run global process.
* @memberof ATInternet.Tracker.Plugins.Offline#
* @function
* @private
*/
var _run = function () {
var isLocalStorageAvailable = ATInternet.Utils.isLocalStorageAvailable();
var isOnLine;
if (window.navigator) {
isOnLine = window.navigator.onLine;
}
if (isLocalStorageAvailable && (typeof isOnLine !== 'undefined')) {
if (_config.storageMode === 'required') {
_processHits(false);
} else if (_config.storageMode === 'always') {
_processHits(true);
}
}
tag.emit('Offline:Ready', {
lvl: 'INFO',
details: {
isLocalStorageAvailable: isLocalStorageAvailable,
storageMode: _config.storageMode,
isOnline: isOnLine
}
});
};
/**
* Init method, check dependencies and launch the plugin when all dependencies are present
* @memberof ATInternet.Tracker.Plugins.Offline#
* @function
* @private
*/
var _init = function () {
var dependencies = ['Utils'];
tag.plugins.waitForDependencies(dependencies, _run);
};
/**
* [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
*/
tag.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
*/
tag.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
*/
tag.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
*/
tag.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
*/
tag.offline.send = function () {
_sendHitsFromStorage(null, null, 'GET');
/* @if debug */
tag.debug('Offline:offline:send', _debug.level, _debug.messageEnd);
/* @endif */
};
// Init global process.
_init();
// For unit tests on private elements !!!
/* @if test */
var _this = this;
_this._getStorageData = _getStorageData;
_this._setStorageData = _setStorageData;
_this._getStorageDataLength = _getStorageDataLength;
_this._getHitsFromStorage = _getHitsFromStorage;
_this._removeHitsFromStorage = _removeHitsFromStorage;
_this._processSingleHit = _processSingleHit;
_this._callback = _callback;
_this._sendHitsFromStorage = _sendHitsFromStorage;
_this._processHits = _processHits;
_this._storeSingleHit = _storeSingleHit;
_this._run = _run;
_this._init = _init;
/* @endif */
};
window['ATInternet']['Tracker']['addPlugin']('Offline');