|
|
@@ -14,20 +14,47 @@ interface FetchApiOptions extends Omit<RequestInit, 'body'> {
|
|
|
body?: unknown;
|
|
|
}
|
|
|
|
|
|
+// 토큰 갱신 중복 방지
|
|
|
+let refreshPromise: Promise<boolean> | null = null;
|
|
|
+
|
|
|
+async function tryRefreshToken(): Promise<boolean> {
|
|
|
+ if (refreshPromise) {
|
|
|
+ return refreshPromise;
|
|
|
+ }
|
|
|
+
|
|
|
+ refreshPromise = fetch('/api/auth/refresh-token', { method: 'POST' })
|
|
|
+ .then(res => res.json())
|
|
|
+ .then((res: ResultDto<unknown>) => res.success)
|
|
|
+ .catch(() => false)
|
|
|
+ .finally(() => { refreshPromise = null; });
|
|
|
+
|
|
|
+ return refreshPromise;
|
|
|
+}
|
|
|
+
|
|
|
export async function fetchApi<T>(url: string, options?: FetchApiOptions): Promise<ResultDto<T>> {
|
|
|
const isFormData = options?.body instanceof FormData;
|
|
|
const hasBody = options?.body !== undefined;
|
|
|
|
|
|
- const res = (await fetch(url, {
|
|
|
+ const fetchOptions: RequestInit = {
|
|
|
...options,
|
|
|
headers: {
|
|
|
...(!isFormData && hasBody ? { 'Content-Type': 'application/json' } : {}),
|
|
|
...options?.headers
|
|
|
},
|
|
|
body: isFormData ? options.body as BodyInit : (options?.body ? JSON.stringify(options.body) : undefined)
|
|
|
- }));
|
|
|
+ };
|
|
|
+
|
|
|
+ const res: ResultDto<T> = await (await fetch(url, fetchOptions)).json();
|
|
|
+
|
|
|
+ // 401 시 토큰 갱신 후 재시도
|
|
|
+ if (!res.success && res.status === 401) {
|
|
|
+ const refreshed = await tryRefreshToken();
|
|
|
+ if (refreshed) {
|
|
|
+ return (await fetch(url, fetchOptions)).json();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- return res.json();
|
|
|
+ return res;
|
|
|
}
|
|
|
|
|
|
// JWT 토큰에서 사용자 정보 추출
|