11import { type AxiosError , type RawAxiosResponseHeaders , type AxiosResponseHeaders } from 'axios' ;
22import { extendError } from './extendError' ;
3+ import { sanitizeHeaders , truncateBody } from './helpers' ;
34
45const IlcAxiosError = extendError ( 'AxiosError' ) ;
56
6- const SENSITIVE_HEADERS = [ 'authorization' , 'cookie' , 'set-cookie' , 'x-api-key' , 'x-auth-token' , 'proxy-authorization' ] ;
7-
8- const MAX_BODY_LENGTH = 1000 ;
9-
107type HeadersInput = RawAxiosResponseHeaders | AxiosResponseHeaders | Record < string , unknown > | undefined ;
118
12- /**
13- * Sanitizes headers by redacting sensitive values
14- */
15- export function sanitizeHeaders ( headers : HeadersInput ) : Record < string , unknown > | undefined {
16- if ( ! headers || typeof headers !== 'object' ) {
17- return undefined ;
18- }
19-
20- const sanitized : Record < string , unknown > = { } ;
21-
22- for ( const [ key , value ] of Object . entries ( headers ) ) {
23- const lowerKey = key . toLowerCase ( ) ;
24- if ( SENSITIVE_HEADERS . includes ( lowerKey ) ) {
25- sanitized [ key ] = '[REDACTED]' ;
26- } else {
27- sanitized [ key ] = value ;
28- }
29- }
30-
31- return sanitized ;
32- }
33-
34- /**
35- * Truncates body content if too large, returns metadata for non-string bodies
36- */
37- export function truncateBody ( body : unknown ) : { content : unknown ; truncated ?: boolean ; type ?: string ; length ?: number } {
38- if ( body === undefined || body === null ) {
39- return { content : body } ;
40- }
41-
42- // Handle string bodies
43- if ( typeof body === 'string' ) {
44- if ( body . length > MAX_BODY_LENGTH ) {
45- return {
46- content : body . substring ( 0 , MAX_BODY_LENGTH ) ,
47- truncated : true ,
48- length : body . length ,
49- } ;
50- }
51- return { content : body } ;
52- }
53-
54- // Handle objects - try to stringify
55- if ( typeof body === 'object' ) {
56- try {
57- const stringified = JSON . stringify ( body ) ;
58- if ( stringified . length > MAX_BODY_LENGTH ) {
59- return {
60- content : stringified . substring ( 0 , MAX_BODY_LENGTH ) ,
61- truncated : true ,
62- length : stringified . length ,
63- type : 'object' ,
64- } ;
65- }
66- return { content : body } ;
67- } catch {
68- // Circular reference or non-serializable
69- return {
70- content : '[Non-serializable object]' ,
71- type : typeof body ,
72- } ;
73- }
74- }
75-
76- // Handle other types (number, boolean, etc.)
77- return { content : body } ;
78- }
79-
80- /**
81- * Safely stringifies a value, handling circular references
82- */
83- export function safeStringify ( value : unknown ) : string | undefined {
84- if ( value === undefined || value === null ) {
85- return undefined ;
86- }
87-
88- try {
89- return JSON . stringify ( value ) ;
90- } catch {
91- return '[Circular or non-serializable]' ;
92- }
93- }
94-
959export function isAxiosError ( err : unknown ) : err is AxiosError {
9610 return Boolean ( ( err as AxiosError ) ?. isAxiosError ) ;
9711}
@@ -103,18 +17,13 @@ interface NetworkErrorDetails {
10317 cause ?: string ;
10418}
10519
106- /**
107- * Extracts network-level error details from an AxiosError
108- */
10920function getNetworkErrorDetails ( err : AxiosError ) : NetworkErrorDetails {
11021 const details : NetworkErrorDetails = { } ;
11122
112- // err.code contains network error codes like ECONNREFUSED, ETIMEDOUT, etc.
11323 if ( err . code ) {
11424 details . code = err . code ;
11525 }
11626
117- // Access low-level error properties if available
11827 const anyErr = err as unknown as Record < string , unknown > ;
11928 if ( typeof anyErr . errno === 'number' || typeof anyErr . errno === 'string' ) {
12029 details . errno = anyErr . errno ;
@@ -123,7 +32,6 @@ function getNetworkErrorDetails(err: AxiosError): NetworkErrorDetails {
12332 details . syscall = anyErr . syscall ;
12433 }
12534
126- // Extract cause message if available
12735 if ( err . cause instanceof Error ) {
12836 details . cause = err . cause . message ;
12937 } else if ( typeof err . cause === 'string' ) {
@@ -133,15 +41,11 @@ function getNetworkErrorDetails(err: AxiosError): NetworkErrorDetails {
13341 return details ;
13442}
13543
136- /**
137- * Generates a meaningful error message, with fallback for network errors
138- */
13944function getErrorMessage ( err : AxiosError ) : string {
14045 if ( err . message ) {
14146 return err . message ;
14247 }
14348
144- // Fallback for network errors that may have empty message
14549 if ( err . code ) {
14650 const url = err . config ?. url || 'unknown URL' ;
14751 return `${ err . code } : ${ url } ` ;
@@ -162,27 +66,22 @@ export function axiosErrorTransformer<T = unknown>(err: T): typeof IlcAxiosError
16266 return new IlcAxiosError ( {
16367 message : getErrorMessage ( err ) ,
16468 data : {
165- // Existing fields (preserved for backward compatibility)
16669 response : {
16770 status : err . response ?. status ,
16871 statusText : err . response ?. statusText ,
16972 data : responseBody . content ,
17073 dataTruncated : responseBody . truncated ,
17174 dataLength : responseBody . length ,
172- headers : sanitizeHeaders ( err . response ?. headers ) ,
75+ headers : sanitizeHeaders ( err . response ?. headers as HeadersInput ) ,
17376 } ,
17477 url : err . config ?. url ,
17578 method : err . config ?. method ,
17679 payload : requestPayload . content ,
17780 payloadTruncated : requestPayload . truncated ,
17881 payloadLength : requestPayload . length ,
17982 headers : sanitizeHeaders ( err . config ?. headers as HeadersInput ) ,
180-
181- // New fields for enhanced diagnostics
18283 baseURL : err . config ?. baseURL ,
18384 timeout : err . config ?. timeout ,
184-
185- // Network error details
18685 ...networkDetails ,
18786 } ,
18887 } ) ;
0 commit comments