From de398f46f4a9ec4169ad2dbe253f4acc7eb4e800 Mon Sep 17 00:00:00 2001
From: James Salter
Date: Wed, 11 Aug 2021 16:11:10 +0100
Subject: [PATCH 01/11] initial spike
---
package.json | 2 +
src/components/structures/MatrixChat.tsx | 11 ++++
.../views/dialogs/BugReportDialog.tsx | 8 +++
.../views/elements/ErrorBoundary.tsx | 1 +
src/rageshake/rageshake.js | 2 +-
src/sentry.ts | 48 ++++++++++++++
yarn.lock | 63 +++++++++++++++++++
7 files changed, 134 insertions(+), 1 deletion(-)
create mode 100644 src/sentry.ts
diff --git a/package.json b/package.json
index 2445e3c9732..beb30e231fc 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,8 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
+ "@sentry/browser": "^6.11.0",
+ "@sentry/tracing": "^6.11.0",
"await-lock": "^2.1.0",
"blurhash": "^1.1.3",
"browser-encrypt-attachment": "^0.3.0",
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 60c78b5f9e4..e288884a5ad 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -108,6 +108,7 @@ import SoftLogout from './auth/SoftLogout';
import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
import { copyPlaintext } from "../../utils/strings";
import { PosthogAnalytics } from '../../PosthogAnalytics';
+import {initSentry, sendSentryReport} from "../../sentry";
/** constants for MatrixChat.state.view */
export enum Views {
@@ -393,6 +394,16 @@ export default class MatrixChat extends React.PureComponent {
PosthogAnalytics.instance.updatePlatformSuperProperties();
CountlyAnalytics.instance.enable(/* anonymous = */ true);
+
+ initSentry(SdkConfig.get()["sentry"]);
+ setTimeout(() => {
+ try {
+ const e = new Error("whoops");
+ throw(e);
+ } catch (e) {
+ sendSentryReport("user text", "label", e);
+ }
+ }, 4000);
}
private async postLoginSetup() {
diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx
index 3df05dac6e5..b12aecd95cd 100644
--- a/src/components/views/dialogs/BugReportDialog.tsx
+++ b/src/components/views/dialogs/BugReportDialog.tsx
@@ -29,11 +29,13 @@ import BaseDialog from "./BaseDialog";
import Field from '../elements/Field';
import Spinner from "../elements/Spinner";
import DialogButtons from "../elements/DialogButtons";
+import {sendSentryReport} from "../../../sentry";
interface IProps {
onFinished: (success: boolean) => void;
initialText?: string;
label?: string;
+ error?: Error;
}
interface IState {
@@ -113,6 +115,12 @@ export default class BugReportDialog extends React.Component {
});
}
});
+
+ // Send a Sentry report if the user agreed to send logs and if there's an error object (Sentry won't be very
+ // useful for grouping errors without exception information to aggregate with)
+ if (sendLogs) {
+ sendSentryReport(userText, this.props.label, this.props.error);
+ }
};
private onDownload = async (): Promise => {
diff --git a/src/components/views/elements/ErrorBoundary.tsx b/src/components/views/elements/ErrorBoundary.tsx
index 334e5691635..a1b67cb3472 100644
--- a/src/components/views/elements/ErrorBoundary.tsx
+++ b/src/components/views/elements/ErrorBoundary.tsx
@@ -71,6 +71,7 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> {
private onBugReport = (): void => {
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {
label: 'react-soft-crash',
+ error: this.state.error
});
};
diff --git a/src/rageshake/rageshake.js b/src/rageshake/rageshake.js
index 9512f62e429..a3a59a472b5 100644
--- a/src/rageshake/rageshake.js
+++ b/src/rageshake/rageshake.js
@@ -84,7 +84,7 @@ class ConsoleLogger {
// In development, it can be useful to log complex cyclic
// objects to the console for inspection. This is fine for
// the console, but default `stringify` can't handle that.
- // We workaround this by using a special replacer function
+ // We w orkaround this by using a special replacer function
// to only log values of the root object and avoid cycles.
return JSON.stringify(arg, (key, value) => {
if (key && typeof value === "object") {
diff --git a/src/sentry.ts b/src/sentry.ts
new file mode 100644
index 00000000000..925aa482517
--- /dev/null
+++ b/src/sentry.ts
@@ -0,0 +1,48 @@
+import * as Sentry from "@sentry/browser";
+import { Integrations } from "@sentry/tracing";
+import PlatformPeg from "./PlatformPeg";
+import SdkConfig from "./SdkConfig";
+
+export function sendSentryReport(userText: string, label: string, error: Error): void {
+ if (!SdkConfig.get()["sentry"]) return;
+
+ // Ignore reports without errors, as they're not useful in sentry and can't easily be aggregated
+ if (error) {
+ Sentry.captureException(error);
+ }
+}
+
+interface ISentryConfig {
+ dsn: string;
+ environment?: string;
+}
+
+export async function initSentry(sentryConfig: ISentryConfig): Promise {
+ if (!sentryConfig) return;
+ const platform = PlatformPeg.get();
+ let appVersion = "unknown";
+ try {
+ appVersion = await platform.getAppVersion();
+ } catch (e) {}
+
+ Sentry.init({
+ dsn: sentryConfig.dsn,
+ release: `${platform.getHumanReadableName()}@${appVersion}`,
+ environment: sentryConfig.environment,
+ defaultIntegrations: false,
+ autoSessionTracking: false,
+ debug: true,
+ integrations: [
+ // specifically disable Integrations.GlobalHandlers, which hooks uncaught exceptions - we don't
+ // want to capture those at this stage, just explicit rageshakes
+ new Sentry.Integrations.InboundFilters(),
+ new Sentry.Integrations.FunctionToString(),
+ new Sentry.Integrations.Breadcrumbs(),
+ new Sentry.Integrations.UserAgent(),
+ new Sentry.Integrations.Dedupe(),
+ ],
+ // Set to 1.0 which is reasonable if we're only submitting Rageshakes; will need to be set < 1.0
+ // if we collect more frequently.
+ tracesSampleRate: 1.0,
+ });
+}
diff --git a/yarn.lock b/yarn.lock
index a780d1ffa03..d9896fe56a5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1453,11 +1453,74 @@
tslib "^2.2.0"
webcrypto-core "^1.2.0"
+"@sentry/browser@^6.11.0":
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.11.0.tgz#9e90bbc0488ebcdd1e67937d8d5b4f13c3f6dee0"
+ integrity sha512-Qr2QRA0t5/S9QQqxzYKvM9W8prvmiWuldfwRX4hubovXzcXLgUi4WK0/H612wSbYZ4dNAEcQbtlxFWJNN4wxdg==
+ dependencies:
+ "@sentry/core" "6.11.0"
+ "@sentry/types" "6.11.0"
+ "@sentry/utils" "6.11.0"
+ tslib "^1.9.3"
+
+"@sentry/core@6.11.0":
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.11.0.tgz#40e94043afcf6407a109be26655c77832c64e740"
+ integrity sha512-09TB+f3pqEq8LFahFWHO6I/4DxHo+NcS52OkbWMDqEi6oNZRD7PhPn3i14LfjsYVv3u3AESU8oxSEGbFrr2UjQ==
+ dependencies:
+ "@sentry/hub" "6.11.0"
+ "@sentry/minimal" "6.11.0"
+ "@sentry/types" "6.11.0"
+ "@sentry/utils" "6.11.0"
+ tslib "^1.9.3"
+
+"@sentry/hub@6.11.0":
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.11.0.tgz#ddf9ddb0577d1c8290dc02c0242d274fe84d6c16"
+ integrity sha512-pT9hf+ZJfVFpoZopoC+yJmFNclr4NPqPcl2cgguqCHb69DklD1NxgBNWK8D6X05qjnNFDF991U6t1mxP9HrGuw==
+ dependencies:
+ "@sentry/types" "6.11.0"
+ "@sentry/utils" "6.11.0"
+ tslib "^1.9.3"
+
+"@sentry/minimal@6.11.0":
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.11.0.tgz#806d5512658370e40827b3e3663061db708fff33"
+ integrity sha512-XkZ7qrdlGp4IM/gjGxf1Q575yIbl5RvPbg+WFeekpo16Ufvzx37Mr8c2xsZaWosISVyE6eyFpooORjUlzy8EDw==
+ dependencies:
+ "@sentry/hub" "6.11.0"
+ "@sentry/types" "6.11.0"
+ tslib "^1.9.3"
+
+"@sentry/tracing@^6.11.0":
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.11.0.tgz#9bd9287addea1ebc12c75b226f71c7713c0fac4f"
+ integrity sha512-9VA1/SY++WeoMQI4K6n/sYgIdRtCu9NLWqmGqu/5kbOtESYFgAt1DqSyqGCr00ZjQiC2s7tkDkTNZb38K6KytQ==
+ dependencies:
+ "@sentry/hub" "6.11.0"
+ "@sentry/minimal" "6.11.0"
+ "@sentry/types" "6.11.0"
+ "@sentry/utils" "6.11.0"
+ tslib "^1.9.3"
+
+"@sentry/types@6.11.0":
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.11.0.tgz#5122685478d32ddacd3a891cbcf550012df85f7c"
+ integrity sha512-gm5H9eZhL6bsIy/h3T+/Fzzz2vINhHhqd92CjHle3w7uXdTdFV98i2pDpErBGNTSNzbntqOMifYEB5ENtZAvcg==
+
"@sentry/types@^6.10.0":
version "6.10.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.10.0.tgz#6b1f44e5ed4dbc2710bead24d1b32fb08daf04e1"
integrity sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw==
+"@sentry/utils@6.11.0":
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.11.0.tgz#d1dee4faf4d9c42c54bba88d5a66fb96b902a14c"
+ integrity sha512-IOvyFHcnbRQxa++jO+ZUzRvFHEJ1cZjrBIQaNVc0IYF0twUOB5PTP6joTcix38ldaLeapaPZ9LGfudbvYvxkdg==
+ dependencies:
+ "@sentry/types" "6.11.0"
+ tslib "^1.9.3"
+
"@sinonjs/commons@^1.7.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
From dface1928dc040632aeeafc58c34db0816f4164e Mon Sep 17 00:00:00 2001
From: James Salter
Date: Wed, 11 Aug 2021 16:49:28 +0100
Subject: [PATCH 02/11] Load all rageshake attributes
---
src/sentry.ts | 117 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 115 insertions(+), 2 deletions(-)
diff --git a/src/sentry.ts b/src/sentry.ts
index 925aa482517..bccfdaad79e 100644
--- a/src/sentry.ts
+++ b/src/sentry.ts
@@ -2,14 +2,127 @@ import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing";
import PlatformPeg from "./PlatformPeg";
import SdkConfig from "./SdkConfig";
+import {MatrixClientPeg} from "./MatrixClientPeg";
+import SettingsStore from "./settings/SettingsStore";
+import {MatrixClient} from "../../matrix-js-sdk";
-export function sendSentryReport(userText: string, label: string, error: Error): void {
+async function getStorageOptions(): Record {
+ const result = {}
+
+ // add storage persistence/quota information
+ if (navigator.storage && navigator.storage.persisted) {
+ try {
+ result["storageManager_persisted"] = String(await navigator.storage.persisted());
+ } catch (e) {}
+ } else if (document.hasStorageAccess) { // Safari
+ try {
+ result["storageManager_persisted"] = String(await document.hasStorageAccess());
+ } catch (e) {}
+ }
+ if (navigator.storage && navigator.storage.estimate) {
+ try {
+ const estimate = await navigator.storage.estimate();
+ result["storageManager_quota"] = String(estimate.quota);
+ result["storageManager_usage"] = String(estimate.usage);
+ if (estimate.usageDetails) {
+ Object.keys(estimate.usageDetails).forEach(k => {
+ result[`storageManager_usage_${k}`] = String(estimate.usageDetails[k]);
+ });
+ }
+ } catch (e) {}
+ }
+
+ return result;
+}
+
+function getUserContext(client: MatrixClient): Record {
+ return {
+ "username": client.credentials.userId,
+ "enabled_labs": getEnabledLabs(),
+ "low_bandwidth": SettingsStore.getValue("lowBandwidth") ? "enabled" : "disabled",
+ };
+}
+
+function getEnabledLabs(): string {
+ const enabledLabs = SettingsStore.getFeatureSettingNames().filter(f => SettingsStore.getValue(f));
+ if (enabledLabs.length) {
+ return enabledLabs.join(", ");
+ }
+}
+
+async function getCryptoContext(client: MatrixClient): Record {
+ if (!client.isCryptoEnabled()) {
+ return {};
+ }
+ const keys = [`ed25519:${client.getDeviceEd25519Key()}`];
+ if (client.getDeviceCurve25519Key) {
+ keys.push(`curve25519:${client.getDeviceCurve25519Key()}`);
+ }
+ const crossSigning = client.crypto.crossSigningInfo;
+ const secretStorage = client.crypto.secretStorage;
+ const pkCache = client.getCrossSigningCacheCallbacks();
+ const sessionBackupKeyFromCache = await client.crypto.getSessionBackupPrivateKey();
+
+ return {
+ "device_keys": keys.join(', '),
+ "cross_signing_ready": String(await client.isCrossSigningReady()),
+ "cross_signing_supported_by_hs":
+ String(await client.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")),
+ "cross_signing_key": crossSigning.getId(),
+ "cross_signing_privkey_in_secret_storage": String(
+ !!(await crossSigning.isStoredInSecretStorage(secretStorage))),
+ "cross_signing_master_privkey_cached": String(
+ !!(pkCache && await pkCache.getCrossSigningKeyCache("master"))),
+ "cross_signing_user_signing_privkey_cached": String(
+ !!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing"))),
+ "secret_storage_ready": String(await client.isSecretStorageReady()),
+ "secret_storage_key_in_account": String(!!(await secretStorage.hasKey())),
+ "session_backup_key_in_secret_storage": String(!!(await client.isKeyBackupKeyStored())),
+ "session_backup_key_cached": String(!!sessionBackupKeyFromCache),
+ "session_backup_key_well_formed": String(sessionBackupKeyFromCache instanceof Uint8Array),
+ };
+}
+
+function getDeviceContext(client: MatrixClient): Record {
+ const result = {
+ "device_id": client?.deviceId,
+ "mx_local_settings": localStorage.getItem('mx_local_settings'),
+ };
+
+ if (window.Modernizr) {
+ const missingFeatures = Object.keys(window.Modernizr).filter(key => window.Modernizr[key] === false);
+ if (missingFeatures.length > 0) {
+ result["modernizr_missing_features"] = missingFeatures.join(", ");
+ }
+ }
+
+ return result;
+}
+
+async function getContext() {
+ const client = MatrixClientPeg.get();
+ return {
+ "contexts": {
+ "user": getUserContext(client),
+ "crypto": await getCryptoContext(client),
+ "device": getDeviceContext(client),
+ "storage": await getStorageOptions()
+ },
+ "extra": {
+
+ },
+ };
+}
+
+export async function sendSentryReport(userText: string, label: string, error: Error): void {
if (!SdkConfig.get()["sentry"]) return;
// Ignore reports without errors, as they're not useful in sentry and can't easily be aggregated
if (error) {
- Sentry.captureException(error);
+ Sentry.captureException(error, await getContext());
}
+
+ // TODO: use https://docs.sentry.io/api/projects/submit-user-feedback/ to submit userText
}
interface ISentryConfig {
From c6202bf6533995e96a4d1062733950c9e6298da5 Mon Sep 17 00:00:00 2001
From: James Salter
Date: Wed, 11 Aug 2021 16:50:33 +0100
Subject: [PATCH 03/11] lint
---
src/sentry.ts | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/sentry.ts b/src/sentry.ts
index bccfdaad79e..31f56e9db50 100644
--- a/src/sentry.ts
+++ b/src/sentry.ts
@@ -1,13 +1,12 @@
import * as Sentry from "@sentry/browser";
-import { Integrations } from "@sentry/tracing";
import PlatformPeg from "./PlatformPeg";
import SdkConfig from "./SdkConfig";
-import {MatrixClientPeg} from "./MatrixClientPeg";
+import { MatrixClientPeg } from "./MatrixClientPeg";
import SettingsStore from "./settings/SettingsStore";
-import {MatrixClient} from "../../matrix-js-sdk";
+import { MatrixClient } from "../../matrix-js-sdk";
async function getStorageOptions(): Record {
- const result = {}
+ const result = {};
// add storage persistence/quota information
if (navigator.storage && navigator.storage.persisted) {
@@ -106,7 +105,7 @@ async function getContext() {
"user": getUserContext(client),
"crypto": await getCryptoContext(client),
"device": getDeviceContext(client),
- "storage": await getStorageOptions()
+ "storage": await getStorageOptions(),
},
"extra": {
From ba1618812c0a1b2d0810ae484833a3a44c8d9235 Mon Sep 17 00:00:00 2001
From: James Salter
Date: Wed, 11 Aug 2021 17:19:15 +0100
Subject: [PATCH 04/11] Send user text as context
---
.../views/dialogs/BugReportDialog.tsx | 8 +---
.../views/messages/TileErrorBoundary.tsx | 1 +
src/sentry.ts | 37 +++++++++++--------
3 files changed, 24 insertions(+), 22 deletions(-)
diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx
index b12aecd95cd..08d295d8b7b 100644
--- a/src/components/views/dialogs/BugReportDialog.tsx
+++ b/src/components/views/dialogs/BugReportDialog.tsx
@@ -29,7 +29,7 @@ import BaseDialog from "./BaseDialog";
import Field from '../elements/Field';
import Spinner from "../elements/Spinner";
import DialogButtons from "../elements/DialogButtons";
-import {sendSentryReport} from "../../../sentry";
+import { sendSentryReport } from "../../../sentry";
interface IProps {
onFinished: (success: boolean) => void;
@@ -116,11 +116,7 @@ export default class BugReportDialog extends React.Component {
}
});
- // Send a Sentry report if the user agreed to send logs and if there's an error object (Sentry won't be very
- // useful for grouping errors without exception information to aggregate with)
- if (sendLogs) {
- sendSentryReport(userText, this.props.label, this.props.error);
- }
+ sendSentryReport(userText, this.state.issueUrl, this.props.error);
};
private onDownload = async (): Promise => {
diff --git a/src/components/views/messages/TileErrorBoundary.tsx b/src/components/views/messages/TileErrorBoundary.tsx
index c61771f3964..a15806ae0ca 100644
--- a/src/components/views/messages/TileErrorBoundary.tsx
+++ b/src/components/views/messages/TileErrorBoundary.tsx
@@ -51,6 +51,7 @@ export default class TileErrorBoundary extends React.Component {
private onBugReport = (): void => {
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {
label: 'react-soft-crash-tile',
+ error: this.state.error,
});
};
diff --git a/src/sentry.ts b/src/sentry.ts
index 31f56e9db50..841eac2f24e 100644
--- a/src/sentry.ts
+++ b/src/sentry.ts
@@ -98,30 +98,35 @@ function getDeviceContext(client: MatrixClient): Record {
return result;
}
-async function getContext() {
+async function getContexts() {
const client = MatrixClientPeg.get();
return {
- "contexts": {
- "user": getUserContext(client),
- "crypto": await getCryptoContext(client),
- "device": getDeviceContext(client),
- "storage": await getStorageOptions(),
- },
- "extra": {
-
- },
+ "user": getUserContext(client),
+ "crypto": await getCryptoContext(client),
+ "device": getDeviceContext(client),
+ "storage": await getStorageOptions(),
};
}
-export async function sendSentryReport(userText: string, label: string, error: Error): void {
- if (!SdkConfig.get()["sentry"]) return;
+export async function sendSentryReport(userText: string, issueUrl: string, error: Error): void {
+ const sentryConfig = SdkConfig.get()["sentry"];
+ if (!sentryConfig) return;
+
+ const captureContext = {
+ "contexts": await getContexts(),
+ "extra": {
+ "userText": userText,
+ "issue_url": issueUrl,
+ },
+ };
- // Ignore reports without errors, as they're not useful in sentry and can't easily be aggregated
+ // If there's no error and no issueUrl, the report will just produce non-grouped noise in Sentry, so don't
+ // upload it
if (error) {
- Sentry.captureException(error, await getContext());
+ Sentry.captureException(error, captureContext);
+ } else if (issueUrl) {
+ Sentry.captureMessage(`Issue: ${issueUrl}`, captureContext);
}
-
- // TODO: use https://docs.sentry.io/api/projects/submit-user-feedback/ to submit userText
}
interface ISentryConfig {
From cc9ddb351f9e2c6c6ed75ca198783bcd3b0b6cbe Mon Sep 17 00:00:00 2001
From: James Salter
Date: Wed, 11 Aug 2021 17:47:54 +0100
Subject: [PATCH 05/11] Remove test error on startup, and send correct user
text
---
src/components/structures/MatrixChat.tsx | 10 +---------
src/components/views/dialogs/BugReportDialog.tsx | 2 +-
src/rageshake/rageshake.js | 2 +-
src/sentry.ts | 2 +-
4 files changed, 4 insertions(+), 12 deletions(-)
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index e288884a5ad..7b3fe260d70 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -108,7 +108,7 @@ import SoftLogout from './auth/SoftLogout';
import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
import { copyPlaintext } from "../../utils/strings";
import { PosthogAnalytics } from '../../PosthogAnalytics';
-import {initSentry, sendSentryReport} from "../../sentry";
+import { initSentry, sendSentryReport } from "../../sentry";
/** constants for MatrixChat.state.view */
export enum Views {
@@ -396,14 +396,6 @@ export default class MatrixChat extends React.PureComponent {
CountlyAnalytics.instance.enable(/* anonymous = */ true);
initSentry(SdkConfig.get()["sentry"]);
- setTimeout(() => {
- try {
- const e = new Error("whoops");
- throw(e);
- } catch (e) {
- sendSentryReport("user text", "label", e);
- }
- }, 4000);
}
private async postLoginSetup() {
diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx
index 08d295d8b7b..cdb463976ac 100644
--- a/src/components/views/dialogs/BugReportDialog.tsx
+++ b/src/components/views/dialogs/BugReportDialog.tsx
@@ -116,7 +116,7 @@ export default class BugReportDialog extends React.Component {
}
});
- sendSentryReport(userText, this.state.issueUrl, this.props.error);
+ sendSentryReport(this.state.text, this.state.issueUrl, this.props.error);
};
private onDownload = async (): Promise => {
diff --git a/src/rageshake/rageshake.js b/src/rageshake/rageshake.js
index a3a59a472b5..9512f62e429 100644
--- a/src/rageshake/rageshake.js
+++ b/src/rageshake/rageshake.js
@@ -84,7 +84,7 @@ class ConsoleLogger {
// In development, it can be useful to log complex cyclic
// objects to the console for inspection. This is fine for
// the console, but default `stringify` can't handle that.
- // We w orkaround this by using a special replacer function
+ // We workaround this by using a special replacer function
// to only log values of the root object and avoid cycles.
return JSON.stringify(arg, (key, value) => {
if (key && typeof value === "object") {
diff --git a/src/sentry.ts b/src/sentry.ts
index 841eac2f24e..26a1ffc2e90 100644
--- a/src/sentry.ts
+++ b/src/sentry.ts
@@ -115,7 +115,7 @@ export async function sendSentryReport(userText: string, issueUrl: string, error
const captureContext = {
"contexts": await getContexts(),
"extra": {
- "userText": userText,
+ "user_text": userText,
"issue_url": issueUrl,
},
};
From 065a70b63fad007d171fa85411e2bf284bf59af0 Mon Sep 17 00:00:00 2001
From: James Salter
Date: Thu, 12 Aug 2021 17:46:28 +0100
Subject: [PATCH 06/11] Add copyright header
---
src/sentry.ts | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/src/sentry.ts b/src/sentry.ts
index 26a1ffc2e90..94ee86838c9 100644
--- a/src/sentry.ts
+++ b/src/sentry.ts
@@ -1,3 +1,19 @@
+/*
+Copyright 2021 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 * as Sentry from "@sentry/browser";
import PlatformPeg from "./PlatformPeg";
import SdkConfig from "./SdkConfig";
From 16d02f24bd7269c8be6d833a058b1a8fedb4ed0e Mon Sep 17 00:00:00 2001
From: James Salter
Date: Mon, 16 Aug 2021 13:06:19 +0100
Subject: [PATCH 07/11] lint
---
src/components/structures/MatrixChat.tsx | 2 +-
src/components/views/elements/ErrorBoundary.tsx | 2 +-
src/components/views/rooms/EventTile.tsx | 2 ++
src/sentry.ts | 10 +++++-----
4 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 7b3fe260d70..902d2a0921a 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -108,7 +108,7 @@ import SoftLogout from './auth/SoftLogout';
import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
import { copyPlaintext } from "../../utils/strings";
import { PosthogAnalytics } from '../../PosthogAnalytics';
-import { initSentry, sendSentryReport } from "../../sentry";
+import { initSentry } from "../../sentry";
/** constants for MatrixChat.state.view */
export enum Views {
diff --git a/src/components/views/elements/ErrorBoundary.tsx b/src/components/views/elements/ErrorBoundary.tsx
index a1b67cb3472..926d3da30c7 100644
--- a/src/components/views/elements/ErrorBoundary.tsx
+++ b/src/components/views/elements/ErrorBoundary.tsx
@@ -71,7 +71,7 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> {
private onBugReport = (): void => {
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {
label: 'react-soft-crash',
- error: this.state.error
+ error: this.state.error,
});
};
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index 884d0045510..1bf65bf202f 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -451,6 +451,8 @@ export default class EventTile extends React.Component {
client.on("Room.receipt", this.onRoomReceipt);
this.isListeningForReceipts = true;
}
+
+ throw new Error("oops");
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
diff --git a/src/sentry.ts b/src/sentry.ts
index 94ee86838c9..3cded20a838 100644
--- a/src/sentry.ts
+++ b/src/sentry.ts
@@ -21,7 +21,7 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
import SettingsStore from "./settings/SettingsStore";
import { MatrixClient } from "../../matrix-js-sdk";
-async function getStorageOptions(): Record {
+async function getStorageOptions(): Promise> {
const result = {};
// add storage persistence/quota information
@@ -65,7 +65,7 @@ function getEnabledLabs(): string {
}
}
-async function getCryptoContext(client: MatrixClient): Record {
+async function getCryptoContext(client: MatrixClient): Promise> {
if (!client.isCryptoEnabled()) {
return {};
}
@@ -98,7 +98,7 @@ async function getCryptoContext(client: MatrixClient): Record {
};
}
-function getDeviceContext(client: MatrixClient): Record {
+function getDeviceContext(client: MatrixClient): Record {
const result = {
"device_id": client?.deviceId,
"mx_local_settings": localStorage.getItem('mx_local_settings'),
@@ -114,7 +114,7 @@ function getDeviceContext(client: MatrixClient): Record {
return result;
}
-async function getContexts() {
+async function getContexts(): Promise> {
const client = MatrixClientPeg.get();
return {
"user": getUserContext(client),
@@ -124,7 +124,7 @@ async function getContexts() {
};
}
-export async function sendSentryReport(userText: string, issueUrl: string, error: Error): void {
+export async function sendSentryReport(userText: string, issueUrl: string, error: Error): Promise {
const sentryConfig = SdkConfig.get()["sentry"];
if (!sentryConfig) return;
From 6c2e98812bd7189eae3dc8317126a53b15d9e357 Mon Sep 17 00:00:00 2001
From: James Salter
Date: Mon, 16 Aug 2021 13:26:46 +0100
Subject: [PATCH 08/11] Remove test code; good job we have tests
---
src/components/views/rooms/EventTile.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index 1bf65bf202f..884d0045510 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -451,8 +451,6 @@ export default class EventTile extends React.Component {
client.on("Room.receipt", this.onRoomReceipt);
this.isListeningForReceipts = true;
}
-
- throw new Error("oops");
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
From 0d9e27f34a9b338b47f1bf657f257d620c152566 Mon Sep 17 00:00:00 2001
From: James Salter
Date: Tue, 17 Aug 2021 15:58:14 +0100
Subject: [PATCH 09/11] Fix import
---
src/sentry.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/sentry.ts b/src/sentry.ts
index 3cded20a838..b0fd00cfbe2 100644
--- a/src/sentry.ts
+++ b/src/sentry.ts
@@ -19,7 +19,7 @@ import PlatformPeg from "./PlatformPeg";
import SdkConfig from "./SdkConfig";
import { MatrixClientPeg } from "./MatrixClientPeg";
import SettingsStore from "./settings/SettingsStore";
-import { MatrixClient } from "../../matrix-js-sdk";
+import { MatrixClient } from "matrix-js-sdk";
async function getStorageOptions(): Promise> {
const result = {};
From ecd142f2a5503a7f7ed4fdaa47dd0b3f2dd28e10 Mon Sep 17 00:00:00 2001
From: James Salter
Date: Wed, 18 Aug 2021 09:21:57 +0100
Subject: [PATCH 10/11] Add type declarations
---
src/sentry.ts | 62 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 55 insertions(+), 7 deletions(-)
diff --git a/src/sentry.ts b/src/sentry.ts
index b0fd00cfbe2..59152f66f2f 100644
--- a/src/sentry.ts
+++ b/src/sentry.ts
@@ -21,7 +21,52 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
import SettingsStore from "./settings/SettingsStore";
import { MatrixClient } from "matrix-js-sdk";
-async function getStorageOptions(): Promise> {
+/* eslint-disable camelcase */
+
+type StorageContext = {
+ storageManager_persisted?: string;
+ storageManager_quota?: string;
+ storageManager_usage?: string;
+ storageManager_usageDetails?: string;
+};
+
+type UserContext = {
+ username: string;
+ enabled_labs: string;
+ low_bandwidth: string;
+};
+
+type CryptoContext = {
+ device_keys?: string;
+ cross_signing_ready?: string;
+ cross_signing_supported_by_hs?: string;
+ cross_signing_key?: string;
+ cross_signing_privkey_in_secret_storage?: string;
+ cross_signing_master_privkey_cached?: string;
+ cross_signing_user_signing_privkey_cached?: string;
+ secret_storage_ready?: string;
+ secret_storage_key_in_account?: string;
+ session_backup_key_in_secret_storage?: string;
+ session_backup_key_cached?: string;
+ session_backup_key_well_formed?: string;
+};
+
+type DeviceContext = {
+ device_id: string;
+ mx_local_settings: string;
+ modernizr_missing_features?: string;
+};
+
+type Contexts = {
+ user: UserContext;
+ crypto: CryptoContext;
+ device: DeviceContext;
+ storage: StorageContext;
+};
+
+/* eslint-enable camelcase */
+
+async function getStorageContext(): Promise {
const result = {};
// add storage persistence/quota information
@@ -40,9 +85,11 @@ async function getStorageOptions(): Promise> {
result["storageManager_quota"] = String(estimate.quota);
result["storageManager_usage"] = String(estimate.usage);
if (estimate.usageDetails) {
+ const usageDetails = [];
Object.keys(estimate.usageDetails).forEach(k => {
- result[`storageManager_usage_${k}`] = String(estimate.usageDetails[k]);
+ usageDetails.push(`${k}: ${String(estimate.usageDetails[k])}`);
});
+ result[`storageManager_usage`] = usageDetails.join(", ");
}
} catch (e) {}
}
@@ -50,7 +97,7 @@ async function getStorageOptions(): Promise> {
return result;
}
-function getUserContext(client: MatrixClient): Record {
+function getUserContext(client: MatrixClient): UserContext {
return {
"username": client.credentials.userId,
"enabled_labs": getEnabledLabs(),
@@ -63,9 +110,10 @@ function getEnabledLabs(): string {
if (enabledLabs.length) {
return enabledLabs.join(", ");
}
+ return "";
}
-async function getCryptoContext(client: MatrixClient): Promise> {
+async function getCryptoContext(client: MatrixClient): Promise {
if (!client.isCryptoEnabled()) {
return {};
}
@@ -98,7 +146,7 @@ async function getCryptoContext(client: MatrixClient): Promise {
+function getDeviceContext(client: MatrixClient): DeviceContext {
const result = {
"device_id": client?.deviceId,
"mx_local_settings": localStorage.getItem('mx_local_settings'),
@@ -114,13 +162,13 @@ function getDeviceContext(client: MatrixClient): Record {
return result;
}
-async function getContexts(): Promise> {
+async function getContexts(): Promise {
const client = MatrixClientPeg.get();
return {
"user": getUserContext(client),
"crypto": await getCryptoContext(client),
"device": getDeviceContext(client),
- "storage": await getStorageOptions(),
+ "storage": await getStorageContext(),
};
}
From 23f70836b613dbe7142a1ac573c8a42875ded493 Mon Sep 17 00:00:00 2001
From: James Salter
Date: Wed, 18 Aug 2021 17:05:15 +0100
Subject: [PATCH 11/11] Update copy to indicate debug logs contain which UI
elements you last interacted with
---
src/components/views/dialogs/BugReportDialog.tsx | 4 ++--
src/components/views/elements/ErrorBoundary.tsx | 5 +++--
.../views/settings/tabs/user/HelpUserSettingsTab.tsx | 3 ++-
src/i18n/strings/en_EN.json | 4 ++--
4 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx
index cdb463976ac..8f22c7ca9a4 100644
--- a/src/components/views/dialogs/BugReportDialog.tsx
+++ b/src/components/views/dialogs/BugReportDialog.tsx
@@ -204,8 +204,8 @@ export default class BugReportDialog extends React.Component {
{ _t(
"Debug logs contain application usage data including your " +
"username, the IDs or aliases of the rooms or groups you " +
- "have visited and the usernames of other users. They do " +
- "not contain messages.",
+ "have visited, which UI elements you last interacted with, " +
+ "and the usernames of other users. They do not contain messages.",
) }
diff --git a/src/components/views/elements/ErrorBoundary.tsx b/src/components/views/elements/ErrorBoundary.tsx
index 926d3da30c7..03d331bd9f6 100644
--- a/src/components/views/elements/ErrorBoundary.tsx
+++ b/src/components/views/elements/ErrorBoundary.tsx
@@ -94,8 +94,9 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> {
"If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
- "the rooms or groups you have visited and the usernames of " +
- "other users. They do not contain messages.",
+ "the rooms or groups you have visited, which UI elements you " +
+ "last interacted with, and the usernames of other users. " +
+ "They do not contain messages.",
) }
{ _t("Submit debug logs") }
diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
index 904fdf09140..6984ccc6f30 100644
--- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
@@ -268,7 +268,8 @@ export default class HelpUserSettingsTab extends React.Component
"If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
- "the rooms or groups you have visited and the usernames of " +
+ "the rooms or groups you have visited, which UI elements you " +
+ "last interacted with, and the usernames of " +
"other users. They do not contain messages.",
) }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index bf352a6294f..e3b1978bee8 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1291,7 +1291,7 @@
"For help with using %(brand)s, click
here or start a chat with our bot using the button below.": "For help with using %(brand)s, click
here or start a chat with our bot using the button below.",
"Chat with %(brand)s Bot": "Chat with %(brand)s Bot",
"Bug reporting": "Bug reporting",
- "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
+ "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.",
"Submit debug logs": "Submit debug logs",
"To report a Matrix-related security issue, please read the Matrix.org
Security Disclosure Policy.": "To report a Matrix-related security issue, please read the Matrix.org
Security Disclosure Policy.",
"Help & About": "Help & About",
@@ -2168,7 +2168,7 @@
"Failed to send logs: ": "Failed to send logs: ",
"Preparing to download logs": "Preparing to download logs",
"Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Reminder: Your browser is unsupported, so your experience may be unpredictable.",
- "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
+ "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.",
"Before submitting logs, you must
create a GitHub issue to describe your problem.": "Before submitting logs, you must
create a GitHub issue to describe your problem.",
"Download logs": "Download logs",
"GitHub issue": "GitHub issue",