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",
|
"name": "lotus-chat",
|
||||||
"version": "4.12.1-lotus",
|
"version": "4.12.1-lotus",
|
||||||
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"@giphy/js-fetch-api": "5.8.0",
|
"@giphy/js-fetch-api": "5.8.0",
|
||||||
"@giphy/js-types": "5.1.0",
|
"@giphy/js-types": "5.1.0",
|
||||||
"@giphy/react-components": "10.1.2",
|
"@giphy/react-components": "10.1.2",
|
||||||
|
"@sentry/react": "10.53.1",
|
||||||
"@tanstack/react-query": "5.24.1",
|
"@tanstack/react-query": "5.24.1",
|
||||||
"@tanstack/react-query-devtools": "5.24.1",
|
"@tanstack/react-query-devtools": "5.24.1",
|
||||||
"@tanstack/react-virtual": "3.2.0",
|
"@tanstack/react-virtual": "3.2.0",
|
||||||
@@ -5375,6 +5377,97 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/@simple-libs/stream-utils": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz",
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "lotus-chat",
|
"name": "lotus-chat",
|
||||||
"version": "4.12.1-lotus",
|
"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",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
"@giphy/js-fetch-api": "5.8.0",
|
"@giphy/js-fetch-api": "5.8.0",
|
||||||
"@giphy/js-types": "5.1.0",
|
"@giphy/js-types": "5.1.0",
|
||||||
"@giphy/react-components": "10.1.2",
|
"@giphy/react-components": "10.1.2",
|
||||||
|
"@sentry/react": "10.53.1",
|
||||||
"@tanstack/react-query": "5.24.1",
|
"@tanstack/react-query": "5.24.1",
|
||||||
"@tanstack/react-query-devtools": "5.24.1",
|
"@tanstack/react-query-devtools": "5.24.1",
|
||||||
"@tanstack/react-virtual": "3.2.0",
|
"@tanstack/react-virtual": "3.2.0",
|
||||||
|
|||||||
+66
-27
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
import { Provider as JotaiProvider } from 'jotai';
|
import { Provider as JotaiProvider } from 'jotai';
|
||||||
import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProvider } from 'folds';
|
import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProvider } from 'folds';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router-dom';
|
||||||
@@ -22,33 +23,71 @@ function App() {
|
|||||||
const portalContainer = document.getElementById('portalContainer') ?? undefined;
|
const portalContainer = document.getElementById('portalContainer') ?? undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipContainerProvider value={portalContainer}>
|
<Sentry.ErrorBoundary
|
||||||
<PopOutContainerProvider value={portalContainer}>
|
fallback={({ error, resetError }) => (
|
||||||
<OverlayContainerProvider value={portalContainer}>
|
<div
|
||||||
<ScreenSizeProvider value={screenSize}>
|
style={{
|
||||||
<FeatureCheck>
|
display: 'flex',
|
||||||
<ClientConfigLoader
|
flexDirection: 'column',
|
||||||
fallback={() => <ConfigConfigLoading />}
|
alignItems: 'center',
|
||||||
error={(err, retry, ignore) => (
|
justifyContent: 'center',
|
||||||
<ConfigConfigError error={err} retry={retry} ignore={ignore} />
|
height: '100vh',
|
||||||
)}
|
gap: '16px',
|
||||||
>
|
fontFamily: 'sans-serif',
|
||||||
{(clientConfig) => (
|
padding: '24px',
|
||||||
<ClientConfigProvider value={clientConfig}>
|
textAlign: 'center',
|
||||||
<QueryClientProvider client={queryClient}>
|
}}
|
||||||
<JotaiProvider>
|
>
|
||||||
<RouterProvider router={createRouter(clientConfig, screenSize)} />
|
<h2 style={{ margin: 0 }}>Something went wrong</h2>
|
||||||
</JotaiProvider>
|
<p style={{ margin: 0, color: '#666', maxWidth: '400px' }}>
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
{error instanceof Error ? error.message : 'An unexpected error occurred.'}
|
||||||
</QueryClientProvider>
|
</p>
|
||||||
</ClientConfigProvider>
|
<button
|
||||||
)}
|
type="button"
|
||||||
</ClientConfigLoader>
|
onClick={resetError}
|
||||||
</FeatureCheck>
|
style={{
|
||||||
</ScreenSizeProvider>
|
padding: '8px 20px',
|
||||||
</OverlayContainerProvider>
|
borderRadius: '6px',
|
||||||
</PopOutContainerProvider>
|
border: 'none',
|
||||||
</TooltipContainerProvider>
|
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 */
|
/* eslint-disable import/first */
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { enableMapSet } from 'immer';
|
import { enableMapSet } from 'immer';
|
||||||
@@ -6,6 +7,25 @@ import '@fontsource/inter/variable.css';
|
|||||||
import 'folds/dist/style.css';
|
import 'folds/dist/style.css';
|
||||||
import { configClass, varsClass } from 'folds';
|
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();
|
enableMapSet();
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|||||||
Reference in New Issue
Block a user