diff --git a/CHANGELOG.md b/CHANGELOG.md index 0828e26f..88cfaf21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## X.X.X +## 23.6.1 - Mitigated an issue where numerical device IDs were parsed at the initialization - You can now add segmentation while presenting a widget diff --git a/cypress/fixtures/referrer.html b/cypress/fixtures/referrer.html new file mode 100644 index 00000000..dca8b096 --- /dev/null +++ b/cypress/fixtures/referrer.html @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/cypress/integration/utm.js b/cypress/integration/utm.js index ded73804..bd56bda5 100644 --- a/cypress/integration/utm.js +++ b/cypress/integration/utm.js @@ -14,38 +14,6 @@ function initMulti(appKey, searchQuery, utmStuff) { } }); } -function validateDefaultUtmTags(aq, source, medium, campaign, term, content) { - if (typeof source === "string") { - expect(aq.utm_source).to.eq(source); - } - else { - expect(aq.utm_source).to.not.exist; - } - if (typeof medium === "string") { - expect(aq.utm_medium).to.eq(medium); - } - else { - expect(aq.utm_medium).to.not.exist; - } - if (typeof campaign === "string") { - expect(aq.utm_campaign).to.eq(campaign); - } - else { - expect(aq.utm_campaign).to.not.exist; - } - if (typeof term === "string") { - expect(aq.utm_term).to.eq(term); - } - else { - expect(aq.utm_term).to.not.exist; - } - if (typeof content === "string") { - expect(aq.utm_content).to.eq(content); - } - else { - expect(aq.utm_content).to.not.exist; - } -} describe("UTM tests ", () => { it("Checks if a single default utm tag works", () => { @@ -54,7 +22,7 @@ describe("UTM tests ", () => { cy.fetch_local_request_queue().then((rq) => { cy.log(rq); const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe", "", "", "", ""); + hp.validateDefaultUtmTags(custom, "hehe", "", "", "", ""); }); }); }); @@ -64,7 +32,7 @@ describe("UTM tests ", () => { cy.fetch_local_request_queue().then((rq) => { cy.log(rq); const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe", "hehe1", "hehe2", "hehe3", "hehe4"); + hp.validateDefaultUtmTags(custom, "hehe", "hehe1", "hehe2", "hehe3", "hehe4"); }); }); }); @@ -74,7 +42,7 @@ describe("UTM tests ", () => { cy.fetch_local_request_queue().then((rq) => { cy.log(rq); const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); + hp.validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); expect(custom.utm_aa).to.eq("hehe"); expect(custom.utm_bb).to.eq(""); }); @@ -86,7 +54,7 @@ describe("UTM tests ", () => { cy.fetch_local_request_queue().then((rq) => { cy.log(rq); const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); + hp.validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); expect(custom.utm_aa).to.eq("hehe"); expect(custom.utm_bb).to.eq("hoho"); }); @@ -112,19 +80,19 @@ describe("UTM tests ", () => { // check original cy.fetch_local_request_queue().then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe", "", "", "", ""); + hp.validateDefaultUtmTags(custom, "hehe", "", "", "", ""); }); // check if custom utm tags works cy.fetch_local_request_queue("Countly_2").then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); + hp.validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); expect(custom.utm_ss).to.eq("hehe2"); }); // check if default utm tags works cy.fetch_local_request_queue("Countly_3").then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe3", "", "", "", ""); + hp.validateDefaultUtmTags(custom, "hehe3", "", "", "", ""); }); // check if no utm tag in request queue if the query is wrong cy.fetch_local_request_queue("Countly_4").then((rq) => { @@ -159,32 +127,32 @@ describe("UTM tests ", () => { // check original cy.fetch_local_request_queue().then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe", "", "", "", ""); + hp.validateDefaultUtmTags(custom, "hehe", "", "", "", ""); }); // check if custom utm tags works for multi 1 cy.fetch_local_request_queue("Countly_multi_1").then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe", "hehe1", "hehe2", "hehe3", "hehe4"); + hp.validateDefaultUtmTags(custom, "hehe", "hehe1", "hehe2", "hehe3", "hehe4"); }); // check if custom utm tags works for multi 2 cy.fetch_local_request_queue("Countly_multi_2").then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe", undefined, undefined, "hehe3", undefined); + hp.validateDefaultUtmTags(custom, "hehe", undefined, undefined, "hehe3", undefined); expect(custom.utm_sthelse).to.eq("hehe5"); }); // check if custom utm tags works for multi 3 cy.fetch_local_request_queue("Countly_multi_3").then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe", "", "", "", ""); + hp.validateDefaultUtmTags(custom, "hehe", "", "", "", ""); }); // check if custom utm tags works for multi 4 cy.fetch_local_request_queue("Countly_multi_4").then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe", "hehe1", "hehe2", "hehe3", "hehe4"); + hp.validateDefaultUtmTags(custom, "hehe", "hehe1", "hehe2", "hehe3", "hehe4"); expect(custom.utm_next).to.eq("hehe5"); }); @@ -211,7 +179,7 @@ describe("UTM tests ", () => { // check original cy.fetch_local_request_queue().then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, "hehe", "", "", "", ""); + hp.validateDefaultUtmTags(custom, "hehe", "", "", "", ""); }); // check if custom utm tags works for multi 1 @@ -227,7 +195,7 @@ describe("UTM tests ", () => { // check if custom utm tags works for multi 3 cy.fetch_local_request_queue("Countly_multi_next_3").then((rq) => { const custom = JSON.parse(rq[0].user_details).custom; - validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); + hp.validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); expect(custom.utm_sauce).to.eq("hehe"); expect(custom.utm_pan).to.eq("hehe2"); }); diff --git a/cypress/integration/view_utm_referrer.js b/cypress/integration/view_utm_referrer.js new file mode 100644 index 00000000..16b22257 --- /dev/null +++ b/cypress/integration/view_utm_referrer.js @@ -0,0 +1,295 @@ +/* eslint-disable require-jsdoc */ +var Countly = require("../../lib/countly"); +var hp = require("../support/helper"); + +function init(appKey, searchQuery, utmStuff) { + Countly.init({ + app_key: appKey, + url: "https://try.count.ly", + test_mode: true, + test_mode_eq: true, + utm: utmStuff, // utm object provided in init + getSearchQuery: function() { // override default search query + return searchQuery; + } + }); +} + +var pageNameOne = "test view page name1"; +var pageNameTwo = "test view page name2"; + +describe("View with utm and referrer tests ", () => { + // we check with no utm object if a default utm tag is recorded in the view event if it is in the query + it("Checks if a single default utm tag is at view segmentation", () => { + hp.haltAndClearStorage(() => { + init("YOUR_APP_KEY", "?utm_source=hehe", undefined); + Countly.track_view(pageNameOne); // first view + // View event should have the utm tag + cy.fetch_local_event_queue().then((eq) => { + cy.check_view_event(eq[0], pageNameOne, undefined, false); + hp.validateDefaultUtmTags(eq[0].segmentation, "hehe", undefined, undefined, undefined, undefined); + expect(eq[0].segmentation.referrer).to.eq(undefined); + }); + // adding utm creates a user_details request + cy.fetch_local_request_queue().then((rq) => { + cy.log(rq); + const custom = JSON.parse(rq[0].user_details).custom; + hp.validateDefaultUtmTags(custom, "hehe", "", "", "", ""); + }); + }); + }); + + // we record 2 views and check if both have the same utm tag + it("Checks if a single default utm tag is at view segmentation of both views", () => { + hp.haltAndClearStorage(() => { + init("YOUR_APP_KEY", "?utm_source=hehe", undefined); + Countly.track_view(pageNameOne); // first view + Countly.track_view(pageNameTwo); // second view + // View event should have the utm tag + cy.fetch_local_event_queue().then((eq) => { + cy.log(eq); + cy.check_view_event(eq[0], pageNameOne, undefined, false); + hp.validateDefaultUtmTags(eq[0].segmentation, "hehe", undefined, undefined, undefined, undefined); + cy.check_view_event(eq[1], pageNameOne, 0, false); // end of view 1 + hp.validateDefaultUtmTags(eq[1].segmentation, undefined, undefined, undefined, undefined, undefined); + cy.check_view_event(eq[2], pageNameTwo, undefined, true); // second view + hp.validateDefaultUtmTags(eq[2].segmentation, "hehe", undefined, undefined, undefined, undefined); + expect(eq[0].segmentation.referrer).to.eq(undefined); + expect(eq[1].segmentation.referrer).to.eq(undefined); + expect(eq[2].segmentation.referrer).to.eq(undefined); + }); + // adding utm creates a user_details request + cy.fetch_local_request_queue().then((rq) => { + cy.log(rq); + const custom = JSON.parse(rq[0].user_details).custom; + hp.validateDefaultUtmTags(custom, "hehe", "", "", "", ""); + expect(rq.length).to.eq(1); + }); + }); + }); + + // we check if multiple default utm tags are recorded in the view event if they are in the query + // and no utm object is provided + it("Checks if default utm tags appear in view", () => { + hp.haltAndClearStorage(() => { + init("YOUR_APP_KEY", "?utm_source=hehe&utm_medium=hehe1&utm_campaign=hehe2&utm_term=hehe3&utm_content=hehe4", undefined); + Countly.track_view(pageNameOne); + cy.fetch_local_event_queue().then((eq) => { + cy.check_view_event(eq[0], pageNameOne, undefined, false); + hp.validateDefaultUtmTags(eq[0].segmentation, "hehe", "hehe1", "hehe2", "hehe3", "hehe4"); + expect(eq[0].segmentation.referrer).to.eq(undefined); + }); + cy.fetch_local_request_queue().then((rq) => { + cy.log(rq); + const custom = JSON.parse(rq[0].user_details).custom; + hp.validateDefaultUtmTags(custom, "hehe", "hehe1", "hehe2", "hehe3", "hehe4"); + }); + }); + }); + + // we check if a single custom utm tag is recorded in the view event if it is in the utm object + // and utm object includes more than one utm tags + it("Checks if a single custom utm tag appears in view", () => { + hp.haltAndClearStorage(() => { + init("YOUR_APP_KEY", "?utm_aa=hehe", { aa: true, bb: true }); + Countly.track_view(pageNameOne); + cy.fetch_local_event_queue().then((eq) => { + cy.check_view_event(eq[0], pageNameOne, undefined, false); + hp.validateDefaultUtmTags(eq[0].segmentation, undefined, undefined, undefined, undefined, undefined); + expect(eq[0].segmentation.utm_aa).to.eq("hehe"); + expect(eq[0].segmentation.utm_bb).to.eq(undefined); + expect(eq[0].segmentation.referrer).to.eq(undefined); + }); + cy.fetch_local_request_queue().then((rq) => { + cy.log(rq); + const custom = JSON.parse(rq[0].user_details).custom; + hp.validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); + expect(custom.utm_aa).to.eq("hehe"); + expect(custom.utm_bb).to.eq(""); + }); + }); + }); + + // we check if multiple custom utm tags are recorded in the view event if they are in the utm object + it("Checks if multiple custom utm tags appears in view", () => { + hp.haltAndClearStorage(() => { + init("YOUR_APP_KEY", "?utm_aa=hehe&utm_bb=hoho", { aa: true, bb: true }); + Countly.track_view(pageNameOne); + cy.fetch_local_event_queue().then((eq) => { + cy.check_view_event(eq[0], pageNameOne, undefined, false); + hp.validateDefaultUtmTags(eq[0].segmentation, undefined, undefined, undefined, undefined, undefined); + expect(eq[0].segmentation.utm_aa).to.eq("hehe"); + expect(eq[0].segmentation.utm_bb).to.eq("hoho"); + expect(eq[0].segmentation.referrer).to.eq(undefined); + }); + cy.fetch_local_request_queue().then((rq) => { + cy.log(rq); + const custom = JSON.parse(rq[0].user_details).custom; + hp.validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); + expect(custom.utm_aa).to.eq("hehe"); + expect(custom.utm_bb).to.eq("hoho"); + }); + }); + }); + + // we check if we add a custom utm tag that is not in the utm object + // it is not recorded in the view event + it("Checks if extra custom utm tags are ignored in view", () => { + hp.haltAndClearStorage(() => { + init("YOUR_APP_KEY", "?utm_aa=hehe&utm_bb=hoho&utm_cc=ignore", { aa: true, bb: true }); + Countly.track_view(pageNameOne); + cy.fetch_local_event_queue().then((eq) => { + cy.check_view_event(eq[0], pageNameOne, undefined, false); + hp.validateDefaultUtmTags(eq[0].segmentation, undefined, undefined, undefined, undefined, undefined); + expect(eq[0].segmentation.utm_aa).to.eq("hehe"); + expect(eq[0].segmentation.utm_bb).to.eq("hoho"); + expect(eq[0].segmentation.utm_cc).to.eq(undefined); + expect(eq[0].segmentation.referrer).to.eq(undefined); + }); + cy.fetch_local_request_queue().then((rq) => { + cy.log(rq); + const custom = JSON.parse(rq[0].user_details).custom; + hp.validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); + expect(custom.utm_aa).to.eq("hehe"); + expect(custom.utm_bb).to.eq("hoho"); + expect(custom.utm_cc).to.eq(undefined); + }); + }); + }); + + // we create 2 instances of countly with different configurations + // then we record the same view with both instances + // then we check if the utm tags are recorded correctly + // and no referrer is recorded (because localhost) + it("Checks if utm tag appears in segmentation in multi instancing", () => { + hp.haltAndClearStorage(() => { + // default (original) init with no custom tags and default query + var C1 = Countly.init({ + app_key: "YOUR_APP_KEY", + url: "https://try.count.ly", + test_mode: true, + test_mode_eq: true, + utm: undefined, // utm object provided in init + getSearchQuery: function() { // override default search query + return "?utm_source=hehe"; + } + }); + C1.track_view(pageNameOne); + + // utm object provided with appropriate query + var C2 = Countly.init({ + app_key: "Countly_2", + url: "https://try.count.ly", + test_mode: true, + test_mode_eq: true, + utm: { ss: true }, // utm object provided in init + getSearchQuery: function() { // override default search query + return "?utm_ss=hehe2"; + } + }); + C2.track_view(pageNameOne); + + // check original + cy.fetch_local_event_queue().then((eq) => { + cy.log(eq); + cy.check_view_event(eq[0], pageNameOne, undefined, false); + hp.validateDefaultUtmTags(eq[0].segmentation, "hehe", undefined, undefined, undefined, undefined); + expect(eq[0].segmentation.referrer).to.eq(undefined); + }); + cy.fetch_local_request_queue().then((rq) => { + cy.log(rq); + const custom = JSON.parse(rq[0].user_details).custom; + hp.validateDefaultUtmTags(custom, "hehe", "", "", "", ""); + }); + + // second instance + cy.fetch_local_event_queue("Countly_2").then((eq) => { + cy.log(eq); + cy.check_view_event(eq[0], pageNameOne, undefined, false); + hp.validateDefaultUtmTags(eq[0].segmentation, undefined, undefined, undefined, undefined, undefined); + expect(eq[0].segmentation.utm_ss).to.eq("hehe2"); + expect(eq[0].segmentation.referrer).to.eq(undefined); + }); + }); + }); + + // we use a custom html at fixtures folder ('referrer.html') + // then we set the referrer to be 'http://www.baidu.com' here manually + // then we check if the referrer is recorded correctly + // also we verify utms are recorded properly too + it("Check if referrer is recorded correctly", () => { + cy.visit("./cypress/fixtures/referrer.html", { + onBeforeLoad(win) { + Object.defineProperty(win.document, "referrer", { + configurable: true, + get() { + return "http://www.baidu.com"; // set custom referrer + } + }); + } + }); + hp.waitFunction(hp.getTimestampMs(), 1000, 100, () => { + cy.fetch_local_event_queue().then((eq) => { + cy.log(eq); + cy.check_view_event(eq[0], "/cypress/fixtures/referrer.html", undefined, false); + hp.validateDefaultUtmTags(eq[0].segmentation, undefined, undefined, undefined, undefined, undefined); + expect(eq[0].segmentation.utm_aa).to.eq("hehe"); + expect(eq[0].segmentation.utm_bb).to.eq(undefined); + expect(eq[0].segmentation.referrer).to.eq("http://www.baidu.com"); + }); + cy.fetch_local_request_queue().then((rq) => { + cy.log(rq); + const custom = JSON.parse(rq[0].user_details).custom; + hp.validateDefaultUtmTags(custom, undefined, undefined, undefined, undefined, undefined); + expect(custom.utm_aa).to.eq("hehe"); + expect(custom.utm_bb).to.eq(undefined); + }); + }); + }); +}); + +describe("isReferrerUsable tests", () => { + it("should return false if document.referrer is undefined", () => { + const result = Countly._internals.isReferrerUsable(undefined); + expect(result).to.eq(false); + }); + + it("should return false if document.referrer is null", () => { + const result = Countly._internals.isReferrerUsable(null); + expect(result).to.eq(false); + }); + + it("should return false if document.referrer is an empty string", () => { + const result = Countly._internals.isReferrerUsable(""); + expect(result).to.eq(false); + }); + + it("should return false if the referrer is the same as the current hostname", () => { + const result = Countly._internals.isReferrerUsable("http://localhost:3000"); + expect(result).to.eq(false); + }); + + it("should return false if the referrer is not a valid URL", () => { + const result = Countly._internals.isReferrerUsable("invalid-url"); + expect(result).to.eq(false); + }); + + it("should return false if the referrer is in the ignore list", () => { + hp.haltAndClearStorage(() => { + Countly.init({ + app_key: "YOUR_APP_KEY", + url: "https://try.count.ly", + ignore_referrers: ["http://example.com"] + }); + const result = Countly._internals.isReferrerUsable("http://example.com/something"); + expect(result).to.eq(false); + }); + }); + + it("should return true if the referrer is valid and not in the ignore list", () => { + hp.haltAndClearStorage(() => { + const result = Countly._internals.isReferrerUsable("http://example.com/path"); + expect(result).to.eq(true); + }); + }); +}); \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js index fa909560..c57e2aae 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -79,7 +79,7 @@ Cypress.Commands.add("check_session", (queueObject, duration, isSessionEnd, appK expect(metrics._locale).to.be.ok; } else if (!isSessionEnd) { - expect(queueObject.session_duration).to.be.within(duration, duration + 1); + expect(queueObject.session_duration).to.be.within(duration, duration + 2); } else { expect(queueObject.end_session).to.equal(1); @@ -355,7 +355,7 @@ Cypress.Commands.add("fetch_local_request_queue", (appKey) => { Cypress.Commands.add("fetch_local_event_queue", (appKey) => { cy.wait(hp.sWait).then(() => { appKey = appKey || hp.appKey; - cy.getLocalStorage(`${hp.appKey}/cly_event`).then((e) => { + cy.getLocalStorage(`${appKey}/cly_event`).then((e) => { if (e === undefined) { expect.fail("event queue inside the local storage should not be undefined"); } @@ -375,7 +375,7 @@ Cypress.Commands.add("fetch_local_event_queue", (appKey) => { Cypress.Commands.add("fetch_from_storage", (appKey, key) => { cy.wait(hp.sWait).then(() => { appKey = appKey || hp.appKey; - cy.getLocalStorage(`${hp.appKey}/${key}`).then((e) => { + cy.getLocalStorage(`${appKey}/${key}`).then((e) => { return JSON.parse(e); }); }); diff --git a/cypress/support/helper.js b/cypress/support/helper.js index 40b02ba4..40c8ea11 100644 --- a/cypress/support/helper.js +++ b/cypress/support/helper.js @@ -206,6 +206,50 @@ function testNormalFlow(rq, viewName, countlyAppKey) { }); } +/** + * Validates utm tags in the request queue/given object + * You can pass undefined if you want to check if utm tags do not exist + * + * @param {*} aq - object to check + * @param {*} source - utm_source + * @param {*} medium - utm_medium + * @param {*} campaign - utm_campaign + * @param {*} term - utm_term + * @param {*} content - utm_content + */ +function validateDefaultUtmTags(aq, source, medium, campaign, term, content) { + if (typeof source === "string") { + expect(aq.utm_source).to.eq(source); + } + else { + expect(aq.utm_source).to.not.exist; + } + if (typeof medium === "string") { + expect(aq.utm_medium).to.eq(medium); + } + else { + expect(aq.utm_medium).to.not.exist; + } + if (typeof campaign === "string") { + expect(aq.utm_campaign).to.eq(campaign); + } + else { + expect(aq.utm_campaign).to.not.exist; + } + if (typeof term === "string") { + expect(aq.utm_term).to.eq(term); + } + else { + expect(aq.utm_term).to.not.exist; + } + if (typeof content === "string") { + expect(aq.utm_content).to.eq(content); + } + else { + expect(aq.utm_content).to.not.exist; + } +} + module.exports = { haltAndClearStorage, sWait, @@ -217,5 +261,6 @@ module.exports = { events, eventArray, testNormalFlow, - interceptAndCheckRequests + interceptAndCheckRequests, + validateDefaultUtmTags }; \ No newline at end of file diff --git a/examples/react/package.json b/examples/react/package.json index 70361e85..cd822bf2 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "countly-sdk-web": "^23.6.0" + "countly-sdk-web": "^23.6.1" }, "devDependencies": { "react": "^18.2.0", diff --git a/lib/countly.js b/lib/countly.js index 6d22d86d..e2b4a26e 100644 --- a/lib/countly.js +++ b/lib/countly.js @@ -194,9 +194,10 @@ */ Countly.onload = Countly.onload || []; - var SDK_VERSION = "23.6.0"; + var SDK_VERSION = "23.6.1"; var SDK_NAME = "javascript_native_web"; + // Using this on document.referrer would return an array with 15 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) var urlParseRE = /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/; var apmLibrariesNotLoaded = true; // used to prevent loading apm scripts multiple times. @@ -248,6 +249,7 @@ var trackingScrolls = false; var currentViewId = null; // this is the global variable for tracking the current view's ID. Used in view tracking. Becomes previous view ID at the end. var previousViewId = null; // this is the global variable for tracking the previous view's ID. Used in view tracking. First view has no previous view ID. + var freshUTMTags = null; try { localStorage.setItem("cly_testLocal", true); @@ -312,7 +314,7 @@ if (maxCrashLogs && !this.maxBreadcrumbCount) { this.maxBreadcrumbCount = maxCrashLogs; - log(logLevelEnums.WARNING, "[Initialization] 'maxCrashLogs' is deprecated. Use 'maxBreadcrumbCount' instead!"); + log(logLevelEnums.WARNING, "initialize, 'maxCrashLogs' is deprecated. Use 'maxBreadcrumbCount' instead!"); } else if (!maxCrashLogs && !this.maxBreadcrumbCount) { this.maxBreadcrumbCount = 100; @@ -323,7 +325,7 @@ } if (!this.rcAutoOptinAb && !this.useExplicitRcApi) { - log(logLevelEnums.WARNING, "[Initialization] Auto opting is disabled, switching to explicit RC API"); + log(logLevelEnums.WARNING, "initialize, Auto opting is disabled, switching to explicit RC API"); this.useExplicitRcApi = true; } @@ -332,7 +334,7 @@ } if (this.url === "") { - log(logLevelEnums.ERROR, "[Initialization] Please provide server URL"); + log(logLevelEnums.ERROR, "initialize, Please provide server URL"); this.ignore_visitor = true; } if (getValueFromStorage("cly_ignore")) { @@ -350,10 +352,10 @@ // retrieve stored device ID and type from local storage and use it to flush existing events if (getValueFromStorage("cly_id") && !tempIdModeWasEnabled) { this.device_id = getValueFromStorage("cly_id"); - log(logLevelEnums.DEBUG, "[Initialization] temporarily using the previous device ID to flush existing events"); + log(logLevelEnums.DEBUG, "initialize, temporarily using the previous device ID to flush existing events"); deviceIdType = getValueFromStorage("cly_id_type"); if (!deviceIdType) { - log(logLevelEnums.DEBUG, "[Initialization] No device ID type info from the previous session, falling back to DEVELOPER_SUPPLIED, for event flushing"); + log(logLevelEnums.DEBUG, "initialize, No device ID type info from the previous session, falling back to DEVELOPER_SUPPLIED, for event flushing"); deviceIdType = DeviceIdTypeInternalEnums.DEVELOPER_SUPPLIED; } sendEventsForced(); @@ -362,7 +364,7 @@ deviceIdType = DeviceIdTypeInternalEnums.SDK_GENERATED; } // then clear the storage so that a new device ID is set again later - log(logLevelEnums.INFO, "[Initialization] Clearing the device ID storage"); + log(logLevelEnums.INFO, "initialize, Clearing the device ID storage"); localStorage.removeItem(this.app_key + "/cly_id"); localStorage.removeItem(this.app_key + "/cly_id_type"); localStorage.removeItem(this.app_key + "/cly_session"); @@ -375,7 +377,7 @@ this.passed_data = JSON.parse(window.name.replace("cly:", "")); } catch (ex) { - log(logLevelEnums.ERROR, "[Initialization] Could not parse name: " + window.name + ", error: " + ex); + log(logLevelEnums.ERROR, "initialize, Could not parse name: " + window.name + ", error: " + ex); } } else if (location.hash && location.hash.indexOf("#cly:") === 0) { @@ -383,7 +385,7 @@ this.passed_data = JSON.parse(location.hash.replace("#cly:", "")); } catch (ex) { - log(logLevelEnums.ERROR, "[Initialization] Could not parse hash: " + location.hash + ", error: " + ex); + log(logLevelEnums.ERROR, "initialize, Could not parse hash: " + location.hash + ", error: " + ex); } } @@ -417,139 +419,139 @@ } if (this.ignore_visitor) { - log(logLevelEnums.WARNING, "[Initialization] ignore_visitor:[" + this.ignore_visitor + "], this user will not be tracked"); + log(logLevelEnums.WARNING, "initialize, ignore_visitor:[" + this.ignore_visitor + "], this user will not be tracked"); return; } // init configuration is printed out here: // key values should be printed out as is - log(logLevelEnums.DEBUG, "[Initialization] app_key:[" + this.app_key + "], url:[" + this.url + "]"); - log(logLevelEnums.DEBUG, "[Initialization] device_id:[" + getConfig("device_id", ob, undefined) + "]"); - log(logLevelEnums.DEBUG, "[Initialization] require_consent is enabled:[" + this.require_consent + "]"); + log(logLevelEnums.DEBUG, "initialize, app_key:[" + this.app_key + "], url:[" + this.url + "]"); + log(logLevelEnums.DEBUG, "initialize, device_id:[" + getConfig("device_id", ob, undefined) + "]"); + log(logLevelEnums.DEBUG, "initialize, require_consent is enabled:[" + this.require_consent + "]"); try { - log(logLevelEnums.DEBUG, "[Initialization] metric override:[" + JSON.stringify(this.metrics) + "]"); - log(logLevelEnums.DEBUG, "[Initialization] header override:[" + JSON.stringify(this.headers) + "]"); + log(logLevelEnums.DEBUG, "initialize, metric override:[" + JSON.stringify(this.metrics) + "]"); + log(logLevelEnums.DEBUG, "initialize, header override:[" + JSON.stringify(this.headers) + "]"); // empty array is truthy and so would be printed if provided - log(logLevelEnums.DEBUG, "[Initialization] number of onload callbacks provided:[" + this.onload.length + "]"); + log(logLevelEnums.DEBUG, "initialize, number of onload callbacks provided:[" + this.onload.length + "]"); // if the utm object is different to default utm object print it here - log(logLevelEnums.DEBUG, "[Initialization] utm tags:[" + JSON.stringify(this.utm) + "]"); + log(logLevelEnums.DEBUG, "initialize, utm tags:[" + JSON.stringify(this.utm) + "]"); // empty array printed if non provided if (ignoreReferrers) { - log(logLevelEnums.DEBUG, "[Initialization] referrers to ignore :[" + JSON.stringify(ignoreReferrers) + "]"); + log(logLevelEnums.DEBUG, "initialize, referrers to ignore :[" + JSON.stringify(ignoreReferrers) + "]"); } } catch (e) { - log(logLevelEnums.ERROR, "[Initialization] Could not stringify some config object values"); + log(logLevelEnums.ERROR, "initialize, Could not stringify some config object values"); } - log(logLevelEnums.DEBUG, "[Initialization] app_version:[" + this.app_version + "]"); + log(logLevelEnums.DEBUG, "initialize, app_version:[" + this.app_version + "]"); // location info printed here - log(logLevelEnums.DEBUG, "[Initialization] provided location info; country_code:[" + this.country_code + "], city:[" + this.city + "], ip_address:[" + this.ip_address + "]"); + log(logLevelEnums.DEBUG, "initialize, provided location info; country_code:[" + this.country_code + "], city:[" + this.city + "], ip_address:[" + this.ip_address + "]"); // print non vital values only if provided by the developer or differs from the default value if (this.namespace !== "") { - log(logLevelEnums.DEBUG, "[Initialization] namespace given:[" + this.namespace + "]"); + log(logLevelEnums.DEBUG, "initialize, namespace given:[" + this.namespace + "]"); } if (this.clearStoredId) { - log(logLevelEnums.DEBUG, "[Initialization] clearStoredId flag set to:[" + this.clearStoredId + "]"); + log(logLevelEnums.DEBUG, "initialize, clearStoredId flag set to:[" + this.clearStoredId + "]"); } if (this.ignore_prefetch) { - log(logLevelEnums.DEBUG, "[Initialization] ignoring pre-fetching and pre-rendering from counting as real website visits :[" + this.ignore_prefetch + "]"); + log(logLevelEnums.DEBUG, "initialize, ignoring pre-fetching and pre-rendering from counting as real website visits :[" + this.ignore_prefetch + "]"); } // if test mode is enabled warn the user if (this.test_mode) { - log(logLevelEnums.WARNING, "[Initialization] test_mode:[" + this.test_mode + "], request queue won't be processed"); + log(logLevelEnums.WARNING, "initialize, test_mode:[" + this.test_mode + "], request queue won't be processed"); } if (this.test_mode_eq) { - log(logLevelEnums.WARNING, "[Initialization] test_mode_eq:[" + this.test_mode_eq + "], event queue won't be processed"); + log(logLevelEnums.WARNING, "initialize, test_mode_eq:[" + this.test_mode_eq + "], event queue won't be processed"); } // if test mode is enabled warn the user if (this.heatmapWhitelist) { - log(logLevelEnums.DEBUG, "[Initialization] heatmap whitelist:[" + JSON.stringify(this.heatmapWhitelist) + "], these domains will be whitelisted"); + log(logLevelEnums.DEBUG, "initialize, heatmap whitelist:[" + JSON.stringify(this.heatmapWhitelist) + "], these domains will be whitelisted"); } // if storage is se to something other than local storage if (this.storage !== "default") { - log(logLevelEnums.DEBUG, "[Initialization] storage is set to:[" + this.storage + "]"); + log(logLevelEnums.DEBUG, "initialize, storage is set to:[" + this.storage + "]"); } if (this.ignore_bots) { - log(logLevelEnums.DEBUG, "[Initialization] ignore traffic from bots :[" + this.ignore_bots + "]"); + log(logLevelEnums.DEBUG, "initialize, ignore traffic from bots :[" + this.ignore_bots + "]"); } if (this.force_post) { - log(logLevelEnums.DEBUG, "[Initialization] forced post method for all requests:[" + this.force_post + "]"); + log(logLevelEnums.DEBUG, "initialize, forced post method for all requests:[" + this.force_post + "]"); } if (this.remote_config) { - log(logLevelEnums.DEBUG, "[Initialization] remote_config callback provided:[" + !!this.remote_config + "]"); + log(logLevelEnums.DEBUG, "initialize, remote_config callback provided:[" + !!this.remote_config + "]"); } if (typeof this.rcAutoOptinAb === "boolean") { - log(logLevelEnums.DEBUG, "[Initialization] automatic RC optin is enabled:[" + this.rcAutoOptinAb + "]"); + log(logLevelEnums.DEBUG, "initialize, automatic RC optin is enabled:[" + this.rcAutoOptinAb + "]"); } if (!this.useExplicitRcApi) { - log(logLevelEnums.WARNING, "[Initialization] will use legacy RC API. Consider enabling new API during init with use_explicit_rc_api flag"); + log(logLevelEnums.WARNING, "initialize, will use legacy RC API. Consider enabling new API during init with use_explicit_rc_api flag"); } if (this.track_domains) { - log(logLevelEnums.DEBUG, "[Initialization] tracking domain info:[" + this.track_domains + "]"); + log(logLevelEnums.DEBUG, "initialize, tracking domain info:[" + this.track_domains + "]"); } if (this.enableOrientationTracking) { - log(logLevelEnums.DEBUG, "[Initialization] enableOrientationTracking:[" + this.enableOrientationTracking + "]"); + log(logLevelEnums.DEBUG, "initialize, enableOrientationTracking:[" + this.enableOrientationTracking + "]"); } if (!useSessionCookie) { - log(logLevelEnums.WARNING, "[Initialization] use_session_cookie is enabled:[" + useSessionCookie + "]"); + log(logLevelEnums.WARNING, "initialize, use_session_cookie is enabled:[" + useSessionCookie + "]"); } if (offlineMode) { - log(logLevelEnums.DEBUG, "[Initialization] offline_mode:[" + offlineMode + "], user info won't be send to the servers"); + log(logLevelEnums.DEBUG, "initialize, offline_mode:[" + offlineMode + "], user info won't be send to the servers"); } if (offlineMode) { - log(logLevelEnums.DEBUG, "[Initialization] stored remote configs:[" + JSON.stringify(remoteConfigs) + "]"); + log(logLevelEnums.DEBUG, "initialize, stored remote configs:[" + JSON.stringify(remoteConfigs) + "]"); } // functions, if provided, would be printed as true without revealing their content - log(logLevelEnums.DEBUG, "[Initialization] 'getViewName' callback override provided:[" + (this.getViewName !== Countly.getViewName) + "]"); - log(logLevelEnums.DEBUG, "[Initialization] 'getSearchQuery' callback override provided:[" + (this.getSearchQuery !== Countly.getSearchQuery) + "]"); + log(logLevelEnums.DEBUG, "initialize, 'getViewName' callback override provided:[" + (this.getViewName !== Countly.getViewName) + "]"); + log(logLevelEnums.DEBUG, "initialize, 'getSearchQuery' callback override provided:[" + (this.getSearchQuery !== Countly.getSearchQuery) + "]"); // limits are printed here if they were modified if (this.maxKeyLength !== configurationDefaultValues.MAX_KEY_LENGTH) { - log(logLevelEnums.DEBUG, "[Initialization] maxKeyLength set to:[" + this.maxKeyLength + "] characters"); + log(logLevelEnums.DEBUG, "initialize, maxKeyLength set to:[" + this.maxKeyLength + "] characters"); } if (this.maxValueSize !== configurationDefaultValues.MAX_VALUE_SIZE) { - log(logLevelEnums.DEBUG, "[Initialization] maxValueSize set to:[" + this.maxValueSize + "] characters"); + log(logLevelEnums.DEBUG, "initialize, maxValueSize set to:[" + this.maxValueSize + "] characters"); } if (this.maxSegmentationValues !== configurationDefaultValues.MAX_SEGMENTATION_VALUES) { - log(logLevelEnums.DEBUG, "[Initialization] maxSegmentationValues set to:[" + this.maxSegmentationValues + "] key/value pairs"); + log(logLevelEnums.DEBUG, "initialize, maxSegmentationValues set to:[" + this.maxSegmentationValues + "] key/value pairs"); } if (this.maxBreadcrumbCount !== configurationDefaultValues.MAX_BREADCRUMB_COUNT) { - log(logLevelEnums.DEBUG, "[Initialization] maxBreadcrumbCount for custom logs set to:[" + this.maxBreadcrumbCount + "] entries"); + log(logLevelEnums.DEBUG, "initialize, maxBreadcrumbCount for custom logs set to:[" + this.maxBreadcrumbCount + "] entries"); } if (this.maxStackTraceLinesPerThread !== configurationDefaultValues.MAX_STACKTRACE_LINES_PER_THREAD) { - log(logLevelEnums.DEBUG, "[Initialization] maxStackTraceLinesPerThread set to:[" + this.maxStackTraceLinesPerThread + "] lines"); + log(logLevelEnums.DEBUG, "initialize, maxStackTraceLinesPerThread set to:[" + this.maxStackTraceLinesPerThread + "] lines"); } if (this.maxStackTraceLineLength !== configurationDefaultValues.MAX_STACKTRACE_LINE_LENGTH) { - log(logLevelEnums.DEBUG, "[Initialization] maxStackTraceLineLength set to:[" + this.maxStackTraceLineLength + "] characters"); + log(logLevelEnums.DEBUG, "initialize, maxStackTraceLineLength set to:[" + this.maxStackTraceLineLength + "] characters"); } if (beatInterval !== configurationDefaultValues.BEAT_INTERVAL) { - log(logLevelEnums.DEBUG, "[Initialization] interval for heartbeats set to:[" + beatInterval + "] milliseconds"); + log(logLevelEnums.DEBUG, "initialize, interval for heartbeats set to:[" + beatInterval + "] milliseconds"); } if (queueSize !== configurationDefaultValues.QUEUE_SIZE) { - log(logLevelEnums.DEBUG, "[Initialization] queue_size set to:[" + queueSize + "] items max"); + log(logLevelEnums.DEBUG, "initialize, queue_size set to:[" + queueSize + "] items max"); } if (failTimeoutAmount !== configurationDefaultValues.FAIL_TIMEOUT_AMOUNT) { - log(logLevelEnums.DEBUG, "[Initialization] fail_timeout set to:[" + failTimeoutAmount + "] seconds of wait time after a failed connection to server"); + log(logLevelEnums.DEBUG, "initialize, fail_timeout set to:[" + failTimeoutAmount + "] seconds of wait time after a failed connection to server"); } if (inactivityTime !== configurationDefaultValues.INACTIVITY_TIME) { - log(logLevelEnums.DEBUG, "[Initialization] inactivity_time set to:[" + inactivityTime + "] minutes to consider a user as inactive after no observable action"); + log(logLevelEnums.DEBUG, "initialize, inactivity_time set to:[" + inactivityTime + "] minutes to consider a user as inactive after no observable action"); } if (sessionUpdate !== configurationDefaultValues.SESSION_UPDATE) { - log(logLevelEnums.DEBUG, "[Initialization] session_update set to:[" + sessionUpdate + "] seconds to check if extending a session is needed while the user is active"); + log(logLevelEnums.DEBUG, "initialize, session_update set to:[" + sessionUpdate + "] seconds to check if extending a session is needed while the user is active"); } if (maxEventBatch !== configurationDefaultValues.MAX_EVENT_BATCH) { - log(logLevelEnums.DEBUG, "[Initialization] max_events set to:[" + maxEventBatch + "] events to send in one batch"); + log(logLevelEnums.DEBUG, "initialize, max_events set to:[" + maxEventBatch + "] events to send in one batch"); } if (maxCrashLogs) { - log(logLevelEnums.WARNING, "[Initialization] max_logs set to:[" + maxCrashLogs + "] breadcrumbs to store for crash logs max, deprecated "); + log(logLevelEnums.WARNING, "initialize, max_logs set to:[" + maxCrashLogs + "] breadcrumbs to store for crash logs max, deprecated "); } if (sessionCookieTimeout !== configurationDefaultValues.SESSION_COOKIE_TIMEOUT) { - log(logLevelEnums.DEBUG, "[Initialization] session_cookie_timeout set to:[" + sessionCookieTimeout + "] minutes to expire a cookies session"); + log(logLevelEnums.DEBUG, "initialize, session_cookie_timeout set to:[" + sessionCookieTimeout + "] minutes to expire a cookies session"); } - log(logLevelEnums.INFO, "[Initialization] Countly initialized"); + log(logLevelEnums.INFO, "initialize, Countly initialized"); var deviceIdParamValue = null; var searchQuery = self.getSearchQuery(); @@ -582,23 +584,23 @@ // check if there wqs stored ID if (getValueFromStorage("cly_id") && !tempIdModeWasEnabled) { this.device_id = getValueFromStorage("cly_id"); - log(logLevelEnums.INFO, "[Initialization] Set the stored device ID"); + log(logLevelEnums.INFO, "initialize, Set the stored device ID"); deviceIdType = getValueFromStorage("cly_id_type"); if (!deviceIdType) { - log(logLevelEnums.INFO, "[Initialization] No device ID type info from the previous session, falling back to DEVELOPER_SUPPLIED"); + log(logLevelEnums.INFO, "initialize, No device ID type info from the previous session, falling back to DEVELOPER_SUPPLIED"); // there is a device ID saved but there is no device ID information saved deviceIdType = DeviceIdTypeInternalEnums.DEVELOPER_SUPPLIED; } } // if not check if device ID was provided with URL else if (deviceIdParamValue !== null) { - log(logLevelEnums.INFO, "[Initialization] Device ID set by URL"); + log(logLevelEnums.INFO, "initialize, Device ID set by URL"); this.device_id = deviceIdParamValue; deviceIdType = DeviceIdTypeInternalEnums.URL_PROVIDED; } // if not check if developer provided any ID else if (developerSetDeviceId) { - log(logLevelEnums.INFO, "[Initialization] Device ID set by developer"); + log(logLevelEnums.INFO, "initialize, Device ID set by developer"); this.device_id = developerSetDeviceId; if (ob && Object.keys(ob).length) { if (ob.device_id !== undefined) { @@ -614,21 +616,21 @@ this.device_id = "[CLY]_temp_id"; deviceIdType = DeviceIdTypeInternalEnums.TEMPORARY_ID; if (offlineMode && tempIdModeWasEnabled) { - log(logLevelEnums.INFO, "[Initialization] Temp ID set, continuing offline mode from previous app session"); + log(logLevelEnums.INFO, "initialize, Temp ID set, continuing offline mode from previous app session"); } else if (offlineMode && !tempIdModeWasEnabled) { // this if we get here then it means either first init we enter offline mode or we cleared the device ID during the init and still user entered the offline mode - log(logLevelEnums.INFO, "[Initialization] Temp ID set, entering offline mode"); + log(logLevelEnums.INFO, "initialize, Temp ID set, entering offline mode"); } else { // no device ID was provided, no offline mode flag was provided, in the previous app session we entered offline mode and now we carry on offlineMode = true; - log(logLevelEnums.INFO, "[Initialization] Temp ID set, enabling offline mode"); + log(logLevelEnums.INFO, "initialize, Temp ID set, enabling offline mode"); } } // if all fails generate an ID else { - log(logLevelEnums.INFO, "[Initialization] Generating the device ID"); + log(logLevelEnums.INFO, "initialize, Generating the device ID"); this.device_id = getConfig("device_id", ob, getStoredIdOrGenerateId()); if (ob && Object.keys(ob).length) { if (ob.device_id !== undefined) { @@ -646,9 +648,11 @@ // as we have assigned the device ID now we can save the tags if (hasUTM) { - for (var tag in this.utm) { - if (utms[tag]) { + freshUTMTags = {}; + for (var tag in this.utm) { // this.utm is a filter for allowed tags + if (utms[tag]) { // utms is the tags that were passed in the URL this.userData.set("utm_" + tag, utms[tag]); + freshUTMTags[tag] = utms[tag]; } else { this.userData.unset("utm_" + tag); @@ -721,6 +725,7 @@ trackingScrolls = false; currentViewId = null; previousViewId = null; + freshUTMTags = null; try { localStorage.setItem("cly_testLocal", true); @@ -789,7 +794,7 @@ * //or call Countly.add_consent("crashes") to allow some separate feature */ this.group_features = function(features) { - log(logLevelEnums.INFO, "[group_features] Grouping features"); + log(logLevelEnums.INFO, "group_features, Grouping features"); if (features) { for (var i in features) { if (!consents[i]) { @@ -800,16 +805,16 @@ consents[i] = { features: features[i] }; } else { - log(logLevelEnums.ERROR, "[group_features] Incorrect feature list for [" + i + "] value: [" + features[i] + "]"); + log(logLevelEnums.ERROR, "group_features, Incorrect feature list for [" + i + "] value: [" + features[i] + "]"); } } else { - log(logLevelEnums.WARNING, "[group_features] Feature name [" + i + "] is already reserved"); + log(logLevelEnums.WARNING, "group_features, Feature name [" + i + "] is already reserved"); } } } else { - log(logLevelEnums.ERROR, "[group_features] Incorrect features:[" + features + "]"); + log(logLevelEnums.ERROR, "group_features, Incorrect features:[" + features + "]"); } }; @@ -819,16 +824,16 @@ * @returns {Boolean} true if consent was given for the feature or false if it was not */ this.check_consent = function(feature) { - log(logLevelEnums.INFO, "[check_consent] Checking if consent is given for specific feature:[" + feature + "]"); + log(logLevelEnums.INFO, "check_consent, Checking if consent is given for specific feature:[" + feature + "]"); if (!this.require_consent) { // we don't need to have specific consents - log(logLevelEnums.INFO, "[check_consent] require_consent is off, no consent is necessary"); + log(logLevelEnums.INFO, "check_consent, require_consent is off, no consent is necessary"); return true; } if (consents[feature]) { return !!(consents[feature] && consents[feature].optin); } - log(logLevelEnums.ERROR, "[check_consent] No feature available for [" + feature + "]"); + log(logLevelEnums.ERROR, "check_consent, No feature available for [" + feature + "]"); return false; }; @@ -837,7 +842,7 @@ * @returns {number} a number that indicates the device id type */ this.get_device_id_type = function() { - log(logLevelEnums.INFO, "[check_device_id_type] Retrieving the current device id type.[" + deviceIdType + "]"); + log(logLevelEnums.INFO, "check_device_id_type, Retrieving the current device id type.[" + deviceIdType + "]"); var type; switch (deviceIdType) { case DeviceIdTypeInternalEnums.SDK_GENERATED: @@ -862,7 +867,7 @@ * @returns {string} device id */ this.get_device_id = function() { - log(logLevelEnums.INFO, "[get_device_id] Retrieving the device id: [" + this.device_id + "]"); + log(logLevelEnums.INFO, "get_device_id, Retrieving the device id: [" + this.device_id + "]"); return this.device_id; }; @@ -871,10 +876,10 @@ * @returns {Boolean} true is has any consent given, false if no consents given */ this.check_any_consent = function() { - log(logLevelEnums.INFO, "[check_any_consent] Checking if any consent is given"); + log(logLevelEnums.INFO, "check_any_consent, Checking if any consent is given"); if (!this.require_consent) { // we don't need to have consents - log(logLevelEnums.INFO, "[check_any_consent] require_consent is off, no consent is necessary"); + log(logLevelEnums.INFO, "check_any_consent, require_consent is off, no consent is necessary"); return true; } for (var i in consents) { @@ -882,7 +887,7 @@ return true; } } - log(logLevelEnums.INFO, "[check_any_consent] No consents given"); + log(logLevelEnums.INFO, "check_any_consent, No consents given"); return false; }; @@ -891,7 +896,7 @@ * @param {string|array} feature - name of the feature, possible values, "sessions","events","views","scrolls","clicks","forms","crashes","attribution","users", etc or custom provided through {@link Countly.group_features} */ this.add_consent = function(feature) { - log(logLevelEnums.INFO, "[add_consent] Adding consent for [" + feature + "]"); + log(logLevelEnums.INFO, "add_consent, Adding consent for [" + feature + "]"); if (Array.isArray(feature)) { for (var i = 0; i < feature.length; i++) { this.add_consent(feature[i]); @@ -923,7 +928,7 @@ } } else { - log(logLevelEnums.ERROR, "[add_consent] No feature available for [" + feature + "]"); + log(logLevelEnums.ERROR, "add_consent, No feature available for [" + feature + "]"); } }; @@ -933,7 +938,7 @@ * @param {Boolean} enforceConsentUpdate - regulates if a request will be sent to the server or not. If true, removing consents will send a request to the server and if false, consents will be removed without a request */ this.remove_consent = function(feature) { - log(logLevelEnums.INFO, "[remove_consent] Removing consent for [" + feature + "]"); + log(logLevelEnums.INFO, "remove_consent, Removing consent for [" + feature + "]"); this.remove_consent_internal(feature, true); }; // removes consent without updating @@ -959,7 +964,7 @@ } } else { - log(logLevelEnums.WARNING, "[remove_consent] No feature available for [" + feature + "]"); + log(logLevelEnums.WARNING, "remove_consent, No feature available for [" + feature + "]"); } }; @@ -981,12 +986,12 @@ } } toRequestQueue({ consent: JSON.stringify(consentMessage) }); - log(logLevelEnums.DEBUG, "[updateConsent] Consent update request has been sent to the queue."); + log(logLevelEnums.DEBUG, "Consent update request has been sent to the queue."); }, 1000); }; this.enable_offline_mode = function() { - log(logLevelEnums.INFO, "[enable_offline_mode] Enabling offline mode"); + log(logLevelEnums.INFO, "enable_offline_mode, Enabling offline mode"); // clear consents this.remove_consent_internal(Countly.features, false); offlineMode = true; @@ -997,10 +1002,10 @@ this.disable_offline_mode = function(device_id) { if (!offlineMode) { - log(logLevelEnums.WARNING, "[disable_offline_mode] Countly was not in offline mode."); + log(logLevelEnums.WARNING, "disable_offline_mode, Countly was not in offline mode."); return; } - log(logLevelEnums.INFO, "[disable_offline_mode] Disabling offline mode"); + log(logLevelEnums.INFO, "disable_offline_mode, Disabling offline mode"); offlineMode = false; if (device_id && this.device_id !== device_id) { this.device_id = device_id; @@ -1008,7 +1013,7 @@ deviceIdType = DeviceIdTypeInternalEnums.DEVELOPER_SUPPLIED; setValueInStorage("cly_id", this.device_id); setValueInStorage("cly_id_type", DeviceIdTypeInternalEnums.DEVELOPER_SUPPLIED); - log(logLevelEnums.INFO, "[disable_offline_mode] Changing id to: " + this.device_id); + log(logLevelEnums.INFO, "disable_offline_mode, Changing id to: " + this.device_id); } else { this.device_id = getStoredIdOrGenerateId(); @@ -1041,12 +1046,12 @@ * @param {bool} force - force begin session request even if session cookie is enabled */ this.begin_session = function(noHeartBeat, force) { - log(logLevelEnums.INFO, "[begin_session] Starting the session. There was an ongoing session: [" + sessionStarted + "]"); + log(logLevelEnums.INFO, "begin_session, Starting the session. There was an ongoing session: [" + sessionStarted + "]"); if (noHeartBeat) { - log(logLevelEnums.INFO, "[begin_session] Heartbeats are disabled"); + log(logLevelEnums.INFO, "begin_session, Heartbeats are disabled"); } if (force) { - log(logLevelEnums.INFO, "[begin_session] Session starts irrespective of session cookie"); + log(logLevelEnums.INFO, "begin_session, Session starts irrespective of session cookie"); } if (this.check_consent(featureEnums.SESSIONS)) { if (!sessionStarted) { @@ -1061,9 +1066,9 @@ sessionStarted = true; autoExtend = !(noHeartBeat); var expire = getValueFromStorage("cly_session"); - log(logLevelEnums.VERBOSE, "[begin_session] Session state, forced: [" + force + "], useSessionCookie: [" + useSessionCookie + "], seconds to expire: [" + (expire - lastBeat) + "], expired: [" + (parseInt(expire) <= getTimestamp()) + "] "); + log(logLevelEnums.VERBOSE, "begin_session, Session state, forced: [" + force + "], useSessionCookie: [" + useSessionCookie + "], seconds to expire: [" + (expire - lastBeat) + "], expired: [" + (parseInt(expire) <= getTimestamp()) + "] "); if (force || !useSessionCookie || !expire || parseInt(expire) <= getTimestamp()) { - log(logLevelEnums.INFO, "[begin_session] Session started"); + log(logLevelEnums.INFO, "begin_session, Session started"); if (firstView === null) { firstView = true; } @@ -1085,10 +1090,10 @@ * @param {int} sec - amount of seconds to report for current session */ this.session_duration = function(sec) { - log(logLevelEnums.INFO, "[session_duration] Reporting session duration"); + log(logLevelEnums.INFO, "session_duration, Reporting session duration"); if (this.check_consent(featureEnums.SESSIONS)) { if (sessionStarted) { - log(logLevelEnums.INFO, "[session_duration] Session extended: ", sec); + log(logLevelEnums.INFO, "session_duration, Session extended: ", sec); toRequestQueue({ session_duration: sec }); extendSession(); } @@ -1101,13 +1106,13 @@ * @param {bool} force - force end session request even if session cookie is enabled */ this.end_session = function(sec, force) { - log(logLevelEnums.INFO, "[end_session] Ending the current session. There was an on going session:[" + sessionStarted + "]"); + log(logLevelEnums.INFO, "end_session, Ending the current session. There was an on going session:[" + sessionStarted + "]"); if (this.check_consent(featureEnums.SESSIONS)) { if (sessionStarted) { sec = sec || getTimestamp() - lastBeat; reportViewDuration(); if (!useSessionCookie || force) { - log(logLevelEnums.INFO, "[end_session] Session ended"); + log(logLevelEnums.INFO, "end_session, Session ended"); toRequestQueue({ end_session: 1, session_duration: sec }); } else { @@ -1124,16 +1129,16 @@ * @param {boolean} merge - move data from old ID to new ID on server * */ this.change_id = function(newId, merge) { - log(logLevelEnums.INFO, "[change_id] Changing the ID"); + log(logLevelEnums.INFO, "change_id, Changing the ID"); if (merge) { - log(logLevelEnums.INFO, "[change_id] Will merge the IDs"); + log(logLevelEnums.INFO, "change_id, Will merge the IDs"); } if (!newId || typeof newId !== "string" || newId.length === 0) { - log(logLevelEnums.ERROR, "[change_id] The provided ID: [" + newId + "] is not a valid ID"); + log(logLevelEnums.ERROR, "change_id, The provided ID: [" + newId + "] is not a valid ID"); return; } if (offlineMode) { - log(logLevelEnums.WARNING, "[change_id] Offline mode was on, initiating disabling sequence instead."); + log(logLevelEnums.WARNING, "change_id, Offline mode was on, initiating disabling sequence instead."); this.disable_offline_mode(newId); return; } @@ -1156,7 +1161,7 @@ deviceIdType = DeviceIdTypeInternalEnums.DEVELOPER_SUPPLIED; setValueInStorage("cly_id", this.device_id); setValueInStorage("cly_id_type", DeviceIdTypeInternalEnums.DEVELOPER_SUPPLIED); - log(logLevelEnums.INFO, "[change_id] Changing ID from:[" + oldId + "] to [" + newId + "]"); + log(logLevelEnums.INFO, "change_id, Changing ID from:[" + oldId + "] to [" + newId + "]"); if (merge) { // no consent check here since 21.11.0 toRequestQueue({ old_device_id: oldId }); @@ -1184,7 +1189,7 @@ * @param {Object=} event.segmentation - object with segments key /values * */ this.add_event = function(event) { - log(logLevelEnums.INFO, "[add_event] Adding event: ", event); + log(logLevelEnums.INFO, "add_event, Adding event: ", event); // initially no consent is given var respectiveConsent = false; switch (event.key) { @@ -1224,12 +1229,12 @@ function add_cly_events(event, eventIdOverride) { // ignore bots if (self.ignore_visitor) { - log(logLevelEnums.ERROR, "[add_event] Adding event failed. Possible bot or user opt out"); + log(logLevelEnums.ERROR, "Adding event failed. Possible bot or user opt out"); return; } if (!event.key) { - log(logLevelEnums.ERROR, "[add_event] Adding event failed. Event must have a key property"); + log(logLevelEnums.ERROR, "Adding event failed. Event must have a key property"); return; } @@ -1257,7 +1262,7 @@ } eventQueue.push(e); setValueInStorage("cly_event", eventQueue); - log(logLevelEnums.INFO, "[add_event] With event ID: [" + e.id + "], successfully adding the last event:", e); + log(logLevelEnums.INFO, "With event ID: [" + e.id + "], successfully adding the last event:", e); } /** @@ -1267,14 +1272,14 @@ * */ this.start_event = function(key) { if (!key || typeof key !== "string") { - log(logLevelEnums.WARNING, "[start_event] you have to provide a valid string key instead of: [" + key + "]"); + log(logLevelEnums.WARNING, "start_event, you have to provide a valid string key instead of: [" + key + "]"); return; } - log(logLevelEnums.INFO, "[start_event] Starting timed event with key: [" + key + "]"); + log(logLevelEnums.INFO, "start_event, Starting timed event with key: [" + key + "]"); // truncate event name to internal limits key = truncateSingleValue(key, self.maxKeyLength, "start_event", log); if (timedEvents[key]) { - log(logLevelEnums.WARNING, "[start_event] Timed event with key: [" + key + "] already started"); + log(logLevelEnums.WARNING, "start_event, Timed event with key: [" + key + "] already started"); return; } timedEvents[key] = getTimestamp(); @@ -1287,18 +1292,18 @@ * */ this.cancel_event = function(key) { if (!key || typeof key !== "string") { - log(logLevelEnums.WARNING, "[cancel_event] you have to provide a valid string key instead of: [" + key + "]"); + log(logLevelEnums.WARNING, "cancel_event, you have to provide a valid string key instead of: [" + key + "]"); return false; } - log(logLevelEnums.INFO, "[cancel_event] Canceling timed event with key: [" + key + "]"); + log(logLevelEnums.INFO, "cancel_event, Canceling timed event with key: [" + key + "]"); // truncate event name to internal limits. This is done incase start_event key was truncated. key = truncateSingleValue(key, self.maxKeyLength, "cancel_event", log); if (timedEvents[key]) { delete timedEvents[key]; - log(logLevelEnums.INFO, "[cancel_event] Timed event with key: [" + key + "] is canceled"); + log(logLevelEnums.INFO, "cancel_event, Timed event with key: [" + key + "] is canceled"); return true; } - log(logLevelEnums.WARNING, "[cancel_event] Timed event with key: [" + key + "] was not found"); + log(logLevelEnums.WARNING, "cancel_event, Timed event with key: [" + key + "] was not found"); return false; }; @@ -1308,21 +1313,21 @@ * */ this.end_event = function(event) { if (!event) { - log(logLevelEnums.WARNING, "[end_event] you have to provide a valid string key or event object instead of: [" + event + "]"); + log(logLevelEnums.WARNING, "end_event, you have to provide a valid string key or event object instead of: [" + event + "]"); return; } - log(logLevelEnums.INFO, "[end_event] Ending timed event"); + log(logLevelEnums.INFO, "end_event, Ending timed event"); if (typeof event === "string") { // truncate event name to internal limits. This is done incase start_event key was truncated. event = truncateSingleValue(event, self.maxKeyLength, "end_event", log); event = { key: event }; } if (!event.key) { - log(logLevelEnums.ERROR, "[end_event] Timed event must have a key property"); + log(logLevelEnums.ERROR, "end_event, Timed event must have a key property"); return; } if (!timedEvents[event.key]) { - log(logLevelEnums.ERROR, "[end_event] Timed event with key: [" + event.key + "] was not started"); + log(logLevelEnums.ERROR, "end_event, Timed event with key: [" + event.key + "] was not started"); return; } event.dur = getTimestamp() - timedEvents[event.key]; @@ -1335,7 +1340,7 @@ * @param {string=} orientation - orientation as landscape or portrait * */ this.report_orientation = function(orientation) { - log(logLevelEnums.INFO, "[report_orientation] Reporting orientation"); + log(logLevelEnums.INFO, "report_orientation, Reporting orientation"); if (this.check_consent(featureEnums.USERS)) { add_cly_events({ key: internalEventKeyEnums.ORIENTATION, @@ -1354,7 +1359,7 @@ * @deprecated use 'recordDirectAttribution' in place of this call * */ this.report_conversion = function(campaign_id, campaign_user_id) { - log(logLevelEnums.WARNING, "[report_conversion] Deprecated function call! Use 'recordDirectAttribution' in place of this call. Call will be redirected now!"); + log(logLevelEnums.WARNING, "report_conversion, Deprecated function call! Use 'recordDirectAttribution' in place of this call. Call will be redirected now!"); this.recordDirectAttribution(campaign_id, campaign_user_id); }; /** @@ -1363,7 +1368,7 @@ * @param {string=} campaign_user_id - id of user's click on campaign, or will use the one that is stored after campaign link click * */ this.recordDirectAttribution = function(campaign_id, campaign_user_id) { - log(logLevelEnums.INFO, "[recordDirectAttribution] Recording the attribution for campaign ID: [" + campaign_id + "] and the user ID: [" + campaign_user_id + "]"); + log(logLevelEnums.INFO, "recordDirectAttribution, Recording the attribution for campaign ID: [" + campaign_id + "] and the user ID: [" + campaign_user_id + "]"); if (this.check_consent(featureEnums.ATTRIBUTION)) { campaign_id = campaign_id || getValueFromStorage("cly_cmp_id") || "cly_organic"; campaign_user_id = campaign_user_id || getValueFromStorage("cly_cmp_uid"); @@ -1391,10 +1396,10 @@ * @param {Object=} user.custom - object with custom key value properties you want to save with user * */ this.user_details = function(user) { - log(logLevelEnums.INFO, "[user_details] Trying to add user details: ", user); + log(logLevelEnums.INFO, "user_details, Trying to add user details: ", user); if (this.check_consent(featureEnums.USERS)) { sendEventsForced(); // flush events to event queue to prevent a drill issue - log(logLevelEnums.INFO, "[user_details] flushed the event queue"); + log(logLevelEnums.INFO, "user_details, flushed the event queue"); // truncating user values and custom object key value pairs user.name = truncateSingleValue(user.name, self.maxValueSize, "user_details", log); user.username = truncateSingleValue(user.username, self.maxValueSize, "user_details", log); @@ -1597,7 +1602,7 @@ log(logLevelEnums.INFO, "[userData] save, Saving changes to user's custom property"); if (self.check_consent(featureEnums.USERS)) { sendEventsForced(); // flush events to event queue to prevent a drill issue - log(logLevelEnums.INFO, "[user_details] flushed the event queue"); + log(logLevelEnums.INFO, "user_details, flushed the event queue"); toRequestQueue({ user_details: JSON.stringify({ custom: customData }) }); } customData = {}; @@ -1615,12 +1620,12 @@ * @param {Object=} trace.apm_attr - object profiling attributes (not yet supported) */ this.report_trace = function(trace) { - log(logLevelEnums.INFO, "[report_trace] Reporting performance trace"); + log(logLevelEnums.INFO, "report_trace, Reporting performance trace"); if (this.check_consent(featureEnums.APM)) { var props = ["type", "name", "stz", "etz", "apm_metrics", "apm_attr"]; for (var i = 0; i < props.length; i++) { if (props[i] !== "apm_attr" && typeof trace[props[i]] === "undefined") { - log(logLevelEnums.WARNING, "[report_trace] APM trace don't have the property: " + props[i]); + log(logLevelEnums.WARNING, "report_trace, APM trace don't have the property: " + props[i]); return; } } @@ -1633,7 +1638,7 @@ e.hour = date.getHours(); e.dow = date.getDay(); toRequestQueue({ apm: JSON.stringify(e) }); - log(logLevelEnums.INFO, "[report_trace] Successfully adding APM trace: ", e); + log(logLevelEnums.INFO, "report_trace, Successfully adding APM trace: ", e); } }; @@ -1642,7 +1647,7 @@ * @param {string=} segments - additional key value pairs you want to provide with error report, like versions of libraries used, etc. * */ this.track_errors = function(segments) { - log(logLevelEnums.INFO, "[track_errors] Started tracking errors"); + log(logLevelEnums.INFO, "track_errors, Started tracking errors"); // Indicated that for this instance of the countly error tracking is enabled Countly.i[this.app_key].tracking_crashes = true; if (!window.cly_crashes) { @@ -1685,7 +1690,7 @@ error += stack.join("\n"); } catch (ex) { - log(logLevelEnums.ERROR, "[track_errors] Call stack generation experienced a problem: " + ex); + log(logLevelEnums.ERROR, "track_errors, Call stack generation experienced a problem: " + ex); } // false indicates fatal error (as in non_fatal:false) dispatchErrors(error, false); @@ -1706,7 +1711,7 @@ * @param {string=} segments - additional key value pairs you want to provide with error report, like versions of libraries used, etc. * */ this.log_error = function(err, segments) { - log(logLevelEnums.INFO, "[log_error] Logging errors"); + log(logLevelEnums.INFO, "log_error, Logging errors"); // true indicates non fatal error (as in non_fatal:true) this.recordError(err, true, segments); }; @@ -1716,13 +1721,13 @@ * @param {string} record - any text describing what user did * */ this.add_log = function(record) { - log(logLevelEnums.INFO, "[add_log] Adding a new log of breadcrumbs: [ " + record + " ]"); + log(logLevelEnums.INFO, "add_log, Adding a new log of breadcrumbs: [ " + record + " ]"); if (this.check_consent(featureEnums.CRASHES)) { // truncate description wrt internal limits record = truncateSingleValue(record, self.maxValueSize, "add_log", log); while (crashLogs.length >= self.maxBreadcrumbCount) { crashLogs.shift(); - log(logLevelEnums.WARNING, "[add_log] Reached maximum crashLogs size. Will erase the oldest one."); + log(logLevelEnums.WARNING, "add_log, Reached maximum crashLogs size. Will erase the oldest one."); } crashLogs.push(record); } @@ -1767,14 +1772,14 @@ // use new RC API if (this.useExplicitRcApi) { - log(logLevelEnums.INFO, "[fetch_remote_config] Fetching remote config"); + log(logLevelEnums.INFO, "fetch_remote_config, Fetching remote config"); // opt in is true(1) or false(0) var opt = this.rcAutoOptinAb ? 1 : 0; fetch_remote_config_explicit(keysFiltered, omitKeysFiltered, opt, null, callbackFiltered); return; } - log(logLevelEnums.WARNING, "[fetch_remote_config] Fetching remote config, with legacy API"); + log(logLevelEnums.WARNING, "fetch_remote_config, Fetching remote config, with legacy API"); fetch_remote_config_explicit(keysFiltered, omitKeysFiltered, null, "legacy", callbackFiltered); }; @@ -1787,7 +1792,7 @@ * @param {function=} callback - Callback to notify with first param error and second param remote config object * */ function fetch_remote_config_explicit(keys, omit_keys, optIn, api, callback) { - log(logLevelEnums.INFO, "[fetch_remote_config_explicit] Fetching sequence initiated"); + log(logLevelEnums.INFO, "fetch_remote_config_explicit, Fetching sequence initiated"); var request = { method: "rc" }; @@ -1820,9 +1825,9 @@ } if (self.check_consent(featureEnums.REMOTE_CONFIG)) { prepareRequest(request); - sendXmlHttpRequest("[fetch_remote_config_explicit]", self.url + readPath, request, function(err, params, responseText) { + sendXmlHttpRequest("fetch_remote_config_explicit", self.url + readPath, request, function(err, params, responseText) { if (err) { - log(logLevelEnums.ERROR, "[fetch_remote_config_explicit] An error occurred: " + err); + log(logLevelEnums.ERROR, "fetch_remote_config_explicit, An error occurred: " + err); return; } try { @@ -1840,17 +1845,17 @@ setValueInStorage("cly_remote_configs", remoteConfigs); } catch (ex) { - log(logLevelEnums.ERROR, "[fetch_remote_config_explicit] Had an issue while parsing the response: " + ex); + log(logLevelEnums.ERROR, "fetch_remote_config_explicit, Had an issue while parsing the response: " + ex); } if (providedCall) { - log(logLevelEnums.INFO, "[fetch_remote_config_explicit] Callback function is provided"); + log(logLevelEnums.INFO, "fetch_remote_config_explicit, Callback function is provided"); providedCall(err, remoteConfigs); } // JSON array can pass }, true); } else { - log(logLevelEnums.ERROR, "[fetch_remote_config_explicit] Remote config requires explicit consent"); + log(logLevelEnums.ERROR, "fetch_remote_config_explicit, Remote config requires explicit consent"); if (providedCall) { providedCall(new Error("Remote config requires explicit consent"), remoteConfigs); } @@ -1862,9 +1867,9 @@ * @param {array=} keys - Array of keys opt in FOR * */ this.enrollUserToAb = function(keys) { - log(logLevelEnums.INFO, "[enrollUserToAb] Providing AB test keys to opt in for"); + log(logLevelEnums.INFO, "enrollUserToAb, Providing AB test keys to opt in for"); if (!keys || !Array.isArray(keys) || keys.length === 0) { - log(logLevelEnums.ERROR, "[enrollUserToAb] No keys provided"); + log(logLevelEnums.ERROR, "enrollUserToAb, No keys provided"); return; } var request = { @@ -1872,17 +1877,17 @@ keys: JSON.stringify(keys) }; prepareRequest(request); - sendXmlHttpRequest("[enrollUserToAb]", this.url + readPath, request, function(err, params, responseText) { + sendXmlHttpRequest("enrollUserToAb", this.url + readPath, request, function(err, params, responseText) { if (err) { - log(logLevelEnums.ERROR, "[enrollUserToAb] An error occurred: " + err); + log(logLevelEnums.ERROR, "enrollUserToAb, An error occurred: " + err); return; } try { var resp = JSON.parse(responseText); - log(logLevelEnums.DEBUG, "[enrollUserToAb] Parsed the response's result: [" + resp.result + "]"); + log(logLevelEnums.DEBUG, "enrollUserToAb, Parsed the response's result: [" + resp.result + "]"); } catch (ex) { - log(logLevelEnums.ERROR, "[enrollUserToAb] Had an issue while parsing the response: " + ex); + log(logLevelEnums.ERROR, "enrollUserToAb, Had an issue while parsing the response: " + ex); } // JSON array can pass }, true); @@ -1894,7 +1899,7 @@ * @returns {object} remote configs * */ this.get_remote_config = function(key) { - log(logLevelEnums.INFO, "[get_remote_config] Getting remote config from storage"); + log(logLevelEnums.INFO, "get_remote_config, Getting remote config from storage"); if (typeof key !== "undefined") { return remoteConfigs[key]; } @@ -1905,7 +1910,7 @@ * Stop tracking duration time for this user * */ this.stop_time = function() { - log(logLevelEnums.INFO, "[stop_time] Stopping tracking duration"); + log(logLevelEnums.INFO, "stop_time, Stopping tracking duration"); if (trackTime) { trackTime = false; storedDuration = getTimestamp() - lastBeat; @@ -1917,7 +1922,7 @@ * Start tracking duration time for this user, by default it is automatically tracked if you are using internal session handling * */ this.start_time = function() { - log(logLevelEnums.INFO, "[start_time] Starting tracking duration"); + log(logLevelEnums.INFO, "start_time, Starting tracking duration"); if (!trackTime) { trackTime = true; lastBeat = getTimestamp() - storedDuration; @@ -1931,7 +1936,7 @@ * Track user sessions automatically, including time user spent on your website * */ this.track_sessions = function() { - log(logLevelEnums.INFO, "[track_session] Starting tracking user session"); + log(logLevelEnums.INFO, "track_session, Starting tracking user session"); // start session this.begin_session(); this.start_time(); @@ -2019,10 +2024,10 @@ * @param {object=} viewSegments - optional key value object with segments to report with the view * */ this.track_pageview = function(page, ignoreList, viewSegments) { - log(logLevelEnums.INFO, "[track_pageview] Tracking page views"); - log(logLevelEnums.VERBOSE, "[track_pageview] last view is:[" + lastView + "], current view ID is:[" + currentViewId + "], previous view ID is:[" + previousViewId + "]"); + log(logLevelEnums.INFO, "track_pageview, Tracking page views"); + log(logLevelEnums.VERBOSE, "track_pageview, last view is:[" + lastView + "], current view ID is:[" + currentViewId + "], previous view ID is:[" + previousViewId + "]"); if (lastView && trackingScrolls) { - log(logLevelEnums.DEBUG, "[track_pageview] Scroll registry triggered"); + log(logLevelEnums.DEBUG, "track_pageview, Scroll registry triggered"); processScrollView(); // for single page site's view change isScrollRegistryOpen = true; scrollRegistryTopPosition = 0; @@ -2042,11 +2047,11 @@ page = this.getViewName(); } if (page === undefined || page === "") { - log(logLevelEnums.ERROR, "[track_pageview] No page name to track (it is either undefined or empty string). No page view can be tracked."); + log(logLevelEnums.ERROR, "track_pageview, No page name to track (it is either undefined or empty string). No page view can be tracked."); return; } if (page === null) { - log(logLevelEnums.ERROR, "[track_pageview] View name returned as null. Page view will be ignored."); + log(logLevelEnums.ERROR, "track_pageview, View name returned as null. Page view will be ignored."); return; } @@ -2055,18 +2060,18 @@ try { var reg = new RegExp(ignoreList[i]); if (reg.test(page)) { - log(logLevelEnums.INFO, "[track_pageview] Ignoring the page: " + page); + log(logLevelEnums.INFO, "track_pageview, Ignoring the page: " + page); return; } } catch (ex) { - log(logLevelEnums.ERROR, "[track_pageview] Problem with finding ignore list item: " + ignoreList[i] + ", error: " + ex); + log(logLevelEnums.ERROR, "track_pageview, Problem with finding ignore list item: " + ignoreList[i] + ", error: " + ex); } } } lastView = page; lastViewTime = getTimestamp(); - log(logLevelEnums.VERBOSE, "[track_pageview] last view is assigned:[" + lastView + "], current view ID is:[" + currentViewId + "], previous view ID is:[" + previousViewId + "]"); + log(logLevelEnums.VERBOSE, "track_pageview, last view is assigned:[" + lastView + "], current view ID is:[" + currentViewId + "], previous view ID is:[" + previousViewId + "]"); var segments = { name: page, visit: 1, @@ -2103,6 +2108,24 @@ } } + // add utm tags + if (freshUTMTags && Object.keys(freshUTMTags).length) { + log(logLevelEnums.INFO, "track_pageview, Adding fresh utm tags to segmentation:[" + JSON.stringify(freshUTMTags) + "]"); + for (var utm in freshUTMTags) { + if (typeof segments["utm_" + utm] === "undefined") { + segments["utm_" + utm] = freshUTMTags[utm]; + } + } + // TODO: Current logic adds utm tags to each view if the user landed with utm tags for that session(in non literal sense) + // we might want to change this logic to add utm tags only to the first view's segmentation by freshUTMTags = null; here + } + + // add referrer if it is usable + if (isReferrerUsable()) { + log(logLevelEnums.INFO, "track_pageview, Adding referrer to segmentation:[" + document.referrer + "]"); + segments.referrer = document.referrer; // add referrer + } + if (viewSegments) { viewSegments = truncateObject(viewSegments, self.maxKeyLength, self.maxValueSize, self.maxSegmentationValues, "track_pageview", log); @@ -2132,7 +2155,7 @@ * @param {object=} segments - optional view segments to track with the view * */ this.track_view = function(page, ignoreList, segments) { - log(logLevelEnums.INFO, "[track_view] Initiating tracking page views"); + log(logLevelEnums.INFO, "track_view, Initiating tracking page views"); this.track_pageview(page, ignoreList, segments); }; @@ -2141,9 +2164,9 @@ * @param {Object=} parent - DOM object which children to track, by default it is document body * */ this.track_clicks = function(parent) { - log(logLevelEnums.INFO, "[track_clicks] Starting to track clicks"); + log(logLevelEnums.INFO, "track_clicks, Starting to track clicks"); if (parent) { - log(logLevelEnums.INFO, "[track_clicks] Tracking the specified children:[" + parent + "]"); + log(logLevelEnums.INFO, "track_clicks, Tracking the specified children:[" + parent + "]"); } parent = parent || document; var shouldProcess = true; @@ -2196,9 +2219,9 @@ * @param {Object=} parent - DOM object which children to track, by default it is document body * */ this.track_scrolls = function(parent) { - log(logLevelEnums.INFO, "[track_scrolls] Starting to track scrolls"); + log(logLevelEnums.INFO, "track_scrolls, Starting to track scrolls"); if (parent) { - log(logLevelEnums.INFO, "[track_scrolls] Tracking the specified children"); + log(logLevelEnums.INFO, "track_scrolls, Tracking the specified children"); } parent = parent || window; isScrollRegistryOpen = true; @@ -2213,9 +2236,9 @@ * @param {Object=} parent - DOM object which children to track, by default it is document body * */ this.track_links = function(parent) { - log(logLevelEnums.INFO, "[track_links] Starting to track clicks to links"); + log(logLevelEnums.INFO, "track_links, Starting to track clicks to links"); if (parent) { - log(logLevelEnums.INFO, "[track_links] Tracking the specified children"); + log(logLevelEnums.INFO, "track_links, Tracking the specified children"); } parent = parent || document; /** @@ -2255,7 +2278,7 @@ * @param {boolean=} trackHidden - provide true to also track hidden inputs, default false * */ this.track_forms = function(parent, trackHidden) { - log(logLevelEnums.INFO, "[track_forms] Starting to track form submissions. DOM object provided:[" + (!!parent) + "] Tracking hidden inputs :[" + (!!trackHidden) + "]"); + log(logLevelEnums.INFO, "track_forms, Starting to track form submissions. DOM object provided:[" + (!!parent) + "] Tracking hidden inputs :[" + (!!trackHidden) + "]"); parent = parent || document; /** * Get name of the input @@ -2345,7 +2368,7 @@ * @param {boolean} [useCustom=false] - submit collected data as custom user properties, by default collects as main user properties * */ this.collect_from_forms = function(parent, useCustom) { - log(logLevelEnums.INFO, "[collect_from_forms] Starting to collect possible user data. DOM object provided:[" + (!!parent) + "] Submitting custom user property:[" + (!!useCustom) + "]"); + log(logLevelEnums.INFO, "collect_from_forms, Starting to collect possible user data. DOM object provided:[" + (!!parent) + "] Submitting custom user property:[" + (!!useCustom) + "]"); parent = parent || document; /** * Process form data @@ -2467,7 +2490,7 @@ // record user info, if any if (hasUserInfo) { - log(logLevelEnums.INFO, "[collect_from_forms] Gathered user data", userdata); + log(logLevelEnums.INFO, "collect_from_forms, Gathered user data", userdata); if (useCustom) { self.user_details({ custom: userdata }); } @@ -2487,10 +2510,10 @@ * */ this.collect_from_facebook = function(custom) { if (typeof FB === "undefined" || !FB || !FB.api) { - log(logLevelEnums.ERROR, "[collect_from_facebook] Facebook SDK is not available"); + log(logLevelEnums.ERROR, "collect_from_facebook, Facebook SDK is not available"); return; } - log(logLevelEnums.INFO, "[collect_from_facebook] Starting to collect possible user data"); + log(logLevelEnums.INFO, "collect_from_facebook, Starting to collect possible user data"); /* globals FB */ FB.api("/me", function(resp) { var data = {}; @@ -2539,7 +2562,7 @@ * Opts out user of any metric tracking * */ this.opt_out = function() { - log(logLevelEnums.INFO, "[opt_out] Opting out the user"); + log(logLevelEnums.INFO, "opt_out, Opting out the user"); this.ignore_visitor = true; setValueInStorage("cly_ignore", true); }; @@ -2548,7 +2571,7 @@ * Opts in user for tracking, if complies with other user ignore rules like bot useragent and prefetch settings * */ this.opt_in = function() { - log(logLevelEnums.INFO, "[opt_in] Opting in the user"); + log(logLevelEnums.INFO, "opt_in, Opting in the user"); setValueInStorage("cly_ignore", false); this.ignore_visitor = false; checkIgnore(); @@ -2570,7 +2593,7 @@ * @deprecated use 'recordRatingWidgetWithID' in place of this call * */ this.report_feedback = function(ratingWidget) { - log(logLevelEnums.WARNING, "[report_feedback] Deprecated function call! Use 'recordRatingWidgetWithID' or 'reportFeedbackWidgetManually' in place of this call. Call will be redirected to 'recordRatingWidgetWithID' now!"); + log(logLevelEnums.WARNING, "report_feedback, Deprecated function call! Use 'recordRatingWidgetWithID' or 'reportFeedbackWidgetManually' in place of this call. Call will be redirected to 'recordRatingWidgetWithID' now!"); this.recordRatingWidgetWithID(ratingWidget); }; /** @@ -2585,16 +2608,16 @@ * @param {string=} ratingWidget.comment - user's comment * */ this.recordRatingWidgetWithID = function(ratingWidget) { - log(logLevelEnums.INFO, "[recordRatingWidgetWithID] Providing information about user with ID: [ " + ratingWidget.widget_id + " ]"); + log(logLevelEnums.INFO, "recordRatingWidgetWithID, Providing information about user with ID: [ " + ratingWidget.widget_id + " ]"); if (!this.check_consent(featureEnums.STAR_RATING)) { return; } if (!ratingWidget.widget_id) { - log(logLevelEnums.ERROR, "[recordRatingWidgetWithID] Rating Widget must contain widget_id property"); + log(logLevelEnums.ERROR, "recordRatingWidgetWithID, Rating Widget must contain widget_id property"); return; } if (!ratingWidget.rating) { - log(logLevelEnums.ERROR, "[recordRatingWidgetWithID] Rating Widget must contain rating property"); + log(logLevelEnums.ERROR, "recordRatingWidgetWithID, Rating Widget must contain rating property"); return; } var props = ["widget_id", "contactMe", "platform", "app_version", "rating", "email", "comment"]; @@ -2608,14 +2631,14 @@ event.segmentation.app_version = this.metrics._app_version || this.app_version; } if (event.segmentation.rating > 5) { - log(logLevelEnums.WARNING, "[recordRatingWidgetWithID] You have entered a rating higher than 5. Changing it back to 5 now."); + log(logLevelEnums.WARNING, "recordRatingWidgetWithID, You have entered a rating higher than 5. Changing it back to 5 now."); event.segmentation.rating = 5; } else if (event.segmentation.rating < 1) { - log(logLevelEnums.WARNING, "[recordRatingWidgetWithID] You have entered a rating lower than 1. Changing it back to 1 now."); + log(logLevelEnums.WARNING, "recordRatingWidgetWithID, You have entered a rating lower than 1. Changing it back to 1 now."); event.segmentation.rating = 1; } - log(logLevelEnums.INFO, "[recordRatingWidgetWithID] Reporting Rating Widget: ", event); + log(logLevelEnums.INFO, "recordRatingWidgetWithID, Reporting Rating Widget: ", event); add_cly_events(event); }; /** @@ -2650,19 +2673,19 @@ return; } if (!(CountlyFeedbackWidget && CountlyWidgetData)) { - log(logLevelEnums.ERROR, "[reportFeedbackWidgetManually] Widget data and/or Widget object not provided. Aborting."); + log(logLevelEnums.ERROR, "reportFeedbackWidgetManually, Widget data and/or Widget object not provided. Aborting."); return; } if (!CountlyFeedbackWidget._id) { - log(logLevelEnums.ERROR, "[reportFeedbackWidgetManually] Feedback Widgets must contain _id property"); + log(logLevelEnums.ERROR, "reportFeedbackWidgetManually, Feedback Widgets must contain _id property"); return; } if (offlineMode) { - log(logLevelEnums.ERROR, "[reportFeedbackWidgetManually] Feedback Widgets can not be reported in offline mode"); + log(logLevelEnums.ERROR, "reportFeedbackWidgetManually, Feedback Widgets can not be reported in offline mode"); return; } - log(logLevelEnums.INFO, "[reportFeedbackWidgetManually] Providing information about user with, provided result of the widget with ID: [ " + CountlyFeedbackWidget._id + " ] and type: [" + CountlyFeedbackWidget.type + "]"); + log(logLevelEnums.INFO, "reportFeedbackWidgetManually, Providing information about user with, provided result of the widget with ID: [ " + CountlyFeedbackWidget._id + " ] and type: [" + CountlyFeedbackWidget.type + "]"); // type specific checks to see if everything was provided var props = []; @@ -2671,16 +2694,16 @@ if (type === "nps") { if (widgetResult) { if (!widgetResult.rating) { - log(logLevelEnums.ERROR, "[reportFeedbackWidgetManually] Widget must contain rating property"); + log(logLevelEnums.ERROR, "reportFeedbackWidgetManually, Widget must contain rating property"); return; } widgetResult.rating = Math.round(widgetResult.rating); if (widgetResult.rating > 10) { - log(logLevelEnums.WARNING, "[reportFeedbackWidgetManually] You have entered a rating higher than 10. Changing it back to 10 now."); + log(logLevelEnums.WARNING, "reportFeedbackWidgetManually, You have entered a rating higher than 10. Changing it back to 10 now."); widgetResult.rating = 10; } else if (widgetResult.rating < 0) { - log(logLevelEnums.WARNING, "[reportFeedbackWidgetManually] You have entered a rating lower than 0. Changing it back to 0 now."); + log(logLevelEnums.WARNING, "reportFeedbackWidgetManually, You have entered a rating lower than 0. Changing it back to 0 now."); widgetResult.rating = 0; } props = ["rating", "comment"]; @@ -2690,7 +2713,7 @@ else if (type === "survey") { if (widgetResult) { if (Object.keys(widgetResult).length < 1) { - log(logLevelEnums.ERROR, "[reportFeedbackWidgetManually] Widget should have answers to be reported"); + log(logLevelEnums.ERROR, "reportFeedbackWidgetManually, Widget should have answers to be reported"); return; } props = Object.keys(widgetResult); @@ -2700,16 +2723,16 @@ else if (type === "rating") { if (widgetResult) { if (!widgetResult.rating) { - log(logLevelEnums.ERROR, "[reportFeedbackWidgetManually] Widget must contain rating property"); + log(logLevelEnums.ERROR, "reportFeedbackWidgetManually, Widget must contain rating property"); return; } widgetResult.rating = Math.round(widgetResult.rating); if (widgetResult.rating > 5) { - log(logLevelEnums.WARNING, "[reportFeedbackWidgetManually] You have entered a rating higher than 5. Changing it back to 5 now."); + log(logLevelEnums.WARNING, "reportFeedbackWidgetManually, You have entered a rating higher than 5. Changing it back to 5 now."); widgetResult.rating = 5; } else if (widgetResult.rating < 1) { - log(logLevelEnums.WARNING, "[reportFeedbackWidgetManually] You have entered a rating lower than 1. Changing it back to 1 now."); + log(logLevelEnums.WARNING, "reportFeedbackWidgetManually, You have entered a rating lower than 1. Changing it back to 1 now."); widgetResult.rating = 1; } props = ["rating", "comment", "email", "contactMe"]; @@ -2717,7 +2740,7 @@ eventKey = internalEventKeyEnums.STAR_RATING; } else { - log(logLevelEnums.ERROR, "[reportFeedbackWidgetManually] Widget has an unacceptable type"); + log(logLevelEnums.ERROR, "reportFeedbackWidgetManually, Widget has an unacceptable type"); return; } @@ -2741,7 +2764,7 @@ } // add event - log(logLevelEnums.INFO, "[reportFeedbackWidgetManually] Reporting " + type + ": ", event); + log(logLevelEnums.INFO, "reportFeedbackWidgetManually, Reporting " + type + ": ", event); add_cly_events(event); }; /** @@ -2751,7 +2774,7 @@ * @deprecated use 'presentRatingWidgetWithID' in place of this call */ this.show_feedback_popup = function(id) { - log(logLevelEnums.WARNING, "[show_feedback_popup] Deprecated function call! Use 'presentRatingWidgetWithID' in place of this call. Call will be redirected now!"); + log(logLevelEnums.WARNING, "show_feedback_popup, Deprecated function call! Use 'presentRatingWidgetWithID' in place of this call. Call will be redirected now!"); this.presentRatingWidgetWithID(id); }; /** @@ -2759,17 +2782,17 @@ * @param {string} id - id value of related rating widget, you can get this value by click "Copy ID" button in row menu at "Feedback widgets" screen */ this.presentRatingWidgetWithID = function(id) { - log(logLevelEnums.INFO, "[presentRatingWidgetWithID] Showing rating widget popup for the widget with ID: [ " + id + " ]"); + log(logLevelEnums.INFO, "presentRatingWidgetWithID, Showing rating widget popup for the widget with ID: [ " + id + " ]"); if (!this.check_consent(featureEnums.STAR_RATING)) { return; } if (offlineMode) { - log(logLevelEnums.ERROR, "[presentRatingWidgetWithID] Cannot show ratingWidget popup in offline mode"); + log(logLevelEnums.ERROR, "presentRatingWidgetWithID, Cannot show ratingWidget popup in offline mode"); } else { - sendXmlHttpRequest("[presentRatingWidgetWithID]", this.url + "/o/feedback/widget", { widget_id: id }, function(err, params, responseText) { + sendXmlHttpRequest("presentRatingWidgetWithID,", this.url + "/o/feedback/widget", { widget_id: id }, function(err, params, responseText) { if (err) { - log(logLevelEnums.ERROR, "[presentRatingWidgetWithID] An error occurred: " + err); + log(logLevelEnums.ERROR, "presentRatingWidgetWithID, An error occurred: " + err); return; } try { @@ -2778,7 +2801,7 @@ processWidget(currentWidget, false); } catch (JSONParseError) { - log(logLevelEnums.ERROR, "[presentRatingWidgetWithID] JSON parse failed: " + JSONParseError); + log(logLevelEnums.ERROR, "presentRatingWidgetWithID, JSON parse failed: " + JSONParseError); } // JSON array can pass }, true); @@ -2792,7 +2815,7 @@ * @deprecated use 'initializeRatingWidgets' in place of this call */ this.initialize_feedback_popups = function(enableWidgets) { - log(logLevelEnums.WARNING, "[initialize_feedback_popups] Deprecated function call! Use 'initializeRatingWidgets' in place of this call. Call will be redirected now!"); + log(logLevelEnums.WARNING, "initialize_feedback_popups, Deprecated function call! Use 'initializeRatingWidgets' in place of this call. Call will be redirected now!"); this.initializeRatingWidgets(enableWidgets); }; /** @@ -2800,7 +2823,7 @@ * @param {array=} enableWidgets - widget ids array */ this.initializeRatingWidgets = function(enableWidgets) { - log(logLevelEnums.INFO, "[initializeRatingWidgets] Initializing rating widget with provided widget IDs:[ " + enableWidgets + "]"); + log(logLevelEnums.INFO, "initializeRatingWidgets, Initializing rating widget with provided widget IDs:[ " + enableWidgets + "]"); if (!this.check_consent(featureEnums.STAR_RATING)) { return; } @@ -2814,9 +2837,9 @@ stickers[0].remove(); } - sendXmlHttpRequest("[initializeRatingWidgets]", this.url + "/o/feedback/multiple-widgets-by-id", { widgets: JSON.stringify(enableWidgets) }, function(err, params, responseText) { + sendXmlHttpRequest("initializeRatingWidgets,", this.url + "/o/feedback/multiple-widgets-by-id", { widgets: JSON.stringify(enableWidgets) }, function(err, params, responseText) { if (err) { - log(logLevelEnums.ERROR, "[initializeRatingWidgets] An error occurred: " + err); + log(logLevelEnums.ERROR, "initializeRatingWidgets, An error occurred: " + err); return; } try { @@ -2853,7 +2876,7 @@ } } catch (JSONParseError) { - log(logLevelEnums.ERROR, "[initializeRatingWidgets] JSON parse error: " + JSONParseError); + log(logLevelEnums.ERROR, "initializeRatingWidgets, JSON parse error: " + JSONParseError); } // JSON array can pass }, true); @@ -2867,7 +2890,7 @@ * @deprecated use 'enableRatingWidgets' in place of this call * */ this.enable_feedback = function(params) { - log(logLevelEnums.WARNING, "[enable_feedback] Deprecated function call! Use 'enableRatingWidgets' in place of this call. Call will be redirected now!"); + log(logLevelEnums.WARNING, "enable_feedback, Deprecated function call! Use 'enableRatingWidgets' in place of this call. Call will be redirected now!"); this.enableRatingWidgets(params); }; /** @@ -2876,12 +2899,12 @@ * example params: {"popups":["5b21581b967c4850a7818617"]} * */ this.enableRatingWidgets = function(params) { - log(logLevelEnums.INFO, "[enableRatingWidgets] Enabling rating widget with params:", params); + log(logLevelEnums.INFO, "enableRatingWidgets, Enabling rating widget with params:", params); if (!this.check_consent(featureEnums.STAR_RATING)) { return; } if (offlineMode) { - log(logLevelEnums.ERROR, "[enableRatingWidgets] Cannot enable rating widgets in offline mode"); + log(logLevelEnums.ERROR, "enableRatingWidgets, Cannot enable rating widgets in offline mode"); } else { setValueInStorage("cly_fb_widgets", params.popups || params.widgets); @@ -2896,7 +2919,7 @@ this.initializeRatingWidgets(enableWidgets); } else { - log(logLevelEnums.ERROR, "[enableRatingWidgets] You should provide at least one widget id as param. Read documentation for more detail. https://resources.count.ly/plugins/feedback"); + log(logLevelEnums.ERROR, "enableRatingWidgets, You should provide at least one widget id as param. Read documentation for more detail. https://resources.count.ly/plugins/feedback"); } } }; @@ -2906,7 +2929,7 @@ * @param {Function} callback - Callback function with two parameters, 1st for returned list, 2nd for error * */ this.get_available_feedback_widgets = function(callback) { - log(logLevelEnums.INFO, "[get_available_feedback_widgets] Getting the feedback list, callback function is provided:[" + (!!callback) + "]"); + log(logLevelEnums.INFO, "get_available_feedback_widgets, Getting the feedback list, callback function is provided:[" + (!!callback) + "]"); if (!this.check_consent(featureEnums.FEEDBACK)) { if (callback) { callback(null, new Error("Consent for feedback not provided.")); @@ -2915,7 +2938,7 @@ } if (offlineMode) { - log(logLevelEnums.ERROR, "[get_available_feedback_widgets] Cannot enable feedback widgets in offline mode."); + log(logLevelEnums.ERROR, "get_available_feedback_widgets, Cannot enable feedback widgets in offline mode."); return; } @@ -2926,9 +2949,9 @@ app_key: this.app_key }; - sendXmlHttpRequest("[get_available_feedback_widgets]", url, data, function(err, params, responseText) { + sendXmlHttpRequest("get_available_feedback_widgets,", url, data, function(err, params, responseText) { if (err) { - log(logLevelEnums.ERROR, "[get_available_feedback_widgets] Error occurred while fetching feedbacks: " + err); + log(logLevelEnums.ERROR, "get_available_feedback_widgets, Error occurred while fetching feedbacks: " + err); if (callback) { callback(null, err); } @@ -2943,7 +2966,7 @@ } } catch (error) { - log(logLevelEnums.ERROR, "[get_available_feedback_widgets] Error while parsing feedback widgets list: " + error); + log(logLevelEnums.ERROR, "get_available_feedback_widgets, Error while parsing feedback widgets list: " + error); if (callback) { callback(null, error); } @@ -2959,10 +2982,10 @@ * */ this.getFeedbackWidgetData = function(CountlyFeedbackWidget, callback) { if (!CountlyFeedbackWidget.type) { - log(logLevelEnums.ERROR, "[getFeedbackWidgetData] Expected the provided widget object to have a type but got: [" + JSON.stringify(CountlyFeedbackWidget) + "], aborting."); + log(logLevelEnums.ERROR, "getFeedbackWidgetData, Expected the provided widget object to have a type but got: [" + JSON.stringify(CountlyFeedbackWidget) + "], aborting."); return; } - log(logLevelEnums.INFO, "[getFeedbackWidgetData] Retrieving data for: [" + JSON.stringify(CountlyFeedbackWidget) + "], callback function is provided:[" + (!!callback) + "]"); + log(logLevelEnums.INFO, "getFeedbackWidgetData, Retrieving data for: [" + JSON.stringify(CountlyFeedbackWidget) + "], callback function is provided:[" + (!!callback) + "]"); if (!this.check_consent(featureEnums.FEEDBACK)) { if (callback) { callback(null, new Error("Consent for feedback not provided.")); @@ -2971,7 +2994,7 @@ } if (offlineMode) { - log(logLevelEnums.ERROR, "[getFeedbackWidgetData] Cannot enable feedback widgets in offline mode."); + log(logLevelEnums.ERROR, "getFeedbackWidgetData, Cannot enable feedback widgets in offline mode."); return; } @@ -2996,11 +3019,11 @@ url += "/o/surveys/rating/widget"; } else { - log(logLevelEnums.ERROR, "[getFeedbackWidgetData] Unknown type info: [" + CountlyFeedbackWidget.type + "]"); + log(logLevelEnums.ERROR, "getFeedbackWidgetData, Unknown type info: [" + CountlyFeedbackWidget.type + "]"); return; } - sendXmlHttpRequest("[getFeedbackWidgetData]", url, data, responseCallback, true); + sendXmlHttpRequest("getFeedbackWidgetData,", url, data, responseCallback, true); /** * Server response would be evaluated here @@ -3010,7 +3033,7 @@ */ function responseCallback(err, params, responseText) { if (err) { - log(logLevelEnums.ERROR, "[getFeedbackWidgetData] Error occurred while fetching feedbacks: " + err); + log(logLevelEnums.ERROR, "getFeedbackWidgetData, Error occurred while fetching feedbacks: " + err); if (callback) { callback(null, err); } @@ -3025,7 +3048,7 @@ } } catch (error) { - log(logLevelEnums.ERROR, "[getFeedbackWidgetData] Error while parsing feedback widgets list: " + error); + log(logLevelEnums.ERROR, "getFeedbackWidgetData, Error while parsing feedback widgets list: " + error); if (callback) { callback(null, error); } @@ -3052,7 +3075,7 @@ || (typeof presentableFeedback !== "object") || Array.isArray(presentableFeedback) ) { - log(logLevelEnums.ERROR, "[present_feedback_widget] Please provide at least one feedback widget object."); + log(logLevelEnums.ERROR, "present_feedback_widget, Please provide at least one feedback widget object."); return; } @@ -3066,19 +3089,19 @@ var url = this.url; if (presentableFeedback.type === "nps") { - log(logLevelEnums.DEBUG, "[present_feedback_widget] Widget type: nps."); + log(logLevelEnums.DEBUG, "present_feedback_widget, Widget type: nps."); url += "/feedback/nps"; } else if (presentableFeedback.type === "survey") { - log(logLevelEnums.DEBUG, "[present_feedback_widget] Widget type: survey."); + log(logLevelEnums.DEBUG, "present_feedback_widget, Widget type: survey."); url += "/feedback/survey"; } else if (presentableFeedback.type === "rating") { - log(logLevelEnums.DEBUG, "[present_feedback_widget] Widget type: rating."); + log(logLevelEnums.DEBUG, "present_feedback_widget, Widget type: rating."); url += "/feedback/rating"; } else { - log(logLevelEnums.ERROR, "[present_feedback_widget] Feedback widget only accepts nps, rating and survey types."); + log(logLevelEnums.ERROR, "present_feedback_widget, Feedback widget only accepts nps, rating and survey types."); return; } @@ -3087,14 +3110,14 @@ // set feedback widget family as ratings and load related style file when type is ratings if (presentableFeedback.type === "rating") { - log(logLevelEnums.DEBUG, "[present_feedback_widget] Loading css for rating widget."); + log(logLevelEnums.DEBUG, "present_feedback_widget, Loading css for rating widget."); feedbackWidgetFamily = "ratings"; loadCSS(this.url + "/star-rating/stylesheets/countly-feedback-web.css"); } // if it's not ratings, it means we need to name it as surveys and load related style file // (at least until we add new type in future) else { - log(logLevelEnums.DEBUG, "[present_feedback_widget] Loading css for survey or nps."); + log(logLevelEnums.DEBUG, "present_feedback_widget, Loading css for survey or nps."); loadCSS(this.url + "/surveys/stylesheets/countly-surveys.css"); feedbackWidgetFamily = "surveys"; } @@ -3137,7 +3160,7 @@ // Any subsequent onload means that the survey is being refreshed or reset. // This time hide it as being done in the above check. initiated = true; - log(logLevelEnums.DEBUG, "[present_feedback_widget] Loaded iframe."); + log(logLevelEnums.DEBUG, "present_feedback_widget, Loaded iframe."); }; var overlay = document.getElementById("csbg"); @@ -3145,14 +3168,14 @@ // Remove any existing overlays overlay.remove(); overlay = document.getElementById("csbg"); - log(logLevelEnums.DEBUG, "[present_feedback_widget] Removing past overlay."); + log(logLevelEnums.DEBUG, "present_feedback_widget, Removing past overlay."); } var wrapper = document.getElementsByClassName("countly-" + feedbackWidgetFamily + "-wrapper"); for (var i = 0; i < wrapper.length; i++) { // Remove any existing feedback wrappers wrapper[i].remove(); - log(logLevelEnums.DEBUG, "[present_feedback_widget] Removed a wrapper."); + log(logLevelEnums.DEBUG, "present_feedback_widget, Removed a wrapper."); } wrapper = document.createElement("div"); @@ -3173,7 +3196,7 @@ found = true; } else { - log(logLevelEnums.ERROR, "[present_feedback_widget] Provided ID not found."); + log(logLevelEnums.ERROR, "present_feedback_widget, Provided ID not found."); } } @@ -3184,7 +3207,7 @@ element = document.getElementsByClassName(className)[0]; } else { - log(logLevelEnums.ERROR, "[present_feedback_widget] Provided class not found."); + log(logLevelEnums.ERROR, "present_feedback_widget, Provided class not found."); } } } @@ -3197,7 +3220,7 @@ ratingsOverlay.className = "countly-ratings-overlay"; ratingsOverlay.id = "countly-ratings-overlay-" + presentableFeedback._id; wrapper.appendChild(ratingsOverlay); - log(logLevelEnums.DEBUG, "[present_feedback_widget] appended the rating overlay to wrapper"); + log(logLevelEnums.DEBUG, "present_feedback_widget, appended the rating overlay to wrapper"); // add an event listener for the overlay // so if someone clicked on the overlay, we can close popup @@ -3207,20 +3230,20 @@ } wrapper.appendChild(iframe); - log(logLevelEnums.DEBUG, "[present_feedback_widget] Appended the iframe"); + log(logLevelEnums.DEBUG, "present_feedback_widget, Appended the iframe"); add_event(window, "message", function(e) { var data = {}; try { data = JSON.parse(e.data); - log(logLevelEnums.DEBUG, "[present_feedback_widget] Parsed response message " + data); + log(logLevelEnums.DEBUG, "present_feedback_widget, Parsed response message " + data); } catch (ex) { - log(logLevelEnums.ERROR, "[present_feedback_widget] Error while parsing message body " + ex); + log(logLevelEnums.ERROR, "present_feedback_widget, Error while parsing message body " + ex); } if (!data.close) { - log(logLevelEnums.DEBUG, "[present_feedback_widget] Closing signal not sent yet"); + log(logLevelEnums.DEBUG, "present_feedback_widget, Closing signal not sent yet"); return; } @@ -3334,7 +3357,7 @@ } } catch (e) { - log(logLevelEnums.ERROR, "[present_feedback_widget] Something went wrong while presenting the widget: " + e); + log(logLevelEnums.ERROR, "present_feedback_widget, Something went wrong while presenting the widget: " + e); } /** @@ -3353,7 +3376,7 @@ function showRatingForFeedbackWidget(feedback) { // render sticker if hide sticker property isn't set if (!feedback.appearance.hideS) { - log(logLevelEnums.DEBUG, "[present_feedback_widget] handling the sticker as it was not set to hidden"); + log(logLevelEnums.DEBUG, "present_feedback_widget, handling the sticker as it was not set to hidden"); // create sticker wrapper element var sticker = document.createElement("div"); sticker.innerText = feedback.appearance.text; @@ -3371,6 +3394,7 @@ } // feedback widget close event handler + // TODO: Check if this is still valid add_event(document.getElementById("countly-feedback-close-icon-" + feedback._id), "click", function() { document.getElementById("countly-ratings-wrapper-" + feedback._id).style.display = "none"; document.getElementById("csbg").style.display = "none"; @@ -3385,7 +3409,7 @@ * @param {Object} segments - custom crash segments */ this.recordError = function(err, nonfatal, segments) { - log(logLevelEnums.INFO, "[recordError] Recording error"); + log(logLevelEnums.INFO, "recordError, Recording error"); if (this.check_consent(featureEnums.CRASHES) && err) { // crashSegments, if not null, was set while enabling error tracking segments = segments || crashSegments; @@ -3518,7 +3542,7 @@ // prevent widget create process if widget exist with same id var isDuplicate = !!document.getElementById("countly-feedback-sticker-" + currentWidget._id); if (isDuplicate) { - log(logLevelEnums.ERROR, "[FeedbackWidget] Widget with same ID exists"); + log(logLevelEnums.ERROR, "Widget with same ID exists"); return; } try { @@ -3589,7 +3613,7 @@ } } catch (e) { - log(logLevelEnums.ERROR, "[FeedbackWidget] Somethings went wrong while element injecting process: " + e); + log(logLevelEnums.ERROR, "Somethings went wrong while element injecting process: " + e); } } @@ -3709,12 +3733,12 @@ */ function toRequestQueue(request) { if (self.ignore_visitor) { - log(logLevelEnums.WARNING, "[RequestQueue] User is opt_out will ignore the request: " + request); + log(logLevelEnums.WARNING, "User is opt_out will ignore the request: " + request); return; } if (!self.app_key || !self.device_id) { - log(logLevelEnums.ERROR, "[RequestQueue] app_key or device_id is missing ", self.app_key, self.device_id); + log(logLevelEnums.ERROR, "app_key or device_id is missing ", self.app_key, self.device_id); return; } @@ -3739,7 +3763,7 @@ // ignore bots if (self.ignore_visitor) { hasPulse = false; - log(logLevelEnums.WARNING, "[HeartBeat] User opt_out, no heartbeat"); + log(logLevelEnums.WARNING, "User opt_out, no heartbeat"); return; } @@ -3752,7 +3776,7 @@ Countly.q = []; for (i = 0; i < q.length; i++) { req = q[i]; - log(logLevelEnums.DEBUG, "[HeartBeat] Processing queued call", req); + log(logLevelEnums.DEBUG, "Processing queued call", req); if (typeof req === "function") { req(); } @@ -3814,10 +3838,10 @@ readyToProcess = false; var params = requestQueue[0]; params.rr = requestQueue.length; // added at 23.2.3. It would give the current length of the queue. That includes the current request. - log(logLevelEnums.DEBUG, "[HeartBeat] Processing request", params); + log(logLevelEnums.DEBUG, "Processing request", params); setValueInStorage("cly_queue", requestQueue, true); if (!self.test_mode) { - sendXmlHttpRequest("[send_request_queue]", self.url + apiPath, params, function(err, parameters) { + sendXmlHttpRequest("send_request_queue", self.url + apiPath, params, function(err, parameters) { log(logLevelEnums.DEBUG, "Request Finished", parameters, err); if (err) { failTimeout = getTimestamp() + failTimeoutAmount; @@ -3917,34 +3941,62 @@ metrics._locale = metrics._locale || locale; } - if (typeof document.referrer !== "undefined" && document.referrer.length) { - var matches = urlParseRE.exec(document.referrer); - // do not report referrers of current website - if (matches && matches[11] && matches[11] !== window.location.hostname) { - var ignoring = false; + if (isReferrerUsable()) { + metrics._store = metrics._store || document.referrer; + } + + log(logLevelEnums.DEBUG, "Got metrics", metrics); + return metrics; + } + + /** + * @memberof Countly._internals + * document.referrer returns the full URL of the page the user was on before they came to your site. + * If the user open your site from bookmarks or by typing the URL in the address bar, then document.referrer is an empty string. + * Inside an iframe, document.referrer will initially be set to the same value as the href of the parent window's Window.location. + * + * @param {string} customReferrer - custom referrer for testing + * @returns {boolean} true if document.referrer is not empty string, undefined, current host or in the ignore list. + */ + function isReferrerUsable(customReferrer) { + var referrer = customReferrer || document.referrer; + var isReferrerLegit = false; + + // do not report referrer if it is empty string or undefined + if (typeof referrer === "undefined" || referrer.length === 0) { + log(logLevelEnums.DEBUG, "Invalid referrer:[" + referrer + "], ignoring."); + } + else { + // dissect the referrer (check urlParseRE's comments for more info on this process) + var matches = urlParseRE.exec(referrer); // this can return null + if (!matches) { + log(logLevelEnums.DEBUG, "Referrer is corrupt:[" + referrer + "], ignoring."); + } + else if (!matches[11]) { + log(logLevelEnums.DEBUG, "No path found in referrer:[" + referrer + "], ignoring."); + } + else if (matches[11] === window.location.hostname) { + log(logLevelEnums.DEBUG, "Referrer is current host:[" + referrer + "], ignoring."); + } + else { if (ignoreReferrers && ignoreReferrers.length) { + isReferrerLegit = true; for (var k = 0; k < ignoreReferrers.length; k++) { - try { - var reg = new RegExp(ignoreReferrers[k]); - if (reg.test(document.referrer)) { - log(logLevelEnums.DEBUG, "[Metrics] Ignored: " + document.referrer); - ignoring = true; - break; - } - } - catch (ex) { - log(logLevelEnums.ERROR, "[Metrics] Problem with ignoring: " + ignoreReferrers[k], ", error: " + ex); + if (referrer.indexOf(ignoreReferrers[k]) >= 0) { + log(logLevelEnums.DEBUG, "Referrer in ignore list:[" + referrer + "], ignoring."); + isReferrerLegit = false; + break; } } } - if (!ignoring) { - metrics._store = metrics._store || document.referrer; + else { + log(logLevelEnums.DEBUG, "Valid referrer:[" + referrer + "]"); + isReferrerLegit = true; } } } - log(logLevelEnums.DEBUG, "[Metrics] Got metrics", metrics); - return metrics; + return isReferrerLegit; } /** @@ -4013,7 +4065,7 @@ function sendXmlHttpRequest(functionName, url, params, callback, useBroadResponseValidator) { useBroadResponseValidator = useBroadResponseValidator || false; try { - log(logLevelEnums.DEBUG, "[sendXmlHttpRequest] Sending XML HTTP request"); + log(logLevelEnums.DEBUG, "Sending XML HTTP request"); var xhr = null; if (window.XMLHttpRequest) { xhr = new window.XMLHttpRequest(); @@ -4040,7 +4092,7 @@ // fallback on error xhr.onreadystatechange = function() { if (this.readyState === 4) { - log(logLevelEnums.DEBUG, functionName + " [sendXmlHttpRequest] HTTP request completed [" + this.status + "][" + this.responseText + "]"); + log(logLevelEnums.DEBUG, functionName + " HTTP request completed [" + this.status + "][" + this.responseText + "]"); // response validation function will be selected to also accept JSON arrays if useBroadResponseValidator is true var isResponseValidated; if (useBroadResponseValidator) { @@ -4058,7 +4110,7 @@ } else { log(logLevelEnums.ERROR, functionName + " Failed Server XML HTTP request, ", this.status); - if (functionName === "[send_request_queue]") { + if (functionName === "send_request_queue") { HealthCheck.saveRequestCounters(this.status, this.responseText); } if (typeof callback === "function") { @@ -4076,7 +4128,7 @@ } catch (e) { // fallback - log(logLevelEnums.ERROR, functionName + " [sendXmlHttpRequest] Failed XML HTTP request: " + e); + log(logLevelEnums.ERROR, functionName + " Failed XML HTTP request: " + e); if (typeof callback === "function") { callback(true, params); } @@ -4095,7 +4147,7 @@ function isResponseValid(statusCode, str) { // status code and response format check if (!(statusCode >= 200 && statusCode < 300)) { - log(logLevelEnums.ERROR, "[ResponseValidation] Http response status code is not within the expected range:[" + statusCode + "]"); + log(logLevelEnums.ERROR, "Http response status code is not within the expected range:[" + statusCode + "]"); return false; } @@ -4105,14 +4157,14 @@ // check if parsed response is a JSON object, if not the response is not valid if (Object.prototype.toString.call(parsedResponse) !== "[object Object]") { - log(logLevelEnums.ERROR, "[ResponseValidation] Http response is not JSON Object"); + log(logLevelEnums.ERROR, "Http response is not JSON Object"); return false; } return !!(parsedResponse.result); } catch (e) { - log(logLevelEnums.ERROR, "[ResponseValidation] Http response is not JSON: " + e); + log(logLevelEnums.ERROR, "Http response is not JSON: " + e); return false; } } @@ -4129,7 +4181,7 @@ function isResponseValidBroad(statusCode, str) { // status code and response format check if (!(statusCode >= 200 && statusCode < 300)) { - log(logLevelEnums.ERROR, "[ResponseValidationBroad] Http response status code is not within the expected range: " + statusCode); + log(logLevelEnums.ERROR, "Http response status code is not within the expected range: " + statusCode); return false; } @@ -4138,7 +4190,7 @@ var parsedResponse = JSON.parse(str); // check if parsed response is a JSON object or JSON array, if not it is not valid if ((Object.prototype.toString.call(parsedResponse) !== "[object Object]") && (!Array.isArray(parsedResponse))) { - log(logLevelEnums.ERROR, "[ResponseValidationBroad] Http response is not JSON Object nor JSON Array"); + log(logLevelEnums.ERROR, "Http response is not JSON Object nor JSON Array"); return false; } @@ -4146,7 +4198,7 @@ return true; } catch (e) { - log(logLevelEnums.ERROR, "[ResponseValidationBroad] Http response is not JSON: " + e); + log(logLevelEnums.ERROR, "Http response is not JSON: " + e); return false; } } @@ -4290,7 +4342,7 @@ function getValueFromStorage(key, useLocalStorage, useRawKey) { // check if we should use storage at all if (self.storage === "none") { - log(logLevelEnums.WARNING, "[Storage] Storage is disabled. Value with key: " + key + " won't be retrieved"); + log(logLevelEnums.WARNING, "Storage is disabled. Value with key: " + key + " won't be retrieved"); return; } @@ -4335,7 +4387,7 @@ function setValueInStorage(key, value, useLocalStorage, useRawKey) { // check if we should use storage options at all if (self.storage === "none") { - log(logLevelEnums.WARNING, "[Storage] Storage is disabled. Value with key: " + key + " won't be stored"); + log(logLevelEnums.WARNING, "Storage is disabled. Value with key: " + key + " won't be stored"); return; } @@ -4374,7 +4426,7 @@ function removeValueFromStorage(key, useLocalStorage, useRawKey) { // check if we should use storage options at all if (self.storage === "none") { - log(logLevelEnums.WARNING, "[Storage] Storage is disabled. Value with key: " + key + " won't be removed"); + log(logLevelEnums.WARNING, "Storage is disabled. Value with key: " + key + " won't be removed"); return; } @@ -4444,7 +4496,7 @@ * @param {Varies} newValue - new value for storage */ this.onStorageChange = function(key, newValue) { - log(logLevelEnums.INFO, "[Storage] onStorageChange, Applying storage changes"); + log(logLevelEnums.INFO, "onStorageChange, Applying storage changes"); switch (key) { // queue of requests case "cly_queue": @@ -4504,6 +4556,7 @@ generateUUID: generateUUID, sendEventsForced: sendEventsForced, isUUID: isUUID, + isReferrerUsable: isReferrerUsable, getId: getStoredIdOrGenerateId, heartBeat: heartBeat, toRequestQueue: toRequestQueue, diff --git a/lib/countly.min.js b/lib/countly.min.js index 64a6bf7b..22d13309 100644 --- a/lib/countly.min.js +++ b/lib/countly.min.js @@ -12,39 +12,39 @@ t.appendChild(document.createTextNode("#cly-loader {height: 4px; width: 100%; po n.appendChild(t);h=document.createElement("div");h.setAttribute("id","cly-loader");document.body.onload=function(){if(!m.showLoaderProtection)try{document.body.appendChild(h)}catch(A){}}}h.style.display="block"}function tb(){m.showLoaderProtection=!0;var h=document.getElementById("cly-loader");h&&(h.style.display="none")}if("undefined"!==typeof window){m=m||{};m.features="sessions events views scrolls clicks forms crashes attribution users star-rating location apm feedback remote-config".split(" "); var I={NPS:"[CLY]_nps",SURVEY:"[CLY]_survey",STAR_RATING:"[CLY]_star_rating",VIEW:"[CLY]_view",ORIENTATION:"[CLY]_orientation",ACTION:"[CLY]_action"},Eb=Object.values(I),d={ERROR:"[ERROR] ",WARNING:"[WARNING] ",INFO:"[INFO] ",DEBUG:"[DEBUG] ",VERBOSE:"[VERBOSE] "},jb={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"},P=Object.freeze({errorCount:"cly_hc_error_count", warningCount:"cly_hc_warning_count",statusCode:"cly_hc_status_code",errorMessage:"cly_hc_error_message"});m.q=m.q||[];m.onload=m.onload||[];var ub=/^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?::([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?::([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,vb=!0;m.CountlyClass=function(h){function n(a,c){if(f.ignore_visitor)b(d.ERROR,"Adding event failed. Possible bot or user opt out");else if(a.key){a.count||(a.count=1);Eb.includes(a.key)|| -(a.key=x(a.key,f.maxKeyLength,"add_cly_event",b));a.segmentation=Z(a.segmentation,f.maxKeyLength,f.maxValueSize,f.maxSegmentationValues,"add_cly_event",b);a=ta(a,["key","count","sum","dur","segmentation"]);a.timestamp=Xa();var e=new Date;a.hour=e.getHours();a.dow=e.getDay();a.id=c||Va();a.key===I.VIEW?a.pvid=va||"":a.cvid=fa||"";J.push(a);u("cly_event",J);b(d.INFO," With event ID: ["+a.id+"], successfully adding the last event:",a)}else b(d.ERROR,"Adding event failed. Event must have a key property")} +(a.key=x(a.key,f.maxKeyLength,"add_cly_event",b));a.segmentation=Z(a.segmentation,f.maxKeyLength,f.maxValueSize,f.maxSegmentationValues,"add_cly_event",b);a=ta(a,["key","count","sum","dur","segmentation"]);a.timestamp=Xa();var e=new Date;a.hour=e.getHours();a.dow=e.getDay();a.id=c||Va();a.key===I.VIEW?a.pvid=va||"":a.cvid=fa||"";J.push(a);u("cly_event",J);b(d.INFO,"With event ID: ["+a.id+"], successfully adding the last event:",a)}else b(d.ERROR,"Adding event failed. Event must have a key property")} function t(a,c,e,k,l){b(d.INFO,"fetch_remote_config_explicit, Fetching sequence initiated");var g={method:"rc"};a&&(g.keys=JSON.stringify(a));c&&(g.omit_keys=JSON.stringify(c));var p;"legacy"===k&&(g.method="fetch_remote_config");0===e&&(g.oi=0);1===e&&(g.oi=1);"function"===typeof l&&(p=l);f.check_consent("sessions")&&(g.metrics=JSON.stringify(Ia()));f.check_consent("remote-config")?(aa(g),ba("fetch_remote_config_explicit",f.url+Ja,g,function(q,v,K){if(q)b(d.ERROR,"fetch_remote_config_explicit, An error occurred: "+ q);else{try{var S=JSON.parse(K);if(g.keys||g.omit_keys)for(var L in S)O[L]=S[L];else O=S;u("cly_remote_configs",O)}catch(oa){b(d.ERROR,"fetch_remote_config_explicit, Had an issue while parsing the response: "+oa)}p&&(b(d.INFO,"fetch_remote_config_explicit, Callback function is provided"),p(q,O))}},!0)):(b(d.ERROR,"fetch_remote_config_explicit, Remote config requires explicit consent"),p&&p(Error("Remote config requires explicit consent"),O))}function A(){f.ignore_prefetch&&"undefined"!==typeof document.visibilityState&& "prerender"===document.visibilityState&&(f.ignore_visitor=!0);f.ignore_bots&&ob()&&(f.ignore_visitor=!0)}function B(){0a.trigger_font_color.length?"#"+a.trigger_font_color:a.trigger_font_color;v.style.backgroundColor=7>a.trigger_bg_color.length?"#"+a.trigger_bg_color:a.trigger_bg_color;v.className="countly-feedback-sticker "+a.trigger_position+"-"+a.trigger_size;v.id="countly-feedback-sticker-"+a._id;g.appendChild(p);v.appendChild(g);v.appendChild(q);document.body.appendChild(v);var K=document.getElementById("smileyPathInStickerSvg"); K&&(K.style.fill=7>a.trigger_font_color.length?"#"+a.trigger_font_color:a.trigger_font_color);y(document.getElementById("countly-feedback-sticker-"+a._id),"click",function(){document.getElementById("countly-iframe-wrapper-"+a._id).style.display="block";document.getElementById("cfbg").style.display="block"})}else document.getElementById("countly-iframe-wrapper-"+a._id).style.display="block",document.getElementById("cfbg").style.display="block"}catch(S){b(d.ERROR,"Somethings went wrong while element injecting process: "+ S)}}function U(){var a;if("undefined"!==typeof f.onload&&0Ka&&F.shift(),F.push(a),u("cly_queue",F,!0)):b(d.ERROR,"app_key or device_id is missing ",f.app_key,f.device_id)}function La(){U();if(f.ignore_visitor)Ma=!1,b(d.WARNING,"User opt_out, no heartbeat");else{Ma=!0;var a=0;if(Na&&"undefined"!==typeof m.q&&0Oa&&(f.session_duration(a-ia),ia=a,0ab&&(Pa=!1,a=F[0],a.rr=F.length,b(d.DEBUG,"Processing request",a),u("cly_queue",F,!0),f.test_mode||ba("send_request_queue",f.url+bb,a,function(p,q){b(d.DEBUG,"Request Finished",q,p);p?(ab=E()+Qa,b(d.ERROR,"Request error: ",p)):F.shift();u("cly_queue",F,!0);Pa=!0},!1));setTimeout(La,Ra)}}function cb(){var a=w("cly_id");return a?(C=w("cly_id_type"),a):Wa()}function wb(){return f.metrics._ua||na()}function Ia(){var a=JSON.parse(JSON.stringify(f.metrics||{})); a._app_version=a._app_version||f.app_version;a._ua=a._ua||na();if(screen.width){var c=screen.width?parseInt(screen.width):0,e=screen.height?parseInt(screen.height):0;if(0!==c&&0!==e){if(navigator.platform&&/iPad|iPhone|iPod/.test(navigator.platform)&&window.devicePixelRatio)c=Math.round(c*window.devicePixelRatio),e=Math.round(e*window.devicePixelRatio);else if(90===Math.abs(window.orientation)){var k=c;c=e;e=k}a._resolution=a._resolution||""+c+"x"+e}}window.devicePixelRatio&&(a._density=a._density|| window.devicePixelRatio);c=navigator.language||navigator.browserLanguage||navigator.systemLanguage||navigator.userLanguage;"undefined"!==typeof c&&(a._locale=a._locale||c);if("undefined"!==typeof document.referrer&&document.referrer.length&&(c=ub.exec(document.referrer))&&c[11]&&c[11]!==window.location.hostname){c=!1;if(W&&W.length)for(e=0;ea))return b(d.ERROR,"Http response status code is not within the expected range:["+a+"]"),!1;try{var e=JSON.parse(c);return"[object Object]"!==Object.prototype.toString.call(e)?(b(d.ERROR,"Http response is not JSON Object"),!1):!!e.result}catch(k){return b(d.ERROR,"Http response is not JSON: "+k),!1}}function xb(a,c){if(!(200<=a&&300>a))return b(d.ERROR,"Http response status code is not within the expected range: "+a),!1;try{var e= -JSON.parse(c);return"[object Object]"===Object.prototype.toString.call(e)||Array.isArray(e)?!0:(b(d.ERROR,"Http response is not JSON Object nor JSON Array"),!1)}catch(k){return b(d.ERROR,"Http response is not JSON: "+k),!1}}function zb(){Ba=Math.max(Ba,window.scrollY,document.body.scrollTop,document.documentElement.scrollTop)}function db(){if(Ca){Ca=!1;var a=Ga(),c=$a(),e=pb();f.check_consent("scrolls")&&(a={type:"scroll",y:Ba+e,width:c,height:a,view:f.getViewUrl()},a=Z(a,f.maxKeyLength,f.maxValueSize, -f.maxSegmentationValues,"processScrollView",b),f.track_domains&&(a.domain=window.location.hostname),n({key:I.ACTION,segmentation:a}))}}function Ab(a){u("cly_token",a)}function Bb(a,c,e){var k=new Date;k.setTime(k.getTime()+864E5*e);e="; expires="+k.toGMTString();document.cookie=a+"="+c+e+"; path=/"}function w(a,c,e){if("none"===f.storage)b(d.WARNING,"Storage is disabled. Value with key: "+a+" won't be retrieved");else{e||(a=f.app_key+"/"+a,f.namespace&&(a=ma(f.namespace)+"/"+a));void 0===c&&(c=ka); -if(c)var k=localStorage.getItem(a);else if("localstorage"!==f.storage)a:{a+="=";c=document.cookie.split(";");e=0;for(k=c.length;ea))return b(d.ERROR,"Http response status code is not within the expected range:["+a+"]"),!1;try{var e=JSON.parse(c);return"[object Object]"!==Object.prototype.toString.call(e)?(b(d.ERROR,"Http response is not JSON Object"),!1):!!e.result}catch(k){return b(d.ERROR,"Http response is not JSON: "+k),!1}}function xb(a,c){if(!(200<=a&&300>a))return b(d.ERROR,"Http response status code is not within the expected range: "+ +a),!1;try{var e=JSON.parse(c);return"[object Object]"===Object.prototype.toString.call(e)||Array.isArray(e)?!0:(b(d.ERROR,"Http response is not JSON Object nor JSON Array"),!1)}catch(k){return b(d.ERROR,"Http response is not JSON: "+k),!1}}function zb(){Ba=Math.max(Ba,window.scrollY,document.body.scrollTop,document.documentElement.scrollTop)}function db(){if(Ca){Ca=!1;var a=Ga(),c=$a(),e=pb();f.check_consent("scrolls")&&(a={type:"scroll",y:Ba+e,width:c,height:a,view:f.getViewUrl()},a=Z(a,f.maxKeyLength, +f.maxValueSize,f.maxSegmentationValues,"processScrollView",b),f.track_domains&&(a.domain=window.location.hostname),n({key:I.ACTION,segmentation:a}))}}function Ab(a){u("cly_token",a)}function Bb(a,c,e){var k=new Date;k.setTime(k.getTime()+864E5*e);e="; expires="+k.toGMTString();document.cookie=a+"="+c+e+"; path=/"}function w(a,c,e){if("none"===f.storage)b(d.WARNING,"Storage is disabled. Value with key: "+a+" won't be retrieved");else{e||(a=f.app_key+"/"+a,f.namespace&&(a=ma(f.namespace)+"/"+a));void 0=== +c&&(c=ka);if(c)var k=localStorage.getItem(a);else if("localstorage"!==f.storage)a:{c=a+"=";e=document.cookie.split(";");k=0;for(var l=e.length;kwindow.innerHeight?"landscape":"portrait")}})};this.report_conversion=function(a,c){b(d.WARNING,"report_conversion, Deprecated function call! Use 'recordDirectAttribution' in place of this call. Call will be redirected now!");this.recordDirectAttribution(a,c)};this.recordDirectAttribution=function(a,c){b(d.INFO,"recordDirectAttribution, Recording the attribution for campaign ID: ["+ a+"] and the user ID: ["+c+"]");this.check_consent("attribution")&&(a=a||w("cly_cmp_id")||"cly_organic",(c=c||w("cly_cmp_uid"))?G({campaign_id:a,campaign_user:c}):G({campaign_id:a}))};this.user_details=function(a){b(d.INFO,"user_details, Trying to add user details: ",a);this.check_consent("users")&&(B(),b(d.INFO,"user_details, flushed the event queue"),a.name=x(a.name,f.maxValueSize,"user_details",b),a.username=x(a.username,f.maxValueSize,"user_details",b),a.email=x(a.email,f.maxValueSize,"user_details", b),a.organization=x(a.organization,f.maxValueSize,"user_details",b),a.phone=x(a.phone,f.maxValueSize,"user_details",b),a.picture=x(a.picture,4096,"user_details",b),a.gender=x(a.gender,f.maxValueSize,"user_details",b),a.byear=x(a.byear,f.maxValueSize,"user_details",b),a.custom=Z(a.custom,f.maxKeyLength,f.maxValueSize,f.maxSegmentationValues,"user_details",b),G({user_details:JSON.stringify(ta(a,"name username email organization phone picture gender byear custom".split(" ")))}))};var Y={},ea=function(a, -c,e){f.check_consent("users")&&(Y[a]||(Y[a]={}),"$push"===e||"$pull"===e||"$addToSet"===e?(Y[a][e]||(Y[a][e]=[]),Y[a][e].push(c)):Y[a][e]=c)};this.userData={set:function(a,c){b(d.INFO,"set, Setting user's custom property value: ["+c+"] under the key: ["+a+"]");a=x(a,f.maxKeyLength,"userData set",b);c=x(c,f.maxValueSize,"userData set",b);Y[a]=c},unset:function(a){b(d.INFO,"unset, Resetting user's custom property with key: ["+a+"] ");Y[a]=""},set_once:function(a,c){b(d.INFO,"set_once, Setting user's unique custom property value: ["+ -c+"] under the key: ["+a+"] ");a=x(a,f.maxKeyLength,"userData set_once",b);c=x(c,f.maxValueSize,"userData set_once",b);ea(a,c,"$setOnce")},increment:function(a){b(d.INFO,"increment, Increasing user's custom property value under the key: ["+a+"] by one");a=x(a,f.maxKeyLength,"userData increment",b);ea(a,1,"$inc")},increment_by:function(a,c){b(d.INFO,"increment_by, Increasing user's custom property value under the key: ["+a+"] by: ["+c+"]");a=x(a,f.maxKeyLength,"userData increment_by",b);c=x(c,f.maxValueSize, -"userData increment_by",b);ea(a,c,"$inc")},multiply:function(a,c){b(d.INFO,"multiply, Multiplying user's custom property value under the key: ["+a+"] by: ["+c+"]");a=x(a,f.maxKeyLength,"userData multiply",b);c=x(c,f.maxValueSize,"userData multiply",b);ea(a,c,"$mul")},max:function(a,c){b(d.INFO,"max, Saving user's maximum custom property value compared to the value: ["+c+"] under the key: ["+a+"]");a=x(a,f.maxKeyLength,"userData max",b);c=x(c,f.maxValueSize,"userData max",b);ea(a,c,"$max")},min:function(a, -c){b(d.INFO,"min, Saving user's minimum custom property value compared to the value: ["+c+"] under the key: ["+a+"]");a=x(a,f.maxKeyLength,"userData min",b);c=x(c,f.maxValueSize,"userData min",b);ea(a,c,"$min")},push:function(a,c){b(d.INFO,"push, Pushing a value: ["+c+"] under the key: ["+a+"] to user's custom property array");a=x(a,f.maxKeyLength,"userData push",b);c=x(c,f.maxValueSize,"userData push",b);ea(a,c,"$push")},push_unique:function(a,c){b(d.INFO,"push_unique, Pushing a unique value: ["+ -c+"] under the key: ["+a+"] to user's custom property array");a=x(a,f.maxKeyLength,"userData push_unique",b);c=x(c,f.maxValueSize,"userData push_unique",b);ea(a,c,"$addToSet")},pull:function(a,c){b(d.INFO,"pull, Removing the value: ["+c+"] under the key: ["+a+"] from user's custom property array");ea(a,c,"$pull")},save:function(){b(d.INFO,"save, Saving changes to user's custom property");f.check_consent("users")&&(B(),b(d.INFO,"user_details, flushed the event queue"),G({user_details:JSON.stringify({custom:Y})})); -Y={}}};this.report_trace=function(a){b(d.INFO,"report_trace, Reporting performance trace");if(this.check_consent("apm")){for(var c="type name stz etz apm_metrics apm_attr".split(" "),e=0;e=f.maxBreadcrumbCount;)la.shift(),b(d.WARNING,"add_log, Reached maximum crashLogs size. Will erase the oldest one.");la.push(a)}};this.fetch_remote_config=function(a,c,e){var k=null,l=null,g=null;a&&(e||"function"!==typeof a?Array.isArray(a)&&(k=a):g=a);c&&(e||"function"!==typeof c?Array.isArray(c)&&(l=c):g=c);g||"function"!== -typeof e||(g=e);this.useExplicitRcApi?(b(d.INFO,"fetch_remote_config, Fetching remote config"),t(k,l,this.rcAutoOptinAb?1:0,null,g)):(b(d.WARNING,"fetch_remote_config, Fetching remote config, with legacy API"),t(k,l,null,"legacy",g))};this.enrollUserToAb=function(a){b(d.INFO,"enrollUserToAb, Providing AB test keys to opt in for");a&&Array.isArray(a)&&0!==a.length?(a={method:"ab",keys:JSON.stringify(a)},aa(a),ba("enrollUserToAb",this.url+Ja,a,function(c,e,k){if(c)b(d.ERROR,"enrollUserToAb, An error occurred: "+ -c);else try{var l=JSON.parse(k);b(d.DEBUG,"enrollUserToAb, Parsed the response's result: ["+l.result+"]")}catch(g){b(d.ERROR,"enrollUserToAb, Had an issue while parsing the response: "+g)}},!0)):b(d.ERROR,"enrollUserToAb, No keys provided")};this.get_remote_config=function(a){b(d.INFO,"get_remote_config, Getting remote config from storage");return"undefined"!==typeof a?O[a]:O};this.stop_time=function(){b(d.INFO,"stop_time, Stopping tracking duration");ja&&(ja=!1,fb=E()-ia,xa=E()-wa)};this.start_time= -function(){b(d.INFO,"start_time, Starting tracking duration");ja||(ja=!0,ia=E()-fb,wa=E()-xa,xa=0,ua())};this.track_sessions=function(){function a(){document[e]||!document.hasFocus()?f.stop_time():f.start_time()}function c(){Ea>=Da&&f.start_time();Ea=0}b(d.INFO,"track_session, Starting tracking user session");this.begin_session();this.start_time();y(window,"beforeunload",function(){B();f.end_session()});var e="hidden";y(window,"focus",a);y(window,"blur",a);y(window,"pageshow",a);y(window,"pagehide", -a);"onfocusin"in document&&(y(window,"focusin",a),y(window,"focusout",a));e in document?document.addEventListener("visibilitychange",a):"mozHidden"in document?(e="mozHidden",document.addEventListener("mozvisibilitychange",a)):"webkitHidden"in document?(e="webkitHidden",document.addEventListener("webkitvisibilitychange",a)):"msHidden"in document&&(e="msHidden",document.addEventListener("msvisibilitychange",a));y(window,"mousemove",c);y(window,"click",c);y(window,"keydown",c);y(window,"scroll",c);setInterval(function(){Ea++; -Ea>=Da&&f.stop_time()},6E4)};this.track_pageview=function(a,c,e){b(d.INFO,"track_pageview, Tracking page views");b(d.VERBOSE,"track_pageview, last view is:["+V+"], current view ID is:["+fa+"], previous view ID is:["+va+"]");V&&gb&&(b(d.DEBUG,"track_pageview, Scroll registry triggered"),db(),Ca=!0,Ba=0);R();va=fa;fa=Va();(a=x(a,f.maxKeyLength,"track_pageview",b))&&Array.isArray(a)&&(c=a,a=null);a||(a=this.getViewName());if(void 0===a||""===a)b(d.ERROR,"track_pageview, No page name to track (it is either undefined or empty string). No page view can be tracked."); -else if(null===a)b(d.ERROR,"track_pageview, View name returned as null. Page view will be ignored.");else{if(c&&c.length)for(var k=0;k