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- */
231import axios , {
242 AxiosError ,
253 AxiosInstance ,
26- AxiosRequestConfig , // Import AxiosRequestConfig
4+ AxiosRequestConfig ,
275 AxiosResponse ,
286 isAxiosError as axiosIsAxiosError ,
297} from 'axios' ;
8+ import { useAuthStore } from '../state/authStore' ; // Adjust path if needed
309
3110// TypeScript declaration for Vite env
3211declare global {
@@ -42,11 +21,11 @@ declare global {
4221
4322// API base URL - should match your backend
4423const API_BASE_URL = import . meta. env . VITE_API_BASE_URL || 'http://localhost:3000/api' ;
24+
4525// Default timeout in milliseconds
4626const DEFAULT_TIMEOUT = 15000 ;
4727
48- // Define an interface for the request config with retry flag
49- // by extending the original AxiosRequestConfig
28+ // Extend AxiosRequestConfig to add _retry flag for token refresh logic
5029interface ExtendedAxiosRequestConfig extends AxiosRequestConfig {
5130 _retry ?: boolean ;
5231}
@@ -66,7 +45,7 @@ const createApiClient = (): AxiosInstance => {
6645
6746 // Request interceptor - adds auth token to requests
6847 apiClient . interceptors . request . use (
69- config => {
48+ ( config ) => {
7049 const token = localStorage . getItem ( 'accessToken' ) ;
7150 if ( token && config . headers ) {
7251 config . headers . Authorization = `Bearer ${ token } ` ;
@@ -76,60 +55,65 @@ const createApiClient = (): AxiosInstance => {
7655 ( error : AxiosError ) => Promise . reject ( error )
7756 ) ;
7857
79- // Response interceptor - handle common errors
58+ // Response interceptor - handle 401 errors and token refresh
8059 apiClient . interceptors . response . use (
8160 ( response : AxiosResponse ) => response ,
8261 async ( error : AxiosError ) => {
83- // Cast error.config to the extended interface
8462 const originalRequest = error . config as ExtendedAxiosRequestConfig ;
8563
86- // Handle 401 Unauthorized errors (expired token)
8764 if ( error . response ?. status === 401 && originalRequest && ! originalRequest . _retry ) {
8865 originalRequest . _retry = true ;
8966
9067 try {
91- // Call refresh token endpoint
68+ // Attempt to refresh token
9269 const refreshResponse = await axios . post (
9370 `${ API_BASE_URL } /auth/refresh-token` ,
9471 { } ,
95- {
96- withCredentials : true , // Needed for cookies
97- }
72+ { withCredentials : true }
9873 ) ;
9974
10075 const newToken = refreshResponse . data . token ;
10176 localStorage . setItem ( 'accessToken' , newToken ) ;
10277
103- // Retry the original request with new token
10478 if ( originalRequest . headers ) {
10579 originalRequest . headers . Authorization = `Bearer ${ newToken } ` ;
10680 }
81+
10782 return apiClient ( originalRequest ) ;
10883 } catch ( refreshError ) {
109- // If refresh token fails, redirect to login
84+ // Refresh token failed - logout user
11085 localStorage . removeItem ( 'accessToken' ) ;
111- // Ensure window is defined (for SSR or test environments)
86+ const store = useAuthStore . getState ( ) ;
87+ store . logout ( ) ;
88+
11289 if ( typeof window !== 'undefined' ) {
11390 window . location . href = '/login' ;
11491 }
11592
116- // Ensure we reject with an Error object
11793 if ( refreshError instanceof Error ) {
11894 return Promise . reject ( refreshError ) ;
11995 } else if ( typeof refreshError === 'string' ) {
12096 return Promise . reject ( new Error ( refreshError ) ) ;
12197 } else {
122- // For other types, you might want to serialize or provide a generic message
12398 return Promise . reject (
12499 new Error (
125100 `An unknown error occurred during token refresh: ${ JSON . stringify ( refreshError ) } `
126101 )
127102 ) ;
128103 }
129104 }
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+ }
130114 }
131115
132- return Promise . reject ( error ) ; // error here is AxiosError, which is an Error instance
116+ return Promise . reject ( error ) ;
133117 }
134118 ) ;
135119
@@ -148,7 +132,7 @@ export interface ApiResponse<T> {
148132 status : number ;
149133}
150134
151- // Common error handler
135+ // Common error handler function
152136export const handleApiError = ( error : unknown ) : string => {
153137 if ( axiosIsAxiosError ( error ) ) {
154138 const response = error . response ;
0 commit comments