mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 20:17:45 +00:00
fix(cliproxy): improve remote proxy error messages
- Add DNS_FAILED and NETWORK_UNREACHABLE error codes - Map "fetch failed" errors to NETWORK_UNREACHABLE for better UX - Extract nested error cause for more accurate error detection - Improve error messages with actionable hints Fixes #142
This commit is contained in:
@@ -8,7 +8,13 @@
|
||||
import * as https from 'https';
|
||||
|
||||
/** Error codes for remote proxy status */
|
||||
export type RemoteProxyErrorCode = 'CONNECTION_REFUSED' | 'TIMEOUT' | 'AUTH_FAILED' | 'UNKNOWN';
|
||||
export type RemoteProxyErrorCode =
|
||||
| 'CONNECTION_REFUSED'
|
||||
| 'TIMEOUT'
|
||||
| 'AUTH_FAILED'
|
||||
| 'DNS_FAILED'
|
||||
| 'NETWORK_UNREACHABLE'
|
||||
| 'UNKNOWN';
|
||||
|
||||
/** Status returned from remote proxy health check */
|
||||
export interface RemoteProxyStatus {
|
||||
@@ -77,8 +83,8 @@ function buildProxyUrl(
|
||||
* Map error to RemoteProxyErrorCode
|
||||
*
|
||||
* Handles various error types including:
|
||||
* - NodeJS.ErrnoException (ECONNREFUSED, ETIMEDOUT)
|
||||
* - Fetch errors (AbortError, TypeError)
|
||||
* - NodeJS.ErrnoException (ECONNREFUSED, ETIMEDOUT, ENOTFOUND, ENETUNREACH)
|
||||
* - Fetch errors (AbortError, TypeError, "fetch failed")
|
||||
* - HTTP status codes (401, 403)
|
||||
*/
|
||||
function mapErrorToCode(error: Error, statusCode?: number): RemoteProxyErrorCode {
|
||||
@@ -87,6 +93,27 @@ function mapErrorToCode(error: Error, statusCode?: number): RemoteProxyErrorCode
|
||||
const rawCode = (error as NodeJS.ErrnoException).code;
|
||||
const code = typeof rawCode === 'string' ? rawCode.toLowerCase() : undefined;
|
||||
|
||||
// DNS resolution failed
|
||||
if (
|
||||
code === 'enotfound' ||
|
||||
code === 'eai_again' ||
|
||||
message.includes('getaddrinfo') ||
|
||||
message.includes('dns')
|
||||
) {
|
||||
return 'DNS_FAILED';
|
||||
}
|
||||
|
||||
// Network unreachable / host unreachable
|
||||
if (
|
||||
code === 'enetunreach' ||
|
||||
code === 'ehostunreach' ||
|
||||
code === 'enetdown' ||
|
||||
message.includes('network') ||
|
||||
message.includes('unreachable')
|
||||
) {
|
||||
return 'NETWORK_UNREACHABLE';
|
||||
}
|
||||
|
||||
// Connection refused
|
||||
if (code === 'econnrefused' || message.includes('connection refused')) {
|
||||
return 'CONNECTION_REFUSED';
|
||||
@@ -107,6 +134,17 @@ function mapErrorToCode(error: Error, statusCode?: number): RemoteProxyErrorCode
|
||||
return 'AUTH_FAILED';
|
||||
}
|
||||
|
||||
// Generic "fetch failed" - try to extract cause
|
||||
if (message.includes('fetch failed') || message.includes('failed to fetch')) {
|
||||
// Check if there's a cause property (Node.js 18+)
|
||||
const cause = (error as Error & { cause?: Error }).cause;
|
||||
if (cause) {
|
||||
return mapErrorToCode(cause);
|
||||
}
|
||||
// Likely network/DNS issue if no specific cause
|
||||
return 'NETWORK_UNREACHABLE';
|
||||
}
|
||||
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
|
||||
@@ -118,11 +156,15 @@ function getErrorMessage(errorCode: RemoteProxyErrorCode, rawError?: string): st
|
||||
case 'CONNECTION_REFUSED':
|
||||
return 'Connection refused - is the proxy running?';
|
||||
case 'TIMEOUT':
|
||||
return 'Connection timed out';
|
||||
return 'Connection timed out - server may be slow or unreachable';
|
||||
case 'AUTH_FAILED':
|
||||
return 'Authentication failed - check auth token';
|
||||
case 'DNS_FAILED':
|
||||
return 'DNS lookup failed - check hostname';
|
||||
case 'NETWORK_UNREACHABLE':
|
||||
return 'Network unreachable - check if host is on same network';
|
||||
default:
|
||||
return rawError || 'Unknown error';
|
||||
return rawError || 'Connection failed';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,18 +47,29 @@ describe('remote-proxy-client', () => {
|
||||
|
||||
describe('RemoteProxyErrorCode', () => {
|
||||
it('should define expected error codes', () => {
|
||||
const validCodes = ['CONNECTION_REFUSED', 'TIMEOUT', 'AUTH_FAILED', 'UNKNOWN'];
|
||||
const validCodes = [
|
||||
'CONNECTION_REFUSED',
|
||||
'TIMEOUT',
|
||||
'AUTH_FAILED',
|
||||
'DNS_FAILED',
|
||||
'NETWORK_UNREACHABLE',
|
||||
'UNKNOWN',
|
||||
];
|
||||
|
||||
// Type-level test - ensure error codes can be used
|
||||
const status1: RemoteProxyStatus = { reachable: false, errorCode: 'CONNECTION_REFUSED' };
|
||||
const status2: RemoteProxyStatus = { reachable: false, errorCode: 'TIMEOUT' };
|
||||
const status3: RemoteProxyStatus = { reachable: false, errorCode: 'AUTH_FAILED' };
|
||||
const status4: RemoteProxyStatus = { reachable: false, errorCode: 'UNKNOWN' };
|
||||
const status5: RemoteProxyStatus = { reachable: false, errorCode: 'DNS_FAILED' };
|
||||
const status6: RemoteProxyStatus = { reachable: false, errorCode: 'NETWORK_UNREACHABLE' };
|
||||
|
||||
expect(validCodes).toContain(status1.errorCode);
|
||||
expect(validCodes).toContain(status2.errorCode);
|
||||
expect(validCodes).toContain(status3.errorCode);
|
||||
expect(validCodes).toContain(status4.errorCode);
|
||||
expect(validCodes).toContain(status5.errorCode);
|
||||
expect(validCodes).toContain(status6.errorCode);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user