103 lines
3.0 KiB
TypeScript
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)));
|
|
}
|
|
};
|
|
}
|