feat: add Sentry error tracking with defensive error boundary
- Initialize Sentry SDK in index.tsx when VITE_SENTRY_DSN env var is set - Wrap entire App with Sentry.ErrorBoundary (replaces the hard crash with a retry UI) - 5% trace sample rate, sendDefaultPii disabled, strip events containing accessToken - Add .env.production template with VITE_SENTRY_DSN placeholder - Get your DSN from sentry.io -> Project Settings -> Client Keys Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# Sentry DSN — get from sentry.io → Project Settings → Client Keys
|
||||
# VITE_SENTRY_DSN=https://xxx@oXXX.ingest.sentry.io/YYYY
|
||||
VITE_SENTRY_DSN=
|
||||
VITE_APP_VERSION=lotus
|
||||
Generated
+93
@@ -7,6 +7,7 @@
|
||||
"": {
|
||||
"name": "lotus-chat",
|
||||
"version": "4.12.1-lotus",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
||||
@@ -16,6 +17,7 @@
|
||||
"@giphy/js-fetch-api": "5.8.0",
|
||||
"@giphy/js-types": "5.1.0",
|
||||
"@giphy/react-components": "10.1.2",
|
||||
"@sentry/react": "10.53.1",
|
||||
"@tanstack/react-query": "5.24.1",
|
||||
"@tanstack/react-query-devtools": "5.24.1",
|
||||
"@tanstack/react-virtual": "3.2.0",
|
||||
@@ -5375,6 +5377,97 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/browser-utils": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.53.1.tgz",
|
||||
"integrity": "sha512-X4d6y8sBMjmNhcDW4eMBU3ASsNIMz8dqaFkhyIMN/dkYr/yZKnbRZPaVuVUGvHKjnlficPpIH0/HK9KBjrYxPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "10.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/feedback": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.53.1.tgz",
|
||||
"integrity": "sha512-vVpTI/aEYN5d9IgZeYJWMqVaN0+iFgidSrYNAsZTh1US5sJUzF/wrl+68KdpmCtFROrN3jiAn1oPSwL5CKvEJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "10.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.53.1.tgz",
|
||||
"integrity": "sha512-wZNzTBYkgGUPWMuUQv7L64+OJmoCnz7GQNiTrTFK6EVAjJXFBCSsPp/nhif0bLhbk8+0g4xz633uOhpXuQbFdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "10.53.1",
|
||||
"@sentry/core": "10.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay-canvas": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.53.1.tgz",
|
||||
"integrity": "sha512-aueLaf/2prExwA76BGU5/bOXCKWqtt6jQXWA6WJQNrmKpPEtZJB4ypnpsou0McXQCF8tur2Y8U0TEkwQP13yJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/replay": "10.53.1",
|
||||
"@sentry/core": "10.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/browser": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.53.1.tgz",
|
||||
"integrity": "sha512-zXF373hzUOGzUOrqd8xb1U3LQi5uYC3mwv+z5OMKUUinQlu30tTWBs7ypy6YTchtix9QlYaHWlayUF8vBZ5UjA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "10.53.1",
|
||||
"@sentry-internal/feedback": "10.53.1",
|
||||
"@sentry-internal/replay": "10.53.1",
|
||||
"@sentry-internal/replay-canvas": "10.53.1",
|
||||
"@sentry/core": "10.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.53.1.tgz",
|
||||
"integrity": "sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/react": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.53.1.tgz",
|
||||
"integrity": "sha512-lrwNq5T/zW84l60894TpKHPcvFuc1I/Hnohecc0TfYVpIcYYuw2orCHoU4v4wgkFaJUpegVetbgdOphViyLVjA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/browser": "10.53.1",
|
||||
"@sentry/core": "10.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14.0 || 17.x || 18.x || 19.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@simple-libs/stream-utils": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz",
|
||||
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "lotus-chat",
|
||||
"version": "4.12.1-lotus",
|
||||
"description": "Lotus Chat \u2014 Matrix client for Lotus Guild",
|
||||
"description": "Lotus Chat — Matrix client for Lotus Guild",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
@@ -70,6 +70,7 @@
|
||||
"@giphy/js-fetch-api": "5.8.0",
|
||||
"@giphy/js-types": "5.1.0",
|
||||
"@giphy/react-components": "10.1.2",
|
||||
"@sentry/react": "10.53.1",
|
||||
"@tanstack/react-query": "5.24.1",
|
||||
"@tanstack/react-query-devtools": "5.24.1",
|
||||
"@tanstack/react-virtual": "3.2.0",
|
||||
|
||||
+66
-27
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Provider as JotaiProvider } from 'jotai';
|
||||
import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProvider } from 'folds';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
@@ -22,33 +23,71 @@ function App() {
|
||||
const portalContainer = document.getElementById('portalContainer') ?? undefined;
|
||||
|
||||
return (
|
||||
<TooltipContainerProvider value={portalContainer}>
|
||||
<PopOutContainerProvider value={portalContainer}>
|
||||
<OverlayContainerProvider value={portalContainer}>
|
||||
<ScreenSizeProvider value={screenSize}>
|
||||
<FeatureCheck>
|
||||
<ClientConfigLoader
|
||||
fallback={() => <ConfigConfigLoading />}
|
||||
error={(err, retry, ignore) => (
|
||||
<ConfigConfigError error={err} retry={retry} ignore={ignore} />
|
||||
)}
|
||||
>
|
||||
{(clientConfig) => (
|
||||
<ClientConfigProvider value={clientConfig}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<JotaiProvider>
|
||||
<RouterProvider router={createRouter(clientConfig, screenSize)} />
|
||||
</JotaiProvider>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
</ClientConfigProvider>
|
||||
)}
|
||||
</ClientConfigLoader>
|
||||
</FeatureCheck>
|
||||
</ScreenSizeProvider>
|
||||
</OverlayContainerProvider>
|
||||
</PopOutContainerProvider>
|
||||
</TooltipContainerProvider>
|
||||
<Sentry.ErrorBoundary
|
||||
fallback={({ error, resetError }) => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100vh',
|
||||
gap: '16px',
|
||||
fontFamily: 'sans-serif',
|
||||
padding: '24px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ margin: 0 }}>Something went wrong</h2>
|
||||
<p style={{ margin: 0, color: '#666', maxWidth: '400px' }}>
|
||||
{error instanceof Error ? error.message : 'An unexpected error occurred.'}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetError}
|
||||
style={{
|
||||
padding: '8px 20px',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
background: '#5865f2',
|
||||
color: '#fff',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<TooltipContainerProvider value={portalContainer}>
|
||||
<PopOutContainerProvider value={portalContainer}>
|
||||
<OverlayContainerProvider value={portalContainer}>
|
||||
<ScreenSizeProvider value={screenSize}>
|
||||
<FeatureCheck>
|
||||
<ClientConfigLoader
|
||||
fallback={() => <ConfigConfigLoading />}
|
||||
error={(err, retry, ignore) => (
|
||||
<ConfigConfigError error={err} retry={retry} ignore={ignore} />
|
||||
)}
|
||||
>
|
||||
{(clientConfig) => (
|
||||
<ClientConfigProvider value={clientConfig}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<JotaiProvider>
|
||||
<RouterProvider router={createRouter(clientConfig, screenSize)} />
|
||||
</JotaiProvider>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
</ClientConfigProvider>
|
||||
)}
|
||||
</ClientConfigLoader>
|
||||
</FeatureCheck>
|
||||
</ScreenSizeProvider>
|
||||
</OverlayContainerProvider>
|
||||
</PopOutContainerProvider>
|
||||
</TooltipContainerProvider>
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable import/first */
|
||||
import * as Sentry from '@sentry/react';
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { enableMapSet } from 'immer';
|
||||
@@ -6,6 +7,25 @@ import '@fontsource/inter/variable.css';
|
||||
import 'folds/dist/style.css';
|
||||
import { configClass, varsClass } from 'folds';
|
||||
|
||||
if (import.meta.env.VITE_SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||
environment: import.meta.env.MODE,
|
||||
release: import.meta.env.VITE_APP_VERSION,
|
||||
tracesSampleRate: 0.05,
|
||||
// Don't send PII — strip user IPs and don't attach user info automatically
|
||||
sendDefaultPii: false,
|
||||
beforeSend(event) {
|
||||
// Scrub any accessToken that may have leaked into breadcrumbs/data
|
||||
const str = JSON.stringify(event);
|
||||
if (str.includes('access_token') || str.includes('accessToken')) {
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
enableMapSet();
|
||||
|
||||
import './index.css';
|
||||
|
||||
Reference in New Issue
Block a user