feat(nc-desktop): add error reporting and monitoring setting (#3553)
This commit is contained in:
@@ -28,11 +28,13 @@
|
||||
"@emotion/styled": "^11.6.0",
|
||||
"@hookform/resolvers": "^2.8.0",
|
||||
"@mui/icons-material": "^5.2.0",
|
||||
"@mui/lab": "^5.0.0-alpha.72",
|
||||
"@mui/material": "^5.2.2",
|
||||
"@mui/styles": "^5.2.2",
|
||||
"@mui/system": ">= 5",
|
||||
"@mui/lab": "^5.0.0-alpha.72",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@sentry/integrations": "^7.54.0",
|
||||
"@sentry/react": "^7.54.0",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||
"clsx": "^1.1.1",
|
||||
|
||||
@@ -40,6 +40,7 @@ fn main() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
crate::operations::config::get_config_file_location,
|
||||
crate::operations::config::get_config_id,
|
||||
crate::operations::common::get_env,
|
||||
crate::operations::connection::connect::get_gateway,
|
||||
crate::operations::connection::connect::get_service_provider,
|
||||
crate::operations::connection::connect::set_gateway,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
use std::env;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_env(variable: String) -> Option<String> {
|
||||
let var = env::var(&variable).ok();
|
||||
log::trace!("get_env {variable} {:?}", var);
|
||||
|
||||
var
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod common;
|
||||
pub mod config;
|
||||
pub mod connection;
|
||||
pub mod directory;
|
||||
|
||||
@@ -9,9 +9,11 @@ import { getItemFromStorage, setItemInStorage } from 'src/utils';
|
||||
import { ConnectionStatusKind, GatewayPerformance } from '../types';
|
||||
import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
import initSentry from '../sentry';
|
||||
|
||||
const FORAGE_GATEWAY_KEY = 'nym-connect-user-gateway';
|
||||
const FORAGE_SP_KEY = 'nym-connect-user-sp';
|
||||
const FORAGE_MONITORING_ENABLED = 'nym-connect-monitoring-enabled';
|
||||
|
||||
type ModeType = 'light' | 'dark';
|
||||
|
||||
@@ -30,6 +32,7 @@ export type TClientContext = {
|
||||
serviceProviders?: ServiceProvider[];
|
||||
setMode: (mode: ModeType) => void;
|
||||
clearError: () => void;
|
||||
monitoringEnabled: boolean;
|
||||
setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void;
|
||||
setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void;
|
||||
setConnectedSince: (connectedSince: DateTime | undefined) => void;
|
||||
@@ -39,6 +42,7 @@ export type TClientContext = {
|
||||
startDisconnecting: () => Promise<void>;
|
||||
setUserDefinedGateway: React.Dispatch<React.SetStateAction<UserDefinedGateway>>;
|
||||
setUserDefinedSPAddress: React.Dispatch<React.SetStateAction<UserDefinedSPAddress>>;
|
||||
setMonitoring: (value: boolean) => Promise<void>;
|
||||
};
|
||||
|
||||
export const ClientContext = createContext({} as TClientContext);
|
||||
@@ -62,12 +66,25 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
isActive: false,
|
||||
address: undefined,
|
||||
});
|
||||
const [monitoringEnabled, setMonitoringEnabled] = useState(false);
|
||||
|
||||
const getAppVersion = async () => {
|
||||
const version = await getVersion();
|
||||
return version;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const initSentryClient = async () => {
|
||||
const monitoring = await getItemFromStorage({ key: FORAGE_MONITORING_ENABLED });
|
||||
setMonitoringEnabled(Boolean(monitoring));
|
||||
if (monitoring === true) {
|
||||
await initSentry();
|
||||
}
|
||||
};
|
||||
|
||||
initSentryClient();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setItemInStorage({ key: FORAGE_GATEWAY_KEY, value: userDefinedGateway });
|
||||
}, [userDefinedGateway]);
|
||||
@@ -85,8 +102,12 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
setAppVersion(AppVersion);
|
||||
setServiceProviders(services as ServiceProvider[]);
|
||||
|
||||
if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway);
|
||||
if (storedUserDefinedSP) setUserDefinedSPAddress(storedUserDefinedSP);
|
||||
if (storedUserDefinedGateway) {
|
||||
setUserDefinedGateway(storedUserDefinedGateway);
|
||||
}
|
||||
if (storedUserDefinedSP) {
|
||||
setUserDefinedSPAddress(storedUserDefinedSP);
|
||||
}
|
||||
};
|
||||
|
||||
useEvents({
|
||||
@@ -162,6 +183,11 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
|
||||
const clearError = () => setError(undefined);
|
||||
|
||||
const setMonitoring = async (value: boolean) => {
|
||||
setMonitoringEnabled(value);
|
||||
await setItemInStorage({ key: FORAGE_MONITORING_ENABLED, value });
|
||||
};
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
mode,
|
||||
@@ -177,6 +203,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
selectedProvider,
|
||||
serviceProviders,
|
||||
connectedSince,
|
||||
monitoringEnabled,
|
||||
setConnectedSince,
|
||||
setSerivceProvider,
|
||||
startConnecting,
|
||||
@@ -187,6 +214,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
userDefinedGateway,
|
||||
setUserDefinedGateway,
|
||||
setUserDefinedSPAddress,
|
||||
setMonitoring,
|
||||
}),
|
||||
[
|
||||
mode,
|
||||
@@ -202,6 +230,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
selectedProvider,
|
||||
userDefinedGateway,
|
||||
userDefinedSPAddress,
|
||||
monitoringEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ const mockValues: TClientContext = {
|
||||
showInfoModal: false,
|
||||
userDefinedGateway: { isActive: false, gateway: '' },
|
||||
userDefinedSPAddress: { isActive: false, address: '' },
|
||||
monitoringEnabled: false,
|
||||
setShowInfoModal: () => {},
|
||||
setMode: () => {},
|
||||
clearError: () => {},
|
||||
@@ -22,6 +23,7 @@ const mockValues: TClientContext = {
|
||||
setSerivceProvider: () => {},
|
||||
setUserDefinedGateway: () => {},
|
||||
setUserDefinedSPAddress: () => {},
|
||||
setMonitoring: async () => {},
|
||||
};
|
||||
|
||||
export const MockProvider: FCWithChildren<{
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import React, { ChangeEvent, useState } from 'react';
|
||||
import { Warning as WarningIcon } from '@mui/icons-material';
|
||||
import { Box, FormControl, FormControlLabel, FormHelperText, Stack, Switch, Typography } from '@mui/material';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
|
||||
export const MonitoringSettings = () => {
|
||||
const { monitoringEnabled, setMonitoring } = useClientContext();
|
||||
const [enabled, setEnabled] = useState(monitoringEnabled);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setLoading(true);
|
||||
setEnabled(e.target.checked);
|
||||
await setMonitoring(e.target.checked);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box height="100%">
|
||||
<Stack justifyContent="space-between" height="100%">
|
||||
<Box>
|
||||
<Typography fontWeight="bold" variant="body2" mb={2}>
|
||||
Error reporting and performance monitoring
|
||||
</Typography>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch checked={enabled} onChange={handleChange} disabled={loading} size="small" sx={{ ml: 1 }} />
|
||||
}
|
||||
label="Enable"
|
||||
/>
|
||||
<FormHelperText sx={{ m: 0, my: 2 }}>
|
||||
Help Nym developers to fix errors, crashes and improve the application by enabling this option. If errors
|
||||
occur or if the app crashes, it will automatically send a report. Also it tracks various performance
|
||||
metrics. We use sentry.io service to handle this.
|
||||
</FormHelperText>
|
||||
<FormHelperText sx={{ m: 0, mb: 2 }}>
|
||||
Note: A report can include your external IP, this can be useful to catch issues related to IP location.
|
||||
All recorded data is used by Nym developers and for app development purposes only.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
<WarningIcon color="warning" fontSize="small" />
|
||||
<Typography variant="caption" color="warning.main">
|
||||
You must restart the application for the change to take effect.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -7,6 +7,7 @@ import { toggleLogViewer } from 'src/utils';
|
||||
const menuSchema = [
|
||||
{ title: 'Select your gateway', path: 'gateway' },
|
||||
{ title: 'Select a service provider', path: 'service-provider' },
|
||||
{ title: 'Help us improve the app', path: 'monitoring' },
|
||||
];
|
||||
|
||||
export const SettingsMenu = () => (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { ConnectionPage } from 'src/pages/connection';
|
||||
import { Menu } from 'src/pages/menu';
|
||||
import { CompatibleApps } from 'src/pages/menu/Apps';
|
||||
@@ -7,19 +8,30 @@ import { HelpGuide } from 'src/pages/menu/Guide';
|
||||
import { SettingsMenu } from 'src/pages/menu/settings';
|
||||
import { GatewaySettings } from 'src/pages/menu/settings/GatewaySettings';
|
||||
import { ServiceProviderSettings } from 'src/pages/menu/settings/ServiceProviderSettings';
|
||||
import { MonitoringSettings } from '../pages/menu/settings/MonitoringSettings';
|
||||
import { useClientContext } from '../context/main';
|
||||
|
||||
export const AppRoutes = () => (
|
||||
<Routes>
|
||||
<Route index path="/" element={<ConnectionPage />} />
|
||||
<Route path="menu">
|
||||
<Route index element={<Menu />} />
|
||||
<Route path="apps" element={<CompatibleApps />} />
|
||||
<Route path="guide" element={<HelpGuide />} />
|
||||
<Route path="settings">
|
||||
<Route index element={<SettingsMenu />} />
|
||||
<Route path="gateway" element={<GatewaySettings />} />
|
||||
<Route path="service-provider" element={<ServiceProviderSettings />} />
|
||||
const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
|
||||
|
||||
export const AppRoutes = () => {
|
||||
const { monitoringEnabled } = useClientContext();
|
||||
|
||||
const RoutesContainer = monitoringEnabled ? SentryRoutes : Routes;
|
||||
|
||||
return (
|
||||
<RoutesContainer>
|
||||
<Route index path="/" element={<ConnectionPage />} />
|
||||
<Route path="menu">
|
||||
<Route index element={<Menu />} />
|
||||
<Route path="apps" element={<CompatibleApps />} />
|
||||
<Route path="guide" element={<HelpGuide />} />
|
||||
<Route path="settings">
|
||||
<Route index element={<SettingsMenu />} />
|
||||
<Route path="gateway" element={<GatewaySettings />} />
|
||||
<Route path="service-provider" element={<ServiceProviderSettings />} />
|
||||
<Route path="monitoring" element={<MonitoringSettings />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
</RoutesContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { createRoutesFromChildren, matchRoutes, useLocation, useNavigationType } from 'react-router-dom';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { CaptureConsole } from '@sentry/integrations';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
|
||||
const SENTRY_DSN = 'https://625e2658da4945a7a253f3ee04413a31@o967446.ingest.sentry.io/4505306292289536';
|
||||
|
||||
async function initSentry() {
|
||||
console.log('⚠ performance monitoring and error reporting enabled');
|
||||
console.log('initializing sentry');
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
integrations: [
|
||||
new Sentry.BrowserTracing({
|
||||
// Set `tracePropagationTargets` to control for which URLs distributed tracing should be enabled
|
||||
tracePropagationTargets: ['localhost'],
|
||||
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
|
||||
React.useEffect,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
createRoutesFromChildren,
|
||||
matchRoutes,
|
||||
),
|
||||
}),
|
||||
new Sentry.Replay(),
|
||||
// captures Console API calls
|
||||
new CaptureConsole({ levels: ['error', 'warn'] }),
|
||||
],
|
||||
|
||||
// TODO adjust this in the future, 100% is not recommended for production
|
||||
tracesSampleRate: 1.0,
|
||||
|
||||
// Capture Replay for 10% of all sessions,
|
||||
// plus for 100% of sessions with an error
|
||||
replaysSessionSampleRate: 0.1,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
environment: process.env.NODE_ENV,
|
||||
});
|
||||
|
||||
getVersion().then((version) => {
|
||||
Sentry.setTag('app_version', version);
|
||||
});
|
||||
}
|
||||
|
||||
export default initSentry;
|
||||
@@ -121,4 +121,4 @@ dependencies {
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
||||
implementation 'com.github.kittinunf.fuel:fuel:3.0.0-alpha1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,4 @@
|
||||
tools:node="remove" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -122,4 +122,4 @@
|
||||
"webpack-favicons": "^1.3.8",
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3042,6 +3042,80 @@
|
||||
resolved "https://registry.yarnpkg.com/@sapphire/utilities/-/utilities-3.11.1.tgz#75d068fe473d9cf8483156ae731bce92fa39a2ef"
|
||||
integrity sha512-SO7WvlPc0QEB+bCT6Z/NGpYTpOtYAJtxpxKV4YU+agEGlMZ5hM8J/xkco4/yFWH7X2gAcG4Ckoi2shFr7CMS/A==
|
||||
|
||||
"@sentry-internal/tracing@7.54.0":
|
||||
version "7.54.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.54.0.tgz#eeb10ee72426d08669a7706faa4264f1ec02c71d"
|
||||
integrity sha512-JsyhZ0wWZ+VqbHJg+azqRGdYJDkcI5R9+pnkO6SzbzxrRewqMAIwzkpPee3oI7vG99uhMEkOkMjHu0nQGwkOQw==
|
||||
dependencies:
|
||||
"@sentry/core" "7.54.0"
|
||||
"@sentry/types" "7.54.0"
|
||||
"@sentry/utils" "7.54.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/browser@7.54.0":
|
||||
version "7.54.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.54.0.tgz#7fe331c776d02b5c902733aa41dcbfac7bef1ae6"
|
||||
integrity sha512-EvLAw03N9WE2m1CMl2/1YMeIs1icw9IEOVJhWmf3uJEysNJOFWXu6ZzdtHEz1E6DiJYhc1HzDya0ExZeJxNARA==
|
||||
dependencies:
|
||||
"@sentry-internal/tracing" "7.54.0"
|
||||
"@sentry/core" "7.54.0"
|
||||
"@sentry/replay" "7.54.0"
|
||||
"@sentry/types" "7.54.0"
|
||||
"@sentry/utils" "7.54.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/core@7.54.0":
|
||||
version "7.54.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.54.0.tgz#8c4cb8800f8df708b3f3f6483026bb9a02820014"
|
||||
integrity sha512-MAn0E2EwgNn1pFQn4qxhU+1kz6edullWg6VE5wCmtpXWOVw6sILBUsQpeIG5djBKMcneJCdOlz5jeqcKPrLvZQ==
|
||||
dependencies:
|
||||
"@sentry/types" "7.54.0"
|
||||
"@sentry/utils" "7.54.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/integrations@^7.54.0":
|
||||
version "7.54.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.54.0.tgz#62c73013ca6040d0c9b045809fc5d6ecefda3339"
|
||||
integrity sha512-RolGsQzJChJzjHTJcCKSZ1HanmY33floc5o13WgU9NoDqJbLGLNcOIrAu+WynqPe8P5VTVrVb8NiwhLqWrKp4g==
|
||||
dependencies:
|
||||
"@sentry/types" "7.54.0"
|
||||
"@sentry/utils" "7.54.0"
|
||||
localforage "^1.8.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/react@^7.54.0":
|
||||
version "7.54.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.54.0.tgz#0d9e1b902fd9ded713ac46a623f6a490e4aa2c8a"
|
||||
integrity sha512-qUbwmRRpTh05m2rbC8A2zAFQYsoHhwIpxT5UXxh0P64ZlA3cSg1/DmTTgwnd1l+7gzKrc31UikXQ4y0YDbMNKg==
|
||||
dependencies:
|
||||
"@sentry/browser" "7.54.0"
|
||||
"@sentry/types" "7.54.0"
|
||||
"@sentry/utils" "7.54.0"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/replay@7.54.0":
|
||||
version "7.54.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.54.0.tgz#f0f44f9413ceefd1809bf1665e82315927ae08db"
|
||||
integrity sha512-C0F0568ybphzGmKGe23duB6n5wJcgM7WLYhoeqW3o2bHeqpj1dGPSka/K3s9KzGaAgzn1zeOUYXJsOs+T/XdsA==
|
||||
dependencies:
|
||||
"@sentry/core" "7.54.0"
|
||||
"@sentry/types" "7.54.0"
|
||||
"@sentry/utils" "7.54.0"
|
||||
|
||||
"@sentry/types@7.54.0":
|
||||
version "7.54.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.54.0.tgz#bfee18107a78e290e6c8ad41646e2b9d9dd95234"
|
||||
integrity sha512-D+i9xogBeawvQi2r0NOrM7zYcUaPuijeME4O9eOTrDF20tj71hWtJLilK+KTGLYFtpGg1h+9bPaz7OHEIyVopg==
|
||||
|
||||
"@sentry/utils@7.54.0":
|
||||
version "7.54.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.54.0.tgz#a3acb5e25a1409cbca7b46d6356d7417a253ea9a"
|
||||
integrity sha512-3Yf5KlKjIcYLddOexSt2ovu2TWlR4Fi7M+aCK8yUTzwNzf/xwFSWOstHlD/WiDy9HvfhWAOB/ukNTuAeJmtasw==
|
||||
dependencies:
|
||||
"@sentry/types" "7.54.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sigstore/protobuf-specs@^0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz#957cb64ea2f5ce527cc9cf02a096baeb0d2b99b4"
|
||||
@@ -12987,7 +13061,7 @@ loader-utils@^2.0.0, loader-utils@^2.0.4:
|
||||
emojis-list "^3.0.0"
|
||||
json5 "^2.1.2"
|
||||
|
||||
localforage@^1.7.3:
|
||||
localforage@^1.7.3, localforage@^1.8.1:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
|
||||
integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==
|
||||
|
||||
Reference in New Issue
Block a user