export const NETWORK_ERROR_CODES = [ 'NETWORK', 'TIMEOUT', 'ABORTED', 'ENOTFOUND', 'EAI_AGAIN', 'ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT', 'CERT_HAS_EXPIRED', 'DEPTH_ZERO_SELF_SIGNED_CERT', 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'ERR_TLS_CERT_ALTNAME_INVALID', ] as const export type NetworkErrorCode = (typeof NETWORK_ERROR_CODES)[number] export class NetworkError extends Error { constructor( message: string, public code: NetworkErrorCode = 'NETWORK', ) { super(message) } } export class SessionChangedError extends Error { constructor() { super('Сессия изменилась. Повторите подключение игры') } } export class ApiError extends Error { constructor(public status: number) { super( status === 401 ? 'Сессия истекла. Войдите снова' : status === 403 ? 'Нет доступа к действию' : status === 400 ? 'Действие пока недоступно для этой игры' : `Ошибка сервера: HTTP ${status}`, ) } } function causeCode(error: unknown, depth = 0): NetworkErrorCode | undefined { if (!error || typeof error !== 'object' || depth > 3) return const value = error as { code?: unknown; cause?: unknown; errors?: unknown[] } if (NETWORK_ERROR_CODES.includes(value.code as NetworkErrorCode)) return value.code as NetworkErrorCode return ( causeCode(value.cause, depth + 1) ?? (Array.isArray(value.errors) ? value.errors.map((e) => causeCode(e, depth + 1)).find(Boolean) : undefined) ) } export function networkFailure(error: unknown, signal: AbortSignal): NetworkError { if (signal.aborted) { return signal.reason?.name === 'TimeoutError' ? new NetworkError('Превышено время ожидания ответа сервера', 'TIMEOUT') : new NetworkError('Подготовка отменена', 'ABORTED') } if (error instanceof NetworkError) return error const code = causeCode(error) ?? 'NETWORK' const message = code === 'ENOTFOUND' || code === 'EAI_AGAIN' ? 'Не удалось определить адрес сервера' : code.includes('CERT') || code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' ? 'Ошибка защищённого соединения с сервером' : code === 'ETIMEDOUT' || code === 'UND_ERR_CONNECT_TIMEOUT' ? 'Превышено время ожидания соединения с сервером' : code === 'ECONNREFUSED' ? 'Сервер отклонил соединение' : code === 'ECONNRESET' ? 'Соединение с сервером прервано' : 'Сервер недоступен' return new NetworkError(message, code) }