1+ /**
2+ * Ensures all Promise rejections use proper Error objects
3+ *
4+ * This fix addresses a TypeScript error where Promise rejections were not
5+ * guaranteed to be Error objects. The code now checks if the rejection reason
6+ * is an Error instance and, if not, converts it to an Error object.
7+ *
8+ * Fixed in:
9+ * - Request interceptor error handler
10+ * - Response interceptor error handler
11+ *
12+ * This ensures type safety and better error handling throughout the application.
13+ */
14+ /**
15+ * TypeScript type declarations for Vite's import.meta.env
16+ *
17+ * These declarations extend the ImportMeta interface to include Vite-specific
18+ * environment variables, resolving the TypeScript error:
19+ * "Property 'env' does not exist on type 'ImportMeta'"
20+ *
21+ * @see https://vitejs.dev/guide/env-and-mode.html#env-files
22+ */
123import axios , {
224 AxiosError ,
325 AxiosInstance ,
4- AxiosRequestConfig ,
26+ AxiosRequestConfig , // Import AxiosRequestConfig
527 AxiosResponse ,
628 isAxiosError as axiosIsAxiosError ,
729} from 'axios' ;
8- import { useAuthStore } from '../state/authStore' ; // Adjust path if needed
930
1031// TypeScript declaration for Vite env
1132declare global {
@@ -21,11 +42,11 @@ declare global {
2142
2243// API base URL - should match your backend
2344const API_BASE_URL = import . meta. env . VITE_API_BASE_URL || 'http://localhost:3000/api' ;
24-
2545// Default timeout in milliseconds
2646const DEFAULT_TIMEOUT = 15000 ;
2747
28- // Extend AxiosRequestConfig to add _retry flag for token refresh logic
48+ // Define an interface for the request config with retry flag
49+ // by extending the original AxiosRequestConfig
2950interface ExtendedAxiosRequestConfig extends AxiosRequestConfig {
3051 _retry ?: boolean ;
3152}
@@ -45,7 +66,7 @@ const createApiClient = (): AxiosInstance => {
4566
4667 // Request interceptor - adds auth token to requests
4768 apiClient . interceptors . request . use (
48- ( config ) => {
69+ config => {
4970 const token = localStorage . getItem ( 'accessToken' ) ;
5071 if ( token && config . headers ) {
5172 config . headers . Authorization = `Bearer ${ token } ` ;
@@ -55,65 +76,60 @@ const createApiClient = (): AxiosInstance => {
5576 ( error : AxiosError ) => Promise . reject ( error )
5677 ) ;
5778
58- // Response interceptor - handle 401 errors and token refresh
79+ // Response interceptor - handle common errors
5980 apiClient . interceptors . response . use (
6081 ( response : AxiosResponse ) => response ,
6182 async ( error : AxiosError ) => {
83+ // Cast error.config to the extended interface
6284 const originalRequest = error . config as ExtendedAxiosRequestConfig ;
6385
86+ // Handle 401 Unauthorized errors (expired token)
6487 if ( error . response ?. status === 401 && originalRequest && ! originalRequest . _retry ) {
6588 originalRequest . _retry = true ;
6689
6790 try {
68- // Attempt to refresh token
91+ // Call refresh token endpoint
6992 const refreshResponse = await axios . post (
7093 `${ API_BASE_URL } /auth/refresh-token` ,
7194 { } ,
72- { withCredentials : true }
95+ {
96+ withCredentials : true , // Needed for cookies
97+ }
7398 ) ;
7499
75100 const newToken = refreshResponse . data . token ;
76101 localStorage . setItem ( 'accessToken' , newToken ) ;
77102
103+ // Retry the original request with new token
78104 if ( originalRequest . headers ) {
79105 originalRequest . headers . Authorization = `Bearer ${ newToken } ` ;
80106 }
81-
82107 return apiClient ( originalRequest ) ;
83108 } catch ( refreshError ) {
84- // Refresh token failed - logout user
109+ // If refresh token fails, redirect to login
85110 localStorage . removeItem ( 'accessToken' ) ;
86- const store = useAuthStore . getState ( ) ;
87- store . logout ( ) ;
88-
111+ // Ensure window is defined (for SSR or test environments)
89112 if ( typeof window !== 'undefined' ) {
90113 window . location . href = '/login' ;
91114 }
92115
116+ // Ensure we reject with an Error object
93117 if ( refreshError instanceof Error ) {
94118 return Promise . reject ( refreshError ) ;
95119 } else if ( typeof refreshError === 'string' ) {
96120 return Promise . reject ( new Error ( refreshError ) ) ;
97121 } else {
122+ // For other types, you might want to serialize or provide a generic message
98123 return Promise . reject (
99124 new Error (
100125 `An unknown error occurred during token refresh: ${ JSON . stringify ( refreshError ) } `
101126 )
102127 ) ;
103128 }
104129 }
105- } else if ( error . response ?. status === 401 ) {
106- // Other 401 errors after retry or no retry - logout user
107- localStorage . removeItem ( 'accessToken' ) ;
108- const store = useAuthStore . getState ( ) ;
109- store . logout ( ) ;
110-
111- if ( typeof window !== 'undefined' ) {
112- window . location . href = '/login' ;
113- }
114130 }
115131
116- return Promise . reject ( error ) ;
132+ return Promise . reject ( error ) ; // error here is AxiosError, which is an Error instance
117133 }
118134 ) ;
119135
@@ -132,7 +148,7 @@ export interface ApiResponse<T> {
132148 status : number ;
133149}
134150
135- // Common error handler function
151+ // Common error handler
136152export const handleApiError = ( error : unknown ) : string => {
137153 if ( axiosIsAxiosError ( error ) ) {
138154 const response = error . response ;
0 commit comments