Files
ERitors-Scribe-Desktop/lib/crashReporter.ts

103 lines
3.0 KiB
TypeScript

import {fetch} from '@tauri-apps/plugin-http';
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 fetch(configs.apiUrl + 'crash-report', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload),
});
} 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)));
}
};
}