mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Implement Sentry error logging for GUI (#1153)
This commit is contained in:
10
.github/workflows/build-gui.yml
vendored
10
.github/workflows/build-gui.yml
vendored
@@ -79,11 +79,15 @@ jobs:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pnpm i
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
|
||||
|
||||
- if: matrix.os == 'windows-latest'
|
||||
name: Upload a Build Artifact (Windows)
|
||||
|
||||
38
.github/workflows/gradle.yaml
vendored
38
.github/workflows/gradle.yaml
vendored
@@ -112,10 +112,13 @@ jobs:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Build GUI
|
||||
run: |
|
||||
pnpm i
|
||||
cd gui && pnpm run build
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: cd gui && pnpm run build
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew :server:android:assembleDebug
|
||||
@@ -191,10 +194,13 @@ jobs:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm run tauri build --config $( ./gui/scripts/gitversion.mjs )
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: pnpm run tauri build --config $( ./gui/scripts/gitversion.mjs )
|
||||
|
||||
- name: Make GUI tarball
|
||||
run: |
|
||||
@@ -266,11 +272,15 @@ jobs:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Build
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
rustup target add x86_64-apple-darwin
|
||||
pnpm i
|
||||
pnpm run tauri build --target universal-apple-darwin --config $( ./gui/scripts/gitversion.mjs )
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: pnpm run tauri build --target universal-apple-darwin --config $( ./gui/scripts/gitversion.mjs )
|
||||
|
||||
- name: Modify Application
|
||||
run: |
|
||||
@@ -338,11 +348,15 @@ jobs:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pnpm i
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
|
||||
|
||||
- name: Bundle to zips
|
||||
shell: bash
|
||||
|
||||
3
gui/.gitignore
vendored
3
gui/.gitignore
vendored
@@ -32,3 +32,6 @@ vite.config.ts.timestamp*
|
||||
|
||||
# eslint
|
||||
.eslintcache
|
||||
|
||||
# Sentry Config File
|
||||
.env.sentry-build-plugin
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"@hookform/resolvers": "^3.6.0",
|
||||
"@react-three/drei": "^9.114.3",
|
||||
"@react-three/fiber": "^8.17.10",
|
||||
"@sentry/react": "^9.9.0",
|
||||
"@sentry/vite-plugin": "^2.22.7",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tanstack/react-query": "^5.48.0",
|
||||
"@tauri-apps/api": "^2.0.2",
|
||||
@@ -91,7 +93,7 @@
|
||||
"spdx-satisfies": "^5.0.1",
|
||||
"tailwind-gradient-mask-image": "^1.2.0",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"vite": "^5.4.8",
|
||||
"typescript-eslint": "^8.8.0"
|
||||
"typescript-eslint": "^8.8.0",
|
||||
"vite": "^5.4.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,6 +359,7 @@ settings-sidebar-utils = Utilities
|
||||
settings-sidebar-serial = Serial console
|
||||
settings-sidebar-appearance = Appearance
|
||||
settings-sidebar-notifications = Notifications
|
||||
settings-sidebar-behavior = Behavior
|
||||
settings-sidebar-firmware-tool = DIY Firmware Tool
|
||||
settings-sidebar-advanced = Advanced
|
||||
|
||||
@@ -531,9 +532,6 @@ settings-general-gesture_control-numberTrackersOverThreshold-description = Incre
|
||||
|
||||
## Appearance settings
|
||||
settings-interface-appearance = Appearance
|
||||
settings-general-interface-dev_mode = Developer Mode
|
||||
settings-general-interface-dev_mode-description = This mode can be useful if you need in-depth data or to interact with connected trackers on a more advanced level.
|
||||
settings-general-interface-dev_mode-label = Developer Mode
|
||||
settings-general-interface-theme = Color theme
|
||||
settings-general-interface-show-navbar-onboarding = Show "{ navbar-onboarding }" on navigation bar
|
||||
settings-general-interface-show-navbar-onboarding-description = This changes if the "{ navbar-onboarding }" button shows on the navigation bar.
|
||||
@@ -565,6 +563,12 @@ settings-general-interface-feedback_sound-volume = Feedback sound volume
|
||||
settings-general-interface-connected_trackers_warning = Connected trackers warning
|
||||
settings-general-interface-connected_trackers_warning-description = This option will show a pop-up every time you try exiting SlimeVR while having one or more connected trackers. It reminds you to turn off your trackers when you are done to preserve battery life.
|
||||
settings-general-interface-connected_trackers_warning-label = Connected trackers warning on exit
|
||||
|
||||
## Behavior settings
|
||||
settings-interface-behavior = Behavior
|
||||
settings-general-interface-dev_mode = Developer Mode
|
||||
settings-general-interface-dev_mode-description = This mode can be useful if you need in-depth data or to interact with connected trackers on a more advanced level.
|
||||
settings-general-interface-dev_mode-label = Developer Mode
|
||||
settings-general-interface-use_tray = Minimize to system tray
|
||||
settings-general-interface-use_tray-description = Lets you close the window without closing the SlimeVR Server so you can continue using it without having the GUI bothering you.
|
||||
settings-general-interface-use_tray-label = Minimize to system tray
|
||||
@@ -576,6 +580,15 @@ settings-general-interface-discord_presence-message = { $amount ->
|
||||
[one] Using 1 tracker
|
||||
*[other] Using { $amount } trackers
|
||||
}
|
||||
settings-interface-behavior-error_tracking = Error collection via Sentry.io
|
||||
settings-interface-behavior-error_tracking-description =
|
||||
To provide the best user experience, we collect anonymized error reports, performance metrics, and operating system information. This helps us detect bugs and issues with SlimeVR. These metrics are collected via Sentry.io.
|
||||
|
||||
<b>We do not collect personal information</b> such as your IP address or wireless credentials. SlimeVR values your privacy!
|
||||
|
||||
Do you consent to the collection of anonymized error data?
|
||||
|
||||
settings-interface-behavior-error_tracking-label = Send errors to developers
|
||||
|
||||
## Serial settings
|
||||
settings-serial = Serial Console
|
||||
@@ -1291,3 +1304,11 @@ unknown_device-modal-description = There is a new tracker with MAC address <b>{$
|
||||
Do you want to connect it to SlimeVR?
|
||||
unknown_device-modal-confirm = Sure!
|
||||
unknown_device-modal-forget = Ignore it
|
||||
|
||||
## Error collection consent modal
|
||||
error_collection_modal-title = Can we collect errors?
|
||||
error_collection_modal-description = { settings-interface-behavior-error_tracking-description }
|
||||
|
||||
You can change this setting later in the Behavior section of the settings page.
|
||||
error_collection_modal-confirm = I agree
|
||||
error_collection_modal-cancel = I don't want to
|
||||
|
||||
@@ -54,6 +54,7 @@ import { AppLayout } from './AppLayout';
|
||||
import { Preload } from './components/Preload';
|
||||
import { UnknownDeviceModal } from './components/UnknownDeviceModal';
|
||||
import { useDiscordPresence } from './hooks/discord-presence';
|
||||
import { withSentryReactRouterV6Routing } from '@sentry/react';
|
||||
import { ScaledProportionsPage } from './components/onboarding/pages/body-proportions/ScaledProportions';
|
||||
import { AdvancedSettings } from './components/settings/pages/AdvancedSettings';
|
||||
import { FirmwareUpdate } from './components/firmware-update/FirmwareUpdate';
|
||||
@@ -64,6 +65,8 @@ export const VersionContext = createContext('');
|
||||
export const DOCS_SITE = 'https://docs.slimevr.dev';
|
||||
export const SLIMEVR_DISCORD = 'https://discord.gg/slimevr';
|
||||
|
||||
const SentryRoutes = withSentryReactRouterV6Routing(Routes);
|
||||
|
||||
function Layout() {
|
||||
const { isMobile } = useBreakpoint('mobile');
|
||||
useDiscordPresence();
|
||||
@@ -73,7 +76,7 @@ function Layout() {
|
||||
<SerialDetectionModal></SerialDetectionModal>
|
||||
<VersionUpdateModal></VersionUpdateModal>
|
||||
<UnknownDeviceModal></UnknownDeviceModal>
|
||||
<Routes>
|
||||
<SentryRoutes>
|
||||
<Route element={<AppLayout />}>
|
||||
<Route
|
||||
path="/"
|
||||
@@ -165,7 +168,7 @@ function Layout() {
|
||||
</Route>
|
||||
<Route path="*" element={<TopBar></TopBar>}></Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
</SentryRoutes>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useLayoutEffect } from 'react';
|
||||
import { useConfig } from './hooks/config';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
export function AppLayout() {
|
||||
const { loading, config } = useConfig();
|
||||
const navigate = useNavigate();
|
||||
const { config } = useConfig();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (loading || !config) return;
|
||||
if (!config) return;
|
||||
if (config.theme !== undefined) {
|
||||
document.documentElement.dataset.theme = config.theme;
|
||||
}
|
||||
@@ -25,23 +24,7 @@ export function AppLayout() {
|
||||
`${config.textSize}rem`
|
||||
);
|
||||
}
|
||||
}, [config, loading]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (config && !config.doneOnboarding) {
|
||||
navigate('/onboarding/home');
|
||||
}
|
||||
}, [config?.doneOnboarding]);
|
||||
|
||||
// const location = useLocation();
|
||||
// const navigationType = useNavigationType();
|
||||
// useEffect(() => {
|
||||
// if (import.meta.env.PROD) return;
|
||||
// console.log('The current URL is', { ...location });
|
||||
// console.log('The last navigation action was', navigationType);
|
||||
// }, [location, navigationType]);
|
||||
|
||||
if (loading) return <></>;
|
||||
}, [config]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
57
gui/src/components/ErrorConsentModal.tsx
Normal file
57
gui/src/components/ErrorConsentModal.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { BaseModal } from './commons/BaseModal';
|
||||
import { Button } from './commons/Button';
|
||||
import { Typography } from './commons/Typography';
|
||||
|
||||
export function ErrorConsentModal({
|
||||
isOpen = true,
|
||||
cancel,
|
||||
accept,
|
||||
}: {
|
||||
/**
|
||||
* Is the parent/sibling component opened?
|
||||
*/
|
||||
isOpen: boolean;
|
||||
/**
|
||||
* Function to trigger when you still want to close the app
|
||||
*/
|
||||
accept: () => void;
|
||||
/**
|
||||
* Function to trigger when cancelling app close
|
||||
*/
|
||||
cancel?: () => void;
|
||||
}) {
|
||||
const { l10n } = useLocalization();
|
||||
|
||||
return (
|
||||
<BaseModal isOpen={isOpen} onRequestClose={cancel} closeable={false}>
|
||||
<div className="flex flex-col gap-3">
|
||||
<>
|
||||
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
|
||||
<div className="flex flex-col items-center gap-2 max-w-[512px]">
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('error_collection_modal-title')}
|
||||
</Typography>
|
||||
<Localized
|
||||
id={'error_collection_modal-description'}
|
||||
elems={{ b: <b></b> }}
|
||||
>
|
||||
<Typography
|
||||
variant="standard"
|
||||
whitespace="whitespace-pre-line"
|
||||
/>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant="primary" onClick={accept}>
|
||||
{l10n.getString('error_collection_modal-confirm')}
|
||||
</Button>
|
||||
<Button variant="tertiary" onClick={cancel}>
|
||||
{l10n.getString('error_collection_modal-cancel')}
|
||||
</Button>
|
||||
</>
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
@@ -110,7 +110,7 @@ export function SerialDetectionModal() {
|
||||
{l10n.getString('serial_detection-new_device-p1')}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 rounded-xl max-w-sm">
|
||||
<div className="flex flex-col gap-3 rounded-xl max-w-sm sentry-mask">
|
||||
<Localized
|
||||
id="onboarding-wifi_creds-ssid"
|
||||
attrs={{ placeholder: true, label: true }}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { TrayOrExitModal } from './TrayOrExitModal';
|
||||
import { error } from '@/utils/logging';
|
||||
import { useDoubleTap } from 'use-double-tap';
|
||||
import { isTrayAvailable } from '@/utils/tauri';
|
||||
import { ErrorConsentModal } from './ErrorConsentModal';
|
||||
import {
|
||||
CloseRequestedEvent,
|
||||
getCurrentWindow,
|
||||
@@ -333,6 +334,11 @@ export function TopBar({
|
||||
getCurrentWindow().requestUserAttention(null);
|
||||
}}
|
||||
></TrackersStillOnModal>
|
||||
<ErrorConsentModal
|
||||
isOpen={config?.errorTracking === null}
|
||||
accept={() => setConfig({ errorTracking: true })}
|
||||
cancel={() => setConfig({ errorTracking: false })}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,17 +5,19 @@ import ReactModal from 'react-modal';
|
||||
export function BaseModal({
|
||||
children,
|
||||
important = false,
|
||||
closeable = true,
|
||||
...props
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
children: ReactNode;
|
||||
important?: boolean;
|
||||
closeable?: boolean;
|
||||
} & ReactModal.Props) {
|
||||
return (
|
||||
<ReactModal
|
||||
{...props}
|
||||
shouldCloseOnOverlayClick
|
||||
shouldCloseOnEsc
|
||||
shouldCloseOnOverlayClick={closeable}
|
||||
shouldCloseOnEsc={closeable}
|
||||
overlayClassName={
|
||||
props.overlayClassName ||
|
||||
classNames(
|
||||
|
||||
@@ -87,7 +87,9 @@ export const InputInside = forwardRef<
|
||||
<div className="relative w-full">
|
||||
<input
|
||||
type={forceText ? 'text' : type}
|
||||
className={classNames(classes, { 'pr-10': type === 'password' })}
|
||||
className={classNames(classes, {
|
||||
'pr-10 sentry-mask': type === 'password',
|
||||
})}
|
||||
placeholder={placeholder || undefined}
|
||||
autoComplete={autocomplete ? 'off' : 'on'}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -11,6 +11,7 @@ export function Typography({
|
||||
italic = false,
|
||||
truncate = false,
|
||||
textAlign,
|
||||
sentryMask = false,
|
||||
}: {
|
||||
variant?:
|
||||
| 'main-title'
|
||||
@@ -37,6 +38,7 @@ export function Typography({
|
||||
| 'text-start'
|
||||
| 'text-end';
|
||||
children?: ReactNode;
|
||||
sentryMask?: boolean;
|
||||
}) {
|
||||
const tag = useMemo(() => {
|
||||
const tags = {
|
||||
@@ -72,6 +74,7 @@ export function Typography({
|
||||
truncate && 'leading-3 text-ellipsis',
|
||||
truncate && (config?.textSize ?? 12) > 12 && 'line-clamp-1',
|
||||
truncate && (config?.textSize ?? 12) <= 12 && 'line-clamp-2',
|
||||
sentryMask && 'sentry-mask',
|
||||
]),
|
||||
},
|
||||
children || []
|
||||
|
||||
@@ -62,3 +62,22 @@ export function ArrowRightIcon() {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrowRightLeftIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2}
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export function WifiCredsPage() {
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex flex-col gap-3 p-10 rounded-xl max-w-sm',
|
||||
'flex flex-col gap-3 p-10 rounded-xl max-w-sm sentry-mask',
|
||||
!state.alonePage && 'bg-background-70',
|
||||
state.alonePage && 'bg-background-60'
|
||||
)}
|
||||
|
||||
@@ -326,7 +326,7 @@ export function BodyProportions({
|
||||
<Typography variant="section-title" bold>
|
||||
{l10n.getString(label)}
|
||||
</Typography>
|
||||
<Typography variant="mobile-title" bold>
|
||||
<Typography variant="mobile-title" bold sentryMask>
|
||||
{type === LabelType.GroupPart
|
||||
? /* Make number rounding so it's based on .5 decimals */
|
||||
percentageFormat.format(Math.round(value * 200) / 200)
|
||||
|
||||
@@ -66,7 +66,9 @@ export function VerifyResultsStep({
|
||||
{bodyParts?.map(({ bone, label, value }) => (
|
||||
<div className="flex justify-between" key={bone}>
|
||||
<Typography color="secondary">{label}</Typography>
|
||||
<Typography bold>{(value * 100).toFixed(2)} CM</Typography>
|
||||
<Typography bold sentryMask>
|
||||
{(value * 100).toFixed(2)} CM
|
||||
</Typography>
|
||||
</div>
|
||||
))}
|
||||
{hasCalibration === ProcessStatus.PENDING &&
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
import { ReactNode, useEffect, useContext } from 'react';
|
||||
import { ConfigContextC, useConfigProvider } from '@/hooks/config';
|
||||
import { ReactNode, useContext, useLayoutEffect } from 'react';
|
||||
import { ConfigContextC, loadConfig, useConfigProvider } from '@/hooks/config';
|
||||
import { DEFAULT_LOCALE, LangContext } from '@/i18n/config';
|
||||
import { getSentryOrCompute } from '@/utils/sentry';
|
||||
|
||||
const config = await loadConfig();
|
||||
if (config?.errorTracking !== undefined) {
|
||||
// load sentry ASAP to catch early errors
|
||||
getSentryOrCompute(config.errorTracking ?? false);
|
||||
}
|
||||
|
||||
export function ConfigContextProvider({ children }: { children: ReactNode }) {
|
||||
const context = useConfigProvider();
|
||||
const context = useConfigProvider(config);
|
||||
const { changeLocales } = useContext(LangContext);
|
||||
|
||||
useEffect(() => {
|
||||
context.loadConfig().then((config) => {
|
||||
changeLocales([config?.lang || DEFAULT_LOCALE]);
|
||||
});
|
||||
useLayoutEffect(() => {
|
||||
changeLocales([config?.lang || DEFAULT_LOCALE]);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (config?.errorTracking !== undefined) {
|
||||
// Alows for sentry to refresh if user change the setting once the gui
|
||||
// is initialized
|
||||
getSentryOrCompute(config.errorTracking ?? false);
|
||||
}
|
||||
}, [config?.errorTracking]);
|
||||
|
||||
return (
|
||||
<ConfigContextC.Provider value={context}>
|
||||
{children}
|
||||
|
||||
@@ -22,7 +22,7 @@ export function SettingSelectorMobile() {
|
||||
},
|
||||
{
|
||||
label: l10n.getString('settings-sidebar-interface'),
|
||||
value: { url: '/settings/interface', scrollTo: 'appearance' },
|
||||
value: { url: '/settings/interface', scrollTo: 'notifications' },
|
||||
},
|
||||
{
|
||||
label: l10n.getString('settings-sidebar-osc_router'),
|
||||
|
||||
@@ -74,6 +74,9 @@ export function SettingsSidebar() {
|
||||
<SettingsLink to="/settings/interface" scrollTo="notifications">
|
||||
{l10n.getString('settings-sidebar-notifications')}
|
||||
</SettingsLink>
|
||||
<SettingsLink to="/settings/interface" scrollTo="behavior">
|
||||
{l10n.getString('settings-sidebar-behavior')}
|
||||
</SettingsLink>
|
||||
<SettingsLink to="/settings/interface" scrollTo="appearance">
|
||||
{l10n.getString('settings-sidebar-appearance')}
|
||||
</SettingsLink>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { CheckBox } from '@/components/commons/Checkbox';
|
||||
@@ -16,24 +16,28 @@ import { LangSelector } from '@/components/commons/LangSelector';
|
||||
import { BellIcon } from '@/components/commons/icon/BellIcon';
|
||||
import { Range } from '@/components/commons/Range';
|
||||
import { Dropdown } from '@/components/commons/Dropdown';
|
||||
import { ArrowRightLeftIcon } from '@/components/commons/icon/ArrowIcons';
|
||||
import { isTrayAvailable } from '@/utils/tauri';
|
||||
|
||||
interface InterfaceSettingsForm {
|
||||
appearance: {
|
||||
devmode: boolean;
|
||||
theme: string;
|
||||
showNavbarOnboarding: boolean;
|
||||
textSize: number;
|
||||
fonts: string;
|
||||
decorations: boolean;
|
||||
};
|
||||
behavior: {
|
||||
devmode: boolean;
|
||||
useTray: boolean;
|
||||
discordPresence: boolean;
|
||||
errorTracking: boolean;
|
||||
};
|
||||
notifications: {
|
||||
watchNewDevices: boolean;
|
||||
feedbackSound: boolean;
|
||||
feedbackSoundVolume: number;
|
||||
connectedTrackersWarning: boolean;
|
||||
useTray: boolean;
|
||||
discordPresence: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,7 +48,6 @@ export function InterfaceSettings() {
|
||||
const { control, watch, handleSubmit } = useForm<InterfaceSettingsForm>({
|
||||
defaultValues: {
|
||||
appearance: {
|
||||
devmode: config?.debug ?? defaultConfig.debug,
|
||||
theme: config?.theme ?? defaultConfig.theme,
|
||||
showNavbarOnboarding:
|
||||
config?.showNavbarOnboarding ?? defaultConfig.showNavbarOnboarding,
|
||||
@@ -61,27 +64,34 @@ export function InterfaceSettings() {
|
||||
connectedTrackersWarning:
|
||||
config?.connectedTrackersWarning ??
|
||||
defaultConfig.connectedTrackersWarning,
|
||||
},
|
||||
behavior: {
|
||||
devmode: config?.debug ?? defaultConfig.debug,
|
||||
useTray: config?.useTray ?? defaultConfig.useTray ?? false,
|
||||
discordPresence:
|
||||
config?.discordPresence ?? defaultConfig.discordPresence,
|
||||
errorTracking: config?.errorTracking ?? false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (values: InterfaceSettingsForm) => {
|
||||
setConfig({
|
||||
debug: values.appearance.devmode,
|
||||
watchNewDevices: values.notifications.watchNewDevices,
|
||||
feedbackSound: values.notifications.feedbackSound,
|
||||
feedbackSoundVolume: values.notifications.feedbackSoundVolume,
|
||||
connectedTrackersWarning: values.notifications.connectedTrackersWarning,
|
||||
|
||||
theme: values.appearance.theme,
|
||||
showNavbarOnboarding: values.appearance.showNavbarOnboarding,
|
||||
fonts: values.appearance.fonts.split(','),
|
||||
textSize: values.appearance.textSize,
|
||||
connectedTrackersWarning: values.notifications.connectedTrackersWarning,
|
||||
useTray: values.notifications.useTray,
|
||||
discordPresence: values.notifications.discordPresence,
|
||||
decorations: values.appearance.decorations,
|
||||
|
||||
useTray: values.behavior.useTray,
|
||||
discordPresence: values.behavior.discordPresence,
|
||||
debug: values.behavior.devmode,
|
||||
errorTracking: values.behavior.errorTracking,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -195,6 +205,17 @@ export function InterfaceSettings() {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</SettingsPagePaneLayout>
|
||||
|
||||
<SettingsPagePaneLayout
|
||||
icon={<ArrowRightLeftIcon></ArrowRightLeftIcon>}
|
||||
id="behavior"
|
||||
>
|
||||
<>
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-interface-behavior')}
|
||||
</Typography>
|
||||
|
||||
{isTrayAvailable && (
|
||||
<>
|
||||
@@ -213,7 +234,7 @@ export function InterfaceSettings() {
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="notifications.useTray"
|
||||
name="behavior.useTray"
|
||||
label={l10n.getString(
|
||||
'settings-general-interface-use_tray-label'
|
||||
)}
|
||||
@@ -237,23 +258,12 @@ export function InterfaceSettings() {
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="notifications.discordPresence"
|
||||
name="behavior.discordPresence"
|
||||
label={l10n.getString(
|
||||
'settings-general-interface-discord_presence-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</SettingsPagePaneLayout>
|
||||
|
||||
<SettingsPagePaneLayout
|
||||
icon={<SquaresIcon></SquaresIcon>}
|
||||
id="appearance"
|
||||
>
|
||||
<>
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-interface-appearance')}
|
||||
</Typography>
|
||||
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-general-interface-dev_mode')}
|
||||
@@ -270,13 +280,49 @@ export function InterfaceSettings() {
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="appearance.devmode"
|
||||
name="behavior.devmode"
|
||||
label={l10n.getString(
|
||||
'settings-general-interface-dev_mode-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-interface-behavior-error_tracking')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Localized
|
||||
id={'settings-interface-behavior-error_tracking-description'}
|
||||
elems={{ b: <b></b> }}
|
||||
>
|
||||
<Typography
|
||||
color="secondary"
|
||||
whitespace="whitespace-pre-line"
|
||||
></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="behavior.errorTracking"
|
||||
label={l10n.getString(
|
||||
'settings-interface-behavior-error_tracking-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</SettingsPagePaneLayout>
|
||||
|
||||
<SettingsPagePaneLayout
|
||||
icon={<SquaresIcon></SquaresIcon>}
|
||||
id="appearance"
|
||||
>
|
||||
<>
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-interface-appearance')}
|
||||
</Typography>
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-interface-appearance-decorations')}
|
||||
</Typography>
|
||||
|
||||
@@ -291,7 +291,7 @@ export function TrackerSettingsPage() {
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('tracker-infos-custom_name')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
<Typography sentry-mask>
|
||||
{tracker?.tracker.info?.customName || '--'}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -489,7 +489,7 @@ export function TrackerSettingsPage() {
|
||||
deviceId={tracker.tracker.trackerId?.deviceId?.id}
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col gap-2 w-full mt-3">
|
||||
<div className="flex flex-col gap-2 w-full mt-3 sentry-mask">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('tracker-settings-name_section')}
|
||||
</Typography>
|
||||
|
||||
@@ -26,6 +26,7 @@ import { useDataFeedConfig } from './datafeed-config';
|
||||
import { useWebsocketAPI } from './websocket-api';
|
||||
import { error } from '@/utils/logging';
|
||||
import { cacheWrap } from './cache';
|
||||
import { updateSentryContext } from '@/utils/sentry';
|
||||
|
||||
export interface FirmwareRelease {
|
||||
name: string;
|
||||
@@ -114,6 +115,10 @@ export function useProvideAppContext(): AppContext {
|
||||
dispatch({ type: 'datafeed', value: packet });
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateSentryContext(state);
|
||||
}, [state.datafeed?.devices]);
|
||||
|
||||
useRPCPacket(RpcMessage.ResetResponse, ({ status, resetType }: ResetResponseT) => {
|
||||
if (!config?.feedbackSound) return;
|
||||
try {
|
||||
|
||||
@@ -38,15 +38,14 @@ export interface Config {
|
||||
mirrorView: boolean;
|
||||
assignMode: AssignMode;
|
||||
discordPresence: boolean;
|
||||
errorTracking: boolean | null;
|
||||
decorations: boolean;
|
||||
showNavbarOnboarding: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigContext {
|
||||
config: Config | null;
|
||||
loading: boolean;
|
||||
setConfig: (config: Partial<Config>) => Promise<void>;
|
||||
loadConfig: () => Promise<Config | null>;
|
||||
saveConfig: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -65,6 +64,7 @@ export const defaultConfig: Omit<Config, 'devSettings'> = {
|
||||
mirrorView: true,
|
||||
assignMode: AssignMode.Core,
|
||||
discordPresence: false,
|
||||
errorTracking: null,
|
||||
decorations: false,
|
||||
showNavbarOnboarding: true,
|
||||
};
|
||||
@@ -87,9 +87,39 @@ function fallbackToDefaults(loadedConfig: any): Config {
|
||||
return Object.assign({}, defaultConfig, loadedConfig);
|
||||
}
|
||||
|
||||
export function useConfigProvider(): ConfigContext {
|
||||
const [currConfig, set] = useState<Config | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
// Move the load of the config ouside of react
|
||||
// allows to load everything before the first render
|
||||
export const loadConfig = async () => {
|
||||
try {
|
||||
const migrated = await store.get('configMigratedToTauri');
|
||||
if (!migrated) {
|
||||
const oldConfig = localStorage.getItem('config.json');
|
||||
|
||||
if (oldConfig) await store.set('config.json', oldConfig);
|
||||
|
||||
store.set('configMigratedToTauri', 'true');
|
||||
}
|
||||
|
||||
const json = await store.get('config.json');
|
||||
|
||||
if (!json) throw new Error('Config has ceased existing for some reason');
|
||||
|
||||
const loadedConfig = fallbackToDefaults(JSON.parse(json));
|
||||
// set(loadedConfig);
|
||||
// setLoading(false);
|
||||
return loadedConfig;
|
||||
} catch (e) {
|
||||
error(e);
|
||||
// setConfig(defaultConfig);
|
||||
// setLoading(false);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export function useConfigProvider(initialConfig: Config | null): ConfigContext {
|
||||
const [currConfig, set] = useState<Config | null>(
|
||||
initialConfig || (defaultConfig as Config)
|
||||
);
|
||||
const tauri = useIsTauri();
|
||||
|
||||
useDebouncedEffect(
|
||||
@@ -146,36 +176,7 @@ export function useConfigProvider(): ConfigContext {
|
||||
|
||||
return {
|
||||
config: currConfig,
|
||||
loading,
|
||||
setConfig,
|
||||
loadConfig: async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const migrated = await store.get('configMigratedToTauri');
|
||||
if (!migrated) {
|
||||
const oldConfig = localStorage.getItem('config.json');
|
||||
|
||||
if (oldConfig) await store.set('config.json', oldConfig);
|
||||
|
||||
store.set('configMigratedToTauri', 'true');
|
||||
}
|
||||
|
||||
const json = await store.get('config.json');
|
||||
|
||||
if (!json) throw new Error('Config has ceased existing for some reason');
|
||||
|
||||
const loadedConfig = fallbackToDefaults(JSON.parse(json));
|
||||
set(loadedConfig);
|
||||
|
||||
setLoading(false);
|
||||
return loadedConfig;
|
||||
} catch (e) {
|
||||
error(e);
|
||||
setConfig(defaultConfig);
|
||||
setLoading(false);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
saveConfig: async () => {
|
||||
if (!tauri) return;
|
||||
await (store as Store).save();
|
||||
|
||||
88
gui/src/utils/sentry.ts
Normal file
88
gui/src/utils/sentry.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { error, log } from './logging';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
createRoutesFromChildren,
|
||||
matchRoutes,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
} from 'react-router-dom';
|
||||
import { AppState } from '@/hooks/app';
|
||||
|
||||
export function getSentryOrCompute(enabled = false) {
|
||||
// if sentry is already initialized - SKIP
|
||||
if (enabled && Sentry.isInitialized()) return;
|
||||
|
||||
const client = Sentry.getClient();
|
||||
if (client) {
|
||||
log(`${enabled ? 'Enabled' : 'Disabled'} error logging with Sentry.`);
|
||||
client.getOptions().enabled = enabled;
|
||||
return client;
|
||||
}
|
||||
if (!enabled) return;
|
||||
|
||||
const newClient = Sentry.init({
|
||||
dsn: 'https://e9ef9f8541352c50cff8600ba520d348@o4507810483535872.ingest.de.sentry.io/4507810579284048',
|
||||
integrations: [
|
||||
Sentry.reactRouterV6BrowserTracingIntegration({
|
||||
useEffect,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
createRoutesFromChildren,
|
||||
matchRoutes,
|
||||
}),
|
||||
Sentry.browserProfilingIntegration(),
|
||||
Sentry.replayIntegration({
|
||||
maskAllText: false,
|
||||
maskAllInputs: true,
|
||||
blockAllMedia: false,
|
||||
}),
|
||||
],
|
||||
beforeSend: (ev) => (newClient?.getOptions().enabled ? ev : null),
|
||||
environment: import.meta.env.MODE,
|
||||
release: (__VERSION_TAG__ || __COMMIT_HASH__) + (__GIT_CLEAN__ ? '' : '-dirty'),
|
||||
// Tracing
|
||||
tracesSampleRate: import.meta.env.PROD ? 0.5 : 1.0, // Capture 50% of the transactions
|
||||
// Set profilesSampleRate to 1.0 to profile every transaction.
|
||||
// Since profilesSampleRate is relative to tracesSampleRate,
|
||||
// the final profiling rate can be computed as tracesSampleRate * profilesSampleRate
|
||||
// For example, a tracesSampleRate of 0.5 and profilesSampleRate of 0.5 would
|
||||
// results in 25% of transactions being profiled (0.5*0.5=0.25)
|
||||
profilesSampleRate: 0.2,
|
||||
// Session Replay
|
||||
replaysSessionSampleRate: import.meta.env.PROD ? 0.1 : 1.0, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
|
||||
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
|
||||
normalizeDepth: 8,
|
||||
enabled,
|
||||
});
|
||||
|
||||
if (!newClient) {
|
||||
error('Couldnt initialize Sentry for error logging');
|
||||
} else {
|
||||
log('Initialized the Sentry client');
|
||||
}
|
||||
|
||||
return newClient;
|
||||
}
|
||||
|
||||
export function updateSentryContext(state: AppState) {
|
||||
// We filter out the shit we dont want. We dont need rotation data or ip addresses
|
||||
const trackers = (state.datafeed?.devices || []).map(
|
||||
({ hardwareInfo, trackers, id }) => ({
|
||||
id: id?.id,
|
||||
hardwareInfo: { ...hardwareInfo, ipAddress: undefined },
|
||||
trackers: trackers.map(({ info, trackerId }) => ({
|
||||
info,
|
||||
trackerId: {
|
||||
trackerNum: trackerId?.trackerNum,
|
||||
deviceId: trackerId?.deviceId?.id,
|
||||
},
|
||||
})),
|
||||
})
|
||||
);
|
||||
|
||||
// Will send the latest context to sentry when an error happens
|
||||
Sentry.setContext('trackers', {
|
||||
trackers,
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { sentryVitePlugin } from '@sentry/vite-plugin';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig, PluginOption } from 'vite';
|
||||
import { execSync } from 'child_process';
|
||||
@@ -39,13 +40,24 @@ export default defineConfig({
|
||||
__VERSION_TAG__: JSON.stringify(versionTag),
|
||||
__GIT_CLEAN__: gitClean,
|
||||
},
|
||||
plugins: [react(), i18nHotReload(), visualizer() as PluginOption],
|
||||
plugins: [
|
||||
react(),
|
||||
i18nHotReload(),
|
||||
visualizer() as PluginOption,
|
||||
sentryVitePlugin({
|
||||
org: 'slimevr',
|
||||
project: 'slimevr-server-gui-react',
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
target: 'es2022',
|
||||
emptyOutDir: true,
|
||||
|
||||
commonjsOptions: {
|
||||
include: [/solarxr-protocol/, /node_modules/],
|
||||
},
|
||||
|
||||
sourcemap: true,
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
|
||||
254
pnpm-lock.yaml
generated
254
pnpm-lock.yaml
generated
@@ -38,6 +38,12 @@ importers:
|
||||
'@react-three/fiber':
|
||||
specifier: ^8.17.10
|
||||
version: 8.17.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0)
|
||||
'@sentry/react':
|
||||
specifier: ^9.9.0
|
||||
version: 9.9.0(react@18.3.1)
|
||||
'@sentry/vite-plugin':
|
||||
specifier: ^2.22.7
|
||||
version: 2.22.7
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.15
|
||||
version: 0.5.15(tailwindcss@3.4.14(ts-node@9.1.1(typescript@5.6.3)))
|
||||
@@ -964,6 +970,94 @@ packages:
|
||||
'@rtsao/scc@1.1.0':
|
||||
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
||||
|
||||
'@sentry-internal/browser-utils@9.9.0':
|
||||
resolution: {integrity: sha512-V/YhKLis98JFkqBGZaEBlDNFpJHJjoCvNb05raAYXdITfDIl37Kxqj0zX+IzyRhqnswkQ+DBTyoEoci09IR2bQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry-internal/feedback@9.9.0':
|
||||
resolution: {integrity: sha512-hrxuOLm0Xsnx75hTNt3eLgNNjER3egrHZShdRzlMiakfKpA9f2X10z75vlZmT5ZUygDQnp9UVUnu28cDuVb9Zw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry-internal/replay-canvas@9.9.0':
|
||||
resolution: {integrity: sha512-YK0ixGjquahGpNsQskCEVwycdHlwNBLCx9XJr1BmGnlOw6fUCmpyVetaGg/ZyhkzKGNXAGoTa4s7FUFnAG4bKg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry-internal/replay@9.9.0':
|
||||
resolution: {integrity: sha512-EWczKMu3qiZ0SUUWU3zkGod+AWD/VQCLiQw+tw+PEpdHbRZIdYKsEptengZCFKthrwe2QmYpVCTSRxGvujJ/6g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry/babel-plugin-component-annotate@2.22.7':
|
||||
resolution: {integrity: sha512-aa7XKgZMVl6l04NY+3X7BP7yvQ/s8scn8KzQfTLrGRarziTlMGrsCOBQtCNWXOPEbtxAIHpZ9dsrAn5EJSivOQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
'@sentry/browser@9.9.0':
|
||||
resolution: {integrity: sha512-pIMdkOC+iggZefBs6ck5fL1mBhbLzjdw/8K99iqSeDh+lLvmlHVZajAhPlmw50xfH8CyQ1s22dhcL+zXbg3NKw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry/bundler-plugin-core@2.22.7':
|
||||
resolution: {integrity: sha512-ouQh5sqcB8vsJ8yTTe0rf+iaUkwmeUlGNFi35IkCFUQlWJ22qS6OfvNjOqFI19e6eGUXks0c/2ieFC4+9wJ+1g==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
'@sentry/cli-darwin@2.39.1':
|
||||
resolution: {integrity: sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ==}
|
||||
engines: {node: '>=10'}
|
||||
os: [darwin]
|
||||
|
||||
'@sentry/cli-linux-arm64@2.39.1':
|
||||
resolution: {integrity: sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux, freebsd]
|
||||
|
||||
'@sentry/cli-linux-arm@2.39.1':
|
||||
resolution: {integrity: sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm]
|
||||
os: [linux, freebsd]
|
||||
|
||||
'@sentry/cli-linux-i686@2.39.1':
|
||||
resolution: {integrity: sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x86, ia32]
|
||||
os: [linux, freebsd]
|
||||
|
||||
'@sentry/cli-linux-x64@2.39.1':
|
||||
resolution: {integrity: sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux, freebsd]
|
||||
|
||||
'@sentry/cli-win32-i686@2.39.1':
|
||||
resolution: {integrity: sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x86, ia32]
|
||||
os: [win32]
|
||||
|
||||
'@sentry/cli-win32-x64@2.39.1':
|
||||
resolution: {integrity: sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@sentry/cli@2.39.1':
|
||||
resolution: {integrity: sha512-JIb3e9vh0+OmQ0KxmexMXg9oZsR/G7HMwxt5BUIKAXZ9m17Xll4ETXTRnRUBT3sf7EpNGAmlQk1xEmVN9pYZYQ==}
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
'@sentry/core@9.9.0':
|
||||
resolution: {integrity: sha512-GxKvx8PSgoWhLLS+/WBGIXy7rsFcnJBPDqFXIfcAGy89k2j06d9IP0kiIc63qBGStSUkh5FFJLPTakZ5RXiFXA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry/react@9.9.0':
|
||||
resolution: {integrity: sha512-7BE2Lx5CNtHtlNSS7Z9HxKquohC0xhdFceO3NlMXlx+dZuVCMoQmLISB8SQEcHw+2VO24MvtP3LPEzdeNbkIfg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: ^16.14.0 || 17.x || 18.x || 19.x
|
||||
|
||||
'@sentry/vite-plugin@2.22.7':
|
||||
resolution: {integrity: sha512-sYRNiNm4toQGq2BfZSJPdw36em3eQaLu+3NTFpA7Hl4g3Sp2Rt3CYObnW5bxlFEruRhxzvdyB383N9OefVZ6KA==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
'@sindresorhus/is@5.6.0':
|
||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||
engines: {node: '>=14.16'}
|
||||
@@ -1406,6 +1500,10 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
||||
@@ -2428,6 +2526,10 @@ packages:
|
||||
resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==}
|
||||
engines: {node: '>=10.19.0'}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
human-signals@1.1.1:
|
||||
resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==}
|
||||
engines: {node: '>=8.12.0'}
|
||||
@@ -2822,6 +2924,10 @@ packages:
|
||||
'@types/three': '>=0.144.0'
|
||||
three: '>=0.144.0'
|
||||
|
||||
magic-string@0.30.8:
|
||||
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
make-error@1.3.6:
|
||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||
|
||||
@@ -3354,6 +3460,10 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
progress@2.0.3:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
promise-worker-transferable@1.0.4:
|
||||
resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==}
|
||||
|
||||
@@ -3370,6 +3480,9 @@ packages:
|
||||
property-information@6.5.0:
|
||||
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
pump@3.0.0:
|
||||
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
|
||||
|
||||
@@ -4074,6 +4187,9 @@ packages:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
unplugin@1.0.1:
|
||||
resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==}
|
||||
|
||||
update-browserslist-db@1.0.16:
|
||||
resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==}
|
||||
hasBin: true
|
||||
@@ -4159,6 +4275,13 @@ packages:
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
webpack-sources@3.2.3:
|
||||
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
webpack-virtual-modules@0.5.0:
|
||||
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
@@ -5066,6 +5189,105 @@ snapshots:
|
||||
|
||||
'@rtsao/scc@1.1.0': {}
|
||||
|
||||
'@sentry-internal/browser-utils@9.9.0':
|
||||
dependencies:
|
||||
'@sentry/core': 9.9.0
|
||||
|
||||
'@sentry-internal/feedback@9.9.0':
|
||||
dependencies:
|
||||
'@sentry/core': 9.9.0
|
||||
|
||||
'@sentry-internal/replay-canvas@9.9.0':
|
||||
dependencies:
|
||||
'@sentry-internal/replay': 9.9.0
|
||||
'@sentry/core': 9.9.0
|
||||
|
||||
'@sentry-internal/replay@9.9.0':
|
||||
dependencies:
|
||||
'@sentry-internal/browser-utils': 9.9.0
|
||||
'@sentry/core': 9.9.0
|
||||
|
||||
'@sentry/babel-plugin-component-annotate@2.22.7': {}
|
||||
|
||||
'@sentry/browser@9.9.0':
|
||||
dependencies:
|
||||
'@sentry-internal/browser-utils': 9.9.0
|
||||
'@sentry-internal/feedback': 9.9.0
|
||||
'@sentry-internal/replay': 9.9.0
|
||||
'@sentry-internal/replay-canvas': 9.9.0
|
||||
'@sentry/core': 9.9.0
|
||||
|
||||
'@sentry/bundler-plugin-core@2.22.7':
|
||||
dependencies:
|
||||
'@babel/core': 7.25.8
|
||||
'@sentry/babel-plugin-component-annotate': 2.22.7
|
||||
'@sentry/cli': 2.39.1
|
||||
dotenv: 16.4.5
|
||||
find-up: 5.0.0
|
||||
glob: 9.3.5
|
||||
magic-string: 0.30.8
|
||||
unplugin: 1.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@sentry/cli-darwin@2.39.1':
|
||||
optional: true
|
||||
|
||||
'@sentry/cli-linux-arm64@2.39.1':
|
||||
optional: true
|
||||
|
||||
'@sentry/cli-linux-arm@2.39.1':
|
||||
optional: true
|
||||
|
||||
'@sentry/cli-linux-i686@2.39.1':
|
||||
optional: true
|
||||
|
||||
'@sentry/cli-linux-x64@2.39.1':
|
||||
optional: true
|
||||
|
||||
'@sentry/cli-win32-i686@2.39.1':
|
||||
optional: true
|
||||
|
||||
'@sentry/cli-win32-x64@2.39.1':
|
||||
optional: true
|
||||
|
||||
'@sentry/cli@2.39.1':
|
||||
dependencies:
|
||||
https-proxy-agent: 5.0.1
|
||||
node-fetch: 2.7.0
|
||||
progress: 2.0.3
|
||||
proxy-from-env: 1.1.0
|
||||
which: 2.0.2
|
||||
optionalDependencies:
|
||||
'@sentry/cli-darwin': 2.39.1
|
||||
'@sentry/cli-linux-arm': 2.39.1
|
||||
'@sentry/cli-linux-arm64': 2.39.1
|
||||
'@sentry/cli-linux-i686': 2.39.1
|
||||
'@sentry/cli-linux-x64': 2.39.1
|
||||
'@sentry/cli-win32-i686': 2.39.1
|
||||
'@sentry/cli-win32-x64': 2.39.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@sentry/core@9.9.0': {}
|
||||
|
||||
'@sentry/react@9.9.0(react@18.3.1)':
|
||||
dependencies:
|
||||
'@sentry/browser': 9.9.0
|
||||
'@sentry/core': 9.9.0
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 18.3.1
|
||||
|
||||
'@sentry/vite-plugin@2.22.7':
|
||||
dependencies:
|
||||
'@sentry/bundler-plugin-core': 2.22.7
|
||||
unplugin: 1.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@sindresorhus/is@5.6.0': {}
|
||||
|
||||
'@swc/core-darwin-arm64@1.6.5':
|
||||
@@ -5524,6 +5746,12 @@ snapshots:
|
||||
|
||||
acorn@8.12.0: {}
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.3.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
ajv@6.12.6:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -6734,6 +6962,13 @@ snapshots:
|
||||
quick-lru: 5.1.1
|
||||
resolve-alpn: 1.2.1
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
human-signals@1.1.1: {}
|
||||
|
||||
human-signals@5.0.0: {}
|
||||
@@ -7114,6 +7349,10 @@ snapshots:
|
||||
'@types/three': 0.163.0
|
||||
three: 0.163.0
|
||||
|
||||
magic-string@0.30.8:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
make-error@1.3.6: {}
|
||||
|
||||
markdown-table@3.0.4: {}
|
||||
@@ -7823,6 +8062,8 @@ snapshots:
|
||||
|
||||
prettier@3.3.3: {}
|
||||
|
||||
progress@2.0.3: {}
|
||||
|
||||
promise-worker-transferable@1.0.4:
|
||||
dependencies:
|
||||
is-promise: 2.2.2
|
||||
@@ -7843,6 +8084,8 @@ snapshots:
|
||||
|
||||
property-information@6.5.0: {}
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
pump@3.0.0:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.4
|
||||
@@ -8690,6 +8933,13 @@ snapshots:
|
||||
|
||||
universalify@2.0.1: {}
|
||||
|
||||
unplugin@1.0.1:
|
||||
dependencies:
|
||||
acorn: 8.12.0
|
||||
chokidar: 3.6.0
|
||||
webpack-sources: 3.2.3
|
||||
webpack-virtual-modules: 0.5.0
|
||||
|
||||
update-browserslist-db@1.0.16(browserslist@4.23.1):
|
||||
dependencies:
|
||||
browserslist: 4.23.1
|
||||
@@ -8751,6 +9001,10 @@ snapshots:
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
webpack-sources@3.2.3: {}
|
||||
|
||||
webpack-virtual-modules@0.5.0: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
|
||||
Reference in New Issue
Block a user