diff --git a/src/config/axios.ts b/src/config/axios.ts index fd0803b..7751219 100644 --- a/src/config/axios.ts +++ b/src/config/axios.ts @@ -1,5 +1,19 @@ import axios from "axios"; -import { getToken } from "./func"; +import { + getToken, + getRefreshToken, + setToken, + setRefreshToken, + removeToken, + removeRefreshToken, +} from "./func"; +import { refreshToken } from "../pages/auth/service/AuthService"; + +declare global { + interface Window { + isRefreshingToken?: boolean; + } +} const axiosInstance = axios.create({ baseURL: import.meta.env.VITE_BASE_URL, @@ -7,14 +21,69 @@ const axiosInstance = axios.create({ axiosInstance.interceptors.response.use( (response) => response, - (error) => { + async (error) => { + const originalRequest = error.config; + + if (error?.response?.status === 401 && !originalRequest._retry) { + if (window.isRefreshingToken) { + // Wait for the refresh to complete + await new Promise((resolve) => { + const checkComplete = setInterval(() => { + if (!window.isRefreshingToken) { + clearInterval(checkComplete); + resolve(true); + } + }, 100); + }); + return axiosInstance(originalRequest); + } + + originalRequest._retry = true; + window.isRefreshingToken = true; + + try { + const refreshTokenValue = await getRefreshToken(); + if (!refreshTokenValue) { + await removeRefreshToken(); + await removeToken(); + window.location.href = `/`; + return; + } + const data = await refreshToken({ + refreshToken: refreshTokenValue, + }); + + if (data?.data?.accessToken?.token) { + await setToken(data.data.accessToken.token); + if (data?.data?.refreshToken?.token) { + await setRefreshToken(data.data.refreshToken.token); + } + + originalRequest.headers.Authorization = `Bearer ${data.data.accessToken.token}`; + return axiosInstance(originalRequest); + } else { + await removeRefreshToken(); + await removeToken(); + window.location.href = `/`; + return; + } + } catch (refreshError) { + console.error("Token refresh failed:", refreshError); + await removeToken(); + await removeRefreshToken(); + // window.location.href = `/`; + return Promise.reject(refreshError); + } finally { + window.isRefreshingToken = false; + } + } return Promise.reject(error); } ); axiosInstance.interceptors.request.use(async function (config) { - const token = getToken(); - + const tokenValue = await getToken(); + const token = tokenValue ? tokenValue : ""; config.headers.Authorization = "Bearer " + token; return config; }); diff --git a/src/pages/auth/service/AuthService.ts b/src/pages/auth/service/AuthService.ts index 85edad8..eb563c9 100644 --- a/src/pages/auth/service/AuthService.ts +++ b/src/pages/auth/service/AuthService.ts @@ -6,6 +6,7 @@ import { type LoginWithPasswordType, type OtpVerifyType, type RefreshTokenType, + type RefreshTokenResponse, type RegisterType, } from "../types/AuthTypes"; @@ -39,7 +40,12 @@ export const register = async (params: RegisterType) => { return data; }; -export const refreshToken = async (params: RefreshTokenType) => { - const { data } = await axios.post(`/auth/refresh`, params); +export const refreshToken = async ( + params: RefreshTokenType +): Promise => { + const { data } = await axios.post( + `/auth/refresh`, + params + ); return data; }; diff --git a/src/pages/auth/types/AuthTypes.ts b/src/pages/auth/types/AuthTypes.ts index e04bad2..a7f6b3d 100644 --- a/src/pages/auth/types/AuthTypes.ts +++ b/src/pages/auth/types/AuthTypes.ts @@ -1,4 +1,5 @@ import { type XOR } from "../../../helpers/types"; +import { type IResponse } from "../../../types/response.types"; export type LoginType = { phone_email: string; @@ -56,3 +57,20 @@ export type RegisterType = { export type RefreshTokenType = { refreshToken: string; }; + +export type AccessToken = { + token: string; + expire: number; +}; + +export type RefreshToken = { + token: string; + expire: number; +}; + +export type RefreshTokenResponseData = { + accessToken: AccessToken; + refreshToken: RefreshToken; +}; + +export type RefreshTokenResponse = IResponse;