countly-sdk-web
Version:
Countly Web SDK
1,135 lines (1,086 loc) • 350 kB
JavaScript
/** **********
* Countly Web SDK
* https://github.com/Countly/countly-sdk-web
*********** */
/**
* Countly object to manage the internal queue and send requests to Countly server. More information on {@link https://resources.count.ly/docs/countly-sdk-for-web}
* @name Countly
* @global
* @namespace Countly
* @example <caption>SDK integration</caption>
* <script type="text/javascript">
*
* //some default pre init
* var Countly = Countly || {};
* Countly.q = Countly.q || [];
*
* //provide your app key that you retrieved from Countly dashboard
* Countly.app_key = "YOUR_APP_KEY";
*
* //provide your server IP or name. Use try.count.ly for EE trial server.
* //if you use your own server, make sure you have https enabled if you use
* //https below.
* Countly.url = "https://yourdomain.com";
*
* //start pushing function calls to queue
* //track sessions automatically
* Countly.q.push(["track_sessions"]);
*
* //track sessions automatically
* Countly.q.push(["track_pageview"]);
*
* //load countly script asynchronously
* (function() {
* var cly = document.createElement("script"); cly.type = "text/javascript";
* cly.async = true;
* //enter url of script here
* cly.src = "https://cdn.jsdelivr.net/countly-sdk-web/latest/countly.min.js";
* cly.onload = function(){Countly.init()};
* var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(cly, s);
* })();
* </script>
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Countly = global.Countly || {}));
})(this, (function (exports) {
'use strict';
function _assertClassBrand(e, t, n) {
if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
throw new TypeError("Private element is not present on this object");
}
function _checkPrivateRedeclaration(e, t) {
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
}
function _classCallCheck(a, n) {
if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
}
function _classPrivateFieldGet2(s, a) {
return s.get(_assertClassBrand(s, a));
}
function _classPrivateFieldInitSpec(e, t, a) {
_checkPrivateRedeclaration(e, t), t.set(e, a);
}
function _classPrivateFieldSet2(s, a, r) {
return s.set(_assertClassBrand(s, a), r), r;
}
function _createClass(e, r, t) {
return Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _readOnlyError(r) {
throw new TypeError('"' + r + '" is read-only');
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != typeof i) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == typeof i ? i : i + "";
}
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
// Feature ENUMS
var featureEnums = {
SESSIONS: "sessions",
EVENTS: "events",
VIEWS: "views",
SCROLLS: "scrolls",
CLICKS: "clicks",
FORMS: "forms",
CRASHES: "crashes",
ATTRIBUTION: "attribution",
USERS: "users",
STAR_RATING: "star-rating",
LOCATION: "location",
APM: "apm",
FEEDBACK: "feedback",
REMOTE_CONFIG: "remote-config"
};
/**
* At the current moment there are following internal events and their respective required consent:
[CLY]_nps - "feedback" consent
[CLY]_survey - "feedback" consent
[CLY]_star_rating - "star_rating" consent
[CLY]_view - "views" consent
[CLY]_orientation - "users" consent
[CLY]_push_action - "push" consent
[CLY]_action - "clicks" or "scroll" consent
*/
var internalEventKeyEnums = {
NPS: "[CLY]_nps",
SURVEY: "[CLY]_survey",
STAR_RATING: "[CLY]_star_rating",
VIEW: "[CLY]_view",
ORIENTATION: "[CLY]_orientation",
ACTION: "[CLY]_action"
};
var internalEventKeyEnumsArray = Object.values(internalEventKeyEnums);
/**
*
*log level Enums:
*Error - this is a issues that needs attention right now.
*Warning - this is something that is potentially a issue. Maybe a deprecated usage of something, maybe consent is enabled but consent is not given.
*Info - All publicly exposed functions should log a call at this level to indicate that they were called. These calls should include the function name.
*Debug - this should contain logs from the internal workings of the SDK and it's important calls. This should include things like the SDK configuration options, success or fail of the current network request, "request queue is full" and the oldest request get's dropped, etc.
*Verbose - this should give a even deeper look into the SDK's inner working and should contain things that are more noisy and happen often.
*/
var logLevelEnums = {
ERROR: "[ERROR] ",
WARNING: "[WARNING] ",
INFO: "[INFO] ",
DEBUG: "[DEBUG] ",
VERBOSE: "[VERBOSE] "
};
/**
*
*device ID type:
*0 - device ID was set by the developer during init
*1 - device ID was auto generated by Countly
*2 - device ID was temporarily given by Countly
*3 - device ID was provided from location.search
*/
var DeviceIdTypeInternalEnums = {
DEVELOPER_SUPPLIED: 0,
SDK_GENERATED: 1,
TEMPORARY_ID: 2,
URL_PROVIDED: 3
};
/**
* to be used as a default value for certain configuration key values
*/
var configurationDefaultValues = {
BEAT_INTERVAL: 500,
QUEUE_SIZE: 1000,
FAIL_TIMEOUT_AMOUNT: 60,
INACTIVITY_TIME: 20,
SESSION_UPDATE: 60,
MAX_EVENT_BATCH: 100,
SESSION_COOKIE_TIMEOUT: 30,
MAX_KEY_LENGTH: 128,
MAX_VALUE_SIZE: 256,
MAX_SEGMENTATION_VALUES: 100,
MAX_BREADCRUMB_COUNT: 100,
MAX_STACKTRACE_LINES_PER_THREAD: 30,
MAX_STACKTRACE_LINE_LENGTH: 200
};
/**
* BoomerangJS and countly
*/
var CDN = {
BOOMERANG_SRC: "https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/plugin/boomerang/boomerang.min.js",
CLY_BOOMERANG_SRC: "https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/plugin/boomerang/countly_boomerang.js"
};
/**
* Health check counters' local storage keys
*/
var healthCheckCounterEnum = Object.freeze({
errorCount: "cly_hc_error_count",
warningCount: "cly_hc_warning_count",
statusCode: "cly_hc_status_code",
errorMessage: "cly_hc_error_message"
});
var SDK_VERSION = "25.1.0";
var SDK_NAME = "javascript_native_web";
// Using this on document.referrer would return an array with 17 elements in it. The 12th element (array[11]) would be the path we are looking for. Others would be things like password and such (use https://regex101.com/ to check more)
// an example URL:
// http://user:pass@host:8080/path/to/resource?query=value#fragment
// this url would yield the following result for matches = urlParseRE.exec(document.referrer);
//
// 0: "http://user:pass@host:8080/path/to/resource?query=value#fragment"
// 1: "http://user:pass@host:8080/path/to/resource?query=value"
// 2: "http://user:pass@host:8080/path/to/resource"
// 3: "http://user:pass@host:8080"
// 4: "http:"
// 5: "//"
// 6: "user:pass@host:8080"
// 7: "user:pass"
// 8: "user"
// 9: "pass"
// 10: "host:8080"
// 11: "host"
// 12: "8080"
// 13: "/path/to/resource"
// 14: "/path/to/"
// 15: "resource"
// 16: "?query=value"
// 17: "#fragment"
var urlParseRE = /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/;
var isBrowser = typeof window !== "undefined";
var Countly = globalThis.Countly || {};
/**
* Get selected values from multi select input
* @param {HTMLElement} input - select with multi true option
* @returns {String} coma concatenated values
*/
function getMultiSelectValues(input) {
var values = [];
if (typeof input.options !== "undefined") {
for (var j = 0; j < input.options.length; j++) {
if (input.options[j].selected) {
values.push(input.options[j].value);
}
}
}
return values.join(", ");
}
/**
* Return a crypto-safe random string
* @memberof Countly._internals
* @returns {string} - random string
*/
function secureRandom() {
var id = "xxxxxxxx";
id = replacePatternWithRandomValues(id, "[x]");
// timestamp in milliseconds
var timestamp = Date.now().toString();
return id + timestamp;
}
/**
* Generate random UUID value
* @memberof Countly._internals
* @returns {String} random UUID value
*/
function generateUUID() {
var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
uuid = replacePatternWithRandomValues(uuid, "[xy]");
return uuid;
}
/**
* Generate random value based on pattern
*
* @param {string} str - string to replace
* @param {string} pattern - pattern to replace
* @returns {string} - replaced string
*/
function replacePatternWithRandomValues(str, pattern) {
var d = new Date().getTime();
var regex = new RegExp(pattern, "g");
return str.replace(regex, function (c) {
var r = (d + Math.random() * 16) % 16 | 0;
return (c === "x" ? r : r & 0x3 | 0x8).toString(16);
});
}
/**
* Get unix timestamp
* @memberof Countly._internals
* @returns {Number} unix timestamp
*/
function getTimestamp() {
return Math.floor(new Date().getTime() / 1000);
}
var lastMsTs = 0;
/**
* Get unique timestamp in milliseconds
* @memberof Countly._internals
* @returns {Number} milliseconds timestamp
*/
function getMsTimestamp() {
var ts = new Date().getTime();
if (lastMsTs >= ts) {
lastMsTs++;
} else {
lastMsTs = ts;
}
return lastMsTs;
}
/**
* Get config value from multiple sources
* like config object, global object or fallback value
* @param {String} key - config key
* @param {Object} ob - config object
* @param {Varies} override - fallback value
* @returns {Varies} value to be used as config
*/
function getConfig(key, ob, override) {
if (ob && Object.keys(ob).length) {
if (typeof ob[key] !== "undefined") {
return ob[key];
}
} else if (typeof Countly[key] !== "undefined") {
return Countly[key];
}
return override;
}
/**
* Dispatch errors to instances that lister to errors
* @param {Error} error - Error object
* @param {Boolean} fatality - fatal if false and nonfatal if true
* @param {Object} segments - custom crash segments
*/
function dispatchErrors(error, fatality, segments) {
// Check each instance like Countly.i[app_key_1], Countly.i[app_key_2] ...
for (var app_key in Countly.i) {
// If track_errors is enabled for that instance
if (Countly.i[app_key].tracking_crashes) {
// Trigger recordError function for that instance
Countly.i[app_key].recordError(error, fatality, segments);
}
}
}
/**
* Convert JSON object to URL encoded query parameter string
* @memberof Countly._internals
* @param {Object} params - object with query parameters
* @param {String} salt - salt to be used for checksum calculation
* @returns {String} URL encode query string
*/
function prepareParams(params, salt) {
var str = [];
for (var i in params) {
str.push(i + "=" + encodeURIComponent(params[i]));
}
var data = str.join("&");
if (salt) {
return calculateChecksum(data, salt).then(function (checksum) {
data += "&checksum256=" + checksum;
return data;
});
}
return Promise.resolve(data);
}
/**
* Removing trailing slashes
* @memberof Countly._internals
* @param {String} str - string from which to remove trailing slash
* @returns {String} modified string
*/
function stripTrailingSlash(str) {
if (typeof str === "string") {
if (str.substring(str.length - 1) === "/") {
return str.substring(0, str.length - 1);
}
}
return str;
}
/**
* Retrieve only specific properties from object
* @memberof Countly._internals
* @param {Object} orig - original object
* @param {Array} props - array with properties to get from object
* @returns {Object} new object with requested properties
*/
function createNewObjectFromProperties(orig, props) {
var ob = {};
var prop;
for (var i = 0, len = props.length; i < len; i++) {
prop = props[i];
if (typeof orig[prop] !== "undefined") {
ob[prop] = orig[prop];
}
}
return ob;
}
/**
* Add specified properties to an object from another object
* @memberof Countly._internals
* @param {Object} orig - original object
* @param {Object} transferOb - object to copy values from
* @param {Array} props - array with properties to get from object
* @returns {Object} original object with additional requested properties
*/
function addNewProperties(orig, transferOb, props) {
if (!props) {
return;
}
var prop;
for (var i = 0, len = props.length; i < len; i++) {
prop = props[i];
if (typeof transferOb[prop] !== "undefined") {
orig[prop] = transferOb[prop];
}
}
return orig;
}
/**
* Truncates an object's key/value pairs to a certain length
* @param {Object} obj - original object to be truncated
* @param {Number} keyLimit - limit for key length
* @param {Number} valueLimit - limit for value length
* @param {Number} segmentLimit - limit for segments pairs
* @param {string} errorLog - prefix for error log
* @param {function} logCall - internal logging function
* @returns {Object} - the new truncated object
*/
function truncateObject(obj, keyLimit, valueLimit, segmentLimit, errorLog, logCall) {
var ob = {};
if (obj) {
if (Object.keys(obj).length > segmentLimit) {
var resizedObj = {};
var i = 0;
for (var e in obj) {
if (i < segmentLimit) {
resizedObj[e] = obj[e];
i++;
}
}
obj = resizedObj;
}
for (var key in obj) {
var newKey = truncateSingleValue(key, keyLimit, errorLog, logCall);
var newValue = truncateSingleValue(obj[key], valueLimit, errorLog, logCall);
ob[newKey] = newValue;
}
}
return ob;
}
/**
* Truncates a single value to a certain length
* @param {string|number} str - original value to be truncated
* @param {Number} limit - limit length
* @param {string} errorLog - prefix for error log
* @param {function} logCall - internal logging function
* @returns {string|number} - the new truncated value
*/
function truncateSingleValue(str, limit, errorLog, logCall) {
var newStr = str;
if (typeof str === "number") {
str = str.toString();
}
if (typeof str === "string") {
if (str.length > limit) {
newStr = str.substring(0, limit);
logCall(logLevelEnums.DEBUG, errorLog + ", Key: [ " + str + " ] is longer than accepted length. It will be truncated.");
}
}
return newStr;
}
/**
* Calculates the checksum of the data with the given salt
* Uses SHA-256 algorithm with web crypto API
* Implementation based on https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
* TODO: Turn to async function when we drop support for older browsers
* @param {string} data - data to be used for checksum calculation (concatenated query parameters)
* @param {string} salt - salt to be used for checksum calculation
* @returns {string} checksum in hex format
*/
function calculateChecksum(data, salt) {
var msgUint8 = new TextEncoder().encode(data + salt); // encode as (utf-8) Uint8Array
return crypto.subtle.digest("SHA-256", msgUint8).then(function (hashBuffer) {
// hash the message
var hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
var hashHex = hashArray.map(function (b) {
return b.toString(16).padStart(2, "0");
}).join(""); // convert bytes to hex string
return hashHex;
});
}
/**
* Polyfill to get closest parent matching nodeName
* @param {HTMLElement} el - element from which to search
* @param {String} nodeName - tag/node name
* @returns {HTMLElement} closest parent element
*/
function get_closest_element(el, nodeName) {
nodeName = nodeName.toUpperCase();
while (el) {
if (el.nodeName.toUpperCase() === nodeName) {
return el;
}
el = el.parentElement;
}
}
/**
* Listen to specific browser event
* @memberof Countly._internals
* @param {HTMLElement} element - HTML element that should listen to event
* @param {String} type - event name or action
* @param {Function} listener - callback when event is fired
*/
function add_event_listener(element, type, listener) {
if (!isBrowser) {
return;
}
if (element === null || typeof element === "undefined") {
// element can be null so lets check it first
if (checkIfLoggingIsOn()) {
// eslint-disable-next-line no-console
console.warn("[WARNING] [Countly] add_event_listener, Can't bind [" + type + "] event to nonexisting element");
}
return;
}
if (typeof element.addEventListener !== "undefined") {
element.addEventListener(type, listener, false);
}
// for old browser use attachEvent instead
else {
element.attachEvent("on" + type, listener);
}
}
/**
* Get element that fired event
* @memberof Countly._internals
* @param {Event} event - event that was filed
* @returns {HTMLElement} HTML element that caused event to fire
*/
function get_event_target(event) {
if (!event) {
return window.event.srcElement;
}
if (typeof event.target !== "undefined") {
return event.target;
}
return event.srcElement;
}
/**
* Returns raw user agent string
* @memberof Countly._internals
* @param {string} uaOverride - a string value to pass instead of ua value
* @returns {string} currentUserAgentString - raw user agent string
*/
function currentUserAgentString(uaOverride) {
if (uaOverride) {
return uaOverride;
}
var ua_raw = navigator.userAgent;
// check if userAgentData is supported and userAgent is not available, then use it
if (!ua_raw) {
ua_raw = currentUserAgentDataString();
}
// RAW USER AGENT STRING
return ua_raw;
}
/**
* Forms user agent string from userAgentData by concatenating brand, version, mobile and platform
* @memberof Countly._internals
* @param {string} uaOverride - a string value to pass instead of ua value
* @returns {string} currentUserAgentString - user agent string from userAgentData
*/
function currentUserAgentDataString(uaOverride) {
if (uaOverride) {
return uaOverride;
}
var ua = "";
if (navigator.userAgentData) {
// turn brands array into string
ua = navigator.userAgentData.brands.map(function (e) {
return e.brand + ":" + e.version;
}).join();
// add mobile info
ua += navigator.userAgentData.mobile ? " mobi " : " ";
// add platform info
ua += navigator.userAgentData.platform;
}
return ua;
}
/**
* Returns device type information according to user agent string
* @memberof Countly._internals
* @param {string} uaOverride - a string value to pass instead of ua value
* @returns {string} userAgentDeviceDetection - current device type (desktop, tablet, phone)
*/
function userAgentDeviceDetection(uaOverride) {
var userAgent;
// TODO: refactor here
if (uaOverride) {
userAgent = uaOverride;
} else if (navigator.userAgentData && navigator.userAgentData.mobile) {
return "phone";
} else {
userAgent = currentUserAgentString();
}
// make it lowercase for regex to work properly
userAgent = userAgent.toLowerCase();
// assign the default device
var device = "desktop";
// regexps corresponding to tablets or phones that can be found in userAgent string
var tabletCheck = /(ipad|tablet|(android(?!.*mobile))|(windows(?!.*phone)(.*touch))|kindle|playbook|silk|(puffin(?!.*(IP|AP|WP))))/;
var phoneCheck = /(mobi|ipod|phone|blackberry|opera mini|fennec|minimo|symbian|psp|nintendo ds|archos|skyfire|puffin|blazer|bolt|gobrowser|iris|maemo|semc|teashark|uzard)/;
// check whether the regexp values corresponds to something in the user agent string
if (tabletCheck.test(userAgent)) {
device = "tablet";
} else if (phoneCheck.test(userAgent)) {
device = "phone";
}
// set the device type
return device;
}
/**
* Returns information regarding if the current user is a search bot or not
* @memberof Countly._internals
* @param {string} uaOverride - a string value to pass instead of ua value
* @returns {boolean} userAgentSearchBotDetection - if a search bot is reaching the site or not
*/
function userAgentSearchBotDetection(uaOverride) {
// search bot regexp
var searchBotRE = /(CountlySiteBot|nuhk|Googlebot|GoogleSecurityScanner|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver|bingbot|Google Web Preview|Mediapartners-Google|AdsBot-Google|Baiduspider|Ezooms|YahooSeeker|AltaVista|AVSearch|Mercator|Scooter|InfoSeek|Ultraseek|Lycos|Wget|YandexBot|Yandex|YaDirectFetcher|SiteBot|Exabot|AhrefsBot|MJ12bot|TurnitinBot|magpie-crawler|Nutch Crawler|CMS Crawler|rogerbot|Domnutch|ssearch_bot|XoviBot|netseer|digincore|fr-crawler|wesee|AliasIO|contxbot|PingdomBot|BingPreview|HeadlessChrome|Lighthouse)/;
// check override first
if (uaOverride) {
return searchBotRE.test(uaOverride);
}
// check both userAgent and userAgentData, as one of them might be containing the information we are looking for
var ua_bot = searchBotRE.test(currentUserAgentString());
var uaData_bot = searchBotRE.test(currentUserAgentDataString());
return ua_bot || uaData_bot;
}
/**
* Modify event to set standard coordinate properties if they are not available
* @memberof Countly._internals
* @param {Event} e - event object
* @returns {Event} modified event object
*/
function get_page_coord(e) {
// checking if pageY and pageX is already available
if (typeof e.pageY === "undefined" && typeof e.clientX === "number" && document.documentElement) {
// if not, then add scrolling positions
e.pageX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
e.pageY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
// return e which now contains pageX and pageY attributes
return e;
}
/**
* Get height of whole document
* @memberof Countly._internals
* @returns {Number} height in pixels
*/
function getDocHeight() {
var D = document;
return Math.max(Math.max(D.body.scrollHeight, D.documentElement.scrollHeight), Math.max(D.body.offsetHeight, D.documentElement.offsetHeight), Math.max(D.body.clientHeight, D.documentElement.clientHeight));
}
/**
* Get width of whole document
* @memberof Countly._internals
* @returns {Number} width in pixels
*/
function getDocWidth() {
var D = document;
return Math.max(Math.max(D.body.scrollWidth, D.documentElement.scrollWidth), Math.max(D.body.offsetWidth, D.documentElement.offsetWidth), Math.max(D.body.clientWidth, D.documentElement.clientWidth));
}
/**
* Get height of viewable area
* @memberof Countly._internals
* @returns {Number} height in pixels
*/
function getViewportHeight() {
var D = document;
return Math.min(Math.min(D.body.clientHeight, D.documentElement.clientHeight), Math.min(D.body.offsetHeight, D.documentElement.offsetHeight), window.innerHeight);
}
/**
* Get device's orientation
* @returns {String} device orientation
*/
function getOrientation() {
return window.innerWidth > window.innerHeight ? "landscape" : "portrait";
}
/**
* Load external js files
* @param {String} tag - Tag/node name to load file in
* @param {String} attr - Attribute name for type
* @param {String} type - Type value
* @param {String} src - Attribute name for file path
* @param {String} data - File path
* @param {Function} callback - callback when done
*/
function loadFile(tag, attr, type, src, data, callback) {
var fileRef = document.createElement(tag);
var loaded;
fileRef.setAttribute(attr, type);
fileRef.setAttribute(src, data);
var callbackFunction = function callbackFunction() {
if (!loaded) {
callback();
}
loaded = true;
};
if (callback) {
fileRef.onreadystatechange = callbackFunction;
fileRef.onload = callbackFunction;
}
document.getElementsByTagName("head")[0].appendChild(fileRef);
}
/**
* Load external js files
* @memberof Countly._internals
* @param {String} js - path to JS file
* @param {Function} callback - callback when done
*/
function loadJS(js, callback) {
loadFile("script", "type", "text/javascript", "src", js, callback);
}
/**
* Load external css files
* @memberof Countly._internals
* @param {String} css - path to CSS file
* @param {Function} callback - callback when done
*/
function loadCSS(css, callback) {
loadFile("link", "rel", "stylesheet", "href", css, callback);
}
/**
* Show loader UI when loading external data
* @memberof Countly._internals
*/
function showLoader() {
if (!isBrowser) {
return;
}
var loader = document.getElementById("cly-loader");
if (!loader) {
var css = "#cly-loader {height: 4px; width: 100%; position: absolute; z-index: 99999; overflow: hidden; background-color: #fff; top:0px; left:0px;}" + "#cly-loader:before{display: block; position: absolute; content: ''; left: -200px; width: 200px; height: 4px; background-color: #2EB52B; animation: cly-loading 2s linear infinite;}" + "@keyframes cly-loading { from {left: -200px; width: 30%;} 50% {width: 30%;} 70% {width: 70%;} 80% { left: 50%;} 95% {left: 120%;} to {left: 100%;}}";
var head = document.head || document.getElementsByTagName("head")[0];
var style = document.createElement("style");
style.type = "text/css";
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
loader = document.createElement("div");
loader.setAttribute("id", "cly-loader");
window.addEventListener("load", function () {
if (Countly.showLoaderProtection) {
if (checkIfLoggingIsOn()) {
console.warn("[WARNING] [Countly] showLoader, Loader is already on");
}
return;
}
try {
document.body.appendChild(loader);
} catch (e) {
if (checkIfLoggingIsOn()) {
console.error("[ERROR] [Countly] showLoader, Body is not loaded for loader to append: " + e);
}
}
});
}
loader.style.display = "block";
}
/**
* Checks if debug is true and console is available in Countly object
* @memberof Countly._internals
* @returns {Boolean} true if debug is true and console is available in Countly object
*/
function checkIfLoggingIsOn() {
// check if logging is enabled
if (Countly && Countly.debug && typeof console !== "undefined") {
return true;
}
return false;
}
/**
* Hide loader UI
* @memberof Countly._internals
*/
function hideLoader() {
if (!isBrowser) {
return;
}
// Inform showLoader that it should not append the loader
Countly.showLoaderProtection = true;
var loader = document.getElementById("cly-loader");
if (loader) {
loader.style.display = "none";
}
}
var _consentTimer = /*#__PURE__*/new WeakMap();
var _self = /*#__PURE__*/new WeakMap();
var _global = /*#__PURE__*/new WeakMap();
var _sessionStarted = /*#__PURE__*/new WeakMap();
var _apiPath = /*#__PURE__*/new WeakMap();
var _readPath = /*#__PURE__*/new WeakMap();
var _beatInterval = /*#__PURE__*/new WeakMap();
var _queueSize = /*#__PURE__*/new WeakMap();
var _requestQueue = /*#__PURE__*/new WeakMap();
var _eventQueue = /*#__PURE__*/new WeakMap();
var _remoteConfigs = /*#__PURE__*/new WeakMap();
var _crashLogs = /*#__PURE__*/new WeakMap();
var _timedEvents = /*#__PURE__*/new WeakMap();
var _ignoreReferrers = /*#__PURE__*/new WeakMap();
var _crashSegments = /*#__PURE__*/new WeakMap();
var _autoExtend = /*#__PURE__*/new WeakMap();
var _lastBeat = /*#__PURE__*/new WeakMap();
var _storedDuration = /*#__PURE__*/new WeakMap();
var _lastView = /*#__PURE__*/new WeakMap();
var _lastViewTime = /*#__PURE__*/new WeakMap();
var _lastViewStoredDuration = /*#__PURE__*/new WeakMap();
var _failTimeout = /*#__PURE__*/new WeakMap();
var _failTimeoutAmount = /*#__PURE__*/new WeakMap();
var _inactivityTime = /*#__PURE__*/new WeakMap();
var _inactivityCounter = /*#__PURE__*/new WeakMap();
var _sessionUpdate = /*#__PURE__*/new WeakMap();
var _maxEventBatch = /*#__PURE__*/new WeakMap();
var _maxCrashLogs = /*#__PURE__*/new WeakMap();
var _useSessionCookie = /*#__PURE__*/new WeakMap();
var _sessionCookieTimeout = /*#__PURE__*/new WeakMap();
var _readyToProcess = /*#__PURE__*/new WeakMap();
var _hasPulse = /*#__PURE__*/new WeakMap();
var _offlineMode = /*#__PURE__*/new WeakMap();
var _lastParams = /*#__PURE__*/new WeakMap();
var _trackTime = /*#__PURE__*/new WeakMap();
var _startTime = /*#__PURE__*/new WeakMap();
var _lsSupport = /*#__PURE__*/new WeakMap();
var _firstView = /*#__PURE__*/new WeakMap();
var _deviceIdType = /*#__PURE__*/new WeakMap();
var _isScrollRegistryOpen = /*#__PURE__*/new WeakMap();
var _scrollRegistryTopPosition = /*#__PURE__*/new WeakMap();
var _trackingScrolls = /*#__PURE__*/new WeakMap();
var _currentViewId = /*#__PURE__*/new WeakMap();
var _previousViewId = /*#__PURE__*/new WeakMap();
var _freshUTMTags = /*#__PURE__*/new WeakMap();
var _sdkName = /*#__PURE__*/new WeakMap();
var _sdkVersion = /*#__PURE__*/new WeakMap();
var _shouldSendHC = /*#__PURE__*/new WeakMap();
var _consents = /*#__PURE__*/new WeakMap();
var _generatedRequests = /*#__PURE__*/new WeakMap();
var _contentTimeInterval = /*#__PURE__*/new WeakMap();
var _contentEndPoint = /*#__PURE__*/new WeakMap();
var _inContentZone = /*#__PURE__*/new WeakMap();
var _contentZoneTimer = /*#__PURE__*/new WeakMap();
var _contentZoneTimerInterval = /*#__PURE__*/new WeakMap();
var _contentIframeID = /*#__PURE__*/new WeakMap();
var _crashFilterCallback = /*#__PURE__*/new WeakMap();
var _initialize = /*#__PURE__*/new WeakMap();
var _updateConsent = /*#__PURE__*/new WeakMap();
var _add_cly_events = /*#__PURE__*/new WeakMap();
var _report_orientation = /*#__PURE__*/new WeakMap();
var _customData = /*#__PURE__*/new WeakMap();
var _change_custom_property = /*#__PURE__*/new WeakMap();
var _fetch_remote_config_explicit = /*#__PURE__*/new WeakMap();
var _stop_time = /*#__PURE__*/new WeakMap();
var _start_time = /*#__PURE__*/new WeakMap();
var _showWidgetInternal = /*#__PURE__*/new WeakMap();
var _checkIgnore = /*#__PURE__*/new WeakMap();
var _enterContentZoneInternal = /*#__PURE__*/new WeakMap();
var _prepareContentRequest = /*#__PURE__*/new WeakMap();
var _sendContentRequest = /*#__PURE__*/new WeakMap();
var _displayContent = /*#__PURE__*/new WeakMap();
var _interpretContentMessage = /*#__PURE__*/new WeakMap();
var _closeContentFrame = /*#__PURE__*/new WeakMap();
var _sendEventsForced = /*#__PURE__*/new WeakMap();
var _processWidget = /*#__PURE__*/new WeakMap();
var _notifyLoaders = /*#__PURE__*/new WeakMap();
var _reportViewDuration = /*#__PURE__*/new WeakMap();
var _getLastView = /*#__PURE__*/new WeakMap();
var _extendSession = /*#__PURE__*/new WeakMap();
var _prepareRequest = /*#__PURE__*/new WeakMap();
var _toRequestQueue = /*#__PURE__*/new WeakMap();
var _heartBeat = /*#__PURE__*/new WeakMap();
var _getGeneratedRequests = /*#__PURE__*/new WeakMap();
var _processAsyncQueue = /*#__PURE__*/new WeakMap();
var _getStoredIdOrGenerateId = /*#__PURE__*/new WeakMap();
var _isUUID = /*#__PURE__*/new WeakMap();
var _getUA = /*#__PURE__*/new WeakMap();
var _getMetrics = /*#__PURE__*/new WeakMap();
var _getResolution = /*#__PURE__*/new WeakMap();
var _isReferrerUsable = /*#__PURE__*/new WeakMap();
var _log = /*#__PURE__*/new WeakMap();
var _makeNetworkRequest = /*#__PURE__*/new WeakMap();
var _sendXmlHttpRequest = /*#__PURE__*/new WeakMap();
var _sendFetchRequest = /*#__PURE__*/new WeakMap();
var _isResponseValid = /*#__PURE__*/new WeakMap();
var _isResponseValidBroad = /*#__PURE__*/new WeakMap();
var _processScroll = /*#__PURE__*/new WeakMap();
var _processScrollView = /*#__PURE__*/new WeakMap();
var _getInternalDeviceIdType = /*#__PURE__*/new WeakMap();
var _setToken = /*#__PURE__*/new WeakMap();
var _getToken = /*#__PURE__*/new WeakMap();
var _getEventQueue = /*#__PURE__*/new WeakMap();
var _getRequestQueue = /*#__PURE__*/new WeakMap();
var _readCookie = /*#__PURE__*/new WeakMap();
var _createCookie = /*#__PURE__*/new WeakMap();
var _getValueFromStorage = /*#__PURE__*/new WeakMap();
var _setValueInStorage = /*#__PURE__*/new WeakMap();
var _removeValueFromStorage = /*#__PURE__*/new WeakMap();
var _migrate = /*#__PURE__*/new WeakMap();
var _onStorageChange = /*#__PURE__*/new WeakMap();
var _clearQueue = /*#__PURE__*/new WeakMap();
var _getLocalQueues = /*#__PURE__*/new WeakMap();
var _HealthCheck = /*#__PURE__*/new WeakMap();
var CountlyClass = /*#__PURE__*/_createClass(function CountlyClass(_ob) {
var _this = this;
_classCallCheck(this, CountlyClass);
_classPrivateFieldInitSpec(this, _consentTimer, void 0);
_classPrivateFieldInitSpec(this, _self, void 0);
_classPrivateFieldInitSpec(this, _global, void 0);
_classPrivateFieldInitSpec(this, _sessionStarted, void 0);
_classPrivateFieldInitSpec(this, _apiPath, void 0);
_classPrivateFieldInitSpec(this, _readPath, void 0);
_classPrivateFieldInitSpec(this, _beatInterval, void 0);
_classPrivateFieldInitSpec(this, _queueSize, void 0);
_classPrivateFieldInitSpec(this, _requestQueue, void 0);
_classPrivateFieldInitSpec(this, _eventQueue, void 0);
_classPrivateFieldInitSpec(this, _remoteConfigs, void 0);
_classPrivateFieldInitSpec(this, _crashLogs, void 0);
_classPrivateFieldInitSpec(this, _timedEvents, void 0);
_classPrivateFieldInitSpec(this, _ignoreReferrers, void 0);
_classPrivateFieldInitSpec(this, _crashSegments, void 0);
_classPrivateFieldInitSpec(this, _autoExtend, void 0);
_classPrivateFieldInitSpec(this, _lastBeat, void 0);
_classPrivateFieldInitSpec(this, _storedDuration, void 0);
_classPrivateFieldInitSpec(this, _lastView, void 0);
_classPrivateFieldInitSpec(this, _lastViewTime, void 0);
_classPrivateFieldInitSpec(this, _lastViewStoredDuration, void 0);
_classPrivateFieldInitSpec(this, _failTimeout, void 0);
_classPrivateFieldInitSpec(this, _failTimeoutAmount, void 0);
_classPrivateFieldInitSpec(this, _inactivityTime, void 0);
_classPrivateFieldInitSpec(this, _inactivityCounter, void 0);
_classPrivateFieldInitSpec(this, _sessionUpdate, void 0);
_classPrivateFieldInitSpec(this, _maxEventBatch, void 0);
_classPrivateFieldInitSpec(this, _maxCrashLogs, void 0);
_classPrivateFieldInitSpec(this, _useSessionCookie, void 0);
_classPrivateFieldInitSpec(this, _sessionCookieTimeout, void 0);
_classPrivateFieldInitSpec(this, _readyToProcess, void 0);
_classPrivateFieldInitSpec(this, _hasPulse, void 0);
_classPrivateFieldInitSpec(this, _offlineMode, void 0);
_classPrivateFieldInitSpec(this, _lastParams, void 0);
_classPrivateFieldInitSpec(this, _trackTime, void 0);
_classPrivateFieldInitSpec(this, _startTime, void 0);
_classPrivateFieldInitSpec(this, _lsSupport, void 0);
_classPrivateFieldInitSpec(this, _firstView, void 0);
_classPrivateFieldInitSpec(this, _deviceIdType, void 0);
_classPrivateFieldInitSpec(this, _isScrollRegistryOpen, void 0);
_classPrivateFieldInitSpec(this, _scrollRegistryTopPosition, void 0);
_classPrivateFieldInitSpec(this, _trackingScrolls, void 0);
_classPrivateFieldInitSpec(this, _currentViewId, void 0);
_classPrivateFieldInitSpec(this, _previousViewId, void 0);
_classPrivateFieldInitSpec(this, _freshUTMTags, void 0);
_classPrivateFieldInitSpec(this, _sdkName, void 0);
_classPrivateFieldInitSpec(this, _sdkVersion, void 0);
_classPrivateFieldInitSpec(this, _shouldSendHC, void 0);
_classPrivateFieldInitSpec(this, _consents, void 0);
_classPrivateFieldInitSpec(this, _generatedRequests, void 0);
_classPrivateFieldInitSpec(this, _contentTimeInterval, void 0);
_classPrivateFieldInitSpec(this, _contentEndPoint, void 0);
_classPrivateFieldInitSpec(this, _inContentZone, void 0);
_classPrivateFieldInitSpec(this, _contentZoneTimer, void 0);
_classPrivateFieldInitSpec(this, _contentZoneTimerInterval, void 0);
_classPrivateFieldInitSpec(this, _contentIframeID, void 0);
_classPrivateFieldInitSpec(this, _crashFilterCallback, void 0);
/**
* Initialize the Countly
* @param {Object} ob - config object
* @returns
*/
_classPrivateFieldInitSpec(this, _initialize, function (ob) {
_this.serialize = getConfig("serialize", ob, Countly.serialize);
_this.deserialize = getConfig("deserialize", ob, Countly.deserialize);
_this.getViewName = getConfig("getViewName", ob, Countly.getViewName);
_this.getViewUrl = getConfig("getViewUrl", ob, Countly.getViewUrl);
_this.getSearchQuery = getConfig("getSearchQuery", ob, Countly.getSearchQuery);
_this.DeviceIdType = Countly.DeviceIdType; // it is Countly device Id type Enums for clients to use
_this.namespace = getConfig("namespace", ob, "");
_this.clearStoredId = getConfig("clear_stored_id", ob, false);
_this.app_key = getConfig("app_key", ob, null);
_this.onload = getConfig("onload", ob, []);
_this.utm = getConfig("utm", ob, {
source: true,
medium: true,
campaign: true,
term: true,
content: true
});
_this.ignore_prefetch = getConfig("ignore_prefetch", ob, true);
_this.rcAutoOptinAb = getConfig("rc_automatic_optin_for_ab", ob, true);
_this.useExplicitRcApi = getConfig("use_explicit_rc_api", ob, false);
_this.debug = getConfig("debug", ob, false);
_this.test_mode = getConfig("test_mode", ob, false);
_this.test_mode_eq = getConfig("test_mode_eq", ob, false);
_this.metrics = getConfig("metrics", ob, {});
_this.headers = getConfig("headers", ob, {});
_this.url = stripTrailingSlash(getConfig("url", ob, ""));
_this.app_version = getConfig("app_version", ob, "0.0");
_this.country_code = getConfig("country_code", ob, null);
_this.city = getConfig("city", ob, null);
_this.ip_address = getConfig("ip_address", ob, null);
_this.ignore_bots = getConfig("ignore_bots", ob, true);
_this.force_post = getConfig("force_post", ob, false);
_this.remote_config = getConfig("remote_config", ob, false);
_this.ignore_visitor = getConfig("ignore_visitor", ob, false);
_this.require_consent = getConfig("require_consent", ob, false);
_this.track_domains = !isBrowser ? undefined : getConfig("track_domains", ob, true);
_this.storage = getConfig("storage", ob, "default");
_this.enableOrientationTracking = !isBrowser ? undefined : getConfig("enable_orientation_tracking", ob, true);
_this.maxKeyLength = getConfig("max_key_length", ob, configurationDefaultValues.MAX_KEY_LENGTH);
_this.maxValueSize = getConfig("max_value_size", ob, configurationDefaultValues.MAX_VALUE_SIZE);
_this.maxSegmentationValues = getConfig("max_segmentation_values", ob, configurationDefaultValues.MAX_SEGMENTATION_VALUES);
_this.maxBreadcrumbCount = getConfig("max_breadcrumb_count", ob, null);
_this.maxStackTraceLinesPerThread = getConfig("max_stack_trace_lines_per_thread", ob, configurationDefaultValues.MAX_STACKTRACE_LINES_PER_THREAD);
_this.maxStackTraceLineLength = getConfig("max_stack_trace_line_length", ob, configurationDefaultValues.MAX_STACKTRACE_LINE_LENGTH);
_this.heatmapWhitelist = getConfig("heatmap_whitelist", ob, []);
_this.salt = getConfig("salt", ob, null);
_this.hcErrorCount = _classPrivateFieldGet2(_getValueFromStorage, _this).call(_this, healthCheckCounterEnum.errorCount) || 0;
_this.hcWarningCount = _classPrivateFieldGet2(_getValueFromStorage, _this).call(_this, healthCheckCounterEnum.warningCount) || 0;
_this.hcStatusCode = _classPrivateFieldGet2(_getValueFromStorage, _this).call(_this, healthCheckCounterEnum.statusCode) || -1;
_this.hcErrorMessage = _classPrivateFieldGet2(_getValueFromStorage, _this).call(_this, healthCheckCounterEnum.errorMessage) || "";
_classPrivateFieldSet2(_contentZoneTimerInterval, _this, getConfig("content_zone_timer_interval", ob, null));
_classPrivateFieldSet2(_crashFilterCallback, _this, getConfig("crash_filter_callback", ob, null));
if (_classPrivateFieldGet2(_contentZoneTimerInterval, _this)) {
_classPrivateFieldSet2(_contentTimeInterval, _this, Math.max(_classPrivateFieldGet2(_contentZoneTimerInterval, _this), 15) * 1000);
}
if (_classPrivateFieldGet2(_maxCrashLogs, _this) && !_this.maxBreadcrumbCount) {
_this.maxBreadcrumbCount = _classPrivateFieldGet2(_maxCrashLogs, _this);
_classPrivateFieldGet2(_log, _this).call(_this, logLevelEnums.WARNING, "initialize, 'maxCrashLogs' is deprecated. Use 'maxBreadcrumbCount' instead!");
} else if (!_classPrivateFieldGet2(_maxCrashLogs, _this) && !_this.maxBreadcrumbCount) {
_this.maxBreadcrumbCount = 100;
}
if (_this.storage === "cookie") {
_classPrivateFieldSet2(_lsSupport, _this, false);
}
if (!_this.rcAutoOptinAb && !_this.useExplicitRcApi) {
_classPrivateFieldGet2(_log, _this).call(_this, logLevelEnums.WARNING, "initialize, Auto opting is disabled, switching to explicit RC API");
_this.useExplicitRcApi = true;
}
if (!Array.isArray(_classPrivateFieldGet2(_ignoreReferrers, _this))) {
_classPrivateFieldSet2(_ignoreReferrers, _this, []);
}
if (_this.url === "") {
_classPrivateFieldGet2(_log, _this).call(_this, logLevelEnums.ERROR, "initialize, Please provide server URL");
_this.ignore_visitor = true;
}
if (_classPrivateFieldGet2(_getValueFromStorage, _this).call(_this, "cly_ignore")) {
// opted out user
_this.ignore_visitor = true;
}
_classPrivateFieldGet2(_checkIgnore, _this).call(_this);
if (isBrowser) {
if (window.name && window.name.indexOf("cly:") === 0) {
try {
_this.passed_data = JSON.parse(window.name.replace("cly:", ""));
} catch (ex) {
_classPrivateFieldGet2(_log, _this).call(_this, logLevelEnums.ERROR, "initialize, Could not parse name: " + window.name + ", error: " + ex);
}
} else if (location.hash && location.hash.indexOf("this.#cly:") === 0) {
try {
_this.passed_data = JSON.parse(location.hash.replace("this.#cly:", ""));
} catch (ex) {
_classPrivateFieldGet2(_log, _this).call(_this, logLevelEnums.ERROR, "initialize, Could not parse hash: " + location.hash + ", error: " + ex);
}
}
}
if (_this.passed_data && _this.passed_data.app_key && _this.passed_data.app_key === _this.app_key || _this.passed_data && !_this.passed_data.app_key && _classPrivateFieldGet2(_global, _this)) {
if (_this.passed_data.token && _this.passed_data.purpose) {
if (_this.passed_data.token !== _classPrivateFieldGet2(_getValueFromStorage, _this).call(_this, "cly_old_token")) {
_classPrivateFieldGet2(_setToken, _this).call(_this, _this.passed_data.token);
_classPrivateFieldGet2(_setValueInStorage, _this).call(_this, "cly_old_token", _this.passed_data.token);
}
var strippedList = [];
// if whitelist is provided is an array
if (Array.isArray(_this.heatmapWhitelist)) {
_this.heatmapWhitelist.push(_this.url);
strippedList = _this.heatmapWhitelist.map(function (e) {
// remove trailing slashes from the entries