11import { createApi , fetchBaseQuery , retry } from "@reduxjs/toolkit/query/react" ;
22import { API_CONFIG , getBaseUrl } from '../config/config' ;
33import { handleApiResponse } from '../config/errors' ;
4- import { refreshTokens } from './tokenManager' ;
4+ import { refreshTokens , setLoggingOut } from './tokenManager' ;
55import { getToken } from './tokenManager' ;
6+ import { logOut } from '../../state/data/authSlice' ;
67
7- // Create base query with automatic token refresh
8+ // Cache configuration for different types of data
9+ export const CACHE_TIMES = {
10+ SHORT : 5 * 60 , // 5 minutes
11+ MEDIUM : 15 * 60 , // 15 minutes
12+ LONG : 60 * 60 , // 1 hour
13+ VERY_LONG : 24 * 60 * 60 , // 24 hours
14+ } ;
15+
16+ // Performance monitoring for API calls
17+ export const performanceTracker = {
18+ calls : [ ] ,
19+ addCall : ( endpoint , duration , status ) => {
20+ const call = {
21+ endpoint,
22+ duration,
23+ status,
24+ timestamp : new Date ( ) . toISOString ( ) ,
25+ } ;
26+
27+ // Keep only last 100 calls
28+ performanceTracker . calls . push ( call ) ;
29+ if ( performanceTracker . calls . length > 100 ) {
30+ performanceTracker . calls = performanceTracker . calls . slice ( - 100 ) ;
31+ }
32+
33+ // Warn about slow API calls
34+ if ( duration > 2000 ) {
35+ console . warn ( `Slow API call detected: ${ endpoint } took ${ duration } ms` ) ;
36+ }
37+ } ,
38+ getStats : ( ) => {
39+ const calls = performanceTracker . calls ;
40+ if ( calls . length === 0 ) return null ;
41+
42+ const totalCalls = calls . length ;
43+ const avgDuration = calls . reduce ( ( sum , call ) => sum + call . duration , 0 ) / totalCalls ;
44+ const slowCalls = calls . filter ( ( call ) => call . duration > 1000 ) . length ;
45+ const errorCalls = calls . filter ( ( call ) => call . status >= 400 ) . length ;
46+
47+ return {
48+ totalCalls,
49+ avgDuration : Math . round ( avgDuration ) ,
50+ slowCalls,
51+ errorCalls,
52+ errorRate : Math . round ( ( errorCalls / totalCalls ) * 100 ) ,
53+ } ;
54+ } ,
55+ } ;
56+
57+ // Track app initialization to prevent premature logout
58+ let appInitialized = false ;
59+ setTimeout ( ( ) => { appInitialized = true ; } , 2000 ) ;
60+
61+ // Enhanced base query with performance tracking
862const baseQuery = fetchBaseQuery ( {
963 baseUrl : getBaseUrl ( ) ,
1064 timeout : API_CONFIG . REQUEST . TIMEOUT ,
11- prepareHeaders : ( headers ) => {
12- const token = getToken ( ) ;
13- if ( token ) {
14- headers . set ( "authorization" , `Bearer ${ token } ` ) ;
65+ prepareHeaders : ( headers , { getState, endpoint } ) => {
66+ const isAuthEndpoint = endpoint ?. includes ( 'auth/' ) ;
67+
68+ if ( ! isAuthEndpoint ) {
69+ const token = getToken ( ) ;
70+ if ( token ) {
71+ headers . set ( "authorization" , `Bearer ${ token } ` ) ;
72+ }
1573 }
74+
1675 headers . set ( "content-type" , "application/json" ) ;
76+ headers . set ( "X-Client-Version" , "2.0" ) ;
77+ headers . set ( "X-Request-Time" , Date . now ( ) . toString ( ) ) ;
1778 return headers ;
1879 } ,
80+ // Enhanced fetch with performance tracking
81+ fetchFn : async ( input , init ) => {
82+ const startTime = performance . now ( ) ;
83+
84+ try {
85+ const response = await fetch ( input , {
86+ ...init ,
87+ signal : AbortSignal . timeout ( API_CONFIG . REQUEST . TIMEOUT || 30000 ) ,
88+ } ) ;
89+
90+ const endTime = performance . now ( ) ;
91+ const duration = endTime - startTime ;
92+
93+ // Track performance
94+ const endpoint = typeof input === "string" ? input : input . url ;
95+ performanceTracker . addCall ( endpoint , duration , response . status ) ;
96+
97+ return response ;
98+ } catch ( error ) {
99+ const endTime = performance . now ( ) ;
100+ const duration = endTime - startTime ;
101+
102+ // Track failed requests
103+ const endpoint = typeof input === "string" ? input : input . url ;
104+ performanceTracker . addCall ( endpoint , duration , 0 ) ;
105+
106+ throw error ;
107+ }
108+ } ,
19109} ) ;
20110
21- // Add retry logic with token refresh
111+ // Enhanced retry logic with token refresh and better error handling
22112const baseQueryWithRetry = retry (
23113 async ( args , api , extraOptions ) => {
24114 let result = await baseQuery ( args , api , extraOptions ) ;
25115
26- if ( result . error && result . error . status === 401 ) {
27- // Try to get a new token
28- try {
29- const refreshResult = await refreshTokens ( api ) ;
30- if ( refreshResult ) {
31- // Retry the initial query
116+ // Handle authentication errors with automatic refresh
117+ if ( result . error ?. status === 401 ) {
118+ const state = api . getState ( ) ;
119+ const hasToken = state ?. auth ?. accessToken ;
120+ const refreshToken = state ?. auth ?. refreshToken ;
121+ const isAuthEndpoint = args . url ?. includes ( '/auth/' ) || args . url ?. includes ( '/login' ) || args . url ?. includes ( '/register' ) ;
122+ const isRefreshEndpoint = args . url ?. includes ( '/refresh' ) ;
123+
124+ // Only attempt refresh if we have tokens, not an auth endpoint, and app is initialized
125+ if ( hasToken && refreshToken && ! isAuthEndpoint && ! isRefreshEndpoint && appInitialized ) {
126+ try {
127+ await refreshTokens ( api ) ;
128+ // Retry the original request with the new token
32129 result = await baseQuery ( args , api , extraOptions ) ;
130+ } catch ( refreshError ) {
131+ console . error ( 'Token refresh failed:' , refreshError ) ;
132+
133+ // Only logout if refresh token is invalid, not for network errors
134+ if ( refreshError . message ?. includes ( 'Refresh failed with status: 401' ) ) {
135+ api . dispatch ( logOut ( ) ) ;
136+
137+ // Redirect to login if not already there
138+ if ( ! window . location . pathname . includes ( '/login' ) && ! window . location . pathname . includes ( '/register' ) ) {
139+ setTimeout ( ( ) => {
140+ window . location . href = '/login' ;
141+ } , 100 ) ;
142+ }
143+ }
144+ }
145+ } else if ( ! hasToken || ! refreshToken ) {
146+ // No tokens available, logout
147+ if ( appInitialized ) {
148+ api . dispatch ( logOut ( ) ) ;
149+ if ( ! window . location . pathname . includes ( '/login' ) && ! window . location . pathname . includes ( '/register' ) ) {
150+ setTimeout ( ( ) => {
151+ window . location . href = '/login' ;
152+ } , 100 ) ;
153+ }
33154 }
34- } catch ( error ) {
35- console . error ( 'Token refresh failed:' , error ) ;
155+ }
156+ }
157+
158+ // Enhanced error logging for non-auth errors
159+ if ( result . error && result . error . status !== 401 ) {
160+ if ( result . error . status === 404 ) {
161+ console . warn ( `API endpoint not found: ${ args . url } ` ) ;
162+ } else if ( result . error . status >= 500 ) {
163+ console . error ( `Server error (${ result . error . status } ) for ${ args . url } :` , result . error ) ;
164+ } else {
165+ console . warn ( `API error (${ result . error . status } ) for ${ args . url } :` , result . error ) ;
36166 }
37167 }
38168
@@ -50,4 +180,29 @@ export const createBaseApi = (options) => {
50180 endpoints : ( ) => ( { } ) ,
51181 ...options ,
52182 } ) ;
53- } ;
183+ } ;
184+
185+ // Create the main base API instance
186+ export const baseApi = createApi ( {
187+ reducerPath : "baseApi" ,
188+ baseQuery : baseQueryWithRetry ,
189+ tagTypes : [
190+ "Auth" ,
191+ "User" ,
192+ "Report" ,
193+ "Appointment" ,
194+ "Chat" ,
195+ "Blog" ,
196+ "ClinicalNote" ,
197+ "Vitals" ,
198+ ] ,
199+ endpoints : ( ) => ( { } ) ,
200+ } ) ;
201+
202+ // Export the enhanced base query for backward compatibility
203+ export const baseQueryWithReauth = baseQueryWithRetry ;
204+
205+ // Export token management utilities
206+ export { setLoggingOut , startPeriodicTokenCheck , stopPeriodicTokenCheck } from './tokenManager' ;
207+
208+ export default baseApi ;
0 commit comments