2021-08-12 16:46:28 +00:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2021-08-11 15:11:10 +00:00
|
|
|
import * as Sentry from "@sentry/browser";
|
2023-08-09 07:18:41 +00:00
|
|
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
2021-10-22 22:23:32 +00:00
|
|
|
|
2021-08-11 15:11:10 +00:00
|
|
|
import SdkConfig from "./SdkConfig";
|
2021-08-11 15:50:33 +00:00
|
|
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
2021-08-11 15:49:28 +00:00
|
|
|
import SettingsStore from "./settings/SettingsStore";
|
2022-03-18 16:12:36 +00:00
|
|
|
import { IConfigOptions } from "./IConfigOptions";
|
2021-08-11 15:11:10 +00:00
|
|
|
|
2021-08-18 08:21:57 +00: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 = {
|
2023-10-26 15:45:05 +00:00
|
|
|
crypto_version?: string;
|
2021-08-18 08:21:57 +00: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 = {
|
2023-02-24 15:28:40 +00:00
|
|
|
device_id?: string;
|
|
|
|
mx_local_settings: string | null;
|
2021-08-18 08:21:57 +00: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 15:49:28 +00:00
|
|
|
|
|
|
|
// 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) {
|
2023-02-13 11:39:16 +00:00
|
|
|
const usageDetails: string[] = [];
|
2021-08-11 15:49:28 +00:00
|
|
|
Object.keys(estimate.usageDetails).forEach((k) => {
|
2023-03-07 13:19:18 +00:00
|
|
|
usageDetails.push(`${k}: ${String(estimate.usageDetails![k])}`);
|
2021-08-11 15:49:28 +00:00
|
|
|
});
|
2021-08-18 08:21:57 +00:00
|
|
|
result[`storageManager_usage`] = usageDetails.join(", ");
|
2021-08-11 15:49:28 +00:00
|
|
|
}
|
|
|
|
} catch (e) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-08-18 08:21:57 +00:00
|
|
|
function getUserContext(client: MatrixClient): UserContext {
|
2021-08-11 15:49:28 +00:00
|
|
|
return {
|
2023-02-13 17:01:43 +00:00
|
|
|
username: client.credentials.userId!,
|
2021-08-11 15:49:28 +00: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 08:21:57 +00:00
|
|
|
return "";
|
2021-08-11 15:49:28 +00:00
|
|
|
}
|
|
|
|
|
2021-08-18 08:21:57 +00:00
|
|
|
async function getCryptoContext(client: MatrixClient): Promise<CryptoContext> {
|
2023-10-26 15:45:05 +00:00
|
|
|
const cryptoApi = client.getCrypto();
|
|
|
|
if (!cryptoApi) {
|
2021-08-11 15:49:28 +00:00
|
|
|
return {};
|
|
|
|
}
|
2024-01-16 08:59:14 +00:00
|
|
|
|
|
|
|
const ownDeviceKeys = await cryptoApi.getOwnDeviceKeys();
|
|
|
|
|
|
|
|
const keys = [`curve25519:${ownDeviceKeys.curve25519}`, `ed25519:${ownDeviceKeys.ed25519}`];
|
|
|
|
|
2023-10-26 15:45:05 +00:00
|
|
|
const crossSigningStatus = await cryptoApi.getCrossSigningStatus();
|
|
|
|
const secretStorage = client.secretStorage;
|
|
|
|
const sessionBackupKeyFromCache = await cryptoApi.getSessionBackupPrivateKey();
|
2021-08-11 15:49:28 +00:00
|
|
|
|
|
|
|
return {
|
2023-10-26 15:45:05 +00:00
|
|
|
crypto_version: cryptoApi.getVersion(),
|
2021-08-11 15:49:28 +00:00
|
|
|
device_keys: keys.join(", "),
|
2023-10-26 15:45:05 +00:00
|
|
|
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 08:43:15 +00:00
|
|
|
secret_storage_key_in_account: String(await secretStorage.hasKey()),
|
2021-08-11 15:49:28 +00: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 08:21:57 +00:00
|
|
|
function getDeviceContext(client: MatrixClient): DeviceContext {
|
2023-02-13 11:39:16 +00:00
|
|
|
const result: DeviceContext = {
|
2023-03-07 13:19:18 +00:00
|
|
|
device_id: client?.deviceId ?? undefined,
|
2021-08-11 15:49:28 +00:00
|
|
|
mx_local_settings: localStorage.getItem("mx_local_settings"),
|
|
|
|
};
|
|
|
|
|
|
|
|
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 15:49:28 +00:00
|
|
|
if (missingFeatures.length > 0) {
|
|
|
|
result["modernizr_missing_features"] = missingFeatures.join(", ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-08-18 08:21:57 +00:00
|
|
|
async function getContexts(): Promise<Contexts> {
|
2023-06-21 16:29:44 +00:00
|
|
|
const client = MatrixClientPeg.safeGet();
|
2021-08-11 15:49:28 +00:00
|
|
|
return {
|
2021-08-11 16:19:15 +00:00
|
|
|
user: getUserContext(client),
|
|
|
|
crypto: await getCryptoContext(client),
|
|
|
|
device: getDeviceContext(client),
|
2021-08-18 08:21:57 +00:00
|
|
|
storage: await getStorageContext(),
|
2021-08-11 15:49:28 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-07-07 13:46:12 +00:00
|
|
|
export async function sendSentryReport(userText: string, issueUrl: string, error?: unknown): Promise<void> {
|
2022-03-18 16:12:36 +00:00
|
|
|
const sentryConfig = SdkConfig.getObject("sentry");
|
2021-08-11 16:19:15 +00:00
|
|
|
if (!sentryConfig) return;
|
|
|
|
|
|
|
|
const captureContext = {
|
|
|
|
contexts: await getContexts(),
|
|
|
|
extra: {
|
2021-08-11 16:47:54 +00:00
|
|
|
user_text: userText,
|
2021-08-11 16:19:15 +00:00
|
|
|
issue_url: issueUrl,
|
|
|
|
},
|
|
|
|
};
|
2021-08-11 15:11:10 +00:00
|
|
|
|
2021-08-11 16:19:15 +00:00
|
|
|
// 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 15:11:10 +00:00
|
|
|
if (error) {
|
2021-08-11 16:19:15 +00:00
|
|
|
Sentry.captureException(error, captureContext);
|
|
|
|
} else if (issueUrl) {
|
|
|
|
Sentry.captureMessage(`Issue: ${issueUrl}`, captureContext);
|
2021-08-11 15:11:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 08:34:25 +00:00
|
|
|
export function setSentryUser(mxid: string): void {
|
|
|
|
if (!SdkConfig.get().sentry || !SettingsStore.getValue("automaticErrorReporting")) return;
|
|
|
|
Sentry.setUser({ username: mxid });
|
|
|
|
}
|
|
|
|
|
2022-03-18 16:12:36 +00:00
|
|
|
export async function initSentry(sentryConfig: IConfigOptions["sentry"]): Promise<void> {
|
2021-08-11 15:11:10 +00:00
|
|
|
if (!sentryConfig) return;
|
2021-10-29 08:34:25 +00:00
|
|
|
// Only enable Integrations.GlobalHandlers, which hooks uncaught exceptions, if automaticErrorReporting is true
|
|
|
|
const integrations = [
|
|
|
|
new Sentry.Integrations.InboundFilters(),
|
|
|
|
new Sentry.Integrations.FunctionToString(),
|
|
|
|
new Sentry.Integrations.Breadcrumbs(),
|
2022-12-05 11:18:50 +00:00
|
|
|
new Sentry.Integrations.HttpContext(),
|
2021-10-29 08:34:25 +00:00
|
|
|
new Sentry.Integrations.Dedupe(),
|
|
|
|
];
|
|
|
|
|
|
|
|
if (SettingsStore.getValue("automaticErrorReporting")) {
|
|
|
|
integrations.push(new Sentry.Integrations.GlobalHandlers({ onerror: false, onunhandledrejection: true }));
|
|
|
|
integrations.push(new Sentry.Integrations.TryCatch());
|
|
|
|
}
|
|
|
|
|
2021-08-11 15:11:10 +00:00
|
|
|
Sentry.init({
|
|
|
|
dsn: sentryConfig.dsn,
|
2021-10-22 14:06:01 +00:00
|
|
|
release: process.env.VERSION,
|
2021-08-11 15:11:10 +00:00
|
|
|
environment: sentryConfig.environment,
|
|
|
|
defaultIntegrations: false,
|
|
|
|
autoSessionTracking: false,
|
2021-10-29 08:34:25 +00:00
|
|
|
integrations,
|
2021-08-11 15:11:10 +00: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,
|
|
|
|
});
|
|
|
|
}
|
2021-10-27 13:05:58 +00:00
|
|
|
|
|
|
|
window.mxSendSentryReport = sendSentryReport;
|