diff --git a/.eslintignore b/.eslintignore index aa63eee3..2f263929 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,6 @@ lib/countly.min.js plugin/boomerang/boomerang.min.js -examples/react -examples/symbolication +examples/ node_modules .vscode .github diff --git a/cypress/integration/web_worker_queues.js b/cypress/integration/web_worker_queues.js new file mode 100644 index 00000000..58dfecad --- /dev/null +++ b/cypress/integration/web_worker_queues.js @@ -0,0 +1,33 @@ +describe("Web Worker Local Queue Tests", () => { + it("Verify queues for all features", () => { + // create a worker + const myWorker = new Worker("../../examples/mpa/worker_for_test.js"); + + // send an event to worker + myWorker.postMessage({ data: { key: "key" }, type: "event" }); + myWorker.postMessage({ data: "begin_session", type: "session" }); + myWorker.postMessage({ data: "end_session", type: "session" }); + myWorker.postMessage({ data: "home_page", type: "view" }); + + // ask for local queues + myWorker.postMessage({ data: "queues", type: "get" }); + + let requestQueue; + let eventQueue; + myWorker.onmessage = function(e) { + requestQueue = e.data.requestQ; // Array of requests + eventQueue = e.data.eventQ; // Array of events + myWorker.terminate(); // terminate worker + + // verify event queue + expect(eventQueue.length).to.equal(2); + cy.check_event(eventQueue[0], { key: "key" }, undefined, false); + cy.check_view_event(eventQueue[1], "home_page", undefined, false); + + // verify request queue + expect(requestQueue.length).to.equal(2); + cy.check_session(requestQueue[0], undefined, false, false, true); + cy.check_session(requestQueue[1], 0, false, false, false); + }; + }); +}); \ No newline at end of file diff --git a/cypress/integration/web_worker_requests.js b/cypress/integration/web_worker_requests.js new file mode 100644 index 00000000..9035e450 --- /dev/null +++ b/cypress/integration/web_worker_requests.js @@ -0,0 +1,125 @@ +import { appKey } from "../support/helper"; + +const myEvent = { + key: "buttonClick", + segmentation: { + id: "id" + } +}; + +describe("Web Worker Request Intercepting Tests", () => { + it("SDK able to send requests for most basic calls", () => { + // create a worker + const myWorker = new Worker("../../examples/worker.js"); + + // send an event to worker + myWorker.postMessage({ data: myEvent, type: "event" }); + myWorker.postMessage({ data: "begin_session", type: "session" }); + myWorker.postMessage({ data: "end_session", type: "session" }); + myWorker.postMessage({ data: "home_page", type: "view" }); + + // intercept requests + cy.intercept("GET", "**/i?**", (req) => { + const { url } = req; + + // check url starts with https://try.count.ly/i? + assert.isTrue(url.startsWith("https://try.count.ly/i?")); + + // turn query string into object + const paramsObject = turnSearchStringToObject(url.split("?")[1]); + + // check common params + check_commons(paramsObject); + + // we expect 4 requests: begin_session, end_session, healthcheck, event(event includes view and buttonClick) + let expectedRequests = 4; + if (paramsObject.hc) { + // check hc params types, values can change + assert.isTrue(typeof paramsObject.hc.el === "number"); + assert.isTrue(typeof paramsObject.hc.wl === "number"); + assert.isTrue(typeof paramsObject.hc.sc === "number"); + assert.isTrue(typeof paramsObject.hc.em === "string"); + expectedRequests--; + } + else if (paramsObject.events) { + // check event params with accordance to event sent (myEvent above) + for (const eventInRequest of paramsObject.events) { + if (eventInRequest.key === "[CLY]_view") { // view event + expect(eventInRequest.segmentation.name).to.equal("home_page"); + expect(eventInRequest.segmentation.visit).to.equal(1); + expect(eventInRequest.segmentation.start).to.equal(1); + expect(eventInRequest.segmentation.view).to.equal("web_worker"); + expect(eventInRequest.pvid).to.equal(""); + } + else { // buttonClick event + expect(eventInRequest.key).to.equal(myEvent.key); + expect(eventInRequest.segmentation).to.deep.equal(myEvent.segmentation); + assert.isTrue(eventInRequest.cvid === ""); + } + assert.isTrue(eventInRequest.count === 1); + expect(eventInRequest.id).to.be.ok; + expect(eventInRequest.id.toString().length).to.equal(21); + expect(eventInRequest.timestamp).to.be.ok; + expect(eventInRequest.timestamp.toString().length).to.equal(13); + expect(eventInRequest.hour).to.be.within(0, 23); + expect(eventInRequest.dow).to.be.within(0, 7); + } + expectedRequests--; + } + else if (paramsObject.begin_session === 1) { // check metrics + expect(paramsObject.metrics._app_version).to.equal("0.0"); + expect(paramsObject.metrics._ua).to.equal("abcd"); + assert.isTrue(typeof paramsObject.metrics._locale === "string"); + expectedRequests--; + } + else if (paramsObject.end_session === 1) { // check metrics and session_duration + expect(paramsObject.metrics._ua).to.equal("abcd"); + expect(paramsObject.session_duration).to.be.above(-1); + expectedRequests--; + } + if (expectedRequests === 0) { + myWorker.terminate(); // we checked everything, terminate worker + } + }); + }); +}); + +/** + * Check common params for all requests + * @param {Object} paramsObject - object from search string + */ +function check_commons(paramsObject) { + expect(paramsObject.timestamp).to.be.ok; + expect(paramsObject.timestamp.toString().length).to.equal(13); + expect(paramsObject.hour).to.be.within(0, 23); + expect(paramsObject.dow).to.be.within(0, 7); + expect(paramsObject.app_key).to.equal(appKey); + expect(paramsObject.device_id).to.be.ok; + expect(paramsObject.sdk_name).to.equal("javascript_native_web"); + expect(paramsObject.sdk_version).to.be.ok; + expect(paramsObject.t).to.be.within(0, 3); + expect(paramsObject.av).to.equal(0); // av is 0 as we parsed parsable things + if (!paramsObject.hc) { // hc is direct request + expect(paramsObject.rr).to.be.above(-1); + } + expect(paramsObject.metrics._ua).to.be.ok; +} + +/** + * Turn search string into object with values parsed + * @param {String} searchString - search string + * @returns {object} - object from search string + */ +function turnSearchStringToObject(searchString) { + const searchParams = new URLSearchParams(searchString); + const paramsObject = {}; + for (const [key, value] of searchParams.entries()) { + try { + paramsObject[key] = JSON.parse(value); // try to parse value + } + catch (e) { + paramsObject[key] = value; + } + } + return paramsObject; +} \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 57753bfa..c023d30f 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -68,15 +68,17 @@ Cypress.Commands.add("check_crash", (testObject, appKey) => { * @param {Number} duration - session extension or end session duration to validate * @param {Boolean} isSessionEnd - a boolean to mark this check is intended for end_session validation */ -Cypress.Commands.add("check_session", (queueObject, duration, isSessionEnd, appKey) => { - if (!duration) { // if duration is not given that means its begin session +Cypress.Commands.add("check_session", (queueObject, duration, isSessionEnd, appKey, worker) => { + if (duration === undefined) { // if duration is not given that means its begin session expect(queueObject.begin_session).to.equal(1); const metrics = JSON.parse(queueObject.metrics); expect(metrics._app_version).to.be.ok; expect(metrics._ua).to.be.ok; - expect(metrics._resolution).to.be.ok; - expect(metrics._density).to.be.ok; expect(metrics._locale).to.be.ok; + if (!worker) { + expect(metrics._resolution).to.be.ok; + expect(metrics._density).to.be.ok; + } } else if (!isSessionEnd) { expect(queueObject.session_duration).to.be.within(duration, duration + 2); @@ -152,7 +154,9 @@ Cypress.Commands.add("check_view_event", (queueObject, name, duration, hasPvid) if (duration === undefined) { expect(queueObject.segmentation.visit).to.equal(1); expect(queueObject.segmentation.view).to.be.ok; - expect(queueObject.segmentation.domain).to.be.ok; + if (queueObject.segmentation.view !== "web_worker") { + expect(queueObject.segmentation.domain).to.be.ok; + } // expect(queue.segmentation.start).to.be.ok; // TODO: this is only for manual tracking? } else { diff --git a/examples/example_web_worker.html b/examples/example_web_worker.html new file mode 100644 index 00000000..4c323636 --- /dev/null +++ b/examples/example_web_worker.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + +
+ +
+ + + + +
+
+ + + + \ No newline at end of file diff --git a/examples/mpa/worker_for_test.js b/examples/mpa/worker_for_test.js new file mode 100644 index 00000000..e87cbbdc --- /dev/null +++ b/examples/mpa/worker_for_test.js @@ -0,0 +1,27 @@ +importScripts("../../lib/countly.js"); + +Countly.init({ + app_key: "YOUR_APP_KEY", + url: "https://try.count.ly", + debug: true, + test_mode: true +}); + +onmessage = function(e) { + console.log(`Worker: Message received from main script:[${JSON.stringify(e.data)}]`); + const data = e.data.data; const type = e.data.type; + if (type === "event") { + Countly.add_event(data); + } else if (type === "view") { + Countly.track_pageview(data); + } else if (type === "session") { + if (data === "begin_session") { + Countly.begin_session(); + return; + } + Countly.end_session(null, true); + } else if (type === "get") { + const queues = Countly._internals.getLocalQueues(); + postMessage(queues); + } +}; \ No newline at end of file diff --git a/examples/worker.js b/examples/worker.js new file mode 100644 index 00000000..1d72ba9d --- /dev/null +++ b/examples/worker.js @@ -0,0 +1,23 @@ +importScripts("../lib/countly.js"); + +Countly.init({ + app_key: "YOUR_APP_KEY", + url: "https://try.count.ly", + debug: true +}); + +onmessage = function (e) { + console.log(`Worker: Message received from main script:[${JSON.stringify(e.data)}]`); + const data = e.data.data; const type = e.data.type; + if (type === "event") { + Countly.add_event(data); + } else if (type === "view") { + Countly.track_pageview(data); + } else if (type === "session") { + if (data === "begin_session") { + Countly.begin_session(); + return; + } + Countly.end_session(null, true); + } +} \ No newline at end of file diff --git a/lib/countly.js b/lib/countly.js index 0e67a01b..ab88c4e6 100644 --- a/lib/countly.js +++ b/lib/countly.js @@ -53,13 +53,7 @@ root.Countly = factory(root.Countly); } }(typeof window !== "undefined" ? window : this, function(Countly) { - // Make sure the code is being run in a browser - // eslint-disable-next-line no-undef - if (typeof (window) === "undefined") { - // TODO: check if logging can be added here, like: - // console.error("Not running in browser"); - return; - } + var isBrowser = typeof window !== "undefined"; /** @lends Countly */ Countly = Countly || {}; @@ -275,7 +269,7 @@ this.getSearchQuery = getConfig("getSearchQuery", ob, Countly.getSearchQuery); this.DeviceIdType = Countly.DeviceIdType; // it is Countly device Id type Enums for clients to use this.namespace = getConfig("namespace", ob, ""); - this.clearStoredId = getConfig("clear_stored_id", ob, false); + this.clearStoredId = !isBrowser ? undefined : getConfig("clear_stored_id", ob, false); this.app_key = getConfig("app_key", ob, null); this.onload = getConfig("onload", ob, []); this.utm = getConfig("utm", ob, { source: true, medium: true, campaign: true, term: true, content: true }); @@ -292,14 +286,14 @@ this.country_code = getConfig("country_code", ob, null); this.city = getConfig("city", ob, null); this.ip_address = getConfig("ip_address", ob, null); - this.ignore_bots = getConfig("ignore_bots", ob, true); + this.ignore_bots = !isBrowser ? undefined : getConfig("ignore_bots", ob, true); this.force_post = getConfig("force_post", ob, false); this.remote_config = getConfig("remote_config", ob, false); this.ignore_visitor = getConfig("ignore_visitor", ob, false); this.require_consent = getConfig("require_consent", ob, false); - this.track_domains = getConfig("track_domains", ob, true); - this.storage = getConfig("storage", ob, "default"); - this.enableOrientationTracking = getConfig("enable_orientation_tracking", ob, true); + this.track_domains = !isBrowser ? undefined : getConfig("track_domains", ob, true); + this.storage = !isBrowser ? "none" : getConfig("storage", ob, "default"); + this.enableOrientationTracking = !isBrowser ? undefined : getConfig("enable_orientation_tracking", ob, true); this.maxKeyLength = getConfig("max_key_length", ob, configurationDefaultValues.MAX_KEY_LENGTH); this.maxValueSize = getConfig("max_value_size", ob, configurationDefaultValues.MAX_VALUE_SIZE); this.maxSegmentationValues = getConfig("max_segmentation_values", ob, configurationDefaultValues.MAX_SEGMENTATION_VALUES); @@ -372,20 +366,22 @@ checkIgnore(); - if (window.name && window.name.indexOf("cly:") === 0) { - try { - this.passed_data = JSON.parse(window.name.replace("cly:", "")); - } - catch (ex) { - log(logLevelEnums.ERROR, "initialize, Could not parse name: " + window.name + ", error: " + ex); - } - } - else if (location.hash && location.hash.indexOf("#cly:") === 0) { - try { - this.passed_data = JSON.parse(location.hash.replace("#cly:", "")); + if (isBrowser) { + if (window.name && window.name.indexOf("cly:") === 0) { + try { + this.passed_data = JSON.parse(window.name.replace("cly:", "")); + } + catch (ex) { + log(logLevelEnums.ERROR, "initialize, Could not parse name: " + window.name + ", error: " + ex); + } } - catch (ex) { - log(logLevelEnums.ERROR, "initialize, Could not parse hash: " + location.hash + ", error: " + ex); + else if (location.hash && location.hash.indexOf("#cly:") === 0) { + try { + this.passed_data = JSON.parse(location.hash.replace("#cly:", "")); + } + catch (ex) { + log(logLevelEnums.ERROR, "initialize, Could not parse hash: " + location.hash + ", error: " + ex); + } } } @@ -672,7 +668,9 @@ self.fetch_remote_config(self.remote_config); } }, 1); - document.documentElement.setAttribute("data-countly-useragent", currentUserAgentString()); + if (isBrowser) { + document.documentElement.setAttribute("data-countly-useragent", currentUserAgentString()); + } // send instant health check request HealthCheck.sendInstantHCRequest(); }; @@ -1650,6 +1648,10 @@ * @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) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "track_errors, window object is not available. Not tracking errors."); + return; + } 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; @@ -1941,6 +1943,10 @@ * Track user sessions automatically, including time user spent on your website * */ this.track_sessions = function() { + if (!isBrowser) { + log(logLevelEnums.WARNING, "track_sessions, window object is not available. Not tracking sessions."); + return; + } log(logLevelEnums.INFO, "track_session, Starting tracking user session"); // start session this.begin_session(); @@ -2029,6 +2035,10 @@ * @param {object=} viewSegments - optional key value object with segments to report with the view * */ this.track_pageview = function(page, ignoreList, viewSegments) { + if (!isBrowser && !page) { + log(logLevelEnums.WARNING, "track_pageview, window object is not available. Not tracking page views is page is not provided."); + return; + } 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) { @@ -2105,7 +2115,7 @@ } // if we are not using session cookie, there is no session state between refreshes // so we fallback to old logic of landing - else if (typeof document.referrer !== "undefined" && document.referrer.length) { + else if (isBrowser && 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) { @@ -2126,7 +2136,7 @@ } // add referrer if it is usable - if (isReferrerUsable()) { + if (isBrowser && isReferrerUsable()) { log(logLevelEnums.INFO, "track_pageview, Adding referrer to segmentation:[" + document.referrer + "]"); segments.referrer = document.referrer; // add referrer } @@ -2169,6 +2179,10 @@ * @param {Object=} parent - DOM object which children to track, by default it is document body * */ this.track_clicks = function(parent) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "track_clicks, window object is not available. Not tracking clicks."); + return; + } log(logLevelEnums.INFO, "track_clicks, Starting to track clicks"); if (parent) { log(logLevelEnums.INFO, "track_clicks, Tracking the specified children:[" + parent + "]"); @@ -2224,6 +2238,10 @@ * @param {Object=} parent - DOM object which children to track, by default it is document body * */ this.track_scrolls = function(parent) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "track_scrolls, window object is not available. Not tracking scrolls."); + return; + } log(logLevelEnums.INFO, "track_scrolls, Starting to track scrolls"); if (parent) { log(logLevelEnums.INFO, "track_scrolls, Tracking the specified children"); @@ -2241,6 +2259,10 @@ * @param {Object=} parent - DOM object which children to track, by default it is document body * */ this.track_links = function(parent) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "track_links, window object is not available. Not tracking links."); + return; + } log(logLevelEnums.INFO, "track_links, Starting to track clicks to links"); if (parent) { log(logLevelEnums.INFO, "track_links, Tracking the specified children"); @@ -2283,6 +2305,10 @@ * @param {boolean=} trackHidden - provide true to also track hidden inputs, default false * */ this.track_forms = function(parent, trackHidden) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "track_forms, window object is not available. Not tracking forms."); + return; + } log(logLevelEnums.INFO, "track_forms, Starting to track form submissions. DOM object provided:[" + (!!parent) + "] Tracking hidden inputs :[" + (!!trackHidden) + "]"); parent = parent || document; /** @@ -2373,6 +2399,10 @@ * @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) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "collect_from_forms, window object is not available. Not collecting from forms."); + return; + } log(logLevelEnums.INFO, "collect_from_forms, Starting to collect possible user data. DOM object provided:[" + (!!parent) + "] Submitting custom user property:[" + (!!useCustom) + "]"); parent = parent || document; /** @@ -2514,6 +2544,10 @@ * @param {Object=} custom - Custom keys to collected from Facebook, key will be used to store as key in custom user properties and value as key in Facebook graph object. For example, {"tz":"timezone"} will collect Facebook's timezone property, if it is available and store it in custom user's property under "tz" key. If you want to get value from some sub object properties, then use dot as delimiter, for example, {"location":"location.name"} will collect data from Facebook's {"location":{"name":"MyLocation"}} object and store it in user's custom property "location" key * */ this.collect_from_facebook = function(custom) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "collect_from_facebook, window object is not available. Not collecting from Facebook."); + return; + } if (typeof FB === "undefined" || !FB || !FB.api) { log(logLevelEnums.ERROR, "collect_from_facebook, Facebook SDK is not available"); return; @@ -2779,6 +2813,10 @@ * @deprecated use 'presentRatingWidgetWithID' in place of this call */ this.show_feedback_popup = function(id) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "show_feedback_popup, window object is not available. Not showing feedback popup."); + return; + } log(logLevelEnums.WARNING, "show_feedback_popup, Deprecated function call! Use 'presentRatingWidgetWithID' in place of this call. Call will be redirected now!"); this.presentRatingWidgetWithID(id); }; @@ -2787,6 +2825,10 @@ * @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) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "presentRatingWidgetWithID, window object is not available. Not showing rating widget popup."); + return; + } log(logLevelEnums.INFO, "presentRatingWidgetWithID, Showing rating widget popup for the widget with ID: [ " + id + " ]"); if (!this.check_consent(featureEnums.STAR_RATING)) { return; @@ -2820,6 +2862,10 @@ * @deprecated use 'initializeRatingWidgets' in place of this call */ this.initialize_feedback_popups = function(enableWidgets) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "initialize_feedback_popups, window object is not available. Not initializing feedback popups."); + return; + } log(logLevelEnums.WARNING, "initialize_feedback_popups, Deprecated function call! Use 'initializeRatingWidgets' in place of this call. Call will be redirected now!"); this.initializeRatingWidgets(enableWidgets); }; @@ -2828,6 +2874,10 @@ * @param {array=} enableWidgets - widget ids array */ this.initializeRatingWidgets = function(enableWidgets) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "initializeRatingWidgets, window object is not available. Not initializing rating widgets."); + return; + } log(logLevelEnums.INFO, "initializeRatingWidgets, Initializing rating widget with provided widget IDs:[ " + enableWidgets + "]"); if (!this.check_consent(featureEnums.STAR_RATING)) { return; @@ -2895,6 +2945,10 @@ * @deprecated use 'enableRatingWidgets' in place of this call * */ this.enable_feedback = function(params) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "enable_feedback, window object is not available. Not enabling feedback."); + return; + } log(logLevelEnums.WARNING, "enable_feedback, Deprecated function call! Use 'enableRatingWidgets' in place of this call. Call will be redirected now!"); this.enableRatingWidgets(params); }; @@ -2904,6 +2958,10 @@ * example params: {"popups":["5b21581b967c4850a7818617"]} * */ this.enableRatingWidgets = function(params) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "enableRatingWidgets, window object is not available. Not enabling rating widgets."); + return; + } log(logLevelEnums.INFO, "enableRatingWidgets, Enabling rating widget with params:", params); if (!this.check_consent(featureEnums.STAR_RATING)) { return; @@ -3069,6 +3127,10 @@ * @param {Object} [feedbackWidgetSegmentation] - Segmentation object to be passed to the feedback widget (optional) * */ this.present_feedback_widget = function(presentableFeedback, id, className, feedbackWidgetSegmentation) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "present_feedback_widget, window object is not available. Not presenting feedback widget."); + return; + } // TODO: feedbackWidgetSegmentation implementation only assumes we want to send segmentation data. Change it if we add more data to the custom object. log(logLevelEnums.INFO, "present_feedback_widget, Presenting the feedback widget by appending to the element with ID: [ " + id + " ] and className: [ " + className + " ]"); @@ -3479,7 +3541,9 @@ obj._online = !!(navigator.onLine); } - obj._background = !(document.hasFocus()); + if (isBrowser) { + obj._background = !(document.hasFocus()); + } if (crashLogs.length > 0) { obj._logs = crashLogs.join("\n"); @@ -3518,7 +3582,7 @@ * Check if user or visit should be ignored */ function checkIgnore() { - if (self.ignore_prefetch && typeof document.visibilityState !== "undefined" && document.visibilityState === "prerender") { + if (self.ignore_prefetch && isBrowser && typeof document.visibilityState !== "undefined" && document.visibilityState === "prerender") { self.ignore_visitor = true; } if (self.ignore_bots && userAgentSearchBotDetection()) { @@ -3544,6 +3608,10 @@ * @param {Boolean} hasSticker - if widget has sticker */ function processWidget(currentWidget, hasSticker) { + if (!isBrowser) { + log(logLevelEnums.WARNING, "processWidget, window object is not available. Not processing widget."); + return; + } // prevent widget create process if widget exist with same id var isDuplicate = !!document.getElementById("countly-feedback-sticker-" + currentWidget._id); if (isDuplicate) { @@ -3913,7 +3981,7 @@ metrics._ua = metrics._ua || currentUserAgentString(); // getting resolution - if (screen.width) { + if (isBrowser && screen.width) { var width = (screen.width) ? parseInt(screen.width) : 0; var height = (screen.height) ? parseInt(screen.height) : 0; if (width !== 0 && height !== 0) { @@ -3937,7 +4005,7 @@ } // getting density ratio - if (window.devicePixelRatio) { + if (isBrowser && window.devicePixelRatio) { metrics._density = metrics._density || window.devicePixelRatio; } @@ -3965,6 +4033,9 @@ * @returns {boolean} true if document.referrer is not empty string, undefined, current host or in the ignore list. */ function isReferrerUsable(customReferrer) { + if (!isBrowser) { + return false; + } var referrer = customReferrer || document.referrer; var isReferrerLegit = false; @@ -4072,13 +4143,7 @@ useBroadResponseValidator = useBroadResponseValidator || false; try { log(logLevelEnums.DEBUG, "Sending XML HTTP request"); - var xhr = null; - if (window.XMLHttpRequest) { - xhr = new window.XMLHttpRequest(); - } - else if (window.ActiveXObject) { - xhr = new window.ActiveXObject("Microsoft.XMLHTTP"); - } + var xhr = new XMLHttpRequest(); params = params || {}; var data = prepareParams(params); var method = "GET"; @@ -4215,6 +4280,10 @@ * */ function processScroll() { + if (!isBrowser) { + log(logLevelEnums.WARNING, "processScroll, window object is not available. Not processing scroll."); + return; + } scrollRegistryTopPosition = Math.max(scrollRegistryTopPosition, window.scrollY, document.body.scrollTop, document.documentElement.scrollTop); } @@ -4223,6 +4292,10 @@ * @memberof Countly._internals */ function processScrollView() { + if (!isBrowser) { + log(logLevelEnums.WARNING, "processScrollView, window object is not available. Not processing scroll view."); + return; + } if (isScrollRegistryOpen) { isScrollRegistryOpen = false; var height = getDocHeight(); @@ -4596,6 +4669,16 @@ setValueInStorage("cly_queue", []); eventQueue = []; setValueInStorage("cly_event", []); + }, + /** + * For testing pusposes only + * @returns {Object} - returns the local queues + */ + getLocalQueues: function() { + return { + eventQ: eventQueue, + requestQ: requestQueue + }; } }; @@ -5235,6 +5318,9 @@ * @param {Function} listener - callback when event is fired */ var add_event_listener = function(element, type, listener) { + if (!isBrowser) { + return; + } if (element === null || typeof element === "undefined") { // element can be null so lets check it first if (checkIfLoggingIsOn()) { // eslint-disable-next-line no-console @@ -5421,15 +5507,16 @@ /** * Monitor parallel storage changes like other opened tabs */ - window.addEventListener("storage", function(e) { - var parts = (e.key + "").split("/"); - var key = parts.pop(); - var appKey = parts.pop(); - - if (Countly.i && Countly.i[appKey]) { - Countly.i[appKey].onStorageChange(key, e.newValue); - } - }); + if (isBrowser) { + window.addEventListener("storage", function(e) { + var parts = (e.key + "").split("/"); + var key = parts.pop(); + var appKey = parts.pop(); + if (Countly.i && Countly.i[appKey]) { + Countly.i[appKey].onStorageChange(key, e.newValue); + } + }); + } /** * Load external js files @@ -5483,6 +5570,9 @@ * @memberof Countly._internals */ function showLoader() { + if (!isBrowser) { + return; + } var loader = document.getElementById("cly-loader"); if (!loader) { var css = "#cly-loader {height: 4px; width: 100%; position: absolute; z-index: 99999; overflow: hidden; background-color: #fff; top:0px; left:0px;}" @@ -5541,6 +5631,9 @@ * @memberof Countly._internals */ function hideLoader() { + if (!isBrowser) { + return; + } // Inform showLoader that it should not append the loader Countly.showLoaderProtection = true; var loader = document.getElementById("cly-loader"); @@ -5590,6 +5683,9 @@ * @return {string} view name * */ Countly.getViewName = function() { + if (!isBrowser) { + return "web_worker"; + } return window.location.pathname; }; @@ -5598,6 +5694,9 @@ * @return {string} view url * */ Countly.getViewUrl = function() { + if (!isBrowser) { + return "web_worker"; + } return window.location.pathname; }; @@ -5606,6 +5705,9 @@ * @return {string} view url * */ Countly.getSearchQuery = function() { + if (!isBrowser) { + return; + } return window.location.search; };