mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 03:05:51 +03:00
Remove Countly analytics integration (#7808)
This commit is contained in:
parent
bb5f62edce
commit
8d4e83084c
29 changed files with 6 additions and 1199 deletions
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -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<void> {
|
||||
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<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: roomId,
|
||||
|
@ -1010,7 +1007,6 @@ export default class CallHandler extends EventEmitter {
|
|||
private async placeJitsiCall(roomId: string, type: string): Promise<void> {
|
||||
logger.info("Place conference call in " + roomId);
|
||||
Analytics.trackEvent('voip', 'placeConferenceCall');
|
||||
CountlyAnalytics.instance.trackStartCall(roomId, type === CallType.Video, true);
|
||||
|
||||
dis.dispatch({
|
||||
action: 'appsDrawer',
|
||||
|
|
|
@ -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<ISendEventResponse> {
|
||||
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<any>,
|
||||
) {
|
||||
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;
|
||||
|
|
|
@ -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<string, unknown>;
|
||||
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<string, Value | Operation>; // `.` 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<string, string>;
|
||||
}
|
||||
|
||||
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<string, Value> {
|
||||
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<string> => {
|
||||
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<string, string>;
|
||||
}
|
||||
|
||||
// Apply fn to all hash path parts after the 1st one
|
||||
async function getViewData(anonymous = true): Promise<IViewData> {
|
||||
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 = `/<redacted_${rand}>/`; // XXX: inject rand because Count.ly doesn't like X->X transitions
|
||||
}
|
||||
|
||||
let [_, screen, ...parts] = hash.split("/");
|
||||
|
||||
if (!knownScreens.has(screen)) {
|
||||
screen = `<redacted_${rand}>`;
|
||||
}
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
parts[i] = anonymous ? `<redacted_${rand}>` : 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<string>) => {
|
||||
const promises: Promise<string>[] = [];
|
||||
// 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<IStarRatingEvent>("[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<IViewEvent>("[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<IParams, "session_duration"> = {};
|
||||
|
||||
// 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<IParams, "app_key" | "device_id" | "timestamp" | "hour" | "dow">
|
||||
& Partial<Pick<IParams, "device_id">> = {},
|
||||
) {
|
||||
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<IParams, "timestamp" | "hour" | "dow"> {
|
||||
const date = new Date();
|
||||
return {
|
||||
timestamp: this.getMsTimestamp(),
|
||||
hour: date.getHours(),
|
||||
dow: date.getDay(),
|
||||
};
|
||||
}
|
||||
|
||||
private queue(args: Omit<IEvent, "timestamp" | "hour" | "dow" | "count"> & Partial<Pick<IEvent, "count">>) {
|
||||
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<IOrientationEvent>("[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<typeof CountlyAnalytics.prototype.request>[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<IViewEvent>("[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<IBeginInviteEvent>("begin_invite", {}, roomId);
|
||||
}
|
||||
|
||||
public trackSendInvite(startTime: number, roomId: string, qty: number) {
|
||||
this.track<ISendInviteEvent>("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<void>(resolve => {
|
||||
const handler = (room) => {
|
||||
if (room.roomId === roomId) {
|
||||
cli.off("Room", handler);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
cli.on("Room", handler);
|
||||
});
|
||||
endTime = CountlyAnalytics.getTimestamp();
|
||||
}
|
||||
|
||||
this.track<ICreateRoomEvent>("create_room", {}, roomId, {
|
||||
dur: endTime - startTime,
|
||||
});
|
||||
}
|
||||
|
||||
public trackRoomJoin(startTime: number, roomId: string, type: IJoinRoomEvent["segmentation"]["type"]) {
|
||||
this.track<IJoinRoomEvent>(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<void>(resolve => {
|
||||
const handler = (ev) => {
|
||||
if (ev.getId() === eventId) {
|
||||
room.off("Room.localEchoUpdated", handler);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
room.on("Room.localEchoUpdated", handler);
|
||||
});
|
||||
endTime = CountlyAnalytics.getTimestamp();
|
||||
}
|
||||
|
||||
this.track<ISendMessageEvent>("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<IStartCallEvent>("start_call", {
|
||||
is_video: isVideo,
|
||||
is_jitsi: isJitsi,
|
||||
}, roomId);
|
||||
}
|
||||
|
||||
public trackJoinCall(roomId: string, isVideo = false, isJitsi = false) {
|
||||
this.track<IJoinCallEvent>("join_call", {
|
||||
is_video: isVideo,
|
||||
is_jitsi: isJitsi,
|
||||
}, roomId);
|
||||
}
|
||||
|
||||
public trackRoomDirectoryBegin() {
|
||||
this.track<IRoomDirectoryEvent>("room_directory");
|
||||
}
|
||||
|
||||
public trackRoomDirectory(startTime: number) {
|
||||
this.track<IRoomDirectoryDoneEvent>("room_directory_done", {}, null, {
|
||||
dur: CountlyAnalytics.getTimestamp() - startTime,
|
||||
});
|
||||
}
|
||||
|
||||
public trackRoomDirectorySearch(numResults: number, query: string) {
|
||||
this.track<IRoomDirectorySearchEvent>("room_directory_search", {
|
||||
query_length: query.length,
|
||||
query_num_words: query.split(" ").length,
|
||||
}, null, {
|
||||
sum: numResults,
|
||||
});
|
||||
}
|
||||
|
||||
public async track<E extends IEvent>(
|
||||
key: E["key"],
|
||||
segments?: Omit<E["segmentation"], "room_id" | "num_users" | "is_encrypted" | "is_public">,
|
||||
roomId?: string,
|
||||
args?: Partial<Pick<E, "dur" | "sum" | "timestamp">>,
|
||||
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;
|
|
@ -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<ErrorEvent>({
|
||||
eventName: "Error",
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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' });
|
||||
};
|
||||
|
|
|
@ -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<IProps, IState> {
|
|||
Analytics.enable();
|
||||
}
|
||||
|
||||
CountlyAnalytics.instance.enable(/* anonymous = */ true);
|
||||
|
||||
initSentry(SdkConfig.get()["sentry"]);
|
||||
}
|
||||
|
||||
|
@ -407,7 +404,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
if (Analytics.canEnable()) {
|
||||
Analytics.enable();
|
||||
}
|
||||
if (CountlyAnalytics.instance.canEnable()) {
|
||||
CountlyAnalytics.instance.enable(/* anonymous = */ false);
|
||||
}
|
||||
break;
|
||||
case Action.AnonymousAnalyticsReject:
|
||||
hideAnalyticsToast();
|
||||
|
@ -1299,13 +1292,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
|
||||
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())
|
||||
) {
|
||||
} else if (Analytics.canEnable() && SettingsStore.getValue("showCookieBar")) {
|
||||
showAnonymousAnalyticsOptInToast();
|
||||
}
|
||||
}
|
||||
|
||||
if (SdkConfig.get().mobileGuideToast) {
|
||||
// The toast contains further logic to detect mobile platforms,
|
||||
|
@ -1723,12 +1712,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
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;
|
||||
|
|
|
@ -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<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
}
|
||||
|
||||
private onFinished = () => {
|
||||
CountlyAnalytics.instance.trackRoomDirectory(this.startTime);
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
|
|
|
@ -1286,7 +1286,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
action: Action.JoinRoom,
|
||||
roomId: this.getRoomId(),
|
||||
opts: { inviteSignUrl: signUrl },
|
||||
_type: "unknown", // TODO: instrumentation
|
||||
});
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
|
|
@ -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<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
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")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
|
@ -311,8 +302,6 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
|||
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"
|
||||
/>
|
||||
<PassphraseConfirmField
|
||||
|
@ -324,8 +313,6 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
|||
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"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -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<IProps, IState>
|
|||
'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
|
||||
|
|
|
@ -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<ICaptchaFormProps, ICap
|
|||
this.state = {
|
||||
errorText: undefined,
|
||||
};
|
||||
|
||||
CountlyAnalytics.instance.track("onboarding_grecaptcha_begin");
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -118,12 +115,10 @@ export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICap
|
|||
this.setState({
|
||||
errorText: null,
|
||||
});
|
||||
CountlyAnalytics.instance.track("onboarding_grecaptcha_loaded");
|
||||
} catch (e) {
|
||||
this.setState({
|
||||
errorText: e.toString(),
|
||||
});
|
||||
CountlyAnalytics.instance.track("onboarding_grecaptcha_error", { error: e.toString() });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import { _t } from '../../../languageHandler';
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { LocalisedPolicy, Policies } from '../../../Terms';
|
||||
import Field from '../elements/Field';
|
||||
|
@ -201,7 +200,6 @@ export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps
|
|||
}
|
||||
|
||||
private onCaptchaResponse = (response: string) => {
|
||||
CountlyAnalytics.instance.track("onboarding_grecaptcha_submit");
|
||||
this.props.submitAuthDict({
|
||||
type: AuthType.Recaptcha,
|
||||
response: response,
|
||||
|
@ -322,8 +320,6 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
|||
toggledPolicies: initToggles,
|
||||
policies: pickedPolicies,
|
||||
};
|
||||
|
||||
CountlyAnalytics.instance.track("onboarding_terms_begin");
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -354,7 +350,6 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
|||
|
||||
if (allChecked) {
|
||||
this.props.submitAuthDict({ type: AuthType.Terms });
|
||||
CountlyAnalytics.instance.track("onboarding_terms_complete");
|
||||
} else {
|
||||
this.setState({ errorText: _t("Please review and accept all of the homeserver's policies") });
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import { _t } from '../../../languageHandler';
|
|||
import SdkConfig from '../../../SdkConfig';
|
||||
import { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import withValidation, { IValidationResult } from "../elements/Validation";
|
||||
import Field from "../elements/Field";
|
||||
import CountryDropdown from "./CountryDropdown";
|
||||
|
@ -99,7 +98,6 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
|||
|
||||
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<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
value={this.props.phoneNumber}
|
||||
prefixComponent={phoneCountry}
|
||||
onChange={this.onPhoneNumberChanged}
|
||||
onFocus={this.onPhoneNumberFocus}
|
||||
onBlur={this.onPhoneNumberBlur}
|
||||
disabled={this.props.busy}
|
||||
autoFocus={autoFocus}
|
||||
onValidate={this.onPhoneNumberValidate}
|
||||
|
|
|
@ -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<IProps, IState
|
|||
passwordConfirm: this.props.defaultPassword || "",
|
||||
passwordComplexity: null,
|
||||
};
|
||||
|
||||
CountlyAnalytics.instance.track("onboarding_registration_begin");
|
||||
}
|
||||
|
||||
private onSubmit = async ev => {
|
||||
|
@ -125,13 +122,11 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
|||
|
||||
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
|
||||
if (!allFieldsValid) {
|
||||
CountlyAnalytics.instance.track("onboarding_registration_submit_failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.email === '') {
|
||||
if (this.showEmail()) {
|
||||
CountlyAnalytics.instance.track("onboarding_registration_submit_warn");
|
||||
Modal.createTrackedDialog("Email prompt dialog", '', RegistrationEmailPromptDialog, {
|
||||
onFinished: async (confirmed: boolean, email?: string) => {
|
||||
if (confirmed) {
|
||||
|
@ -156,10 +151,6 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
|||
private doSubmit(ev) {
|
||||
const email = this.state.email.trim();
|
||||
|
||||
CountlyAnalytics.instance.track("onboarding_registration_submit_ok", {
|
||||
email: !!email,
|
||||
});
|
||||
|
||||
const promise = this.props.onRegisterClick({
|
||||
username: this.state.username.trim(),
|
||||
password: this.state.password.trim(),
|
||||
|
@ -455,8 +446,6 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
|||
validationRules={this.validateEmailRules.bind(this)}
|
||||
onChange={this.onEmailChange}
|
||||
onValidate={this.onEmailValidate}
|
||||
onFocus={() => 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<IProps, IState
|
|||
value={this.state.password}
|
||||
onChange={this.onPasswordChange}
|
||||
onValidate={this.onPasswordValidate}
|
||||
onFocus={() => 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<IProps, IState
|
|||
password={this.state.password}
|
||||
onChange={this.onPasswordConfirmChange}
|
||||
onValidate={this.onPasswordConfirmValidate}
|
||||
onFocus={() => 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<IProps, IState
|
|||
value={this.state.username}
|
||||
onChange={this.onUsernameChange}
|
||||
onValidate={this.onUsernameValidate}
|
||||
onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_username_focus")}
|
||||
onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_username_blur")}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<IProps> {
|
||||
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");
|
||||
|
|
|
@ -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<IProps> = (props: IProps) => {
|
||||
const feedbackRef = useRef<Field>();
|
||||
const [rating, setRating] = useState<Rating>();
|
||||
const [comment, setComment] = useState<string>("");
|
||||
const [canContact, toggleCanContact] = useStateToggle(false);
|
||||
|
||||
|
@ -53,16 +50,12 @@ const FeedbackDialog: React.FC<IProps> = (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<IProps> = (props: IProps) => {
|
|||
props.onFinished();
|
||||
};
|
||||
|
||||
const brand = SdkConfig.get().brand;
|
||||
|
||||
let feedbackSection;
|
||||
if (rageshakeUrl) {
|
||||
feedbackSection = <div className="mx_FeedbackDialog_section mx_FeedbackDialog_rateApp">
|
||||
|
@ -102,40 +93,6 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
|||
{ _t("You may contact me if you want to follow up or to let me test out upcoming ideas") }
|
||||
</StyledCheckbox>
|
||||
</div>;
|
||||
} else if (countlyEnabled) {
|
||||
feedbackSection = <div className="mx_FeedbackDialog_section mx_FeedbackDialog_rateApp">
|
||||
<h3>{ _t("Rate %(brand)s", { brand }) }</h3>
|
||||
|
||||
<p>{ _t("Tell us below how you feel about %(brand)s so far.", { brand }) }</p>
|
||||
<p>{ _t("Please go into as much detail as you like, so we can track down the problem.") }</p>
|
||||
|
||||
<StyledRadioGroup
|
||||
name="feedbackRating"
|
||||
value={String(rating)}
|
||||
onChange={(r) => setRating(parseInt(r, 10) as Rating)}
|
||||
definitions={[
|
||||
{ value: "1", label: "😠" },
|
||||
{ value: "2", label: "😞" },
|
||||
{ value: "3", label: "😑" },
|
||||
{ value: "4", label: "😄" },
|
||||
{ value: "5", label: "😍" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<Field
|
||||
id="feedbackComment"
|
||||
label={_t("Add comment")}
|
||||
placeholder={_t("Comment")}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
value={comment}
|
||||
element="textarea"
|
||||
onChange={(ev) => {
|
||||
setComment(ev.target.value);
|
||||
}}
|
||||
ref={feedbackRef}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
let bugReports = null;
|
||||
|
@ -175,7 +132,7 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
|||
{ feedbackSection }
|
||||
</React.Fragment>}
|
||||
button={hasFeedback ? _t("Send feedback") : _t("Go back")}
|
||||
buttonDisabled={hasFeedback && !rating && !comment}
|
||||
buttonDisabled={hasFeedback && !comment}
|
||||
onFinished={onFinished}
|
||||
/>);
|
||||
};
|
||||
|
|
|
@ -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<IInviteDialogProps
|
|||
room.getMembersWithMembership('join').forEach(m => 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<IInviteDialogProps
|
|||
};
|
||||
|
||||
private inviteUsers = async () => {
|
||||
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<IInviteDialogProps
|
|||
|
||||
try {
|
||||
const result = await inviteMultipleToRoom(this.props.roomId, targetIds, true);
|
||||
CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length);
|
||||
if (!this.shouldAbortAfterInviteError(result, room)) { // handles setting error message too
|
||||
this.props.onFinished(true);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import { useRef, useState } from "react";
|
|||
import { _t, _td } from '../../../languageHandler';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import Field from "../elements/Field";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import EmailField from "../auth/EmailField";
|
||||
|
@ -70,8 +69,6 @@ const RegistrationEmailPromptDialog: React.FC<IProps> = ({ 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")}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -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<IProps> = ({ 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);
|
||||
|
|
|
@ -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<IEditMessageComposerProps, ISt
|
|||
isReply: !!editedEvent.replyEventId,
|
||||
});
|
||||
|
||||
const startTime = CountlyAnalytics.getTimestamp();
|
||||
|
||||
// Replace emoticon at the end of the message
|
||||
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
|
||||
const caret = this.editorRef.current?.getCaret();
|
||||
|
@ -354,10 +351,9 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
|||
const event = this.props.editState.getEvent();
|
||||
const threadId = event.threadRootId || null;
|
||||
|
||||
const prom = this.props.mxClient.sendMessage(roomId, threadId, editContent);
|
||||
this.props.mxClient.sendMessage(roomId, threadId, editContent);
|
||||
this.clearStoredEditorState();
|
||||
dis.dispatch({ action: "message_sent" });
|
||||
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, true, false, editContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ import { withMatrixClientHOC, MatrixClientProps } from "../../../contexts/Matrix
|
|||
import { Action } from "../../../dispatcher/actions";
|
||||
import { containsEmoji } from "../../../effects/utils";
|
||||
import { CHAT_EFFECTS } from '../../../effects';
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { getKeyBindingsManager } from '../../../KeyBindingsManager';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
@ -404,7 +403,6 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
|||
}
|
||||
|
||||
if (shouldSend) {
|
||||
const startTime = CountlyAnalytics.getTimestamp();
|
||||
const { roomId } = this.props.room;
|
||||
if (!content) {
|
||||
content = createMessageContent(
|
||||
|
@ -452,7 +450,6 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
|||
sendRoundTripMetric(this.props.mxClient, roomId, resp.event_id);
|
||||
});
|
||||
}
|
||||
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content);
|
||||
}
|
||||
|
||||
this.sendHistoryManager.save(model, replyToEvent);
|
||||
|
|
|
@ -26,7 +26,6 @@ import withValidation, { IFieldState, IValidationResult } from '../elements/Vali
|
|||
import { _t, _td } from '../../../languageHandler';
|
||||
import Modal from "../../../Modal";
|
||||
import PassphraseField from "../auth/PassphraseField";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { PASSWORD_MIN_SCORE } from '../auth/RegistrationForm';
|
||||
import SetEmailDialog from "../dialogs/SetEmailDialog";
|
||||
|
@ -270,7 +269,6 @@ export default class ChangePassword extends React.Component<IProps, IState> {
|
|||
|
||||
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
|
||||
if (!allFieldsValid) {
|
||||
CountlyAnalytics.instance.track("onboarding_registration_submit_failed");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<IProps, ISt
|
|||
|
||||
private updateAnalytics = (checked: boolean): void => {
|
||||
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<IProps, ISt
|
|||
}
|
||||
|
||||
let privacySection;
|
||||
if (Analytics.canEnable() || CountlyAnalytics.instance.canEnable() || PosthogAnalytics.instance.isEnabled()) {
|
||||
if (Analytics.canEnable() || PosthogAnalytics.instance.isEnabled()) {
|
||||
const onClickAnalyticsLearnMore = () => {
|
||||
if (PosthogAnalytics.instance.isEnabled()) {
|
||||
showAnalyticsLearnMoreDialog({
|
||||
|
|
|
@ -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<string | null> {
|
|||
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<string | null> {
|
|||
_trigger: "Created",
|
||||
});
|
||||
}
|
||||
CountlyAnalytics.instance.trackRoomCreate(startTime, roomId);
|
||||
return roomId;
|
||||
}, function(err) {
|
||||
// Raise the error if the caller requested that we do so.
|
||||
|
|
|
@ -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 <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.",
|
||||
"Report a bug": "Report a bug",
|
||||
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.",
|
||||
|
|
|
@ -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<ActionPayload> {
|
|||
}
|
||||
|
||||
private async joinRoom(payload: ActionPayload) {
|
||||
const startTime = CountlyAnalytics.getTimestamp();
|
||||
this.setState({
|
||||
joining: true,
|
||||
});
|
||||
|
@ -318,20 +316,6 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
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.
|
||||
|
|
|
@ -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<IStickyActionRequest>) => {
|
||||
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, <IWidgetApiRequestEmptyData>{}); // ack
|
||||
|
|
Loading…
Reference in a new issue