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 WidgetStore from "../stores/WidgetStore";
|
||||||
import CallHandler from "../CallHandler";
|
import CallHandler from "../CallHandler";
|
||||||
import { Analytics } from "../Analytics";
|
import { Analytics } from "../Analytics";
|
||||||
import CountlyAnalytics from "../CountlyAnalytics";
|
|
||||||
import UserActivity from "../UserActivity";
|
import UserActivity from "../UserActivity";
|
||||||
import { ModalWidgetStore } from "../stores/ModalWidgetStore";
|
import { ModalWidgetStore } from "../stores/ModalWidgetStore";
|
||||||
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
||||||
|
@ -93,7 +92,6 @@ declare global {
|
||||||
mxWidgetLayoutStore: WidgetLayoutStore;
|
mxWidgetLayoutStore: WidgetLayoutStore;
|
||||||
mxCallHandler: CallHandler;
|
mxCallHandler: CallHandler;
|
||||||
mxAnalytics: Analytics;
|
mxAnalytics: Analytics;
|
||||||
mxCountlyAnalytics: typeof CountlyAnalytics;
|
|
||||||
mxUserActivity: UserActivity;
|
mxUserActivity: UserActivity;
|
||||||
mxModalWidgetStore: ModalWidgetStore;
|
mxModalWidgetStore: ModalWidgetStore;
|
||||||
mxVoipUserMapper: VoipUserMapper;
|
mxVoipUserMapper: VoipUserMapper;
|
||||||
|
|
|
@ -44,7 +44,6 @@ import WidgetStore from "./stores/WidgetStore";
|
||||||
import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
|
import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
|
||||||
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
|
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import CountlyAnalytics from "./CountlyAnalytics";
|
|
||||||
import { UIFeature } from "./settings/UIFeature";
|
import { UIFeature } from "./settings/UIFeature";
|
||||||
import { Action } from './dispatcher/actions';
|
import { Action } from './dispatcher/actions';
|
||||||
import VoipUserMapper from './VoipUserMapper';
|
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> {
|
private async placeMatrixCall(roomId: string, type: CallType, transferee?: MatrixCall): Promise<void> {
|
||||||
Analytics.trackEvent('voip', 'placeCall', 'type', type);
|
Analytics.trackEvent('voip', 'placeCall', 'type', type);
|
||||||
CountlyAnalytics.instance.trackStartCall(roomId, type === CallType.Video, false);
|
|
||||||
|
|
||||||
const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId;
|
const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId;
|
||||||
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
|
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
|
||||||
|
@ -890,7 +888,6 @@ export default class CallHandler extends EventEmitter {
|
||||||
|
|
||||||
call.answer();
|
call.answer();
|
||||||
this.setActiveCallRoomId(roomId);
|
this.setActiveCallRoomId(roomId);
|
||||||
CountlyAnalytics.instance.trackJoinCall(roomId, call.type === CallType.Video, false);
|
|
||||||
dis.dispatch<ViewRoomPayload>({
|
dis.dispatch<ViewRoomPayload>({
|
||||||
action: Action.ViewRoom,
|
action: Action.ViewRoom,
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
|
@ -1010,7 +1007,6 @@ export default class CallHandler extends EventEmitter {
|
||||||
private async placeJitsiCall(roomId: string, type: string): Promise<void> {
|
private async placeJitsiCall(roomId: string, type: string): Promise<void> {
|
||||||
logger.info("Place conference call in " + roomId);
|
logger.info("Place conference call in " + roomId);
|
||||||
Analytics.trackEvent('voip', 'placeConferenceCall');
|
Analytics.trackEvent('voip', 'placeConferenceCall');
|
||||||
CountlyAnalytics.instance.trackStartCall(roomId, type === CallType.Video, true);
|
|
||||||
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'appsDrawer',
|
action: 'appsDrawer',
|
||||||
|
|
|
@ -34,7 +34,6 @@ import Modal from './Modal';
|
||||||
import RoomViewStore from './stores/RoomViewStore';
|
import RoomViewStore from './stores/RoomViewStore';
|
||||||
import Spinner from "./components/views/elements/Spinner";
|
import Spinner from "./components/views/elements/Spinner";
|
||||||
import { Action } from "./dispatcher/actions";
|
import { Action } from "./dispatcher/actions";
|
||||||
import CountlyAnalytics from "./CountlyAnalytics";
|
|
||||||
import {
|
import {
|
||||||
UploadCanceledPayload,
|
UploadCanceledPayload,
|
||||||
UploadErrorPayload,
|
UploadErrorPayload,
|
||||||
|
@ -430,12 +429,10 @@ export default class ContentMessages {
|
||||||
text: string,
|
text: string,
|
||||||
matrixClient: MatrixClient,
|
matrixClient: MatrixClient,
|
||||||
): Promise<ISendEventResponse> {
|
): Promise<ISendEventResponse> {
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
|
||||||
const prom = matrixClient.sendStickerMessage(roomId, threadId, url, info, text).catch((e) => {
|
const prom = matrixClient.sendStickerMessage(roomId, threadId, url, info, text).catch((e) => {
|
||||||
logger.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
|
logger.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, false, { msgtype: "m.sticker" });
|
|
||||||
return prom;
|
return prom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,7 +570,6 @@ export default class ContentMessages {
|
||||||
matrixClient: MatrixClient,
|
matrixClient: MatrixClient,
|
||||||
promBefore: Promise<any>,
|
promBefore: Promise<any>,
|
||||||
) {
|
) {
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
|
||||||
const content: IContent = {
|
const content: IContent = {
|
||||||
body: file.name || 'Attachment',
|
body: file.name || 'Attachment',
|
||||||
info: {
|
info: {
|
||||||
|
@ -671,7 +667,6 @@ export default class ContentMessages {
|
||||||
sendRoundTripMetric(matrixClient, roomId, resp.event_id);
|
sendRoundTripMetric(matrixClient, roomId, resp.event_id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, false, content);
|
|
||||||
return prom;
|
return prom;
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
error = 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 { Error as ErrorEvent } from "matrix-analytics-events/types/typescript/Error";
|
||||||
|
|
||||||
import Analytics from "./Analytics";
|
import Analytics from "./Analytics";
|
||||||
import CountlyAnalytics from "./CountlyAnalytics";
|
|
||||||
import { PosthogAnalytics } from './PosthogAnalytics';
|
import { PosthogAnalytics } from './PosthogAnalytics';
|
||||||
|
|
||||||
export class DecryptionFailure {
|
export class DecryptionFailure {
|
||||||
|
@ -39,7 +38,6 @@ export type ErrCodeMapFn = (errcode: string) => ErrorCode;
|
||||||
export class DecryptionFailureTracker {
|
export class DecryptionFailureTracker {
|
||||||
private static internalInstance = new DecryptionFailureTracker((total, errorCode) => {
|
private static internalInstance = new DecryptionFailureTracker((total, errorCode) => {
|
||||||
Analytics.trackEvent('E2E', 'Decryption failure', errorCode, String(total));
|
Analytics.trackEvent('E2E', 'Decryption failure', errorCode, String(total));
|
||||||
CountlyAnalytics.instance.track("decryption_failure", { errorCode }, null, { sum: total });
|
|
||||||
for (let i = 0; i < total; i++) {
|
for (let i = 0; i < total; i++) {
|
||||||
PosthogAnalytics.instance.trackEvent<ErrorEvent>({
|
PosthogAnalytics.instance.trackEvent<ErrorEvent>({
|
||||||
eventName: "Error",
|
eventName: "Error",
|
||||||
|
|
|
@ -48,7 +48,6 @@ import DeviceListener from "./DeviceListener";
|
||||||
import { Jitsi } from "./widgets/Jitsi";
|
import { Jitsi } from "./widgets/Jitsi";
|
||||||
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY } from "./BasePlatform";
|
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY } from "./BasePlatform";
|
||||||
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
||||||
import CountlyAnalytics from "./CountlyAnalytics";
|
|
||||||
import { PosthogAnalytics } from "./PosthogAnalytics";
|
import { PosthogAnalytics } from "./PosthogAnalytics";
|
||||||
import CallHandler from './CallHandler';
|
import CallHandler from './CallHandler';
|
||||||
import LifecycleCustomisations from "./customisations/Lifecycle";
|
import LifecycleCustomisations from "./customisations/Lifecycle";
|
||||||
|
@ -708,10 +707,6 @@ let _isLoggingOut = false;
|
||||||
*/
|
*/
|
||||||
export function logout(): void {
|
export function logout(): void {
|
||||||
if (!MatrixClientPeg.get()) return;
|
if (!MatrixClientPeg.get()) return;
|
||||||
if (!CountlyAnalytics.instance.disabled) {
|
|
||||||
// user has logged out, fall back to anonymous
|
|
||||||
CountlyAnalytics.instance.enable(/* anonymous = */ true);
|
|
||||||
}
|
|
||||||
|
|
||||||
PosthogAnalytics.instance.logout();
|
PosthogAnalytics.instance.logout();
|
||||||
|
|
||||||
|
|
|
@ -32,24 +32,20 @@ import { useEventEmitter } from "../../hooks/useEventEmitter";
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import MiniAvatarUploader, { AVATAR_SIZE } from "../views/elements/MiniAvatarUploader";
|
import MiniAvatarUploader, { AVATAR_SIZE } from "../views/elements/MiniAvatarUploader";
|
||||||
import Analytics from "../../Analytics";
|
import Analytics from "../../Analytics";
|
||||||
import CountlyAnalytics from "../../CountlyAnalytics";
|
|
||||||
import PosthogTrackers from "../../PosthogTrackers";
|
import PosthogTrackers from "../../PosthogTrackers";
|
||||||
|
|
||||||
const onClickSendDm = () => {
|
const onClickSendDm = () => {
|
||||||
Analytics.trackEvent('home_page', 'button', 'dm');
|
Analytics.trackEvent('home_page', 'button', 'dm');
|
||||||
CountlyAnalytics.instance.track("home_page_button", { button: "dm" });
|
|
||||||
dis.dispatch({ action: 'view_create_chat' });
|
dis.dispatch({ action: 'view_create_chat' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickExplore = () => {
|
const onClickExplore = () => {
|
||||||
Analytics.trackEvent('home_page', 'button', 'room_directory');
|
Analytics.trackEvent('home_page', 'button', 'room_directory');
|
||||||
CountlyAnalytics.instance.track("home_page_button", { button: "room_directory" });
|
|
||||||
dis.fire(Action.ViewRoomDirectory);
|
dis.fire(Action.ViewRoomDirectory);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickNewRoom = (ev: ButtonEvent) => {
|
const onClickNewRoom = (ev: ButtonEvent) => {
|
||||||
Analytics.trackEvent('home_page', 'button', 'create_room');
|
Analytics.trackEvent('home_page', 'button', 'create_room');
|
||||||
CountlyAnalytics.instance.track("home_page_button", { button: "create_room" });
|
|
||||||
PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev);
|
PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev);
|
||||||
dis.dispatch({ action: 'view_create_room' });
|
dis.dispatch({ action: 'view_create_room' });
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,7 +31,6 @@ import 'what-input';
|
||||||
|
|
||||||
import PosthogTrackers from '../../PosthogTrackers';
|
import PosthogTrackers from '../../PosthogTrackers';
|
||||||
import Analytics from "../../Analytics";
|
import Analytics from "../../Analytics";
|
||||||
import CountlyAnalytics from "../../CountlyAnalytics";
|
|
||||||
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
|
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
|
||||||
import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
|
import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
|
@ -346,8 +345,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
Analytics.enable();
|
Analytics.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
CountlyAnalytics.instance.enable(/* anonymous = */ true);
|
|
||||||
|
|
||||||
initSentry(SdkConfig.get()["sentry"]);
|
initSentry(SdkConfig.get()["sentry"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +404,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
if (this.shouldTrackPageChange(prevState, this.state)) {
|
if (this.shouldTrackPageChange(prevState, this.state)) {
|
||||||
const durationMs = this.stopPageChangeTimer();
|
const durationMs = this.stopPageChangeTimer();
|
||||||
Analytics.trackPageChange(durationMs);
|
Analytics.trackPageChange(durationMs);
|
||||||
CountlyAnalytics.instance.trackPageChange(durationMs);
|
|
||||||
PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs);
|
PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs);
|
||||||
}
|
}
|
||||||
if (this.focusComposer) {
|
if (this.focusComposer) {
|
||||||
|
@ -812,9 +808,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
if (Analytics.canEnable()) {
|
if (Analytics.canEnable()) {
|
||||||
Analytics.enable();
|
Analytics.enable();
|
||||||
}
|
}
|
||||||
if (CountlyAnalytics.instance.canEnable()) {
|
|
||||||
CountlyAnalytics.instance.enable(/* anonymous = */ false);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Action.AnonymousAnalyticsReject:
|
case Action.AnonymousAnalyticsReject:
|
||||||
hideAnalyticsToast();
|
hideAnalyticsToast();
|
||||||
|
@ -1299,13 +1292,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
if (PosthogAnalytics.instance.isEnabled() && SettingsStore.isLevelSupported(SettingLevel.ACCOUNT)) {
|
if (PosthogAnalytics.instance.isEnabled() && SettingsStore.isLevelSupported(SettingLevel.ACCOUNT)) {
|
||||||
this.initPosthogAnalyticsToast();
|
this.initPosthogAnalyticsToast();
|
||||||
} else if (Analytics.canEnable() || CountlyAnalytics.instance.canEnable()) {
|
} else if (Analytics.canEnable() && SettingsStore.getValue("showCookieBar")) {
|
||||||
if (SettingsStore.getValue("showCookieBar") &&
|
|
||||||
(Analytics.canEnable() || CountlyAnalytics.instance.canEnable())
|
|
||||||
) {
|
|
||||||
showAnonymousAnalyticsOptInToast();
|
showAnonymousAnalyticsOptInToast();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (SdkConfig.get().mobileGuideToast) {
|
if (SdkConfig.get().mobileGuideToast) {
|
||||||
// The toast contains further logic to detect mobile platforms,
|
// 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',
|
action: 'require_registration',
|
||||||
});
|
});
|
||||||
} else if (screen === 'directory') {
|
} else if (screen === 'directory') {
|
||||||
if (this.state.view === Views.WELCOME) {
|
|
||||||
CountlyAnalytics.instance.track("onboarding_room_directory");
|
|
||||||
}
|
|
||||||
dis.fire(Action.ViewRoomDirectory);
|
dis.fire(Action.ViewRoomDirectory);
|
||||||
} else if (screen === "start_sso" || screen === "start_cas") {
|
} else if (screen === "start_sso" || screen === "start_cas") {
|
||||||
// TODO if logged in, skip SSO
|
|
||||||
let cli = MatrixClientPeg.get();
|
let cli = MatrixClientPeg.get();
|
||||||
if (!cli) {
|
if (!cli) {
|
||||||
const { hsUrl, isUrl } = this.props.serverConfig;
|
const { hsUrl, isUrl } = this.props.serverConfig;
|
||||||
|
|
|
@ -34,7 +34,6 @@ import SettingsStore from "../../settings/SettingsStore";
|
||||||
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
|
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
|
||||||
import GroupStore from "../../stores/GroupStore";
|
import GroupStore from "../../stores/GroupStore";
|
||||||
import FlairStore from "../../stores/FlairStore";
|
import FlairStore from "../../stores/FlairStore";
|
||||||
import CountlyAnalytics from "../../CountlyAnalytics";
|
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import { mediaFromMxc } from "../../customisations/Media";
|
import { mediaFromMxc } from "../../customisations/Media";
|
||||||
import { IDialogProps } from "../views/dialogs/IDialogProps";
|
import { IDialogProps } from "../views/dialogs/IDialogProps";
|
||||||
|
@ -88,9 +87,6 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
CountlyAnalytics.instance.trackRoomDirectoryBegin();
|
|
||||||
this.startTime = CountlyAnalytics.getTimestamp();
|
|
||||||
|
|
||||||
const selectedCommunityId = SettingsStore.getValue("feature_communities_v2_prototypes")
|
const selectedCommunityId = SettingsStore.getValue("feature_communities_v2_prototypes")
|
||||||
? GroupFilterOrderStore.getSelectedTags()[0]
|
? GroupFilterOrderStore.getSelectedTags()[0]
|
||||||
: null;
|
: null;
|
||||||
|
@ -262,11 +258,6 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
return false;
|
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.nextBatch = data.next_batch;
|
||||||
this.setState((s) => ({
|
this.setState((s) => ({
|
||||||
...s,
|
...s,
|
||||||
|
@ -668,7 +659,6 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFinished = () => {
|
private onFinished = () => {
|
||||||
CountlyAnalytics.instance.trackRoomDirectory(this.startTime);
|
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1286,7 +1286,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
action: Action.JoinRoom,
|
action: Action.JoinRoom,
|
||||||
roomId: this.getRoomId(),
|
roomId: this.getRoomId(),
|
||||||
opts: { inviteSignUrl: signUrl },
|
opts: { inviteSignUrl: signUrl },
|
||||||
_type: "unknown", // TODO: instrumentation
|
|
||||||
});
|
});
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,7 +25,6 @@ import Modal from "../../../Modal";
|
||||||
import PasswordReset from "../../../PasswordReset";
|
import PasswordReset from "../../../PasswordReset";
|
||||||
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
||||||
import AuthPage from "../../views/auth/AuthPage";
|
import AuthPage from "../../views/auth/AuthPage";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import ServerPicker from "../../views/elements/ServerPicker";
|
import ServerPicker from "../../views/elements/ServerPicker";
|
||||||
import EmailField from "../../views/auth/EmailField";
|
import EmailField from "../../views/auth/EmailField";
|
||||||
import PassphraseField from '../../views/auth/PassphraseField';
|
import PassphraseField from '../../views/auth/PassphraseField';
|
||||||
|
@ -102,12 +101,6 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
serverDeadError: "",
|
serverDeadError: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
CountlyAnalytics.instance.track("onboarding_forgot_password_begin");
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
this.reset = null;
|
this.reset = null;
|
||||||
this.checkServerLiveliness(this.props.serverConfig);
|
this.checkServerLiveliness(this.props.serverConfig);
|
||||||
|
@ -298,8 +291,6 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
fieldRef={field => this[ForgotPasswordField.Email] = field}
|
fieldRef={field => this[ForgotPasswordField.Email] = field}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
onChange={this.onInputChanged.bind(this, "email")}
|
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>
|
||||||
<div className="mx_AuthBody_fieldRow">
|
<div className="mx_AuthBody_fieldRow">
|
||||||
|
@ -311,8 +302,6 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
minScore={PASSWORD_MIN_SCORE}
|
minScore={PASSWORD_MIN_SCORE}
|
||||||
fieldRef={field => this[ForgotPasswordField.Password] = field}
|
fieldRef={field => this[ForgotPasswordField.Password] = field}
|
||||||
onChange={this.onInputChanged.bind(this, "password")}
|
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"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
<PassphraseConfirmField
|
<PassphraseConfirmField
|
||||||
|
@ -324,8 +313,6 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
password={this.state.password}
|
password={this.state.password}
|
||||||
fieldRef={field => this[ForgotPasswordField.PasswordConfirm] = field}
|
fieldRef={field => this[ForgotPasswordField.PasswordConfirm] = field}
|
||||||
onChange={this.onInputChanged.bind(this, "password2")}
|
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"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,7 +28,6 @@ import AuthPage from "../../views/auth/AuthPage";
|
||||||
import PlatformPeg from '../../../PlatformPeg';
|
import PlatformPeg from '../../../PlatformPeg';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { UIFeature } from "../../../settings/UIFeature";
|
import { UIFeature } from "../../../settings/UIFeature";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import { IMatrixClientCreds } from "../../../MatrixClientPeg";
|
import { IMatrixClientCreds } from "../../../MatrixClientPeg";
|
||||||
import PasswordLogin from "../../views/auth/PasswordLogin";
|
import PasswordLogin from "../../views/auth/PasswordLogin";
|
||||||
import InlineSpinner from "../../views/elements/InlineSpinner";
|
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.cas': () => this.renderSsoStep("cas"),
|
||||||
'm.login.sso': () => this.renderSsoStep("sso"),
|
'm.login.sso': () => this.renderSsoStep("sso"),
|
||||||
};
|
};
|
||||||
|
|
||||||
CountlyAnalytics.instance.track("onboarding_login_begin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// 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 { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
const DIV_ID = 'mx_recaptcha';
|
const DIV_ID = 'mx_recaptcha';
|
||||||
|
@ -51,8 +50,6 @@ export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICap
|
||||||
this.state = {
|
this.state = {
|
||||||
errorText: undefined,
|
errorText: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
CountlyAnalytics.instance.track("onboarding_grecaptcha_begin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -118,12 +115,10 @@ export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICap
|
||||||
this.setState({
|
this.setState({
|
||||||
errorText: null,
|
errorText: null,
|
||||||
});
|
});
|
||||||
CountlyAnalytics.instance.track("onboarding_grecaptcha_loaded");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
errorText: e.toString(),
|
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 SettingsStore from "../../../settings/SettingsStore";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import Spinner from "../elements/Spinner";
|
import Spinner from "../elements/Spinner";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { LocalisedPolicy, Policies } from '../../../Terms';
|
import { LocalisedPolicy, Policies } from '../../../Terms';
|
||||||
import Field from '../elements/Field';
|
import Field from '../elements/Field';
|
||||||
|
@ -201,7 +200,6 @@ export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCaptchaResponse = (response: string) => {
|
private onCaptchaResponse = (response: string) => {
|
||||||
CountlyAnalytics.instance.track("onboarding_grecaptcha_submit");
|
|
||||||
this.props.submitAuthDict({
|
this.props.submitAuthDict({
|
||||||
type: AuthType.Recaptcha,
|
type: AuthType.Recaptcha,
|
||||||
response: response,
|
response: response,
|
||||||
|
@ -322,8 +320,6 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
||||||
toggledPolicies: initToggles,
|
toggledPolicies: initToggles,
|
||||||
policies: pickedPolicies,
|
policies: pickedPolicies,
|
||||||
};
|
};
|
||||||
|
|
||||||
CountlyAnalytics.instance.track("onboarding_terms_begin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -354,7 +350,6 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
||||||
|
|
||||||
if (allChecked) {
|
if (allChecked) {
|
||||||
this.props.submitAuthDict({ type: AuthType.Terms });
|
this.props.submitAuthDict({ type: AuthType.Terms });
|
||||||
CountlyAnalytics.instance.track("onboarding_terms_complete");
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({ errorText: _t("Please review and accept all of the homeserver's policies") });
|
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 SdkConfig from '../../../SdkConfig';
|
||||||
import { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
import { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import withValidation, { IValidationResult } from "../elements/Validation";
|
import withValidation, { IValidationResult } from "../elements/Validation";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import CountryDropdown from "./CountryDropdown";
|
import CountryDropdown from "./CountryDropdown";
|
||||||
|
@ -99,7 +98,6 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
|
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
|
||||||
if (!allFieldsValid) {
|
if (!allFieldsValid) {
|
||||||
CountlyAnalytics.instance.track("onboarding_registration_submit_failed");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,20 +123,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||||
this.props.onUsernameChanged(ev.target.value);
|
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 => {
|
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);
|
this.props.onUsernameBlur(ev.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -146,7 +131,6 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||||
const loginType = ev.target.value;
|
const loginType = ev.target.value;
|
||||||
this.setState({ loginType });
|
this.setState({ loginType });
|
||||||
this.props.onUsernameChanged(""); // Reset because email and username use the same state
|
this.props.onUsernameChanged(""); // Reset because email and username use the same state
|
||||||
CountlyAnalytics.instance.track("onboarding_login_type_changed", { loginType });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onPhoneCountryChanged = country => {
|
private onPhoneCountryChanged = country => {
|
||||||
|
@ -157,14 +141,6 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||||
this.props.onPhoneNumberChanged(ev.target.value);
|
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 => {
|
private onPasswordChanged = ev => {
|
||||||
this.setState({ password: ev.target.value });
|
this.setState({ password: ev.target.value });
|
||||||
};
|
};
|
||||||
|
@ -324,7 +300,6 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||||
placeholder="joe@example.com"
|
placeholder="joe@example.com"
|
||||||
value={this.props.username}
|
value={this.props.username}
|
||||||
onChange={this.onUsernameChanged}
|
onChange={this.onUsernameChanged}
|
||||||
onFocus={this.onUsernameFocus}
|
|
||||||
onBlur={this.onUsernameBlur}
|
onBlur={this.onUsernameBlur}
|
||||||
disabled={this.props.busy}
|
disabled={this.props.busy}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
@ -344,7 +319,6 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||||
placeholder={_t("Username").toLocaleLowerCase()}
|
placeholder={_t("Username").toLocaleLowerCase()}
|
||||||
value={this.props.username}
|
value={this.props.username}
|
||||||
onChange={this.onUsernameChanged}
|
onChange={this.onUsernameChanged}
|
||||||
onFocus={this.onUsernameFocus}
|
|
||||||
onBlur={this.onUsernameBlur}
|
onBlur={this.onUsernameBlur}
|
||||||
disabled={this.props.busy}
|
disabled={this.props.busy}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
@ -372,8 +346,6 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||||
value={this.props.phoneNumber}
|
value={this.props.phoneNumber}
|
||||||
prefixComponent={phoneCountry}
|
prefixComponent={phoneCountry}
|
||||||
onChange={this.onPhoneNumberChanged}
|
onChange={this.onPhoneNumberChanged}
|
||||||
onFocus={this.onPhoneNumberFocus}
|
|
||||||
onBlur={this.onPhoneNumberBlur}
|
|
||||||
disabled={this.props.busy}
|
disabled={this.props.busy}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
onValidate={this.onPhoneNumberValidate}
|
onValidate={this.onPhoneNumberValidate}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import withValidation, { IValidationResult } from '../elements/Validation';
|
||||||
import { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
import { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
||||||
import EmailField from "./EmailField";
|
import EmailField from "./EmailField";
|
||||||
import PassphraseField from "./PassphraseField";
|
import PassphraseField from "./PassphraseField";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import Field from '../elements/Field';
|
import Field from '../elements/Field';
|
||||||
import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog';
|
import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
@ -113,8 +112,6 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
||||||
passwordConfirm: this.props.defaultPassword || "",
|
passwordConfirm: this.props.defaultPassword || "",
|
||||||
passwordComplexity: null,
|
passwordComplexity: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
CountlyAnalytics.instance.track("onboarding_registration_begin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSubmit = async ev => {
|
private onSubmit = async ev => {
|
||||||
|
@ -125,13 +122,11 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
||||||
|
|
||||||
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
|
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
|
||||||
if (!allFieldsValid) {
|
if (!allFieldsValid) {
|
||||||
CountlyAnalytics.instance.track("onboarding_registration_submit_failed");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.email === '') {
|
if (this.state.email === '') {
|
||||||
if (this.showEmail()) {
|
if (this.showEmail()) {
|
||||||
CountlyAnalytics.instance.track("onboarding_registration_submit_warn");
|
|
||||||
Modal.createTrackedDialog("Email prompt dialog", '', RegistrationEmailPromptDialog, {
|
Modal.createTrackedDialog("Email prompt dialog", '', RegistrationEmailPromptDialog, {
|
||||||
onFinished: async (confirmed: boolean, email?: string) => {
|
onFinished: async (confirmed: boolean, email?: string) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
|
@ -156,10 +151,6 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
||||||
private doSubmit(ev) {
|
private doSubmit(ev) {
|
||||||
const email = this.state.email.trim();
|
const email = this.state.email.trim();
|
||||||
|
|
||||||
CountlyAnalytics.instance.track("onboarding_registration_submit_ok", {
|
|
||||||
email: !!email,
|
|
||||||
});
|
|
||||||
|
|
||||||
const promise = this.props.onRegisterClick({
|
const promise = this.props.onRegisterClick({
|
||||||
username: this.state.username.trim(),
|
username: this.state.username.trim(),
|
||||||
password: this.state.password.trim(),
|
password: this.state.password.trim(),
|
||||||
|
@ -455,8 +446,6 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
||||||
validationRules={this.validateEmailRules.bind(this)}
|
validationRules={this.validateEmailRules.bind(this)}
|
||||||
onChange={this.onEmailChange}
|
onChange={this.onEmailChange}
|
||||||
onValidate={this.onEmailValidate}
|
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}
|
value={this.state.password}
|
||||||
onChange={this.onPasswordChange}
|
onChange={this.onPasswordChange}
|
||||||
onValidate={this.onPasswordValidate}
|
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}
|
password={this.state.password}
|
||||||
onChange={this.onPasswordConfirmChange}
|
onChange={this.onPasswordConfirmChange}
|
||||||
onValidate={this.onPasswordConfirmValidate}
|
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}
|
value={this.state.username}
|
||||||
onChange={this.onUsernameChange}
|
onChange={this.onUsernameChange}
|
||||||
onValidate={this.onUsernameValidate}
|
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 { _td } from "../../../languageHandler";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { UIFeature } from "../../../settings/UIFeature";
|
import { UIFeature } from "../../../settings/UIFeature";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import LanguageSelector from "./LanguageSelector";
|
import LanguageSelector from "./LanguageSelector";
|
||||||
|
|
||||||
|
@ -36,12 +35,6 @@ interface IProps {
|
||||||
|
|
||||||
@replaceableComponent("views.auth.Welcome")
|
@replaceableComponent("views.auth.Welcome")
|
||||||
export default class Welcome extends React.PureComponent<IProps> {
|
export default class Welcome extends React.PureComponent<IProps> {
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
CountlyAnalytics.instance.track("onboarding_welcome");
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
// FIXME: Using an import will result in wrench-element-tests failures
|
// FIXME: Using an import will result in wrench-element-tests failures
|
||||||
const EmbeddedPage = sdk.getComponent("structures.EmbeddedPage");
|
const EmbeddedPage = sdk.getComponent("structures.EmbeddedPage");
|
||||||
|
|
|
@ -20,12 +20,10 @@ import QuestionDialog from './QuestionDialog';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import CountlyAnalytics, { Rating } from "../../../CountlyAnalytics";
|
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import BugReportDialog from "./BugReportDialog";
|
import BugReportDialog from "./BugReportDialog";
|
||||||
import InfoDialog from "./InfoDialog";
|
import InfoDialog from "./InfoDialog";
|
||||||
import StyledRadioGroup from "../elements/StyledRadioGroup";
|
|
||||||
import { IDialogProps } from "./IDialogProps";
|
import { IDialogProps } from "./IDialogProps";
|
||||||
import { submitFeedback } from "../../../rageshake/submit-rageshake";
|
import { submitFeedback } from "../../../rageshake/submit-rageshake";
|
||||||
import { useStateToggle } from "../../../hooks/useStateToggle";
|
import { useStateToggle } from "../../../hooks/useStateToggle";
|
||||||
|
@ -39,7 +37,6 @@ interface IProps extends IDialogProps {}
|
||||||
|
|
||||||
const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
||||||
const feedbackRef = useRef<Field>();
|
const feedbackRef = useRef<Field>();
|
||||||
const [rating, setRating] = useState<Rating>();
|
|
||||||
const [comment, setComment] = useState<string>("");
|
const [comment, setComment] = useState<string>("");
|
||||||
const [canContact, toggleCanContact] = useStateToggle(false);
|
const [canContact, toggleCanContact] = useStateToggle(false);
|
||||||
|
|
||||||
|
@ -53,16 +50,12 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
||||||
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
|
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
const countlyEnabled = CountlyAnalytics.instance.canEnable();
|
|
||||||
const rageshakeUrl = SdkConfig.get().bug_report_endpoint_url;
|
const rageshakeUrl = SdkConfig.get().bug_report_endpoint_url;
|
||||||
|
const hasFeedback = !!rageshakeUrl;
|
||||||
const hasFeedback = countlyEnabled || rageshakeUrl;
|
|
||||||
const onFinished = (sendFeedback: boolean): void => {
|
const onFinished = (sendFeedback: boolean): void => {
|
||||||
if (hasFeedback && sendFeedback) {
|
if (hasFeedback && sendFeedback) {
|
||||||
if (rageshakeUrl) {
|
if (rageshakeUrl) {
|
||||||
submitFeedback(rageshakeUrl, "feedback", comment, canContact);
|
submitFeedback(rageshakeUrl, "feedback", comment, canContact);
|
||||||
} else if (countlyEnabled) {
|
|
||||||
CountlyAnalytics.instance.reportFeedback(rating, comment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.createTrackedDialog('Feedback sent', '', InfoDialog, {
|
Modal.createTrackedDialog('Feedback sent', '', InfoDialog, {
|
||||||
|
@ -73,8 +66,6 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
||||||
props.onFinished();
|
props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
const brand = SdkConfig.get().brand;
|
|
||||||
|
|
||||||
let feedbackSection;
|
let feedbackSection;
|
||||||
if (rageshakeUrl) {
|
if (rageshakeUrl) {
|
||||||
feedbackSection = <div className="mx_FeedbackDialog_section mx_FeedbackDialog_rateApp">
|
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") }
|
{ _t("You may contact me if you want to follow up or to let me test out upcoming ideas") }
|
||||||
</StyledCheckbox>
|
</StyledCheckbox>
|
||||||
</div>;
|
</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;
|
let bugReports = null;
|
||||||
|
@ -175,7 +132,7 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
||||||
{ feedbackSection }
|
{ feedbackSection }
|
||||||
</React.Fragment>}
|
</React.Fragment>}
|
||||||
button={hasFeedback ? _t("Send feedback") : _t("Go back")}
|
button={hasFeedback ? _t("Send feedback") : _t("Go back")}
|
||||||
buttonDisabled={hasFeedback && !rating && !comment}
|
buttonDisabled={hasFeedback && !comment}
|
||||||
onFinished={onFinished}
|
onFinished={onFinished}
|
||||||
/>);
|
/>);
|
||||||
};
|
};
|
||||||
|
|
|
@ -52,7 +52,6 @@ import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||||
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { UIFeature } from "../../../settings/UIFeature";
|
import { UIFeature } from "../../../settings/UIFeature";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { mediaFromMxc } from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
import { getAddressType } from "../../../UserAddress";
|
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));
|
room.getMembersWithMembership('join').forEach(m => alreadyInvited.add(m.userId));
|
||||||
// add banned users, so we don't try to invite them
|
// add banned users, so we don't try to invite them
|
||||||
room.getMembersWithMembership('ban').forEach(m => alreadyInvited.add(m.userId));
|
room.getMembersWithMembership('ban').forEach(m => alreadyInvited.add(m.userId));
|
||||||
|
|
||||||
CountlyAnalytics.instance.trackBeginInvite(props.roomId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -745,7 +742,6 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
};
|
};
|
||||||
|
|
||||||
private inviteUsers = async () => {
|
private inviteUsers = async () => {
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
|
||||||
this.setState({ busy: true });
|
this.setState({ busy: true });
|
||||||
this.convertFilter();
|
this.convertFilter();
|
||||||
const targets = this.convertFilter();
|
const targets = this.convertFilter();
|
||||||
|
@ -764,7 +760,6 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await inviteMultipleToRoom(this.props.roomId, targetIds, true);
|
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
|
if (!this.shouldAbortAfterInviteError(result, room)) { // handles setting error message too
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { useRef, useState } from "react";
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import { IDialogProps } from "./IDialogProps";
|
import { IDialogProps } from "./IDialogProps";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
import EmailField from "../auth/EmailField";
|
import EmailField from "../auth/EmailField";
|
||||||
|
@ -70,8 +69,6 @@ const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => {
|
||||||
const target = ev.target as HTMLInputElement;
|
const target = ev.target as HTMLInputElement;
|
||||||
setEmail(target.value);
|
setEmail(target.value);
|
||||||
}}
|
}}
|
||||||
onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_email2_focus")}
|
|
||||||
onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_email2_blur")}
|
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,7 +23,6 @@ import Spinner from "./Spinner";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { useTimeout } from "../../../hooks/useTimeout";
|
import { useTimeout } from "../../../hooks/useTimeout";
|
||||||
import Analytics from "../../../Analytics";
|
import Analytics from "../../../Analytics";
|
||||||
import CountlyAnalytics from '../../../CountlyAnalytics';
|
|
||||||
import { TranslatedString } from '../../../languageHandler';
|
import { TranslatedString } from '../../../languageHandler';
|
||||||
import RoomContext from "../../../contexts/RoomContext";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
|
|
||||||
|
@ -67,7 +66,6 @@ const MiniAvatarUploader: React.FC<IProps> = ({ hasAvatar, hasAvatarLabel, noAva
|
||||||
if (!ev.target.files?.length) return;
|
if (!ev.target.files?.length) return;
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
Analytics.trackEvent("mini_avatar", "upload");
|
Analytics.trackEvent("mini_avatar", "upload");
|
||||||
CountlyAnalytics.instance.track("mini_avatar_upload");
|
|
||||||
const file = ev.target.files[0];
|
const file = ev.target.files[0];
|
||||||
const uri = await cli.uploadContent(file);
|
const uri = await cli.uploadContent(file);
|
||||||
await setAvatarUrl(uri);
|
await setAvatarUrl(uri);
|
||||||
|
|
|
@ -34,7 +34,6 @@ import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
||||||
import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer";
|
import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer";
|
||||||
import { CommandCategories } from '../../../SlashCommands';
|
import { CommandCategories } from '../../../SlashCommands';
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import { getKeyBindingsManager } from '../../../KeyBindingsManager';
|
import { getKeyBindingsManager } from '../../../KeyBindingsManager';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import SendHistoryManager from '../../../SendHistoryManager';
|
import SendHistoryManager from '../../../SendHistoryManager';
|
||||||
|
@ -306,8 +305,6 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||||
isReply: !!editedEvent.replyEventId,
|
isReply: !!editedEvent.replyEventId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
|
||||||
|
|
||||||
// Replace emoticon at the end of the message
|
// Replace emoticon at the end of the message
|
||||||
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
|
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
|
||||||
const caret = this.editorRef.current?.getCaret();
|
const caret = this.editorRef.current?.getCaret();
|
||||||
|
@ -354,10 +351,9 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||||
const event = this.props.editState.getEvent();
|
const event = this.props.editState.getEvent();
|
||||||
const threadId = event.threadRootId || null;
|
const threadId = event.threadRootId || null;
|
||||||
|
|
||||||
const prom = this.props.mxClient.sendMessage(roomId, threadId, editContent);
|
this.props.mxClient.sendMessage(roomId, threadId, editContent);
|
||||||
this.clearStoredEditorState();
|
this.clearStoredEditorState();
|
||||||
dis.dispatch({ action: "message_sent" });
|
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 { Action } from "../../../dispatcher/actions";
|
||||||
import { containsEmoji } from "../../../effects/utils";
|
import { containsEmoji } from "../../../effects/utils";
|
||||||
import { CHAT_EFFECTS } from '../../../effects';
|
import { CHAT_EFFECTS } from '../../../effects';
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { getKeyBindingsManager } from '../../../KeyBindingsManager';
|
import { getKeyBindingsManager } from '../../../KeyBindingsManager';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
@ -404,7 +403,6 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldSend) {
|
if (shouldSend) {
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
|
||||||
const { roomId } = this.props.room;
|
const { roomId } = this.props.room;
|
||||||
if (!content) {
|
if (!content) {
|
||||||
content = createMessageContent(
|
content = createMessageContent(
|
||||||
|
@ -452,7 +450,6 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
sendRoundTripMetric(this.props.mxClient, roomId, resp.event_id);
|
sendRoundTripMetric(this.props.mxClient, roomId, resp.event_id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendHistoryManager.save(model, replyToEvent);
|
this.sendHistoryManager.save(model, replyToEvent);
|
||||||
|
|
|
@ -26,7 +26,6 @@ import withValidation, { IFieldState, IValidationResult } from '../elements/Vali
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import PassphraseField from "../auth/PassphraseField";
|
import PassphraseField from "../auth/PassphraseField";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { PASSWORD_MIN_SCORE } from '../auth/RegistrationForm';
|
import { PASSWORD_MIN_SCORE } from '../auth/RegistrationForm';
|
||||||
import SetEmailDialog from "../dialogs/SetEmailDialog";
|
import SetEmailDialog from "../dialogs/SetEmailDialog";
|
||||||
|
@ -270,7 +269,6 @@ export default class ChangePassword extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
|
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
|
||||||
if (!allFieldsValid) {
|
if (!allFieldsValid) {
|
||||||
CountlyAnalytics.instance.track("onboarding_registration_submit_failed");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@ import SecureBackupPanel from "../../SecureBackupPanel";
|
||||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||||
import E2eAdvancedPanel, { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel";
|
import E2eAdvancedPanel, { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel";
|
||||||
import CountlyAnalytics from "../../../../../CountlyAnalytics";
|
|
||||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||||
import { ActionPayload } from "../../../../../dispatcher/payloads";
|
import { ActionPayload } from "../../../../../dispatcher/payloads";
|
||||||
import CryptographyPanel from "../../CryptographyPanel";
|
import CryptographyPanel from "../../CryptographyPanel";
|
||||||
|
@ -116,7 +115,6 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||||
|
|
||||||
private updateAnalytics = (checked: boolean): void => {
|
private updateAnalytics = (checked: boolean): void => {
|
||||||
checked ? Analytics.enable() : Analytics.disable();
|
checked ? Analytics.enable() : Analytics.disable();
|
||||||
CountlyAnalytics.instance.enable(/* anonymous = */ !checked);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onMyMembership = (room: Room, membership: string): void => {
|
private onMyMembership = (room: Room, membership: string): void => {
|
||||||
|
@ -308,7 +306,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||||
}
|
}
|
||||||
|
|
||||||
let privacySection;
|
let privacySection;
|
||||||
if (Analytics.canEnable() || CountlyAnalytics.instance.canEnable() || PosthogAnalytics.instance.isEnabled()) {
|
if (Analytics.canEnable() || PosthogAnalytics.instance.isEnabled()) {
|
||||||
const onClickAnalyticsLearnMore = () => {
|
const onClickAnalyticsLearnMore = () => {
|
||||||
if (PosthogAnalytics.instance.isEnabled()) {
|
if (PosthogAnalytics.instance.isEnabled()) {
|
||||||
showAnalyticsLearnMoreDialog({
|
showAnalyticsLearnMoreDialog({
|
||||||
|
|
|
@ -38,7 +38,6 @@ import DMRoomMap from "./utils/DMRoomMap";
|
||||||
import { getAddressType } from "./UserAddress";
|
import { getAddressType } from "./UserAddress";
|
||||||
import { getE2EEWellKnown } from "./utils/WellKnownUtils";
|
import { getE2EEWellKnown } from "./utils/WellKnownUtils";
|
||||||
import GroupStore from "./stores/GroupStore";
|
import GroupStore from "./stores/GroupStore";
|
||||||
import CountlyAnalytics from "./CountlyAnalytics";
|
|
||||||
import { isJoinedOrNearlyJoined } from "./utils/membership";
|
import { isJoinedOrNearlyJoined } from "./utils/membership";
|
||||||
import { VIRTUAL_ROOM_EVENT_TYPE } from "./CallHandler";
|
import { VIRTUAL_ROOM_EVENT_TYPE } from "./CallHandler";
|
||||||
import SpaceStore from "./stores/spaces/SpaceStore";
|
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.guestAccess === undefined) opts.guestAccess = true;
|
||||||
if (opts.encryption === undefined) opts.encryption = false;
|
if (opts.encryption === undefined) opts.encryption = false;
|
||||||
|
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
|
||||||
|
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (client.isGuest()) {
|
if (client.isGuest()) {
|
||||||
dis.dispatch({ action: 'require_registration' });
|
dis.dispatch({ action: 'require_registration' });
|
||||||
|
@ -270,7 +267,6 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
|
||||||
_trigger: "Created",
|
_trigger: "Created",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CountlyAnalytics.instance.trackRoomCreate(startTime, roomId);
|
|
||||||
return roomId;
|
return roomId;
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
// Raise the error if the caller requested that we do so.
|
// 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.",
|
"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",
|
"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",
|
"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.",
|
"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",
|
"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>.",
|
"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 { ActionPayload } from "../dispatcher/payloads";
|
||||||
import { Action } from "../dispatcher/actions";
|
import { Action } from "../dispatcher/actions";
|
||||||
import { retry } from "../utils/promise";
|
import { retry } from "../utils/promise";
|
||||||
import CountlyAnalytics, { IJoinRoomEvent } from "../CountlyAnalytics";
|
|
||||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||||
import { PosthogAnalytics } from "../PosthogAnalytics";
|
import { PosthogAnalytics } from "../PosthogAnalytics";
|
||||||
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
||||||
|
@ -299,7 +298,6 @@ class RoomViewStore extends Store<ActionPayload> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async joinRoom(payload: ActionPayload) {
|
private async joinRoom(payload: ActionPayload) {
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
|
||||||
this.setState({
|
this.setState({
|
||||||
joining: true,
|
joining: true,
|
||||||
});
|
});
|
||||||
|
@ -318,20 +316,6 @@ class RoomViewStore extends Store<ActionPayload> {
|
||||||
return err.httpStatus === 504;
|
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
|
// 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
|
// have come down the sync stream yet, and that's the point at which we'd consider the user joined to the
|
||||||
// room.
|
// room.
|
||||||
|
|
|
@ -53,7 +53,6 @@ import { ElementWidgetActions, IViewRoomApiRequest } from "./ElementWidgetAction
|
||||||
import { ModalWidgetStore } from "../ModalWidgetStore";
|
import { ModalWidgetStore } from "../ModalWidgetStore";
|
||||||
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
||||||
import { getCustomTheme } from "../../theme";
|
import { getCustomTheme } from "../../theme";
|
||||||
import CountlyAnalytics from "../../CountlyAnalytics";
|
|
||||||
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
|
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
|
||||||
import { ELEMENT_CLIENT_ID } from "../../identifiers";
|
import { ELEMENT_CLIENT_ID } from "../../identifiers";
|
||||||
import { getUserLanguage } from "../../languageHandler";
|
import { getUserLanguage } from "../../languageHandler";
|
||||||
|
@ -322,9 +321,6 @@ export class StopGapWidget extends EventEmitter {
|
||||||
this.messaging.on(`action:${WidgetApiFromWidgetAction.UpdateAlwaysOnScreen}`,
|
this.messaging.on(`action:${WidgetApiFromWidgetAction.UpdateAlwaysOnScreen}`,
|
||||||
(ev: CustomEvent<IStickyActionRequest>) => {
|
(ev: CustomEvent<IStickyActionRequest>) => {
|
||||||
if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) {
|
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);
|
ActiveWidgetStore.instance.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value);
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack
|
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack
|
||||||
|
|
Loading…
Reference in a new issue