[NOT FULLY WORKING] Added frontend react native interface.
This commit is contained in:
139
interfaces/nativeapp/src/api/client.ts
Normal file
139
interfaces/nativeapp/src/api/client.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
// src/api/client.ts
|
||||
import axios, { AxiosError } from 'axios'; // Import AxiosError
|
||||
import { Platform } from 'react-native';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
|
||||
const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:8000/api';
|
||||
const TOKEN_KEY = 'maia_access_token';
|
||||
|
||||
console.log("Using API Base URL:", API_BASE_URL);
|
||||
|
||||
// Helper functions for storage (assuming they are defined above or imported)
|
||||
// const getToken = async (): Promise<string | null> => { ... };
|
||||
// const deleteToken = async () => { ... };
|
||||
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: 10000,
|
||||
...(Platform.OS === 'web' ? { withCredentials: true } : {}),
|
||||
});
|
||||
|
||||
// --- Request Interceptor remains the same ---
|
||||
apiClient.interceptors.request.use(
|
||||
async (config) => {
|
||||
// Using AsyncStorage for web token retrieval here too for consistency
|
||||
const token = Platform.OS === 'web'
|
||||
? await AsyncStorage.getItem(TOKEN_KEY)
|
||||
: await SecureStore.getItemAsync(TOKEN_KEY).catch(() => null); // Handle potential SecureStore error
|
||||
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
console.log('[API Client] Starting Request', config.method?.toUpperCase(), config.url);
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
console.error('[API Client] Request Setup Error:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// --- Modified Response Interceptor ---
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => {
|
||||
// Success case
|
||||
return response;
|
||||
},
|
||||
async (error: AxiosError) => { // Explicitly type error as AxiosError
|
||||
const originalRequest = error.config;
|
||||
|
||||
// Check if the error has a response object (i.e., server responded with error status)
|
||||
if (error.response) {
|
||||
// Server responded with an error status code (4xx, 5xx)
|
||||
console.error('[API Client] Response Error Status:', error.response.status);
|
||||
console.error('[API Client] Response Error Data:', error.response.data);
|
||||
|
||||
// Handle 401 specifically
|
||||
if (error.response.status === 401) {
|
||||
console.warn('[API Client] Unauthorized (401). Token might be expired or invalid.');
|
||||
|
||||
if (!originalRequest?._retry) {
|
||||
originalRequest._retry = true; // Mark the request as retried to avoid infinite loops
|
||||
|
||||
try {
|
||||
console.log('[API Client] Attempting token refresh...');
|
||||
const refreshResponse = await apiClient.post('/auth/refresh', {}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (refreshResponse.status === 200) {
|
||||
const newToken = refreshResponse.data?.accessToken;
|
||||
|
||||
if (newToken) {
|
||||
console.log('[API Client] Token refreshed successfully.');
|
||||
|
||||
// Save the new token
|
||||
if (Platform.OS === 'web') {
|
||||
await AsyncStorage.setItem(TOKEN_KEY, newToken);
|
||||
} else {
|
||||
await SecureStore.setItemAsync(TOKEN_KEY, newToken);
|
||||
}
|
||||
|
||||
// Update the Authorization header for future requests
|
||||
apiClient.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
|
||||
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
|
||||
|
||||
// Retry the original request with the new token
|
||||
return apiClient(originalRequest);
|
||||
}
|
||||
}
|
||||
} catch (refreshError) {
|
||||
console.error('[API Client] Token refresh failed:', refreshError);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear potentially invalid token due to 401
|
||||
console.log('[API Client] Clearing potentially invalid token due to 401.');
|
||||
if (Platform.OS === 'web') {
|
||||
await AsyncStorage.removeItem(TOKEN_KEY);
|
||||
} else {
|
||||
await SecureStore.deleteItemAsync(TOKEN_KEY).catch(() => {}); // Ignore delete error
|
||||
}
|
||||
delete apiClient.defaults.headers.common['Authorization'];
|
||||
|
||||
// How to trigger logout? Propagating error is simplest for now.
|
||||
}
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
// (e.g., network error, CORS block preventing response reading, server timeout)
|
||||
console.error('[API Client] Network Error or No Response:', error.message);
|
||||
// Log the request object for debugging if needed
|
||||
// console.error('[API Client] Error Request Object:', error.request);
|
||||
// If CORS is suspected, this is often where the error ends up.
|
||||
if (error.message.toLowerCase().includes('network error') && Platform.OS === 'web') {
|
||||
console.warn('[API Client] Hint: A "Network Error" on web often masks a CORS issue. Check browser console & backend CORS config.');
|
||||
}
|
||||
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('[API Client] Request Setup Error (Interceptor):', error.message);
|
||||
}
|
||||
|
||||
// Log the config that failed (optional, can be verbose)
|
||||
// console.error("[API Client] Failing Request Config:", error.config);
|
||||
|
||||
// Always reject the promise to propagate the error
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default apiClient;
|
||||
Reference in New Issue
Block a user