Files
ThreadNet-Web/apps/web/src/sentry.ts
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

219 lines
7.3 KiB
TypeScript
Raw Normal View History

2021-08-12 17:46:28 +01:00
/*
2024-09-09 14:57:16 +01:00
Copyright 2024 New Vector Ltd.
2021-08-12 17:46:28 +01:00
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
2024-09-09 14:57:16 +01:00
Please see LICENSE files in the repository root for full details.
2021-08-12 17:46:28 +01:00
*/
2021-08-11 16:11:10 +01:00
import * as Sentry from "@sentry/browser";
2025-02-05 13:25:06 +00:00
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
2021-10-22 17:23:32 -05:00
2021-08-11 16:11:10 +01:00
import SdkConfig from "./SdkConfig";
2021-08-11 16:50:33 +01:00
import { MatrixClientPeg } from "./MatrixClientPeg";
2021-08-11 16:49:28 +01:00
import SettingsStore from "./settings/SettingsStore";
2025-02-05 13:25:06 +00:00
import { type IConfigOptions } from "./IConfigOptions";
2021-08-11 16:11:10 +01:00
2021-08-18 09:21:57 +01:00
/* 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 = {
crypto_version?: string;
2021-08-18 09:21:57 +01:00
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 | null;
2021-08-18 09:21:57 +01:00
modernizr_missing_features?: string;
};
type Contexts = {
user: UserContext;
crypto: CryptoContext;
device: DeviceContext;
storage: StorageContext;
};
/* eslint-enable camelcase */
async function getStorageContext(): Promise<StorageContext> {
2023-02-13 11:39:16 +00:00
const result: StorageContext = {};
2021-08-11 16:49:28 +01:00
// add storage persistence/quota information
if (navigator.storage && navigator.storage.persisted) {
try {
result["storageManager_persisted"] = String(await navigator.storage.persisted());
2024-10-16 16:43:07 +01:00
} catch {}
2021-08-11 16:49:28 +01:00
} else if (document.hasStorageAccess) {
// Safari
try {
result["storageManager_persisted"] = String(await document.hasStorageAccess());
2024-10-16 16:43:07 +01:00
} catch {}
2021-08-11 16:49:28 +01:00
}
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) {
2023-02-13 11:39:16 +00:00
const usageDetails: string[] = [];
2021-08-11 16:49:28 +01:00
Object.keys(estimate.usageDetails).forEach((k) => {
usageDetails.push(`${k}: ${String(estimate.usageDetails![k])}`);
2021-08-11 16:49:28 +01:00
});
2021-08-18 09:21:57 +01:00
result[`storageManager_usage`] = usageDetails.join(", ");
2021-08-11 16:49:28 +01:00
}
2024-10-16 16:43:07 +01:00
} catch {}
2021-08-11 16:49:28 +01:00
}
return result;
}
2021-08-18 09:21:57 +01:00
function getUserContext(client: MatrixClient): UserContext {
2021-08-11 16:49:28 +01:00
return {
username: client.credentials.userId!,
2021-08-11 16:49:28 +01:00
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(", ");
}
2021-08-18 09:21:57 +01:00
return "";
2021-08-11 16:49:28 +01:00
}
2021-08-18 09:21:57 +01:00
async function getCryptoContext(client: MatrixClient): Promise<CryptoContext> {
const cryptoApi = client.getCrypto();
if (!cryptoApi) {
2021-08-11 16:49:28 +01:00
return {};
}
const ownDeviceKeys = await cryptoApi.getOwnDeviceKeys();
const keys = [`curve25519:${ownDeviceKeys.curve25519}`, `ed25519:${ownDeviceKeys.ed25519}`];
const crossSigningStatus = await cryptoApi.getCrossSigningStatus();
const secretStorage = client.secretStorage;
const sessionBackupKeyFromCache = await cryptoApi.getSessionBackupPrivateKey();
2021-08-11 16:49:28 +01:00
return {
crypto_version: cryptoApi.getVersion(),
2021-08-11 16:49:28 +01:00
device_keys: keys.join(", "),
cross_signing_ready: String(await cryptoApi.isCrossSigningReady()),
cross_signing_key: (await cryptoApi.getCrossSigningKeyId()) ?? undefined,
cross_signing_privkey_in_secret_storage: String(crossSigningStatus.privateKeysInSecretStorage),
cross_signing_master_privkey_cached: String(crossSigningStatus.privateKeysCachedLocally.masterKey),
cross_signing_user_signing_privkey_cached: String(crossSigningStatus.privateKeysCachedLocally.userSigningKey),
secret_storage_ready: String(await cryptoApi.isSecretStorageReady()),
2023-08-15 09:43:15 +01:00
secret_storage_key_in_account: String(await secretStorage.hasKey()),
2021-08-11 16:49:28 +01:00
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),
};
}
2021-08-18 09:21:57 +01:00
function getDeviceContext(client: MatrixClient): DeviceContext {
2023-02-13 11:39:16 +00:00
const result: DeviceContext = {
device_id: client?.deviceId ?? undefined,
mx_local_settings: SettingsStore.exportForRageshake(),
2021-08-11 16:49:28 +01:00
};
if (window.Modernizr) {
2023-02-13 11:39:16 +00:00
const missingFeatures = Object.keys(window.Modernizr).filter(
(key) => window.Modernizr[key as keyof ModernizrStatic] === false,
);
2021-08-11 16:49:28 +01:00
if (missingFeatures.length > 0) {
result["modernizr_missing_features"] = missingFeatures.join(", ");
}
}
return result;
}
2021-08-18 09:21:57 +01:00
async function getContexts(): Promise<Contexts> {
const client = MatrixClientPeg.safeGet();
2021-08-11 16:49:28 +01:00
return {
2021-08-11 17:19:15 +01:00
user: getUserContext(client),
crypto: await getCryptoContext(client),
device: getDeviceContext(client),
2021-08-18 09:21:57 +01:00
storage: await getStorageContext(),
2021-08-11 16:49:28 +01:00
};
}
export async function sendSentryReport(userText: string, issueUrl: string, error?: unknown): Promise<void> {
const sentryConfig = SdkConfig.getObject("sentry");
2021-08-11 17:19:15 +01:00
if (!sentryConfig) return;
2021-08-11 16:11:10 +01:00
2021-08-11 17:19:15 +01:00
const captureContext = {
contexts: await getContexts(),
extra: {
user_text: userText,
2021-08-11 17:19:15 +01:00
issue_url: issueUrl,
},
};
// If there's no error and no issueUrl, the report will just produce non-grouped noise in Sentry, so don't
// upload it
2021-08-11 16:11:10 +01:00
if (error) {
2021-08-11 17:19:15 +01:00
Sentry.captureException(error, captureContext);
} else if (issueUrl) {
Sentry.captureMessage(`Issue: ${issueUrl}`, captureContext);
2021-08-11 16:11:10 +01:00
}
}
2021-10-29 09:34:25 +01:00
export function setSentryUser(mxid: string): void {
if (SdkConfig.get().sentry) {
Sentry.setUser({ username: mxid });
}
2021-10-29 09:34:25 +01:00
}
export async function initSentry(sentryConfig: IConfigOptions["sentry"]): Promise<void> {
2021-08-11 16:11:10 +01:00
if (!sentryConfig) return;
2024-11-05 18:35:53 +00:00
const integrations = [
Sentry.inboundFiltersIntegration(),
Sentry.functionToStringIntegration(),
Sentry.breadcrumbsIntegration(),
Sentry.httpContextIntegration(),
Sentry.dedupeIntegration(),
2021-10-29 09:34:25 +01:00
];
2021-08-11 16:11:10 +01:00
Sentry.init({
dsn: sentryConfig.dsn,
release: process.env.VERSION,
2021-08-11 16:11:10 +01:00
environment: sentryConfig.environment,
defaultIntegrations: false,
2021-10-29 09:34:25 +01:00
integrations,
2021-08-11 16:11:10 +01:00
// 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,
});
}
window.mxSendSentryReport = sendSentryReport;