import { ResponseUtils, tokenStorage, TokenUtils } from "@/utils";
import Constants from "expo-constants";
import { Platform } from "react-native";
const API_BASE_URL = Constants.expoConfig?.extra?.apiBaseUrl as string;
const DEFAULT_HEADERS = {
"Content-Type": "application/json",
"Ramble-Client-Platform": Platform.OS,
};
export async function fetchWithAuth(
input: RequestInfo,
init: RequestInit = {}
): Promise<Response> {
let storedAccess = await tokenStorage.getAccessToken();
const makeRequest = async (token?: string) => {
const headers = {
...(init.headers || {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
...DEFAULT_HEADERS,
};
const url =
typeof input === "string" && input.startsWith("/")
? `${API_BASE_URL}${input}`
: input;
return fetch(url, {
...init,
headers,
});
};
const reissueToken = async (): Promise<string | null> => {
try {
const reissueResponse = await fetch(`${API_BASE_URL}/auth/reissue`, {
method: 'POST',
credentials: 'include' as RequestCredentials,
headers: DEFAULT_HEADERS,
});
if (!reissueResponse.ok) {
throw new Error("토큰 재발급 실패");
}
const tokens = TokenUtils.extractTokensFromHeaders(reissueResponse.headers);
await tokenStorage.setAccessToken(tokens.accessToken);
return tokens.accessToken;
} catch {
await tokenStorage.removeAccessToken();
return null;
}
};
// 1. 사전 토큰 만료 체크 및 갱신 (네트워크 요청 최적화)
if (storedAccess && TokenUtils.isTokenExpired(storedAccess)) {
const newToken = await reissueToken();
if (newToken) {
storedAccess = newToken;
} else {
storedAccess = null;
}
}
// 2. 메인 요청 실행
let response = await makeRequest(storedAccess ?? undefined);
// 3. 서버 응답 기반 토큰 재발급 (사전 체크 실패 케이스 대응)
if (response.status === 401) {
const res = await ResponseUtils.parseResponse(response);
if (res.message !== "Expired Access Token") {
return response;
}
const newToken = await reissueToken();
if (newToken) {
response = await makeRequest(newToken);
}
}
return response;
}