import axios from 'axios' // Get runtime config for Nuxt 3 const config = useRuntimeConfig() // Keycloak configuration for token refresh const KEYCLOAK_TOKEN_URL = (config.public.keycloakUrl as string) || 'https://auth.rssa.top/realms/sandbox/protocol/openid-connect/token' const KEYCLOAK_CLIENT_ID = (config.public.keycloakClientId as string) || 'akbar-test' const KEYCLOAK_CLIENT_SECRET = (config.public.keycloakClientSecret as string) || 'FDyv3UYMgJOYPnvzXVVv6diRtcgEevKg' // Create axios instance for local API const api = axios.create({ baseURL: config.public.baseUrl, timeout: 10000, }) // Create axios instance for GoMed API const apiGomed = axios.create({ baseURL: config.public.baseUrlGomed, timeout: 10000, }) // Flag to prevent multiple simultaneous refresh attempts let isRefreshing = false let failedQueue: any[] = [] const processQueue = (error: any, token: string | null = null) => { failedQueue.forEach(prom => { if (error) { prom.reject(error) } else { prom.resolve(token) } }) failedQueue = [] } const refreshAccessToken = async (): Promise => { try { const refreshToken = localStorage.getItem('refreshToken') if (!refreshToken) { throw new Error('No refresh token available') } const params = new URLSearchParams() params.append('grant_type', 'refresh_token') params.append('client_id', KEYCLOAK_CLIENT_ID) params.append('client_secret', KEYCLOAK_CLIENT_SECRET) params.append('refresh_token', refreshToken) const response = await fetch(KEYCLOAK_TOKEN_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params.toString() }) if (!response.ok) { throw new Error('Token refresh failed') } const data = await response.json() // Update tokens in localStorage if (data.access_token) { localStorage.setItem('accessToken', data.access_token) } if (data.id_token) { localStorage.setItem('idToken', data.id_token) } if (data.refresh_token) { localStorage.setItem('refreshToken', data.refresh_token) } console.log('✅ Token refreshed successfully') return data.id_token || data.access_token } catch (error) { console.error('❌ Token refresh failed:', error) return null } } // Shared request interceptor const requestInterceptor = (config: any) => { // Only access localStorage on client-side (avoid SSR errors) if (process.client) { const idToken = localStorage.getItem('idToken') if (idToken) { config.headers.Authorization = `Bearer ${idToken}` } } return config } // Shared response interceptor const responseInterceptor = (response: any) => response const responseErrorInterceptor = async (error: any) => { const originalRequest = error.config // centralized error console.error('API Error:', error.response) // Handle token expiration on client-side if (process.client && error.response) { const { status } = error.response // Check if response is 401 (token expired or unauthorized) if (status === 401 && !originalRequest._retry) { if (isRefreshing) { // If already refreshing, queue this request return new Promise((resolve, reject) => { failedQueue.push({ resolve, reject }) }) .then(token => { originalRequest.headers['Authorization'] = 'Bearer ' + token return axios(originalRequest) }) .catch(err => { return Promise.reject(err) }) } originalRequest._retry = true isRefreshing = true // Try to refresh the token const newToken = await refreshAccessToken() if (newToken) { // Token refresh successful, retry original request isRefreshing = false processQueue(null, newToken) originalRequest.headers['Authorization'] = 'Bearer ' + newToken return axios(originalRequest) } else { // Token refresh failed (refresh token expired or error), logout user isRefreshing = false processQueue(new Error('Token refresh failed'), null) // Clear expired tokens localStorage.removeItem('idToken') localStorage.removeItem('accessToken') localStorage.removeItem('refreshToken') // Call logout endpoint to clear session fetch('/api/auth/logout', { method: 'POST' }) .catch(err => console.error('Logout failed:', err)) // Redirect to login with error message const errorMessage = encodeURIComponent('Token is expired') window.location.href = `/auth/login?error=${errorMessage}` return Promise.reject(error) } } } return Promise.reject(error) } // Apply interceptors to both instances api.interceptors.request.use(requestInterceptor) api.interceptors.response.use(responseInterceptor, responseErrorInterceptor) apiGomed.interceptors.request.use(requestInterceptor) apiGomed.interceptors.response.use(responseInterceptor, responseErrorInterceptor) export default api export { apiGomed }