import { AuthTokens } from "@/types"; import { ResponseUtils, tokenStorage, TokenUtils } from "@/utils"; import { cookieStorage } from "@/utils/cookieStorage"; 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 { let storedAccess = await tokenStorage.getAccessToken(); let storedRefresh = await cookieStorage.getRefreshToken(); 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 (token?: string): Promise => { try { const reissueResponse = await fetch(`${API_BASE_URL}/auth/reissue`, { method: 'POST', headers: { ...DEFAULT_HEADERS, ...(token ? { "set-cookie": `refresh=${token}` } : {}), }, }); if (!reissueResponse.ok) { throw new Error("토큰 재발급 실패"); } const tokens = TokenUtils.extractTokensFromHeaders(reissueResponse.headers); await tokenStorage.setAccessToken(tokens.accessToken); await cookieStorage.setRefreshToken(tokens.cookieHeader); return tokens; } catch { await tokenStorage.removeAccessToken(); await cookieStorage.removeRefreshToken(); return null; } } // 1. 사전 토큰 만료 체크 및 갱신 (네트워크 요청 최적화) if (storedAccess && TokenUtils.isTokenExpired(storedAccess)) { const newToken = await reissueToken(storedRefresh ?? undefined); if (newToken) { storedAccess = newToken.accessToken; storedRefresh = newToken.cookieHeader; } else { storedAccess = null; storedRefresh = 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 newTokens = await reissueToken(storedRefresh ?? undefined); if (newTokens) { response = await makeRequest(newTokens.accessToken); } } return response; }