diff --git a/.env b/.env index 03b6f18..a0804e6 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -VITE_API_BASE_URL = 'http://192.168.99.218:4000' +VITE_API_BASE_URL = 'http://192.168.99.217:4000' VITE_TOKEN_NAME = 'negareh_at' VITE_REFRESH_TOKEN_NAME = 'negareh_art' \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index c6814b4..2ae71ce 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,22 +4,123 @@ import 'swiper/swiper-bundle.css'; import 'react-loading-skeleton/dist/skeleton.css' import 'react-toastify/dist/ReactToastify.css' import { BrowserRouter } from 'react-router-dom' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ToastContainer } from 'react-toastify' -import { getToken } from './config/func' +import { getRefreshToken, getToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func' import MainRouter from './router/MainRouter' import AuthRouter from './router/AuthRouter' +import { Paths } from './config/Paths'; +import type { IApiErrorRepsonse } from './shared/types/Types'; +import { refreshToken } from './pages/auth/service/AuthService'; + +declare global { + interface Window { + isRefreshingToken?: boolean; + isRefreshTokenExpired?: boolean; + } +} -// ایجاد QueryClient برای React Query const queryClient = new QueryClient({ + queryCache: new QueryCache({ + onError: async (error: IApiErrorRepsonse) => { + if (error?.response?.status === 401) { + // اگر refresh token منقضی شده باشد، دیگر تلاش نکن + if (window.isRefreshTokenExpired) { + return; + } + + // بررسی کن که آیا خطا از refresh endpoint است + const isRefreshEndpoint = error?.config?.url?.includes('/public/auth/refresh'); + + // اگر خطا از refresh endpoint است، refresh token منقضی شده + if (isRefreshEndpoint) { + window.isRefreshTokenExpired = true; + await removeToken(); + await removeRefreshToken(); + window.location.href = Paths.auth.login; + return; + } + + try { + // Use a flag to track if refresh is in progress + 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; + } + + // Set the flag to indicate refresh is in progress + window.isRefreshingToken = true; + + try { + const refreshTokenValue = await getRefreshToken(); + + if (!refreshTokenValue) { + window.isRefreshTokenExpired = true; + await removeToken(); + await removeRefreshToken(); + window.location.href = Paths.auth.login; + return; + } + + const { data } = await refreshToken({ refreshToken: refreshTokenValue || '' }); + + if (data?.accessToken?.token) { + // Save the new token + await setToken(data?.accessToken?.token); + + // If refresh token also returned, update it + if (data.refreshToken?.token) { + await setRefreshToken(data.refreshToken?.token); + } + + // Reset expired flag if refresh was successful + window.isRefreshTokenExpired = false; + + // Retry the failed request + queryClient.invalidateQueries(); + return; + } else { + throw new Error('Invalid token response'); + } + } finally { + window.isRefreshingToken = false; + } + } catch (refreshError: unknown) { + console.error('Token refresh failed:', refreshError); + + // بررسی کن که آیا خطا از refresh endpoint است + const errorWithConfig = refreshError as IApiErrorRepsonse; + const isRefreshError = errorWithConfig?.config?.url?.includes('/admin/auth/refresh'); + + // اگر refresh token خودش 401 خورده، آن را منقضی شده علامت بزن + if (isRefreshError || errorWithConfig?.response?.statusCode === 401) { + window.isRefreshTokenExpired = true; + } + + // Clear tokens and redirect to login + await removeToken(); + await removeRefreshToken(); + window.location.href = Paths.auth.login; + } + } + } + }), defaultOptions: { queries: { refetchOnWindowFocus: false, - retry: 1, - staleTime: 5 * 60 * 1000, // 5 minutes - }, - }, -}) + retry: false + // staleTime: 86400000 // 1 day in milliseconds + } + } +}); const App: FC = () => { diff --git a/src/pages/auth/service/AuthService.ts b/src/pages/auth/service/AuthService.ts index a3c7d7f..b37d3c3 100644 --- a/src/pages/auth/service/AuthService.ts +++ b/src/pages/auth/service/AuthService.ts @@ -29,7 +29,7 @@ export const checkHasAccount = async (params: CheckHasAccountType) => { }; export const checkHasAccountRegister = async ( - params: CheckHasAccountPhoneType, + params: CheckHasAccountPhoneType ) => { const { data } = await axios.post(`/auth/register/initiate`, params); return data; @@ -40,7 +40,7 @@ export const register = async (params: RegisterType) => { }; export const refreshToken = async (params: RefreshTokenType) => { - const { data } = await axios.post(`/public/auth/refresh`, params); + const { data } = await axios.post(`/admin/auth/refresh`, params); return data; }; diff --git a/src/shared/types/Types.ts b/src/shared/types/Types.ts index c8cf3eb..ca0f3ae 100644 --- a/src/shared/types/Types.ts +++ b/src/shared/types/Types.ts @@ -1,3 +1,5 @@ +import type { DefaultError } from "@tanstack/react-query"; + export type SharedStoreType = { openSidebar: boolean; setOpenSidebar: (open: boolean) => void; @@ -6,6 +8,7 @@ export type SharedStoreType = { export interface BaseResponse { success: boolean; statusCode: number; + status: number; data: T; meta?: { limit: number; @@ -14,3 +17,21 @@ export interface BaseResponse { totalPages: number; }; } + +interface IError { + message: string[]; +} + +type MyErrorRespone = { + error: IError; +}; + +export type IErrorResponse = BaseResponse; + +export interface IApiErrorRepsonse extends DefaultError { + response?: IErrorResponse; + config?: { + url?: string; + [key: string]: unknown; + }; +}