diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index f20b02c50a..4a1d5877d1 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -36,7 +36,6 @@ import RightPanelStore from "../stores/right-panel/RightPanelStore"; import WidgetStore from "../stores/WidgetStore"; import CallHandler from "../CallHandler"; import { Analytics } from "../Analytics"; -import CountlyAnalytics from "../CountlyAnalytics"; import UserActivity from "../UserActivity"; import { ModalWidgetStore } from "../stores/ModalWidgetStore"; import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; @@ -93,7 +92,6 @@ declare global { mxWidgetLayoutStore: WidgetLayoutStore; mxCallHandler: CallHandler; mxAnalytics: Analytics; - mxCountlyAnalytics: typeof CountlyAnalytics; mxUserActivity: UserActivity; mxModalWidgetStore: ModalWidgetStore; mxVoipUserMapper: VoipUserMapper; diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 699a97d12c..a060846890 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -44,7 +44,6 @@ import WidgetStore from "./stores/WidgetStore"; import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; import Analytics from './Analytics'; -import CountlyAnalytics from "./CountlyAnalytics"; import { UIFeature } from "./settings/UIFeature"; import { Action } from './dispatcher/actions'; import VoipUserMapper from './VoipUserMapper'; @@ -745,7 +744,6 @@ export default class CallHandler extends EventEmitter { private async placeMatrixCall(roomId: string, type: CallType, transferee?: MatrixCall): Promise { Analytics.trackEvent('voip', 'placeCall', 'type', type); - CountlyAnalytics.instance.trackStartCall(roomId, type === CallType.Video, false); const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId; logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId); @@ -890,7 +888,6 @@ export default class CallHandler extends EventEmitter { call.answer(); this.setActiveCallRoomId(roomId); - CountlyAnalytics.instance.trackJoinCall(roomId, call.type === CallType.Video, false); dis.dispatch({ action: Action.ViewRoom, room_id: roomId, @@ -1010,7 +1007,6 @@ export default class CallHandler extends EventEmitter { private async placeJitsiCall(roomId: string, type: string): Promise { logger.info("Place conference call in " + roomId); Analytics.trackEvent('voip', 'placeConferenceCall'); - CountlyAnalytics.instance.trackStartCall(roomId, type === CallType.Video, true); dis.dispatch({ action: 'appsDrawer', diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index 78a91efaf4..caaaf7e9a1 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -34,7 +34,6 @@ import Modal from './Modal'; import RoomViewStore from './stores/RoomViewStore'; import Spinner from "./components/views/elements/Spinner"; import { Action } from "./dispatcher/actions"; -import CountlyAnalytics from "./CountlyAnalytics"; import { UploadCanceledPayload, UploadErrorPayload, @@ -430,12 +429,10 @@ export default class ContentMessages { text: string, matrixClient: MatrixClient, ): Promise { - const startTime = CountlyAnalytics.getTimestamp(); const prom = matrixClient.sendStickerMessage(roomId, threadId, url, info, text).catch((e) => { logger.warn(`Failed to send content with URL ${url} to room ${roomId}`, e); throw e; }); - CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, false, { msgtype: "m.sticker" }); return prom; } @@ -573,7 +570,6 @@ export default class ContentMessages { matrixClient: MatrixClient, promBefore: Promise, ) { - const startTime = CountlyAnalytics.getTimestamp(); const content: IContent = { body: file.name || 'Attachment', info: { @@ -671,7 +667,6 @@ export default class ContentMessages { sendRoundTripMetric(matrixClient, roomId, resp.event_id); }); } - CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, false, content); return prom; }, function(err) { error = err; diff --git a/src/CountlyAnalytics.ts b/src/CountlyAnalytics.ts deleted file mode 100644 index 11196c1f3a..0000000000 --- a/src/CountlyAnalytics.ts +++ /dev/null @@ -1,975 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { randomString } from "matrix-js-sdk/src/randomstring"; -import { IContent } from "matrix-js-sdk/src/models/event"; -import { sleep } from "matrix-js-sdk/src/utils"; -import { logger } from "matrix-js-sdk/src/logger"; - -import { getCurrentLanguage } from './languageHandler'; -import PlatformPeg from './PlatformPeg'; -import SdkConfig from './SdkConfig'; -import { MatrixClientPeg } from "./MatrixClientPeg"; -import RoomViewStore from "./stores/RoomViewStore"; -import { Action } from "./dispatcher/actions"; - -const INACTIVITY_TIME = 20; // seconds -const HEARTBEAT_INTERVAL = 5_000; // ms -const SESSION_UPDATE_INTERVAL = 60; // seconds -const MAX_PENDING_EVENTS = 1000; - -export type Rating = 1 | 2 | 3 | 4 | 5; - -enum Orientation { - Landscape = "landscape", - Portrait = "portrait", -} - -/* eslint-disable camelcase */ -interface IMetrics { - _resolution?: string; - _app_version?: string; - _density?: number; - _ua?: string; - _locale?: string; -} - -interface IEvent { - key: string; - count: number; - sum?: number; - dur?: number; - segmentation?: Record; - timestamp?: number; // TODO should we use the timestamp when we start or end for the event timestamp - hour?: unknown; - dow?: unknown; -} - -interface IViewEvent extends IEvent { - key: "[CLY]_view"; -} - -interface IOrientationEvent extends IEvent { - key: "[CLY]_orientation"; - segmentation: { - mode: Orientation; - }; -} - -interface IStarRatingEvent extends IEvent { - key: "[CLY]_star_rating"; - segmentation: { - // we just care about collecting feedback, no need to associate with a feedback widget - widget_id?: string; - contactMe?: boolean; - email?: string; - rating: 1 | 2 | 3 | 4 | 5; - comment: string; - }; -} - -type Value = string | number | boolean; - -interface IOperationInc { - "$inc": number; -} -interface IOperationMul { - "$mul": number; -} -interface IOperationMax { - "$max": number; -} -interface IOperationMin { - "$min": number; -} -interface IOperationSetOnce { - "$setOnce": Value; -} -interface IOperationPush { - "$push": Value | Value[]; -} -interface IOperationAddToSet { - "$addToSet": Value | Value[]; -} -interface IOperationPull { - "$pull": Value | Value[]; -} - -type Operation = - IOperationInc | - IOperationMul | - IOperationMax | - IOperationMin | - IOperationSetOnce | - IOperationPush | - IOperationAddToSet | - IOperationPull; - -interface IUserDetails { - name?: string; - username?: string; - email?: string; - organization?: string; - phone?: string; - picture?: string; - gender?: string; - byear?: number; - custom?: Record; // `.` and `$` will be stripped out -} - -interface ICrash { - _resolution?: string; - _app_version: string; - - _ram_current?: number; - _ram_total?: number; - _disk_current?: number; - _disk_total?: number; - _orientation?: Orientation; - - _online?: boolean; - _muted?: boolean; - _background?: boolean; - _view?: string; - - _name?: string; - _error: string; - _nonfatal?: boolean; - _logs?: string; - _run?: number; - - _custom?: Record; -} - -interface IParams { - // APP_KEY of an app for which to report - app_key: string; - // User identifier - device_id: string; - - // Should provide value 1 to indicate session start - begin_session?: number; - // JSON object as string to provide metrics to track with the user - metrics?: string; - // Provides session duration in seconds, can be used as heartbeat to update current sessions duration, recommended time every 60 seconds - session_duration?: number; - // Should provide value 1 to indicate session end - end_session?: number; - - // 10 digit UTC timestamp for recording past data. - timestamp?: number; - // current user local hour (0 - 23) - hour?: number; - // day of the week (0-sunday, 1 - monday, ... 6 - saturday) - dow?: number; - - // JSON array as string containing event objects - events?: string; // IEvent[] - // JSON object as string containing information about users - user_details?: string; - - // provide when changing device ID, so server would merge the data - old_device_id?: string; - - // See ICrash - crash?: string; -} - -interface IRoomSegments extends Record { - room_id: string; // hashed - num_users: number; - is_encrypted: boolean; - is_public: boolean; -} - -interface ISendMessageEvent extends IEvent { - key: "send_message"; - dur: number; // how long it to send (until remote echo) - segmentation: IRoomSegments & { - is_edit: boolean; - is_reply: boolean; - msgtype: string; - format?: string; - }; -} - -interface IRoomDirectoryEvent extends IEvent { - key: "room_directory"; -} - -interface IRoomDirectoryDoneEvent extends IEvent { - key: "room_directory_done"; - dur: number; // time spent in the room directory modal -} - -interface IRoomDirectorySearchEvent extends IEvent { - key: "room_directory_search"; - sum: number; // number of search results - segmentation: { - query_length: number; - query_num_words: number; - }; -} - -interface IStartCallEvent extends IEvent { - key: "start_call"; - segmentation: IRoomSegments & { - is_video: boolean; - is_jitsi: boolean; - }; -} - -interface IJoinCallEvent extends IEvent { - key: "join_call"; - segmentation: IRoomSegments & { - is_video: boolean; - is_jitsi: boolean; - }; -} - -interface IBeginInviteEvent extends IEvent { - key: "begin_invite"; - segmentation: IRoomSegments; -} - -interface ISendInviteEvent extends IEvent { - key: "send_invite"; - sum: number; // quantity that was invited - segmentation: IRoomSegments; -} - -interface ICreateRoomEvent extends IEvent { - key: "create_room"; - dur: number; // how long it took to create (until remote echo) - segmentation: { - room_id: string; // hashed - num_users: number; - is_encrypted: boolean; - is_public: boolean; - }; -} - -export interface IJoinRoomEvent extends IEvent { - key: Action.JoinRoom; - dur: number; // how long it took to join (until remote echo) - segmentation: { - room_id: string; // hashed - num_users: number; - is_encrypted: boolean; - is_public: boolean; - type: "room_directory" | "slash_command" | "link" | "invite" | "tombstone"; - }; -} -/* eslint-enable camelcase */ - -const hashHex = async (input: string): Promise => { - const buf = new TextEncoder().encode(input); - const digestBuf = await window.crypto.subtle.digest("sha-256", buf); - return [...new Uint8Array(digestBuf)].map((b: number) => b.toString(16).padStart(2, "0")).join(""); -}; - -const knownScreens = new Set([ - "register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory", - "start_sso", "start_cas", "groups", "complete_security", "post_registration", "room", "user", "group", -]); - -interface IViewData { - name: string; - url: string; - meta: Record; -} - -// Apply fn to all hash path parts after the 1st one -async function getViewData(anonymous = true): Promise { - const rand = randomString(8); - const { origin, hash } = window.location; - let { pathname } = window.location; - - // Redact paths which could contain unexpected PII - if (origin.startsWith('file://')) { - pathname = `//`; // XXX: inject rand because Count.ly doesn't like X->X transitions - } - - let [_, screen, ...parts] = hash.split("/"); - - if (!knownScreens.has(screen)) { - screen = ``; - } - - for (let i = 0; i < parts.length; i++) { - parts[i] = anonymous ? `` : await hashHex(parts[i]); - } - - const hashStr = `${_}/${screen}/${parts.join("/")}`; - const url = origin + pathname + hashStr; - - const meta = {}; - - let name = "$/" + hash; - switch (screen) { - case "room": { - name = "view_room"; - const roomId = RoomViewStore.getRoomId(); - name += " " + parts[0]; // XXX: workaround Count.ly missing X->X transitions - meta["room_id"] = parts[0]; - Object.assign(meta, getRoomStats(roomId)); - break; - } - } - - return { name, url, meta }; -} - -const getRoomStats = (roomId: string) => { - const cli = MatrixClientPeg.get(); - const room = cli?.getRoom(roomId); - - return { - "num_users": room?.getJoinedMemberCount(), - "is_encrypted": cli?.isRoomEncrypted(roomId), - // eslint-disable-next-line camelcase - "is_public": room?.currentState.getStateEvents("m.room.join_rules", "")?.getContent()?.join_rule === "public", - }; -}; - -// async wrapper for regex-powered String.prototype.replace -const strReplaceAsync = async (str: string, regex: RegExp, fn: (...args: string[]) => Promise) => { - const promises: Promise[] = []; - // dry-run to calculate the replace values - str.replace(regex, (...args: string[]) => { - promises.push(fn(...args)); - return ""; - }); - const values = await Promise.all(promises); - return str.replace(regex, () => values.shift()); -}; - -export default class CountlyAnalytics { - private baseUrl: URL = null; - private appKey: string = null; - private userKey: string = null; - private anonymous: boolean; - private appPlatform: string; - private appVersion = "unknown"; - - private initTime = CountlyAnalytics.getTimestamp(); - private firstPage = true; - private heartbeatIntervalId: number; - private activityIntervalId: number; - private trackTime = true; - private lastBeat: number; - private storedDuration = 0; - private lastView: string; - private lastViewTime = 0; - private lastViewStoredDuration = 0; - private sessionStarted = false; - private heartbeatEnabled = false; - private inactivityCounter = 0; - private pendingEvents: IEvent[] = []; - - private static internalInstance = new CountlyAnalytics(); - - public static get instance(): CountlyAnalytics { - return CountlyAnalytics.internalInstance; - } - - public get disabled() { - return !this.baseUrl; - } - - public canEnable() { - const config = SdkConfig.get(); - return Boolean(navigator.doNotTrack !== "1" && config?.countly?.url && config?.countly?.appKey); - } - - private async changeUserKey(userKey: string, merge = false) { - const oldUserKey = this.userKey; - this.userKey = userKey; - if (oldUserKey && merge) { - await this.request({ old_device_id: oldUserKey }); - } - } - - public async enable(anonymous = true) { - if (!this.disabled && this.anonymous === anonymous) return; - if (!this.canEnable()) return; - - if (!this.disabled) { - // flush request queue as our userKey is going to change, no need to await it - this.request(); - } - - const config = SdkConfig.get(); - this.baseUrl = new URL("/i", config.countly.url); - this.appKey = config.countly.appKey; - - this.anonymous = anonymous; - if (anonymous) { - await this.changeUserKey(randomString(64)); - } else { - await this.changeUserKey(await hashHex(MatrixClientPeg.get().getUserId()), true); - } - - const platform = PlatformPeg.get(); - this.appPlatform = platform.getHumanReadableName(); - try { - this.appVersion = await platform.getAppVersion(); - } catch (e) { - logger.warn("Failed to get app version, using 'unknown'"); - } - - // start heartbeat - this.heartbeatIntervalId = setInterval(this.heartbeat.bind(this), HEARTBEAT_INTERVAL); - this.trackSessions(); - this.trackErrors(); - } - - public async disable() { - if (this.disabled) return; - await this.track("Opt-Out"); - this.endSession(); - window.clearInterval(this.heartbeatIntervalId); - window.clearTimeout(this.activityIntervalId); - this.baseUrl = null; - // remove listeners bound in trackSessions() - window.removeEventListener("beforeunload", this.endSession); - window.removeEventListener("unload", this.endSession); - window.removeEventListener("visibilitychange", this.onVisibilityChange); - window.removeEventListener("mousemove", this.onUserActivity); - window.removeEventListener("click", this.onUserActivity); - window.removeEventListener("keydown", this.onUserActivity); - window.removeEventListener("scroll", this.onUserActivity); - } - - public reportFeedback(rating: Rating, comment: string) { - this.track("[CLY]_star_rating", { rating, comment }, null, {}, true); - } - - public trackPageChange(generationTimeMs?: number) { - if (this.disabled) return; - // TODO use generationTimeMs - this.trackPageView(); - } - - private async trackPageView() { - this.reportViewDuration(); - - await sleep(0); // XXX: we sleep here because otherwise we get the old hash and not the new one - const viewData = await getViewData(this.anonymous); - - const page = viewData.name; - this.lastView = page; - this.lastViewTime = CountlyAnalytics.getTimestamp(); - const segments = { - ...viewData.meta, - name: page, - visit: 1, - domain: window.location.hostname, - view: viewData.url, - segment: this.appPlatform, - start: this.firstPage, - }; - - if (this.firstPage) { - this.firstPage = false; - } - - this.track("[CLY]_view", segments); - } - - public static getTimestamp() { - return Math.floor(new Date().getTime() / 1000); - } - - // store the last ms timestamp returned - // we do this to prevent the ts from ever decreasing in the case of system time changing - private lastMsTs = 0; - - private getMsTimestamp() { - const ts = new Date().getTime(); - if (this.lastMsTs >= ts) { - // increment ts as to keep our data points well-ordered - this.lastMsTs++; - } else { - this.lastMsTs = ts; - } - return this.lastMsTs; - } - - public async recordError(err: Error | string, fatal = false) { - if (this.disabled || this.anonymous) return; - - let error = ""; - if (typeof err === "object") { - if (typeof err.stack !== "undefined") { - error = err.stack; - } else { - if (typeof err.name !== "undefined") { - error += err.name + ":"; - } - if (typeof err.message !== "undefined") { - error += err.message + "\n"; - } - if (typeof err.fileName !== "undefined") { - error += "in " + err.fileName + "\n"; - } - if (typeof err.lineNumber !== "undefined") { - error += "on " + err.lineNumber; - } - if (typeof err.columnNumber !== "undefined") { - error += ":" + err.columnNumber; - } - } - } else { - error = err + ""; - } - - // sanitize the error from identifiers - error = await strReplaceAsync(error, /([!@+#]).+?:[\w:.]+/g, async (substring: string, glyph: string) => { - return glyph + (await hashHex(substring.substring(1))); - }); - - const metrics = this.getMetrics(); - const ob: ICrash = { - _resolution: metrics?._resolution, - _error: error, - _app_version: this.appVersion, - _run: CountlyAnalytics.getTimestamp() - this.initTime, - _nonfatal: !fatal, - _view: this.lastView, - }; - - if (typeof navigator.onLine !== "undefined") { - ob._online = navigator.onLine; - } - - ob._background = document.hasFocus(); - - this.request({ crash: JSON.stringify(ob) }); - } - - private trackErrors() { - //override global uncaught error handler - window.onerror = (msg, url, line, col, err) => { - if (typeof err !== "undefined") { - this.recordError(err, false); - } else { - let error = ""; - if (typeof msg !== "undefined") { - error += msg + "\n"; - } - if (typeof url !== "undefined") { - error += "at " + url; - } - if (typeof line !== "undefined") { - error += ":" + line; - } - if (typeof col !== "undefined") { - error += ":" + col; - } - error += "\n"; - - try { - const stack = []; - // eslint-disable-next-line no-caller - let f = arguments.callee.caller; - while (f) { - stack.push(f.name); - f = f.caller; - } - error += stack.join("\n"); - } catch (ex) { - //silent error - } - this.recordError(error, false); - } - }; - - window.addEventListener('unhandledrejection', (event) => { - this.recordError(new Error(`Unhandled rejection (reason: ${event.reason?.stack || event.reason}).`), true); - }); - } - - private heartbeat() { - const args: Pick = {}; - - // extend session if needed - if (this.sessionStarted && this.trackTime) { - const last = CountlyAnalytics.getTimestamp(); - if (last - this.lastBeat >= SESSION_UPDATE_INTERVAL) { - args.session_duration = last - this.lastBeat; - this.lastBeat = last; - } - } - - // process event queue - if (this.pendingEvents.length > 0 || args.session_duration) { - this.request(args); - } - } - - private async request( - args: Omit - & Partial> = {}, - ) { - const request: IParams = { - app_key: this.appKey, - device_id: this.userKey, - ...this.getTimeParams(), - ...args, - }; - - if (this.pendingEvents.length > 0) { - const EVENT_BATCH_SIZE = 10; - const events = this.pendingEvents.splice(0, EVENT_BATCH_SIZE); - request.events = JSON.stringify(events); - } - - const params = new URLSearchParams(request as {}); - - try { - await window.fetch(this.baseUrl.toString(), { - method: "POST", - mode: "no-cors", - cache: "no-cache", - redirect: "follow", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: params, - }); - } catch (e) { - logger.error("Analytics error: ", e); - } - } - - private getTimeParams(): Pick { - const date = new Date(); - return { - timestamp: this.getMsTimestamp(), - hour: date.getHours(), - dow: date.getDay(), - }; - } - - private queue(args: Omit & Partial>) { - const { count = 1, ...rest } = args; - const ev = { - ...this.getTimeParams(), - ...rest, - count, - platform: this.appPlatform, - app_version: this.appVersion, - }; - - this.pendingEvents.push(ev); - if (this.pendingEvents.length > MAX_PENDING_EVENTS) { - this.pendingEvents.shift(); - } - } - - private getOrientation = (): Orientation => { - return window.matchMedia("(orientation: landscape)").matches - ? Orientation.Landscape - : Orientation.Portrait; - }; - - private reportOrientation = () => { - this.track("[CLY]_orientation", { - mode: this.getOrientation(), - }); - }; - - private startTime() { - if (!this.trackTime) { - this.trackTime = true; - this.lastBeat = CountlyAnalytics.getTimestamp() - this.storedDuration; - this.lastViewTime = CountlyAnalytics.getTimestamp() - this.lastViewStoredDuration; - this.lastViewStoredDuration = 0; - } - } - - private stopTime() { - if (this.trackTime) { - this.trackTime = false; - this.storedDuration = CountlyAnalytics.getTimestamp() - this.lastBeat; - this.lastViewStoredDuration = CountlyAnalytics.getTimestamp() - this.lastViewTime; - } - } - - private getMetrics(): IMetrics { - if (this.anonymous) return undefined; - const metrics: IMetrics = {}; - - // getting app version - metrics._app_version = this.appVersion; - metrics._ua = navigator.userAgent; - - // getting resolution - if (screen.width && screen.height) { - metrics._resolution = `${screen.width}x${screen.height}`; - } - - // getting density ratio - if (window.devicePixelRatio) { - metrics._density = window.devicePixelRatio; - } - - // getting locale - metrics._locale = getCurrentLanguage(); - - return metrics; - } - - private async beginSession(heartbeat = true) { - if (!this.sessionStarted) { - this.reportOrientation(); - window.addEventListener("resize", this.reportOrientation); - - this.lastBeat = CountlyAnalytics.getTimestamp(); - this.sessionStarted = true; - this.heartbeatEnabled = heartbeat; - - const userDetails: IUserDetails = { - custom: { - "home_server": MatrixClientPeg.get() && MatrixClientPeg.getHomeserverName(), // TODO hash? - "anonymous": this.anonymous, - }, - }; - - const request: Parameters[0] = { - begin_session: 1, - user_details: JSON.stringify(userDetails), - }; - - const metrics = this.getMetrics(); - if (metrics) { - request.metrics = JSON.stringify(metrics); - } - - await this.request(request); - } - } - - private reportViewDuration() { - if (this.lastView) { - this.track("[CLY]_view", { - name: this.lastView, - }, null, { - dur: this.trackTime ? CountlyAnalytics.getTimestamp() - this.lastViewTime : this.lastViewStoredDuration, - }); - this.lastView = null; - } - } - - private endSession = () => { - if (this.sessionStarted) { - window.removeEventListener("resize", this.reportOrientation); - - this.reportViewDuration(); - this.request({ - end_session: 1, - session_duration: CountlyAnalytics.getTimestamp() - this.lastBeat, - }); - } - this.sessionStarted = false; - }; - - private onVisibilityChange = () => { - if (document.hidden) { - this.stopTime(); - } else { - this.startTime(); - } - }; - - private onUserActivity = () => { - if (this.inactivityCounter >= INACTIVITY_TIME) { - this.startTime(); - } - this.inactivityCounter = 0; - }; - - private trackSessions() { - this.beginSession(); - this.startTime(); - - window.addEventListener("beforeunload", this.endSession); - window.addEventListener("unload", this.endSession); - window.addEventListener("visibilitychange", this.onVisibilityChange); - window.addEventListener("mousemove", this.onUserActivity); - window.addEventListener("click", this.onUserActivity); - window.addEventListener("keydown", this.onUserActivity); - // Using the passive option to not block the main thread - // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners - window.addEventListener("scroll", this.onUserActivity, { passive: true }); - - this.activityIntervalId = setInterval(() => { - this.inactivityCounter++; - if (this.inactivityCounter >= INACTIVITY_TIME) { - this.stopTime(); - } - }, 60_000); - } - - public trackBeginInvite(roomId: string) { - this.track("begin_invite", {}, roomId); - } - - public trackSendInvite(startTime: number, roomId: string, qty: number) { - this.track("send_invite", {}, roomId, { - dur: CountlyAnalytics.getTimestamp() - startTime, - sum: qty, - }); - } - - public async trackRoomCreate(startTime: number, roomId: string) { - if (this.disabled) return; - - let endTime = CountlyAnalytics.getTimestamp(); - const cli = MatrixClientPeg.get(); - if (!cli.getRoom(roomId)) { - await new Promise(resolve => { - const handler = (room) => { - if (room.roomId === roomId) { - cli.off("Room", handler); - resolve(); - } - }; - cli.on("Room", handler); - }); - endTime = CountlyAnalytics.getTimestamp(); - } - - this.track("create_room", {}, roomId, { - dur: endTime - startTime, - }); - } - - public trackRoomJoin(startTime: number, roomId: string, type: IJoinRoomEvent["segmentation"]["type"]) { - this.track(Action.JoinRoom, { type }, roomId, { - dur: CountlyAnalytics.getTimestamp() - startTime, - }); - } - - public async trackSendMessage( - startTime: number, - // eslint-disable-next-line camelcase - sendPromise: Promise<{event_id: string}>, - roomId: string, - isEdit: boolean, - isReply: boolean, - content: IContent, - ) { - if (this.disabled) return; - const cli = MatrixClientPeg.get(); - const room = cli.getRoom(roomId); - - const eventId = (await sendPromise).event_id; - let endTime = CountlyAnalytics.getTimestamp(); - - if (!room.findEventById(eventId)) { - await new Promise(resolve => { - const handler = (ev) => { - if (ev.getId() === eventId) { - room.off("Room.localEchoUpdated", handler); - resolve(); - } - }; - - room.on("Room.localEchoUpdated", handler); - }); - endTime = CountlyAnalytics.getTimestamp(); - } - - this.track("send_message", { - is_edit: isEdit, - is_reply: isReply, - msgtype: content.msgtype, - format: content.format, - }, roomId, { - dur: endTime - startTime, - }); - } - - public trackStartCall(roomId: string, isVideo = false, isJitsi = false) { - this.track("start_call", { - is_video: isVideo, - is_jitsi: isJitsi, - }, roomId); - } - - public trackJoinCall(roomId: string, isVideo = false, isJitsi = false) { - this.track("join_call", { - is_video: isVideo, - is_jitsi: isJitsi, - }, roomId); - } - - public trackRoomDirectoryBegin() { - this.track("room_directory"); - } - - public trackRoomDirectory(startTime: number) { - this.track("room_directory_done", {}, null, { - dur: CountlyAnalytics.getTimestamp() - startTime, - }); - } - - public trackRoomDirectorySearch(numResults: number, query: string) { - this.track("room_directory_search", { - query_length: query.length, - query_num_words: query.split(" ").length, - }, null, { - sum: numResults, - }); - } - - public async track( - key: E["key"], - segments?: Omit, - roomId?: string, - args?: Partial>, - anonymous = false, - ) { - if (this.disabled && !anonymous) return; - - let segmentation = segments || {}; - - if (roomId) { - segmentation = { - room_id: await hashHex(roomId), - ...getRoomStats(roomId), - ...segments, - }; - } - - this.queue({ - key, - count: 1, - segmentation, - ...args, - }); - - // if this event can be sent anonymously and we are disabled then dispatch it right away - if (this.disabled && anonymous) { - await this.request({ device_id: randomString(64) }); - } - } -} - -// expose on window for easy access from the console -window.mxCountlyAnalytics = CountlyAnalytics; diff --git a/src/DecryptionFailureTracker.ts b/src/DecryptionFailureTracker.ts index 1ee985dfc7..2bb522e7fe 100644 --- a/src/DecryptionFailureTracker.ts +++ b/src/DecryptionFailureTracker.ts @@ -19,7 +19,6 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Error as ErrorEvent } from "matrix-analytics-events/types/typescript/Error"; import Analytics from "./Analytics"; -import CountlyAnalytics from "./CountlyAnalytics"; import { PosthogAnalytics } from './PosthogAnalytics'; export class DecryptionFailure { @@ -39,7 +38,6 @@ export type ErrCodeMapFn = (errcode: string) => ErrorCode; export class DecryptionFailureTracker { private static internalInstance = new DecryptionFailureTracker((total, errorCode) => { Analytics.trackEvent('E2E', 'Decryption failure', errorCode, String(total)); - CountlyAnalytics.instance.track("decryption_failure", { errorCode }, null, { sum: total }); for (let i = 0; i < total; i++) { PosthogAnalytics.instance.trackEvent({ eventName: "Error", diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 2f1c948839..b4f48ec511 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -48,7 +48,6 @@ import DeviceListener from "./DeviceListener"; import { Jitsi } from "./widgets/Jitsi"; import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY } from "./BasePlatform"; import ThreepidInviteStore from "./stores/ThreepidInviteStore"; -import CountlyAnalytics from "./CountlyAnalytics"; import { PosthogAnalytics } from "./PosthogAnalytics"; import CallHandler from './CallHandler'; import LifecycleCustomisations from "./customisations/Lifecycle"; @@ -708,10 +707,6 @@ let _isLoggingOut = false; */ export function logout(): void { if (!MatrixClientPeg.get()) return; - if (!CountlyAnalytics.instance.disabled) { - // user has logged out, fall back to anonymous - CountlyAnalytics.instance.enable(/* anonymous = */ true); - } PosthogAnalytics.instance.logout(); diff --git a/src/components/structures/HomePage.tsx b/src/components/structures/HomePage.tsx index 9069d790bf..52ac9cc1ea 100644 --- a/src/components/structures/HomePage.tsx +++ b/src/components/structures/HomePage.tsx @@ -32,24 +32,20 @@ import { useEventEmitter } from "../../hooks/useEventEmitter"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import MiniAvatarUploader, { AVATAR_SIZE } from "../views/elements/MiniAvatarUploader"; import Analytics from "../../Analytics"; -import CountlyAnalytics from "../../CountlyAnalytics"; import PosthogTrackers from "../../PosthogTrackers"; const onClickSendDm = () => { Analytics.trackEvent('home_page', 'button', 'dm'); - CountlyAnalytics.instance.track("home_page_button", { button: "dm" }); dis.dispatch({ action: 'view_create_chat' }); }; const onClickExplore = () => { Analytics.trackEvent('home_page', 'button', 'room_directory'); - CountlyAnalytics.instance.track("home_page_button", { button: "room_directory" }); dis.fire(Action.ViewRoomDirectory); }; const onClickNewRoom = (ev: ButtonEvent) => { Analytics.trackEvent('home_page', 'button', 'create_room'); - CountlyAnalytics.instance.track("home_page_button", { button: "create_room" }); PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev); dis.dispatch({ action: 'view_create_room' }); }; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d27de7a37a..d5658321ec 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -31,7 +31,6 @@ import 'what-input'; import PosthogTrackers from '../../PosthogTrackers'; import Analytics from "../../Analytics"; -import CountlyAnalytics from "../../CountlyAnalytics"; import { DecryptionFailureTracker } from "../../DecryptionFailureTracker"; import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg"; import PlatformPeg from "../../PlatformPeg"; @@ -346,8 +345,6 @@ export default class MatrixChat extends React.PureComponent { Analytics.enable(); } - CountlyAnalytics.instance.enable(/* anonymous = */ true); - initSentry(SdkConfig.get()["sentry"]); } @@ -407,7 +404,6 @@ export default class MatrixChat extends React.PureComponent { if (this.shouldTrackPageChange(prevState, this.state)) { const durationMs = this.stopPageChangeTimer(); Analytics.trackPageChange(durationMs); - CountlyAnalytics.instance.trackPageChange(durationMs); PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs); } if (this.focusComposer) { @@ -812,9 +808,6 @@ export default class MatrixChat extends React.PureComponent { if (Analytics.canEnable()) { Analytics.enable(); } - if (CountlyAnalytics.instance.canEnable()) { - CountlyAnalytics.instance.enable(/* anonymous = */ false); - } break; case Action.AnonymousAnalyticsReject: hideAnalyticsToast(); @@ -1299,12 +1292,8 @@ export default class MatrixChat extends React.PureComponent { if (PosthogAnalytics.instance.isEnabled() && SettingsStore.isLevelSupported(SettingLevel.ACCOUNT)) { this.initPosthogAnalyticsToast(); - } else if (Analytics.canEnable() || CountlyAnalytics.instance.canEnable()) { - if (SettingsStore.getValue("showCookieBar") && - (Analytics.canEnable() || CountlyAnalytics.instance.canEnable()) - ) { - showAnonymousAnalyticsOptInToast(); - } + } else if (Analytics.canEnable() && SettingsStore.getValue("showCookieBar")) { + showAnonymousAnalyticsOptInToast(); } if (SdkConfig.get().mobileGuideToast) { @@ -1723,12 +1712,8 @@ export default class MatrixChat extends React.PureComponent { action: 'require_registration', }); } else if (screen === 'directory') { - if (this.state.view === Views.WELCOME) { - CountlyAnalytics.instance.track("onboarding_room_directory"); - } dis.fire(Action.ViewRoomDirectory); } else if (screen === "start_sso" || screen === "start_cas") { - // TODO if logged in, skip SSO let cli = MatrixClientPeg.get(); if (!cli) { const { hsUrl, isUrl } = this.props.serverConfig; diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx index 662e8c7c45..0f8a98ca15 100644 --- a/src/components/structures/RoomDirectory.tsx +++ b/src/components/structures/RoomDirectory.tsx @@ -34,7 +34,6 @@ import SettingsStore from "../../settings/SettingsStore"; import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore"; import GroupStore from "../../stores/GroupStore"; import FlairStore from "../../stores/FlairStore"; -import CountlyAnalytics from "../../CountlyAnalytics"; import { replaceableComponent } from "../../utils/replaceableComponent"; import { mediaFromMxc } from "../../customisations/Media"; import { IDialogProps } from "../views/dialogs/IDialogProps"; @@ -88,9 +87,6 @@ export default class RoomDirectory extends React.Component { constructor(props) { super(props); - CountlyAnalytics.instance.trackRoomDirectoryBegin(); - this.startTime = CountlyAnalytics.getTimestamp(); - const selectedCommunityId = SettingsStore.getValue("feature_communities_v2_prototypes") ? GroupFilterOrderStore.getSelectedTags()[0] : null; @@ -262,11 +258,6 @@ export default class RoomDirectory extends React.Component { return false; } - if (this.state.filterString) { - const count = data.total_room_count_estimate || data.chunk.length; - CountlyAnalytics.instance.trackRoomDirectorySearch(count, this.state.filterString); - } - this.nextBatch = data.next_batch; this.setState((s) => ({ ...s, @@ -668,7 +659,6 @@ export default class RoomDirectory extends React.Component { } private onFinished = () => { - CountlyAnalytics.instance.trackRoomDirectory(this.startTime); this.props.onFinished(false); }; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 3213d28688..82bed241b4 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1286,7 +1286,6 @@ export class RoomView extends React.Component { action: Action.JoinRoom, roomId: this.getRoomId(), opts: { inviteSignUrl: signUrl }, - _type: "unknown", // TODO: instrumentation }); return Promise.resolve(); }); diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 79ae5e201f..14b201afc6 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -25,7 +25,6 @@ import Modal from "../../../Modal"; import PasswordReset from "../../../PasswordReset"; import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils"; import AuthPage from "../../views/auth/AuthPage"; -import CountlyAnalytics from "../../../CountlyAnalytics"; import ServerPicker from "../../views/elements/ServerPicker"; import EmailField from "../../views/auth/EmailField"; import PassphraseField from '../../views/auth/PassphraseField'; @@ -102,12 +101,6 @@ export default class ForgotPassword extends React.Component { serverDeadError: "", }; - constructor(props: IProps) { - super(props); - - CountlyAnalytics.instance.track("onboarding_forgot_password_begin"); - } - public componentDidMount() { this.reset = null; this.checkServerLiveliness(this.props.serverConfig); @@ -298,8 +291,6 @@ export default class ForgotPassword extends React.Component { fieldRef={field => this[ForgotPasswordField.Email] = field} autoFocus={true} onChange={this.onInputChanged.bind(this, "email")} - onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_focus")} - onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_blur")} />
@@ -311,8 +302,6 @@ export default class ForgotPassword extends React.Component { minScore={PASSWORD_MIN_SCORE} fieldRef={field => this[ForgotPasswordField.Password] = field} onChange={this.onInputChanged.bind(this, "password")} - onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword_focus")} - onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword_blur")} autoComplete="new-password" /> { password={this.state.password} fieldRef={field => this[ForgotPasswordField.PasswordConfirm] = field} onChange={this.onInputChanged.bind(this, "password2")} - onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword2_focus")} - onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword2_blur")} autoComplete="new-password" />
diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 3fc47d6693..cbf524c1bb 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -28,7 +28,6 @@ import AuthPage from "../../views/auth/AuthPage"; import PlatformPeg from '../../../PlatformPeg'; import SettingsStore from "../../../settings/SettingsStore"; import { UIFeature } from "../../../settings/UIFeature"; -import CountlyAnalytics from "../../../CountlyAnalytics"; import { IMatrixClientCreds } from "../../../MatrixClientPeg"; import PasswordLogin from "../../views/auth/PasswordLogin"; import InlineSpinner from "../../views/elements/InlineSpinner"; @@ -141,8 +140,6 @@ export default class LoginComponent extends React.PureComponent 'm.login.cas': () => this.renderSsoStep("cas"), 'm.login.sso': () => this.renderSsoStep("sso"), }; - - CountlyAnalytics.instance.track("onboarding_login_begin"); } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event diff --git a/src/components/views/auth/CaptchaForm.tsx b/src/components/views/auth/CaptchaForm.tsx index 5801d627bc..887f174c00 100644 --- a/src/components/views/auth/CaptchaForm.tsx +++ b/src/components/views/auth/CaptchaForm.tsx @@ -18,7 +18,6 @@ import React, { createRef } from 'react'; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from '../../../languageHandler'; -import CountlyAnalytics from "../../../CountlyAnalytics"; import { replaceableComponent } from "../../../utils/replaceableComponent"; const DIV_ID = 'mx_recaptcha'; @@ -51,8 +50,6 @@ export default class CaptchaForm extends React.Component { - CountlyAnalytics.instance.track("onboarding_grecaptcha_submit"); this.props.submitAuthDict({ type: AuthType.Recaptcha, response: response, @@ -322,8 +320,6 @@ export class TermsAuthEntry extends React.Component { const allFieldsValid = await this.verifyFieldsBeforeSubmit(); if (!allFieldsValid) { - CountlyAnalytics.instance.track("onboarding_registration_submit_failed"); return; } @@ -125,20 +123,7 @@ export default class PasswordLogin extends React.PureComponent { this.props.onUsernameChanged(ev.target.value); }; - private onUsernameFocus = () => { - if (this.state.loginType === LoginField.MatrixId) { - CountlyAnalytics.instance.track("onboarding_login_mxid_focus"); - } else { - CountlyAnalytics.instance.track("onboarding_login_email_focus"); - } - }; - private onUsernameBlur = ev => { - if (this.state.loginType === LoginField.MatrixId) { - CountlyAnalytics.instance.track("onboarding_login_mxid_blur"); - } else { - CountlyAnalytics.instance.track("onboarding_login_email_blur"); - } this.props.onUsernameBlur(ev.target.value); }; @@ -146,7 +131,6 @@ export default class PasswordLogin extends React.PureComponent { const loginType = ev.target.value; this.setState({ loginType }); this.props.onUsernameChanged(""); // Reset because email and username use the same state - CountlyAnalytics.instance.track("onboarding_login_type_changed", { loginType }); }; private onPhoneCountryChanged = country => { @@ -157,14 +141,6 @@ export default class PasswordLogin extends React.PureComponent { this.props.onPhoneNumberChanged(ev.target.value); }; - private onPhoneNumberFocus = () => { - CountlyAnalytics.instance.track("onboarding_login_phone_number_focus"); - }; - - private onPhoneNumberBlur = ev => { - CountlyAnalytics.instance.track("onboarding_login_phone_number_blur"); - }; - private onPasswordChanged = ev => { this.setState({ password: ev.target.value }); }; @@ -324,7 +300,6 @@ export default class PasswordLogin extends React.PureComponent { placeholder="joe@example.com" value={this.props.username} onChange={this.onUsernameChanged} - onFocus={this.onUsernameFocus} onBlur={this.onUsernameBlur} disabled={this.props.busy} autoFocus={autoFocus} @@ -344,7 +319,6 @@ export default class PasswordLogin extends React.PureComponent { placeholder={_t("Username").toLocaleLowerCase()} value={this.props.username} onChange={this.onUsernameChanged} - onFocus={this.onUsernameFocus} onBlur={this.onUsernameBlur} disabled={this.props.busy} autoFocus={autoFocus} @@ -372,8 +346,6 @@ export default class PasswordLogin extends React.PureComponent { value={this.props.phoneNumber} prefixComponent={phoneCountry} onChange={this.onPhoneNumberChanged} - onFocus={this.onPhoneNumberFocus} - onBlur={this.onPhoneNumberBlur} disabled={this.props.busy} autoFocus={autoFocus} onValidate={this.onPhoneNumberValidate} diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 3a0e6ef0ce..2665e1789f 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -29,7 +29,6 @@ import withValidation, { IValidationResult } from '../elements/Validation'; import { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils"; import EmailField from "./EmailField"; import PassphraseField from "./PassphraseField"; -import CountlyAnalytics from "../../../CountlyAnalytics"; import Field from '../elements/Field'; import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog'; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -113,8 +112,6 @@ export default class RegistrationForm extends React.PureComponent { @@ -125,13 +122,11 @@ export default class RegistrationForm extends React.PureComponent { if (confirmed) { @@ -156,10 +151,6 @@ export default class RegistrationForm extends React.PureComponent CountlyAnalytics.instance.track("onboarding_registration_email_focus")} - onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_email_blur")} />; } @@ -468,8 +457,6 @@ export default class RegistrationForm extends React.PureComponent CountlyAnalytics.instance.track("onboarding_registration_password_focus")} - onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_password_blur")} />; } @@ -482,8 +469,6 @@ export default class RegistrationForm extends React.PureComponent CountlyAnalytics.instance.track("onboarding_registration_passwordConfirm_focus")} - onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_passwordConfirm_blur")} />; } @@ -522,8 +507,6 @@ export default class RegistrationForm extends React.PureComponent CountlyAnalytics.instance.track("onboarding_registration_username_focus")} - onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_username_blur")} />; } diff --git a/src/components/views/auth/Welcome.tsx b/src/components/views/auth/Welcome.tsx index 0e12025fbd..5f1de17450 100644 --- a/src/components/views/auth/Welcome.tsx +++ b/src/components/views/auth/Welcome.tsx @@ -23,7 +23,6 @@ import AuthPage from "./AuthPage"; import { _td } from "../../../languageHandler"; import SettingsStore from "../../../settings/SettingsStore"; import { UIFeature } from "../../../settings/UIFeature"; -import CountlyAnalytics from "../../../CountlyAnalytics"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import LanguageSelector from "./LanguageSelector"; @@ -36,12 +35,6 @@ interface IProps { @replaceableComponent("views.auth.Welcome") export default class Welcome extends React.PureComponent { - constructor(props: IProps) { - super(props); - - CountlyAnalytics.instance.track("onboarding_welcome"); - } - public render(): React.ReactNode { // FIXME: Using an import will result in wrench-element-tests failures const EmbeddedPage = sdk.getComponent("structures.EmbeddedPage"); diff --git a/src/components/views/dialogs/FeedbackDialog.tsx b/src/components/views/dialogs/FeedbackDialog.tsx index a25b328fd3..f4bddf8e87 100644 --- a/src/components/views/dialogs/FeedbackDialog.tsx +++ b/src/components/views/dialogs/FeedbackDialog.tsx @@ -20,12 +20,10 @@ import QuestionDialog from './QuestionDialog'; import { _t } from '../../../languageHandler'; import Field from "../elements/Field"; import AccessibleButton from "../elements/AccessibleButton"; -import CountlyAnalytics, { Rating } from "../../../CountlyAnalytics"; import SdkConfig from "../../../SdkConfig"; import Modal from "../../../Modal"; import BugReportDialog from "./BugReportDialog"; import InfoDialog from "./InfoDialog"; -import StyledRadioGroup from "../elements/StyledRadioGroup"; import { IDialogProps } from "./IDialogProps"; import { submitFeedback } from "../../../rageshake/submit-rageshake"; import { useStateToggle } from "../../../hooks/useStateToggle"; @@ -39,7 +37,6 @@ interface IProps extends IDialogProps {} const FeedbackDialog: React.FC = (props: IProps) => { const feedbackRef = useRef(); - const [rating, setRating] = useState(); const [comment, setComment] = useState(""); const [canContact, toggleCanContact] = useStateToggle(false); @@ -53,16 +50,12 @@ const FeedbackDialog: React.FC = (props: IProps) => { Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {}); }; - const countlyEnabled = CountlyAnalytics.instance.canEnable(); const rageshakeUrl = SdkConfig.get().bug_report_endpoint_url; - - const hasFeedback = countlyEnabled || rageshakeUrl; + const hasFeedback = !!rageshakeUrl; const onFinished = (sendFeedback: boolean): void => { if (hasFeedback && sendFeedback) { if (rageshakeUrl) { submitFeedback(rageshakeUrl, "feedback", comment, canContact); - } else if (countlyEnabled) { - CountlyAnalytics.instance.reportFeedback(rating, comment); } Modal.createTrackedDialog('Feedback sent', '', InfoDialog, { @@ -73,8 +66,6 @@ const FeedbackDialog: React.FC = (props: IProps) => { props.onFinished(); }; - const brand = SdkConfig.get().brand; - let feedbackSection; if (rageshakeUrl) { feedbackSection =
@@ -102,40 +93,6 @@ const FeedbackDialog: React.FC = (props: IProps) => { { _t("You may contact me if you want to follow up or to let me test out upcoming ideas") }
; - } else if (countlyEnabled) { - feedbackSection =
-

{ _t("Rate %(brand)s", { brand }) }

- -

{ _t("Tell us below how you feel about %(brand)s so far.", { brand }) }

-

{ _t("Please go into as much detail as you like, so we can track down the problem.") }

- - setRating(parseInt(r, 10) as Rating)} - definitions={[ - { value: "1", label: "😠" }, - { value: "2", label: "😞" }, - { value: "3", label: "😑" }, - { value: "4", label: "😄" }, - { value: "5", label: "😍" }, - ]} - /> - - { - setComment(ev.target.value); - }} - ref={feedbackRef} - /> -
; } let bugReports = null; @@ -175,7 +132,7 @@ const FeedbackDialog: React.FC = (props: IProps) => { { feedbackSection } } button={hasFeedback ? _t("Send feedback") : _t("Go back")} - buttonDisabled={hasFeedback && !rating && !comment} + buttonDisabled={hasFeedback && !comment} onFinished={onFinished} />); }; diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index bf4e1e1b2a..3a79cb732c 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -52,7 +52,6 @@ import RoomListStore from "../../../stores/room-list/RoomListStore"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; import SettingsStore from "../../../settings/SettingsStore"; import { UIFeature } from "../../../settings/UIFeature"; -import CountlyAnalytics from "../../../CountlyAnalytics"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; import { getAddressType } from "../../../UserAddress"; @@ -420,8 +419,6 @@ export default class InviteDialog extends React.PureComponent alreadyInvited.add(m.userId)); // add banned users, so we don't try to invite them room.getMembersWithMembership('ban').forEach(m => alreadyInvited.add(m.userId)); - - CountlyAnalytics.instance.trackBeginInvite(props.roomId); } this.state = { @@ -745,7 +742,6 @@ export default class InviteDialog extends React.PureComponent { - const startTime = CountlyAnalytics.getTimestamp(); this.setState({ busy: true }); this.convertFilter(); const targets = this.convertFilter(); @@ -764,7 +760,6 @@ export default class InviteDialog extends React.PureComponent = ({ onFinished }) => { const target = ev.target as HTMLInputElement; setEmail(target.value); }} - onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_email2_focus")} - onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_email2_blur")} /> diff --git a/src/components/views/elements/MiniAvatarUploader.tsx b/src/components/views/elements/MiniAvatarUploader.tsx index 837433e013..a590427d23 100644 --- a/src/components/views/elements/MiniAvatarUploader.tsx +++ b/src/components/views/elements/MiniAvatarUploader.tsx @@ -23,7 +23,6 @@ import Spinner from "./Spinner"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { useTimeout } from "../../../hooks/useTimeout"; import Analytics from "../../../Analytics"; -import CountlyAnalytics from '../../../CountlyAnalytics'; import { TranslatedString } from '../../../languageHandler'; import RoomContext from "../../../contexts/RoomContext"; @@ -67,7 +66,6 @@ const MiniAvatarUploader: React.FC = ({ hasAvatar, hasAvatarLabel, noAva if (!ev.target.files?.length) return; setBusy(true); Analytics.trackEvent("mini_avatar", "upload"); - CountlyAnalytics.instance.track("mini_avatar_upload"); const file = ev.target.files[0]; const uri = await cli.uploadContent(file); await setAvatarUrl(uri); diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index 73361bafad..03f00cf8f3 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -34,7 +34,6 @@ import EditorStateTransfer from '../../../utils/EditorStateTransfer'; import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer"; import { CommandCategories } from '../../../SlashCommands'; import { Action } from "../../../dispatcher/actions"; -import CountlyAnalytics from "../../../CountlyAnalytics"; import { getKeyBindingsManager } from '../../../KeyBindingsManager'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import SendHistoryManager from '../../../SendHistoryManager'; @@ -306,8 +305,6 @@ class EditMessageComposer extends React.Component { const allFieldsValid = await this.verifyFieldsBeforeSubmit(); if (!allFieldsValid) { - CountlyAnalytics.instance.track("onboarding_registration_submit_failed"); return; } diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx index ef9902e551..f346969df1 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx @@ -31,7 +31,6 @@ import SecureBackupPanel from "../../SecureBackupPanel"; import SettingsStore from "../../../../../settings/SettingsStore"; import { UIFeature } from "../../../../../settings/UIFeature"; import E2eAdvancedPanel, { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel"; -import CountlyAnalytics from "../../../../../CountlyAnalytics"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; import { ActionPayload } from "../../../../../dispatcher/payloads"; import CryptographyPanel from "../../CryptographyPanel"; @@ -116,7 +115,6 @@ export default class SecurityUserSettingsTab extends React.Component { checked ? Analytics.enable() : Analytics.disable(); - CountlyAnalytics.instance.enable(/* anonymous = */ !checked); }; private onMyMembership = (room: Room, membership: string): void => { @@ -308,7 +306,7 @@ export default class SecurityUserSettingsTab extends React.Component { if (PosthogAnalytics.instance.isEnabled()) { showAnalyticsLearnMoreDialog({ diff --git a/src/createRoom.ts b/src/createRoom.ts index 06946fa42a..dab6dd75e0 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -38,7 +38,6 @@ import DMRoomMap from "./utils/DMRoomMap"; import { getAddressType } from "./UserAddress"; import { getE2EEWellKnown } from "./utils/WellKnownUtils"; import GroupStore from "./stores/GroupStore"; -import CountlyAnalytics from "./CountlyAnalytics"; import { isJoinedOrNearlyJoined } from "./utils/membership"; import { VIRTUAL_ROOM_EVENT_TYPE } from "./CallHandler"; import SpaceStore from "./stores/spaces/SpaceStore"; @@ -94,8 +93,6 @@ export default async function createRoom(opts: IOpts): Promise { if (opts.guestAccess === undefined) opts.guestAccess = true; if (opts.encryption === undefined) opts.encryption = false; - const startTime = CountlyAnalytics.getTimestamp(); - const client = MatrixClientPeg.get(); if (client.isGuest()) { dis.dispatch({ action: 'require_registration' }); @@ -270,7 +267,6 @@ export default async function createRoom(opts: IOpts): Promise { _trigger: "Created", }); } - CountlyAnalytics.instance.trackRoomCreate(startTime, roomId); return roomId; }, function(err) { // Raise the error if the caller requested that we do so. diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a985e7d5dc..74f10b8000 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2577,10 +2577,6 @@ "Your platform and username will be noted to help us use your feedback as much as we can.": "Your platform and username will be noted to help us use your feedback as much as we can.", "Feedback": "Feedback", "You may contact me if you want to follow up or to let me test out upcoming ideas": "You may contact me if you want to follow up or to let me test out upcoming ideas", - "Rate %(brand)s": "Rate %(brand)s", - "Tell us below how you feel about %(brand)s so far.": "Tell us below how you feel about %(brand)s so far.", - "Please go into as much detail as you like, so we can track down the problem.": "Please go into as much detail as you like, so we can track down the problem.", - "Add comment": "Add comment", "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.": "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.", "Report a bug": "Report a bug", "Please view existing bugs on Github first. No match? Start a new one.": "Please view existing bugs on Github first. No match? Start a new one.", diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index c6d0e952f6..c2dcb256d0 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -32,7 +32,6 @@ import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCach import { ActionPayload } from "../dispatcher/payloads"; import { Action } from "../dispatcher/actions"; import { retry } from "../utils/promise"; -import CountlyAnalytics, { IJoinRoomEvent } from "../CountlyAnalytics"; import { TimelineRenderingType } from "../contexts/RoomContext"; import { PosthogAnalytics } from "../PosthogAnalytics"; import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; @@ -299,7 +298,6 @@ class RoomViewStore extends Store { } private async joinRoom(payload: ActionPayload) { - const startTime = CountlyAnalytics.getTimestamp(); this.setState({ joining: true, }); @@ -318,20 +316,6 @@ class RoomViewStore extends Store { return err.httpStatus === 504; }); - let type: IJoinRoomEvent["segmentation"]["type"] = undefined; - switch ((payload as ViewRoomPayload)._trigger) { - case "SlashCommand": - type = "slash_command"; - break; - case "Tombstone": - type = "tombstone"; - break; - case "RoomDirectory": - type = "room_directory"; - break; - } - CountlyAnalytics.instance.trackRoomJoin(startTime, roomId, type); - // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the // room. diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 37aae0d9f2..a3e8c105d7 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -53,7 +53,6 @@ import { ElementWidgetActions, IViewRoomApiRequest } from "./ElementWidgetAction import { ModalWidgetStore } from "../ModalWidgetStore"; import ThemeWatcher from "../../settings/watchers/ThemeWatcher"; import { getCustomTheme } from "../../theme"; -import CountlyAnalytics from "../../CountlyAnalytics"; import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities"; import { ELEMENT_CLIENT_ID } from "../../identifiers"; import { getUserLanguage } from "../../languageHandler"; @@ -322,9 +321,6 @@ export class StopGapWidget extends EventEmitter { this.messaging.on(`action:${WidgetApiFromWidgetAction.UpdateAlwaysOnScreen}`, (ev: CustomEvent) => { if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) { - if (WidgetType.JITSI.matches(this.mockWidget.type)) { - CountlyAnalytics.instance.trackJoinCall(this.appTileProps.room.roomId, true, true); - } ActiveWidgetStore.instance.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value); ev.preventDefault(); this.messaging.transport.reply(ev.detail, {}); // ack