Tidy up and fix some edge cases
This commit is contained in:
parent
c3a355097d
commit
b01055f962
4 changed files with 58 additions and 68 deletions
|
@ -292,22 +292,23 @@ interface IViewData {
|
||||||
|
|
||||||
// Apply fn to all hash path parts after the 1st one
|
// Apply fn to all hash path parts after the 1st one
|
||||||
async function getViewData(anonymous = true): Promise<IViewData> {
|
async function getViewData(anonymous = true): Promise<IViewData> {
|
||||||
|
const rand = randomString(8);
|
||||||
const { origin, hash } = window.location;
|
const { origin, hash } = window.location;
|
||||||
let { pathname } = window.location;
|
let { pathname } = window.location;
|
||||||
|
|
||||||
// Redact paths which could contain unexpected PII
|
// Redact paths which could contain unexpected PII
|
||||||
if (origin.startsWith('file://')) {
|
if (origin.startsWith('file://')) {
|
||||||
pathname = "/<redacted>/";
|
pathname = `/<redacted_${rand}>/`; // XXX: inject rand because Count.ly doesn't like X->X transitions
|
||||||
}
|
}
|
||||||
|
|
||||||
let [_, screen, ...parts] = hash.split("/");
|
let [_, screen, ...parts] = hash.split("/");
|
||||||
|
|
||||||
if (!knownScreens.has(screen)) {
|
if (!knownScreens.has(screen)) {
|
||||||
screen = "<redacted>";
|
screen = `<redacted_${rand}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
for (let i = 0; i < parts.length; i++) {
|
||||||
parts[i] = anonymous ? "<redacted>" : await hashHex(parts[i]);
|
parts[i] = anonymous ? `<redacted_${rand}>` : await hashHex(parts[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashStr = `${_}/${screen}/${parts.join("/")}`;
|
const hashStr = `${_}/${screen}/${parts.join("/")}`;
|
||||||
|
@ -342,19 +343,28 @@ const getRoomStats = (roomId: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CountlyAnalytics {
|
export default class CountlyAnalytics {
|
||||||
private baseUrl: URL = null;
|
private baseUrl: URL = null;
|
||||||
private appKey: string = null;
|
private appKey: string = null;
|
||||||
private userKey: string = null;
|
private userKey: string = null;
|
||||||
private firstPage = true;
|
private anonymous: boolean;
|
||||||
private heartbeatIntervalID: number = null;
|
|
||||||
|
|
||||||
private anonymous = true;
|
|
||||||
private pendingEvents: IEvent[] = [];
|
|
||||||
|
|
||||||
private appPlatform: string;
|
private appPlatform: string;
|
||||||
private appVersion = "unknown";
|
private appVersion = "unknown";
|
||||||
|
|
||||||
private initTime = CountlyAnalytics.getTimestamp();
|
private initTime = CountlyAnalytics.getTimestamp();
|
||||||
|
private firstPage = true;
|
||||||
|
private heartbeatIntervalId: NodeJS.Timeout;
|
||||||
|
private activityIntervalId: NodeJS.Timeout;
|
||||||
|
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();
|
private static internalInstance = new CountlyAnalytics();
|
||||||
|
|
||||||
|
@ -374,8 +384,8 @@ export class CountlyAnalytics {
|
||||||
private async changeUserKey(userKey: string, merge = false) {
|
private async changeUserKey(userKey: string, merge = false) {
|
||||||
const oldUserKey = this.userKey;
|
const oldUserKey = this.userKey;
|
||||||
this.userKey = userKey;
|
this.userKey = userKey;
|
||||||
if (merge) {
|
if (oldUserKey && merge) {
|
||||||
this.request({ old_device_id: oldUserKey });
|
await this.request({ old_device_id: oldUserKey });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,26 +393,15 @@ export class CountlyAnalytics {
|
||||||
if (!this.disabled && this.anonymous === anonymous) return;
|
if (!this.disabled && this.anonymous === anonymous) return;
|
||||||
if (!this.canEnable()) return;
|
if (!this.canEnable()) return;
|
||||||
|
|
||||||
if (!this.disabled && this.anonymous !== anonymous) {
|
|
||||||
this.anonymous = anonymous;
|
|
||||||
if (anonymous) {
|
|
||||||
await this.changeUserKey(randomString(64))
|
|
||||||
} else {
|
|
||||||
await this.changeUserKey(await hashHex(MatrixClientPeg.get().getUserId()), true);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = SdkConfig.get();
|
const config = SdkConfig.get();
|
||||||
|
|
||||||
this.baseUrl = new URL("/i", config.countly.url);
|
this.baseUrl = new URL("/i", config.countly.url);
|
||||||
this.appKey = config.countly.appKey;
|
this.appKey = config.countly.appKey;
|
||||||
|
|
||||||
this.anonymous = anonymous;
|
this.anonymous = anonymous;
|
||||||
if (this.anonymous) {
|
if (anonymous) {
|
||||||
this.userKey = randomString(64);
|
await this.changeUserKey(randomString(64))
|
||||||
} else {
|
} else {
|
||||||
this.userKey = await hashHex(MatrixClientPeg.get().getUserId());
|
await this.changeUserKey(await hashHex(MatrixClientPeg.get().getUserId()), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const platform = PlatformPeg.get();
|
const platform = PlatformPeg.get();
|
||||||
|
@ -414,19 +413,26 @@ export class CountlyAnalytics {
|
||||||
}
|
}
|
||||||
|
|
||||||
// start heartbeat
|
// start heartbeat
|
||||||
this.heartbeatIntervalID = window.setInterval(this.heartbeat.bind(this), HEARTBEAT_INTERVAL);
|
this.heartbeatIntervalId = setInterval(this.heartbeat.bind(this), HEARTBEAT_INTERVAL);
|
||||||
this.trackSessions(); // TODO clear on disable
|
this.trackSessions();
|
||||||
this.trackErrors(); // TODO clear on disable
|
this.trackErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public async disable() {
|
||||||
* Disable Analytics, stop the heartbeat and clear identifiers from localStorage
|
|
||||||
*/
|
|
||||||
public disable() {
|
|
||||||
if (this.disabled) return;
|
if (this.disabled) return;
|
||||||
this.queue({ key: "Opt-Out" });
|
await this.track("Opt-Out" );
|
||||||
window.clearInterval(this.heartbeatIntervalID);
|
this.endSession();
|
||||||
|
window.clearInterval(this.heartbeatIntervalId);
|
||||||
|
window.clearTimeout(this.activityIntervalId)
|
||||||
this.baseUrl = null;
|
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: 1 | 2 | 3 | 4 | 5, comment: string) {
|
public reportFeedback(rating: 1 | 2 | 3 | 4 | 5, comment: string) {
|
||||||
|
@ -435,12 +441,6 @@ export class CountlyAnalytics {
|
||||||
|
|
||||||
public trackPageChange(generationTimeMs?: number) {
|
public trackPageChange(generationTimeMs?: number) {
|
||||||
if (this.disabled) return;
|
if (this.disabled) return;
|
||||||
|
|
||||||
if (typeof generationTimeMs !== 'number') {
|
|
||||||
console.warn('Analytics.trackPageChange: expected generationTimeMs to be a number');
|
|
||||||
// But continue anyway because we still want to track the change
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO use generationTimeMs
|
// TODO use generationTimeMs
|
||||||
this.trackPageView();
|
this.trackPageView();
|
||||||
}
|
}
|
||||||
|
@ -491,7 +491,7 @@ export class CountlyAnalytics {
|
||||||
}
|
}
|
||||||
|
|
||||||
public recordError(err: Error | string, fatal = false) {
|
public recordError(err: Error | string, fatal = false) {
|
||||||
if (this.disabled) return;
|
if (this.disabled || this.anonymous) return;
|
||||||
|
|
||||||
let error = "";
|
let error = "";
|
||||||
if (typeof err === "object") {
|
if (typeof err === "object") {
|
||||||
|
@ -534,11 +534,6 @@ export class CountlyAnalytics {
|
||||||
|
|
||||||
ob._background = document.hasFocus();
|
ob._background = document.hasFocus();
|
||||||
|
|
||||||
// if (crashLogs.length > 0) {
|
|
||||||
// ob._logs = crashLogs.join("\n");
|
|
||||||
// }
|
|
||||||
// crashLogs = [];
|
|
||||||
|
|
||||||
this.request({ crash: JSON.stringify(ob) });
|
this.request({ crash: JSON.stringify(ob) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,13 +667,6 @@ export class CountlyAnalytics {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private trackTime = true;
|
|
||||||
private lastBeat: number;
|
|
||||||
private storedDuration = 0;
|
|
||||||
private lastView: string;
|
|
||||||
private lastViewTime = 0;
|
|
||||||
private lastViewStoredDuration = 0;
|
|
||||||
|
|
||||||
private startTime() {
|
private startTime() {
|
||||||
if (!this.trackTime) {
|
if (!this.trackTime) {
|
||||||
this.trackTime = true;
|
this.trackTime = true;
|
||||||
|
@ -697,6 +685,7 @@ export class CountlyAnalytics {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMetrics(): IMetrics {
|
private getMetrics(): IMetrics {
|
||||||
|
if (this.anonymous) return undefined;
|
||||||
const metrics: IMetrics = {};
|
const metrics: IMetrics = {};
|
||||||
|
|
||||||
// getting app version
|
// getting app version
|
||||||
|
@ -719,13 +708,10 @@ export class CountlyAnalytics {
|
||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sessionStarted = false;
|
|
||||||
private heartbeatEnabled = false;
|
|
||||||
|
|
||||||
private async beginSession(heartbeat = true) {
|
private async beginSession(heartbeat = true) {
|
||||||
if (!this.sessionStarted) {
|
if (!this.sessionStarted) {
|
||||||
this.reportOrientation();
|
this.reportOrientation();
|
||||||
window.addEventListener("resize", this.reportOrientation)
|
window.addEventListener("resize", this.reportOrientation);
|
||||||
|
|
||||||
this.lastBeat = CountlyAnalytics.getTimestamp();
|
this.lastBeat = CountlyAnalytics.getTimestamp();
|
||||||
this.sessionStarted = true;
|
this.sessionStarted = true;
|
||||||
|
@ -738,7 +724,7 @@ export class CountlyAnalytics {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.request({
|
await this.request({
|
||||||
begin_session: 1,
|
begin_session: 1,
|
||||||
metrics: JSON.stringify(this.getMetrics()),
|
metrics: JSON.stringify(this.getMetrics()),
|
||||||
user_details: JSON.stringify(userDetails),
|
user_details: JSON.stringify(userDetails),
|
||||||
|
@ -761,9 +747,11 @@ export class CountlyAnalytics {
|
||||||
if (this.sessionStarted) {
|
if (this.sessionStarted) {
|
||||||
window.removeEventListener("resize", this.reportOrientation)
|
window.removeEventListener("resize", this.reportOrientation)
|
||||||
|
|
||||||
const sec = CountlyAnalytics.getTimestamp() - this.lastBeat;
|
|
||||||
this.reportViewDuration();
|
this.reportViewDuration();
|
||||||
this.request({ end_session: 1, session_duration: sec });
|
this.request({
|
||||||
|
end_session: 1,
|
||||||
|
session_duration: CountlyAnalytics.getTimestamp() - this.lastBeat,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.sessionStarted = false;
|
this.sessionStarted = false;
|
||||||
}
|
}
|
||||||
|
@ -783,8 +771,6 @@ export class CountlyAnalytics {
|
||||||
this.inactivityCounter = 0;
|
this.inactivityCounter = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
private inactivityCounter = 0;
|
|
||||||
|
|
||||||
private trackSessions() {
|
private trackSessions() {
|
||||||
this.beginSession();
|
this.beginSession();
|
||||||
this.startTime();
|
this.startTime();
|
||||||
|
@ -797,7 +783,7 @@ export class CountlyAnalytics {
|
||||||
window.addEventListener("keydown", this.onUserActivity);
|
window.addEventListener("keydown", this.onUserActivity);
|
||||||
window.addEventListener("scroll", this.onUserActivity);
|
window.addEventListener("scroll", this.onUserActivity);
|
||||||
|
|
||||||
setInterval(() => {
|
this.activityIntervalId = setInterval(() => {
|
||||||
this.inactivityCounter++;
|
this.inactivityCounter++;
|
||||||
if (this.inactivityCounter >= INACTIVITY_TIME) {
|
if (this.inactivityCounter >= INACTIVITY_TIME) {
|
||||||
this.stopTime();
|
this.stopTime();
|
||||||
|
@ -942,10 +928,9 @@ export class CountlyAnalytics {
|
||||||
|
|
||||||
// if this event can be sent anonymously and we are disabled then dispatch it right away
|
// if this event can be sent anonymously and we are disabled then dispatch it right away
|
||||||
if (this.disabled && anonymous) {
|
if (this.disabled && anonymous) {
|
||||||
this.request({ device_id: randomString(64) });
|
await this.request({ device_id: randomString(64) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.mxCountlyAnalytics = CountlyAnalytics;
|
window.mxCountlyAnalytics = CountlyAnalytics;
|
||||||
export default CountlyAnalytics;
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ import DeviceListener from "./DeviceListener";
|
||||||
import {Jitsi} from "./widgets/Jitsi";
|
import {Jitsi} from "./widgets/Jitsi";
|
||||||
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
|
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
|
||||||
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
||||||
|
import CountlyAnalytics from "./CountlyAnalytics";
|
||||||
|
|
||||||
const HOMESERVER_URL_KEY = "mx_hs_url";
|
const HOMESERVER_URL_KEY = "mx_hs_url";
|
||||||
const ID_SERVER_URL_KEY = "mx_is_url";
|
const ID_SERVER_URL_KEY = "mx_is_url";
|
||||||
|
@ -580,6 +581,10 @@ 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(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
// logout doesn't work for guest sessions
|
// logout doesn't work for guest sessions
|
||||||
|
|
|
@ -103,7 +103,7 @@ export default class SecurityUserSettingsTab extends React.Component {
|
||||||
|
|
||||||
_updateAnalytics = (checked) => {
|
_updateAnalytics = (checked) => {
|
||||||
checked ? Analytics.enable() : Analytics.disable();
|
checked ? Analytics.enable() : Analytics.disable();
|
||||||
checked ? CountlyAnalytics.enable() : CountlyAnalytics.disable();
|
CountlyAnalytics.instance.enable(!checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onExportE2eKeysClicked = () => {
|
_onExportE2eKeysClicked = () => {
|
||||||
|
|
|
@ -1705,8 +1705,8 @@
|
||||||
"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.",
|
"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",
|
"Add comment": "Add comment",
|
||||||
"Comment": "Comment",
|
"Comment": "Comment",
|
||||||
"Feedback": "Feedback",
|
|
||||||
"There are two ways you can provide feedback and help us improve %(brand)s.": "There are two ways you can provide feedback and help us improve %(brand)s.",
|
"There are two ways you can provide feedback and help us improve %(brand)s.": "There are two ways you can provide feedback and help us improve %(brand)s.",
|
||||||
|
"Feedback": "Feedback",
|
||||||
"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>.",
|
||||||
"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.",
|
||||||
|
|
Loading…
Reference in a new issue