Files
DeskClaw/src/lib/error-model.ts
Lingxuan Zuo 3d664c017a refactor(new merge) (#369)
Co-authored-by: paisley <8197966+su8su@users.noreply.github.com>
Co-authored-by: zuolingxuan <zuolingxuan@bytedance.com>
2026-03-09 20:18:25 +08:00

103 lines
2.5 KiB
TypeScript

export type AppErrorCode =
| 'AUTH_INVALID'
| 'TIMEOUT'
| 'RATE_LIMIT'
| 'PERMISSION'
| 'CHANNEL_UNAVAILABLE'
| 'NETWORK'
| 'CONFIG'
| 'GATEWAY'
| 'UNKNOWN';
export class AppError extends Error {
code: AppErrorCode;
cause?: unknown;
details?: Record<string, unknown>;
constructor(code: AppErrorCode, message: string, cause?: unknown, details?: Record<string, unknown>) {
super(message);
this.code = code;
this.cause = cause;
this.details = details;
}
}
export function mapBackendErrorCode(code?: string): AppErrorCode {
switch (code) {
case 'TIMEOUT':
return 'TIMEOUT';
case 'PERMISSION':
return 'PERMISSION';
case 'GATEWAY':
return 'GATEWAY';
case 'VALIDATION':
return 'CONFIG';
case 'UNSUPPORTED':
return 'CHANNEL_UNAVAILABLE';
default:
return 'UNKNOWN';
}
}
function classifyMessage(message: string): AppErrorCode {
const lower = message.toLowerCase();
if (
lower.includes('invalid ipc channel')
|| lower.includes('no handler registered')
|| lower.includes('window is not defined')
|| lower.includes('unsupported')
) {
return 'CHANNEL_UNAVAILABLE';
}
if (
lower.includes('invalid authentication')
|| lower.includes('unauthorized')
|| lower.includes('auth failed')
|| lower.includes('401')
) {
return 'AUTH_INVALID';
}
if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('abort')) {
return 'TIMEOUT';
}
if (lower.includes('rate limit') || lower.includes('429')) {
return 'RATE_LIMIT';
}
if (
lower.includes('permission')
|| lower.includes('forbidden')
|| lower.includes('denied')
|| lower.includes('403')
) {
return 'PERMISSION';
}
if (
lower.includes('network')
|| lower.includes('fetch')
|| lower.includes('econnrefused')
|| lower.includes('econnreset')
|| lower.includes('enotfound')
) {
return 'NETWORK';
}
if (lower.includes('gateway')) {
return 'GATEWAY';
}
if (lower.includes('config') || lower.includes('invalid') || lower.includes('validation') || lower.includes('400')) {
return 'CONFIG';
}
return 'UNKNOWN';
}
export function normalizeAppError(err: unknown, details?: Record<string, unknown>): AppError {
if (err instanceof AppError) {
return new AppError(err.code, err.message, err.cause ?? err, { ...(err.details ?? {}), ...(details ?? {}) });
}
const message = err instanceof Error ? err.message : String(err);
return new AppError(classifyMessage(message), message, err, details);
}