mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 18:16:29 +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';
|
import * as https from 'https';
|
||||||
|
|
||||||
/** Error codes for remote proxy status */
|
/** 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 */
|
/** Status returned from remote proxy health check */
|
||||||
export interface RemoteProxyStatus {
|
export interface RemoteProxyStatus {
|
||||||
@@ -77,8 +83,8 @@ function buildProxyUrl(
|
|||||||
* Map error to RemoteProxyErrorCode
|
* Map error to RemoteProxyErrorCode
|
||||||
*
|
*
|
||||||
* Handles various error types including:
|
* Handles various error types including:
|
||||||
* - NodeJS.ErrnoException (ECONNREFUSED, ETIMEDOUT)
|
* - NodeJS.ErrnoException (ECONNREFUSED, ETIMEDOUT, ENOTFOUND, ENETUNREACH)
|
||||||
* - Fetch errors (AbortError, TypeError)
|
* - Fetch errors (AbortError, TypeError, "fetch failed")
|
||||||
* - HTTP status codes (401, 403)
|
* - HTTP status codes (401, 403)
|
||||||
*/
|
*/
|
||||||
function mapErrorToCode(error: Error, statusCode?: number): RemoteProxyErrorCode {
|
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 rawCode = (error as NodeJS.ErrnoException).code;
|
||||||
const code = typeof rawCode === 'string' ? rawCode.toLowerCase() : undefined;
|
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
|
// Connection refused
|
||||||
if (code === 'econnrefused' || message.includes('connection refused')) {
|
if (code === 'econnrefused' || message.includes('connection refused')) {
|
||||||
return 'CONNECTION_REFUSED';
|
return 'CONNECTION_REFUSED';
|
||||||
@@ -107,6 +134,17 @@ function mapErrorToCode(error: Error, statusCode?: number): RemoteProxyErrorCode
|
|||||||
return 'AUTH_FAILED';
|
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';
|
return 'UNKNOWN';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,11 +156,15 @@ function getErrorMessage(errorCode: RemoteProxyErrorCode, rawError?: string): st
|
|||||||
case 'CONNECTION_REFUSED':
|
case 'CONNECTION_REFUSED':
|
||||||
return 'Connection refused - is the proxy running?';
|
return 'Connection refused - is the proxy running?';
|
||||||
case 'TIMEOUT':
|
case 'TIMEOUT':
|
||||||
return 'Connection timed out';
|
return 'Connection timed out - server may be slow or unreachable';
|
||||||
case 'AUTH_FAILED':
|
case 'AUTH_FAILED':
|
||||||
return 'Authentication failed - check auth token';
|
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:
|
default:
|
||||||
return rawError || 'Unknown error';
|
return rawError || 'Connection failed';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,18 +47,29 @@ describe('remote-proxy-client', () => {
|
|||||||
|
|
||||||
describe('RemoteProxyErrorCode', () => {
|
describe('RemoteProxyErrorCode', () => {
|
||||||
it('should define expected error codes', () => {
|
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
|
// Type-level test - ensure error codes can be used
|
||||||
const status1: RemoteProxyStatus = { reachable: false, errorCode: 'CONNECTION_REFUSED' };
|
const status1: RemoteProxyStatus = { reachable: false, errorCode: 'CONNECTION_REFUSED' };
|
||||||
const status2: RemoteProxyStatus = { reachable: false, errorCode: 'TIMEOUT' };
|
const status2: RemoteProxyStatus = { reachable: false, errorCode: 'TIMEOUT' };
|
||||||
const status3: RemoteProxyStatus = { reachable: false, errorCode: 'AUTH_FAILED' };
|
const status3: RemoteProxyStatus = { reachable: false, errorCode: 'AUTH_FAILED' };
|
||||||
const status4: RemoteProxyStatus = { reachable: false, errorCode: 'UNKNOWN' };
|
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(status1.errorCode);
|
||||||
expect(validCodes).toContain(status2.errorCode);
|
expect(validCodes).toContain(status2.errorCode);
|
||||||
expect(validCodes).toContain(status3.errorCode);
|
expect(validCodes).toContain(status3.errorCode);
|
||||||
expect(validCodes).toContain(status4.errorCode);
|
expect(validCodes).toContain(status4.errorCode);
|
||||||
|
expect(validCodes).toContain(status5.errorCode);
|
||||||
|
expect(validCodes).toContain(status6.errorCode);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user