From ec6fd0ef7c96267770920c9011d6c45258046d77 Mon Sep 17 00:00:00 2001 From: turtledreams Date: Tue, 10 Oct 2023 13:47:29 +0900 Subject: [PATCH] utm and referrer to view segment --- cypress/fixtures/referrer.html | 20 ++ cypress/integration/utm.js | 60 ++--- cypress/integration/view_utm_referrer.js | 295 +++++++++++++++++++++++ cypress/support/commands.js | 6 +- cypress/support/helper.js | 47 +++- lib/countly.js | 94 ++++++-- 6 files changed, 451 insertions(+), 71 deletions(-) create mode 100644 cypress/fixtures/referrer.html create mode 100644 cypress/integration/view_utm_referrer.js 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/lib/countly.js b/lib/countly.js index cc89c768..e0c36340 100644 --- a/lib/countly.js +++ b/lib/countly.js @@ -197,6 +197,7 @@ 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); @@ -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); @@ -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); @@ -3904,34 +3927,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, "Ignored: " + document.referrer); - ignoring = true; - break; - } - } - catch (ex) { - log(logLevelEnums.ERROR, "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, "Got metrics", metrics); - return metrics; + return isReferrerLegit; } /** @@ -4491,6 +4542,7 @@ generateUUID: generateUUID, sendEventsForced: sendEventsForced, isUUID: isUUID, + isReferrerUsable: isReferrerUsable, getId: getStoredIdOrGenerateId, heartBeat: heartBeat, toRequestQueue: toRequestQueue,