Files
ERitors-Scribe-Desktop/lib/crashReporter.ts
natreex d4765e6576 Add foundational components and logic for migration, UI design, and input handling
- Introduced foundational UI components (`Badge`, `LockCard`, `SectionHeader`, `AvatarIcon`, etc.) for flexible layouts and consistent design.
- Added migration support with the `MigrationModal` component and backend integration for exporting/importing data between Electron and Tauri.
- Extended form components with `TextAreaInput`, `OrderInput`, `ToggleField`, and `ToolbarSelect` for improved input handling.
- Updated `ScribeShell` with migration popup logic to prompt users for data migration.
- Integrated `AlertStack` for better alert handling and notification management.
- Enhanced Rust/Tauri services with migration command implementations.
- Added translations and styles for new components.
2026-04-05 12:52:54 -04:00

102 lines
2.9 KiB
TypeScript

import axios from 'axios';
import {configs, isDesktop} from '@/lib/configs';
interface CrashReportPayload {
appName: string;
appVersion: string;
platform: string;
osVersion?: string;
errorType: string;
errorMessage: string;
stackTrace?: string;
screenName?: string;
userId?: string;
breadcrumbs?: Breadcrumb[];
extraData?: Record<string, unknown>;
}
interface Breadcrumb {
timestamp: number;
action: string;
detail?: string;
}
const MAX_BREADCRUMBS = 30;
const breadcrumbs: Breadcrumb[] = [];
export function addBreadcrumb(action: string, detail?: string): void {
breadcrumbs.push({timestamp: Date.now(), action, detail});
if (breadcrumbs.length > MAX_BREADCRUMBS) breadcrumbs.shift();
}
function getPlatform(): string {
if (!isDesktop) return 'web';
const ua: string = navigator.userAgent.toLowerCase();
if (ua.includes('mac')) return 'desktop-macos';
if (ua.includes('win')) return 'desktop-windows';
if (ua.includes('linux')) return 'desktop-linux';
return 'desktop';
}
function getUserId(): string | undefined {
try {
const raw: string | null = localStorage.getItem('userId');
return raw ?? undefined;
} catch {
return undefined;
}
}
async function sendCrashReport(payload: CrashReportPayload): Promise<void> {
try {
await axios.post(configs.apiUrl + 'crash-report', payload, {
headers: {'Content-Type': 'application/json'},
timeout: 5000,
});
} catch {
// Silently fail — we can't crash while reporting a crash
}
}
function buildReport(errorType: string, errorMessage: string, stackTrace?: string): CrashReportPayload {
return {
appName: configs.appName,
appVersion: configs.appVersion,
platform: getPlatform(),
osVersion: navigator.userAgent,
errorType,
errorMessage,
stackTrace,
screenName: window.location.pathname,
userId: getUserId(),
breadcrumbs: [...breadcrumbs],
};
}
export function reportError(error: Error, extraData?: Record<string, unknown>): void {
const report: CrashReportPayload = buildReport(
error.name || 'Error',
error.message,
error.stack,
);
if (extraData) report.extraData = extraData;
sendCrashReport(report);
}
export function initCrashReporter(): void {
window.onerror = (_message, _source, _lineno, _colno, error: Error | undefined): void => {
if (error) {
sendCrashReport(buildReport('UncaughtError', error.message, error.stack));
}
};
window.onunhandledrejection = (event: PromiseRejectionEvent): void => {
const reason: unknown = event.reason;
if (reason instanceof Error) {
sendCrashReport(buildReport('UnhandledRejection', reason.message, reason.stack));
} else {
sendCrashReport(buildReport('UnhandledRejection', String(reason)));
}
};
}