From 0106bdb1d5f2c26f07bbd44b0e186b4decc6aef2 Mon Sep 17 00:00:00 2001
From: "gpt-engineer-app[bot]"
<159125892+gpt-engineer-app[bot]@users.noreply.github.com>
Date: Wed, 5 Nov 2025 15:07:31 +0000
Subject: [PATCH] feat: Integrate Cronitor RUM
---
package-lock.json | 16 ++++++++++++++
package.json | 1 +
src/App.tsx | 44 ++++++++++++++++++++++++-------------
src/lib/errorBreadcrumbs.ts | 29 +++++++++++++++++++++++-
src/lib/errorHandler.ts | 44 +++++++++++++++++++++++++++++++++++++
src/lib/retryHelpers.ts | 19 ++++++++++++++++
src/types/cronitor.d.ts | 23 +++++++++++++++++++
7 files changed, 160 insertions(+), 16 deletions(-)
create mode 100644 src/types/cronitor.d.ts
diff --git a/package-lock.json b/package-lock.json
index 11192f21..d8297901 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "vite_react_shadcn_ts",
"version": "0.0.0",
"dependencies": {
+ "@cronitorio/cronitor-rum": "^0.4.1",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
@@ -654,6 +655,15 @@
"solid-js": "^1.8"
}
},
+ "node_modules/@cronitorio/cronitor-rum": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@cronitorio/cronitor-rum/-/cronitor-rum-0.4.1.tgz",
+ "integrity": "sha512-myOOC8Cvv+881hasEV/fsnUswKXMVKfHtioZIy5SCcRPvH2jCV7T/DyZLXjm97FaeWIfTlu4s5P0dKW1zi7T3A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "web-vitals": "^3.3.0"
+ }
+ },
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -12819,6 +12829,12 @@
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
+ "node_modules/web-vitals": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz",
+ "integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg==",
+ "license": "Apache-2.0"
+ },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
diff --git a/package.json b/package.json
index b2329cd3..c1dfb536 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@cronitorio/cronitor-rum": "^0.4.1",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
diff --git a/src/App.tsx b/src/App.tsx
index 82170995..8d7e817d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,6 +5,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";
+import * as Cronitor from '@cronitorio/cronitor-rum';
import { AuthProvider } from "@/hooks/useAuth";
import { AuthModalProvider } from "@/contexts/AuthModalContext";
import { MFAStepUpProvider } from "@/contexts/MFAStepUpContext";
@@ -387,20 +388,33 @@ function AppContent(): React.JSX.Element {
);
}
-const App = (): React.JSX.Element => (
-
-
-
-
-
-
-
-
-
-
- {import.meta.env.DEV && }
-
-
-);
+const App = (): React.JSX.Element => {
+ // Initialize Cronitor RUM on app mount
+ useEffect(() => {
+ Cronitor.load("0b5d17d3f7625ce8766c2c4c85c1895d", {
+ debug: import.meta.env.DEV, // Enable debug logs in development only
+ trackMode: 'history', // Automatically track page views with React Router
+ });
+
+ // Log successful initialization
+ console.log('[Cronitor] RUM initialized');
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {import.meta.env.DEV && }
+
+
+ );
+};
export default App;
diff --git a/src/lib/errorBreadcrumbs.ts b/src/lib/errorBreadcrumbs.ts
index 1ae5de05..07022386 100644
--- a/src/lib/errorBreadcrumbs.ts
+++ b/src/lib/errorBreadcrumbs.ts
@@ -58,15 +58,42 @@ export const breadcrumb = {
level: 'info',
data,
});
+
+ // Track critical user actions in Cronitor
+ const criticalActions = ['submit', 'delete', 'update', 'create', 'login', 'logout'];
+ const isCritical = criticalActions.some(ca => action.toLowerCase().includes(ca));
+
+ if (isCritical && typeof window !== 'undefined' && window.cronitor) {
+ window.cronitor.track('critical_user_action', {
+ action,
+ component,
+ timestamp: new Date().toISOString(),
+ });
+ }
},
apiCall: (endpoint: string, method: string, status?: number) => {
+ const isError = status && status >= 400;
+
breadcrumbManager.add({
category: 'api_call',
message: `API ${method} ${endpoint}`,
- level: status && status >= 400 ? 'error' : 'info',
+ level: isError ? 'error' : 'info',
data: { endpoint, method, status },
});
+
+ // Track significant API calls in Cronitor
+ if (typeof window !== 'undefined' && window.cronitor) {
+ // Only track errors and slow requests
+ if (isError || (status && status >= 500)) {
+ window.cronitor.track('api_error', {
+ endpoint,
+ method,
+ status,
+ severity: status >= 500 ? 'high' : 'medium',
+ });
+ }
+ }
},
stateChange: (description: string, data?: Record) => {
diff --git a/src/lib/errorHandler.ts b/src/lib/errorHandler.ts
index 255c8572..79e21935 100644
--- a/src/lib/errorHandler.ts
+++ b/src/lib/errorHandler.ts
@@ -4,6 +4,16 @@ import { supabase } from '@/integrations/supabase/client';
import { breadcrumbManager } from './errorBreadcrumbs';
import { captureEnvironmentContext } from './environmentContext';
+// Cronitor RUM integration
+declare global {
+ interface Window {
+ cronitor?: {
+ track: (eventName: string, data?: Record) => void;
+ error: (error: Error | string, metadata?: Record) => void;
+ };
+ }
+}
+
export type ErrorContext = {
action: string;
userId?: string;
@@ -123,6 +133,25 @@ export const handleError = (
});
}
+ // Track error in Cronitor RUM
+ if (typeof window !== 'undefined' && window.cronitor) {
+ try {
+ window.cronitor.error(
+ error instanceof Error ? error : new Error(errorMessage),
+ {
+ errorId,
+ errorName,
+ action: context.action,
+ userId: context.userId,
+ supabaseError: supabaseErrorDetails,
+ breadcrumbCount: breadcrumbManager.getAll().length,
+ }
+ );
+ } catch (cronitorError) {
+ logger.error('Failed to track error in Cronitor', { cronitorError });
+ }
+ }
+
// Log to database with breadcrumbs (non-blocking)
try {
const envContext = captureEnvironmentContext();
@@ -218,6 +247,21 @@ export const handleNonCriticalError = (
severity: 'low',
});
+ // Track non-critical error in Cronitor (lower severity)
+ if (typeof window !== 'undefined' && window.cronitor) {
+ try {
+ window.cronitor.track('non_critical_error', {
+ errorId,
+ errorMessage,
+ action: context.action,
+ userId: context.userId,
+ severity: 'low',
+ });
+ } catch (cronitorError) {
+ logger.error('Failed to track non-critical error in Cronitor', { cronitorError });
+ }
+ }
+
// Log to database with breadcrumbs (non-blocking, fire-and-forget)
try {
const envContext = captureEnvironmentContext();
diff --git a/src/lib/retryHelpers.ts b/src/lib/retryHelpers.ts
index 643ad6bd..d33e6292 100644
--- a/src/lib/retryHelpers.ts
+++ b/src/lib/retryHelpers.ts
@@ -243,9 +243,28 @@ export async function withRetry(
error: error instanceof Error ? error.message : String(error)
});
+ // Track exhausted retries in Cronitor
+ if (typeof window !== 'undefined' && window.cronitor) {
+ window.cronitor.track('retries_exhausted', {
+ totalAttempts: config.maxAttempts,
+ error: error instanceof Error ? error.name : 'UnknownError',
+ severity: 'high',
+ });
+ }
+
throw error;
}
+ // Track retry attempts in Cronitor
+ if (attempt > 0 && typeof window !== 'undefined' && window.cronitor) {
+ window.cronitor.track('retry_attempt', {
+ attempt: attempt + 1,
+ maxAttempts: config.maxAttempts,
+ error: error instanceof Error ? error.name : 'UnknownError',
+ willRetry: attempt < config.maxAttempts - 1,
+ });
+ }
+
// Calculate delay for next attempt
const delay = calculateBackoffDelay(attempt, config);
diff --git a/src/types/cronitor.d.ts b/src/types/cronitor.d.ts
new file mode 100644
index 00000000..808fda32
--- /dev/null
+++ b/src/types/cronitor.d.ts
@@ -0,0 +1,23 @@
+declare module '@cronitorio/cronitor-rum' {
+ export interface CronitorConfig {
+ debug?: boolean;
+ trackMode?: 'history' | 'off';
+ }
+
+ export function load(apiKey: string, config?: CronitorConfig): void;
+
+ export function track(eventName: string, data?: Record): void;
+
+ export function error(error: Error | string, metadata?: Record): void;
+}
+
+declare global {
+ interface Window {
+ cronitor?: {
+ track: (eventName: string, data?: Record) => void;
+ error: (error: Error | string, metadata?: Record) => void;
+ };
+ }
+}
+
+export {};