Files
Yusron alamsyah 133f3b48be fix dockerfile
2026-04-13 09:18:28 +07:00

164 lines
4.7 KiB
TypeScript

import axios from 'axios'
import { useAuthSession } from '~/composables/useAuth';
const config = useRuntimeConfig()
const { sessionData } = useAuthSession();
const api = axios.create({
baseURL: config.public.baseUrl
})
// 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<string | null> => {
try {
const refreshToken = localStorage.getItem('refreshToken')
if (!refreshToken) {
throw new Error('No refresh token available')
}
const response = await fetch(config.public.baseUrl+"/api/v1/auth/refresh", {
method: 'POST',
body: JSON.stringify({
refresh_token: refreshToken,
provider: sessionData.value?.user?.auth_provider || 'keycloak'
}),
})
const responseBody = await response.json()
const data = responseBody.data
// Update tokens in localStorage
if (data.access_token) {
localStorage.setItem('accessToken', data.access_token)
}
if (data.refresh_token) {
localStorage.setItem('refreshToken', data.refresh_token)
}
// Update server-side session with new tokens
try {
await fetch('/api/auth/sessionUserStore', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
accessToken: data.access_token,
refreshToken: data.refresh_token,
// expiresAt: expiresAt
}),
})
console.log('✅ Server session updated with new tokens')
} catch (sessionError) {
console.error('⚠️ Failed to update server session:', sessionError)
// Don't throw error here - token refresh was successful, session update is secondary
}
console.log('✅ Token refreshed successfully')
return 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 accessToken = localStorage.getItem('accessToken')
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`
}
}
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('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)
}
api.interceptors.request.use(requestInterceptor)
api.interceptors.response.use(responseInterceptor, responseErrorInterceptor)
export default api