import Constants from "expo-constants"; import { Platform } from "react-native"; import { cookieStorage } from "./cookieStorage"; import { tokenStorage } from "./tokenStorage"; import { TokenUtils } from "./tokenUtils"; const API_BASE_URL = Constants.expoConfig?.extra?.apiBaseUrl as string; const DEFAULT_HEADERS = { "Content-Type": "application/json", "Ramble-Client-Platform": Platform.OS, }; /** * 토큰 재발급 중복 호출 방지를 위한 Promise 메모이제이션 * 여러 요청이 동시에 토큰 재발급을 시도해도 실제 API 호출은 1번만 수행 */ let reissuePromise: Promise | null = null; const isWeb = Platform.OS === "web"; export const TokenReissueManager = { /** * 토큰 재발급 (중복 호출 방지) * @param refreshToken - Native 환경에서 사용할 refresh token (optional) * @returns 새로운 accessToken 또는 null (실패 시) */ async reissueToken(refreshToken?: string): Promise { if (!isWeb && !refreshToken) { console.warn("[TokenReissueManager] Native 환경에서 refresh token이 제공되지 않았습니다."); return null; } if (reissuePromise) { console.log("[TokenReissueManager] 진행 중인 토큰 재발급 대기"); return reissuePromise; } console.log("[TokenReissueManager] 토큰 재발급 시작"); // 새로운 재발급 Promise 생성 reissuePromise = (async () => { try { const response = await fetch(`${API_BASE_URL}/auth/reissue`, { method: "POST", ...(isWeb ? { credentials: "include" as RequestCredentials } : {}), headers: { ...DEFAULT_HEADERS, ...(!isWeb ? { "set-cookie": `refresh=${refreshToken}` } : {}), // Native 환경에서는 refresh token을 헤더로 전달 }, }); if (!response.ok) { throw new Error(`토큰 재발급 실패: ${response.status}`); } const tokens = TokenUtils.extractTokensFromHeaders(response.headers); await Promise.all([ tokenStorage.setAccessToken(tokens.accessToken), cookieStorage.setRefreshToken(tokens.cookieHeader), ]); console.log("[TokenReissueManager] 토큰 재발급 성공"); return tokens.accessToken; } catch (error) { console.error("[TokenReissueManager] 토큰 재발급 실패:", error); await Promise.all([ tokenStorage.removeAccessToken(), cookieStorage.removeRefreshToken(), ]); return null; } finally { // 완료 후 Promise 초기화하여 다음 재발급 가능하도록 함 reissuePromise = null; } })(); return reissuePromise; }, /** * 현재 진행 중인 토큰 재발급이 있는지 확인 */ isReissuing(): boolean { return reissuePromise !== null; }, };