1- // src/api/axiosInstance.ts
21import axios , { AxiosError } from 'axios' ;
32import type { AxiosResponse , InternalAxiosRequestConfig } from 'axios' ;
43import { useAuthStore } from '../store/useAuthStore' ;
54
6- // --- 기본 설정 & 환경 변수 검증 ---
5+ // 기본 환경 변수와 설정
76const baseURL = import . meta. env . VITE_API_BASE_URL as string | undefined ;
87if ( ! baseURL ) {
98 throw new Error ( 'VITE_API_BASE_URL environment variable is required' ) ;
@@ -15,7 +14,7 @@ export const api = axios.create({
1514 timeout : 10000 ,
1615} ) ;
1716
18- // --- 유틸들 ---
17+ // 토큰 관리 함수
1918function getAccessToken ( ) : string | null {
2019 const { tokens } = useAuthStore . getState ( ) ;
2120 return tokens ?. accessToken ?? localStorage . getItem ( 'access_token' ) ;
@@ -26,50 +25,32 @@ function getIdToken(): string | null {
2625 return tokens ?. idToken ?? localStorage . getItem ( 'id_token' ) ;
2726}
2827
29- function toAbsoluteURL ( url ?: string , cfgBaseURL ?: string ) : URL | null {
30- if ( ! url ) return null ;
31- try {
32- return new URL ( url ) ; // 절대 URL
33- } catch {
34- try {
35- return new URL ( url , cfgBaseURL ?? baseURL ) ; // 상대 → 절대
36- } catch {
37- return null ;
38- }
39- }
40- }
41-
42- const SKIP_AUTH_PATHS = [ '/auth/refresh' , '/auth/logout' , '/health' ] ;
28+ // 인증이 필요하지 않은 경로
29+ const SKIP_AUTH_PATHS = [ '/' , '/auth/refresh' , '/auth/logout' , '/health' ] ;
4330
4431function shouldSkipAuth ( config : InternalAxiosRequestConfig ) : boolean {
45- // 사용자가 직접 Authorization을 준 경우 존중
46- const hasAuthHeader =
47- ( config . headers ?. Authorization as string | undefined ) ??
48- ( config . headers as Record < string , unknown > | undefined ) ?. authorization ;
32+ // Authorization 헤더가 이미 있으면 스킵
33+ const hasAuthHeader = config . headers ?. Authorization || config . headers ?. authorization ;
4934 if ( hasAuthHeader ) return true ;
5035
51- const reqUrl = toAbsoluteURL ( config . url , config . baseURL ?? baseURL ) ;
52- if ( ! reqUrl ) return false ;
53-
54- // baseURL과 동일한 오리진에만 토큰 부착
55- const base = new URL ( baseURL ! ) ;
56- if ( reqUrl . origin !== base . origin ) return true ;
57-
58- // baseURL에 path prefix가 있는 경우(예: https://host/api) 그 하위 경로만 적용
59- if ( ! reqUrl . pathname . startsWith ( base . pathname ) ) return true ;
60-
61- // 스킵 리스트 적용 (base pathname 이후의 상대 경로 기준)
62- const relPath = reqUrl . pathname . slice ( base . pathname . length ) || '/' ;
63- return SKIP_AUTH_PATHS . some ( ( p ) => relPath . startsWith ( p ) ) ;
36+ // 스킵 경로 확인
37+ const url = config . url || '' ;
38+ return SKIP_AUTH_PATHS . some ( ( path ) => url . startsWith ( path ) ) ;
6439}
6540
41+ // 로그 함수들 (토큰 마스킹)
6642function safeRequestLog ( config : InternalAxiosRequestConfig ) {
6743 if ( ! DEBUG ) return ;
6844 const headers = { ...( config . headers as Record < string , unknown > ) } ;
6945 if ( 'Authorization' in headers ) headers . Authorization = 'Bearer ***' ;
7046 if ( 'authorization' in headers ) headers . authorization = 'Bearer ***' ;
7147 if ( 'X-ID-Token' in headers ) headers [ 'X-ID-Token' ] = '***' ;
7248 if ( 'x-id-token' in headers ) headers [ 'x-id-token' ] = '***' ;
49+ console . log ( 'API Request:' , {
50+ method : config . method ?. toUpperCase ( ) ,
51+ url : config . url ,
52+ headers,
53+ } ) ;
7354}
7455
7556function safeResponseLog ( res : AxiosResponse ) {
@@ -91,41 +72,45 @@ function safeErrorLog(err: AxiosError) {
9172 } ) ;
9273}
9374
94- // --- 인터셉터들 ---
95- // Request: Bearer 자동 부착(+ 안전 로깅)
75+ // Request 인터셉터 - 자동 토큰 부착
9676api . interceptors . request . use ( ( config ) => {
9777 if ( ! shouldSkipAuth ( config ) ) {
98- const token = getAccessToken ( ) ;
99- if ( token ) {
78+ const accessToken = getAccessToken ( ) ;
79+ const idToken = getIdToken ( ) ;
80+
81+ if ( accessToken ) {
10082 config . headers = config . headers ?? { } ;
101- ( config . headers as Record < string , string > ) . Authorization = `Bearer ${ token } ` ;
83+ config . headers . Authorization = `Bearer ${ accessToken } ` ;
10284 }
103- const idToken = getIdToken ( ) ;
85+
10486 if ( idToken ) {
10587 config . headers = config . headers ?? { } ;
106- ( config . headers as Record < string , string > ) [ 'X-ID-Token' ] = idToken ;
88+ config . headers [ 'X-ID-Token' ] = idToken ;
10789 }
10890 }
91+
10992 safeRequestLog ( config ) ;
11093 return config ;
11194} ) ;
11295
113- // Response: 공통 로깅 + 401 처리(토큰 정리)
96+ // Response 인터셉터 - 401 에러 처리
11497api . interceptors . response . use (
115- ( res ) => {
116- safeResponseLog ( res ) ;
117- return res ;
98+ ( response ) => {
99+ safeResponseLog ( response ) ;
100+ return response ;
118101 } ,
119- ( err : AxiosError ) => {
120- if ( err . response ?. status === 401 ) {
121- // 토큰 정리
102+ ( error : AxiosError ) => {
103+ safeErrorLog ( error ) ;
104+
105+ // 401 에러 시 토큰 정리
106+ if ( error . response ?. status === 401 ) {
122107 const { clearTokens } = useAuthStore . getState ( ) ;
123- if ( clearTokens ) clearTokens ( ) ;
108+ clearTokens ?. ( ) ;
124109 localStorage . removeItem ( 'access_token' ) ;
125110 localStorage . removeItem ( 'id_token' ) ;
126111 localStorage . removeItem ( 'refresh_token' ) ;
127112 }
128- safeErrorLog ( err ) ;
129- return Promise . reject ( err ) ;
113+
114+ return Promise . reject ( error ) ;
130115 } ,
131116) ;
0 commit comments