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<string | null> | null = null;
const isWeb = Platform.OS === "web";
export const TokenReissueManager = {
/**
* 토큰 재발급 (중복 호출 방지)
* @param refreshToken - Native 환경에서 사용할 refresh token (optional)
* @returns 새로운 accessToken 또는 null (실패 시)
*/
async reissueToken(refreshToken?: string): Promise<string | null> {
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;
},
};