@@ -82,64 +82,71 @@ export function AuthProvider({
8282
8383 const isRefreshing = useRef ( false ) ; // 중복 호출 방지용 Lock
8484
85- const refreshSession = useCallback ( async ( ) => {
86- if ( isLoggingOut || isRefreshing . current ) return false ;
85+ const refreshSession = useCallback (
86+ async ( manualToken ?: string ) => {
87+ if ( isLoggingOut || isRefreshing . current ) return false ;
8788
88- // 쿨타임 체크 추가 (함수 진입 시점에서도 보호)
89- const now = Date . now ( ) ;
90- if ( now - lastCheckTime . current < 2000 ) return false ;
89+ // 쿨타임 체크 추가 (함수 진입 시점에서도 보호)
90+ const now = Date . now ( ) ;
91+ if ( now - lastCheckTime . current < 2000 ) return false ;
9192
92- isRefreshing . current = true ; // 실행 시작 시 잠금
93- lastCheckTime . current = now ;
93+ isRefreshing . current = true ; // 실행 시작 시 잠금
94+ lastCheckTime . current = now ;
9495
95- // 만약 글로벌 로그아웃 신호가 있다면 백엔드에 묻지 않고 바로 로컬 파기
96- if ( hasGlobalLogoutSignal ( ) ) {
97- await clearLocalAuth ( ) ;
98- isInitialCheckDone . current = true ;
99- setLoading ( false ) ;
100- return false ;
101- }
102-
103- try {
104- const response = await apiClient . get ( "/auth/users/me" ) ;
105- if ( response . data ?. data ) {
106- const backendUser = response . data . data ;
107- const newUid = backendUser . id || backendUser . uid ;
108-
109- setUser ( ( prev : any ) => {
110- // 필요한 핵심 필드만 비교
111- const isSameUser =
112- prev &&
113- prev . uid === newUid &&
114- prev . email === backendUser . email &&
115- prev . photoURL ===
116- ( backendUser . photoURL || backendUser . profile_image ) ;
117-
118- if ( isSameUser ) return prev ; // 데이터가 같으면 '절대' 새 객체를 만들지 않음
119-
120- // 꼭 필요한 데이터만 정제해서 저장 (서버의 가변 필드 제외)
121- return {
122- uid : newUid ,
123- email : backendUser . email ,
124- displayName : backendUser . name || backendUser . displayName ,
125- photoURL : backendUser . photoURL || backendUser . profile_image ,
126- } ;
127- } ) ;
128- return true ;
96+ // 만약 글로벌 로그아웃 신호가 있다면 백엔드에 묻지 않고 바로 로컬 파기
97+ if ( hasGlobalLogoutSignal ( ) ) {
98+ await clearLocalAuth ( ) ;
99+ isInitialCheckDone . current = true ;
100+ setLoading ( false ) ;
101+ return false ;
129102 }
130- throw new Error ( "No session" ) ;
131- } catch ( error : any ) {
132- if ( error . response ?. status === 401 ) {
133- await clearLocalAuth ( ) ; // 확실한 세션 만료 시에만 파기
103+
104+ try {
105+ const config = manualToken
106+ ? { headers : { Authorization : `Bearer ${ manualToken } ` } }
107+ : { } ;
108+
109+ const response = await apiClient . get ( "/auth/users/me" , config ) ;
110+ if ( response . data ?. data ) {
111+ const backendUser = response . data . data ;
112+ const newUid = backendUser . id || backendUser . uid ;
113+
114+ setUser ( ( prev : any ) => {
115+ // 필요한 핵심 필드만 비교
116+ const isSameUser =
117+ prev &&
118+ prev . uid === newUid &&
119+ prev . email === backendUser . email &&
120+ prev . photoURL ===
121+ ( backendUser . photoURL || backendUser . profile_image ) ;
122+
123+ if ( isSameUser ) return prev ; // 데이터가 같으면 '절대' 새 객체를 만들지 않음
124+
125+ // 꼭 필요한 데이터만 정제해서 저장 (서버의 가변 필드 제외)
126+ return {
127+ uid : newUid ,
128+ email : backendUser . email ,
129+ displayName : backendUser . name || backendUser . displayName ,
130+ photoURL : backendUser . photoURL || backendUser . profile_image ,
131+ } ;
132+ } ) ;
133+ return true ;
134+ }
135+ throw new Error ( "No session" ) ;
136+ } catch ( error : any ) {
137+ if ( error . response ?. status === 401 ) {
138+ await clearLocalAuth ( ) ; // 확실한 세션 만료 시에만 파기
139+ }
140+ // 그 외의 에러(500 등)는 세션을 유지하며 false만 반환
141+ return false ;
142+ } finally {
143+ isInitialCheckDone . current = true ;
144+ setLoading ( false ) ;
145+ isRefreshing . current = false ; // 실행 완료 후 잠금 해제
134146 }
135- // 그 외의 에러(500 등)는 세션을 유지하며 false만 반환
136- return false ;
137- } finally {
138- isInitialCheckDone . current = true ;
139- setLoading ( false ) ;
140- isRefreshing . current = false ; // 실행 완료 후 잠금 해제
141- }
142- } , [ clearLocalAuth , isLoggingOut , hasGlobalLogoutSignal ] ) ;
147+ } ,
148+ [ clearLocalAuth , isLoggingOut , hasGlobalLogoutSignal ] ,
149+ ) ;
143150
144151 useEffect ( ( ) => {
145152 const initAuth = async ( ) => {
@@ -164,15 +171,20 @@ export function AuthProvider({
164171 if ( ssoToken ) {
165172 clearGlobalLogoutSignal ( ) ;
166173 window . sessionStorage . setItem ( "sso_token" , ssoToken ) ;
167- const newUrl =
168- window . location . pathname +
169- window . location . search
170- . replace ( / [ ? & ] s s o _ t o k e n = [ ^ & ] + / , "" )
171- . replace ( / ^ & / , "?" )
172- . replace ( / \? $ / , "" ) ;
173- window . history . replaceState ( { } , "" , newUrl ) ;
174- // SSO 토큰이 새로 들어왔을 때는 세션 갱신 필요
175- await refreshSession ( ) ;
174+
175+ // 1. 먼저 세션을 확실히 갱신
176+ const success = await refreshSession ( ssoToken ) ;
177+
178+ // 2. 세션 갱신에 성공했을 때만 URL을 청소
179+ if ( success ) {
180+ const newUrl =
181+ window . location . pathname +
182+ window . location . search
183+ . replace ( / [ ? & ] s s o _ t o k e n = [ ^ & ] + / , "" )
184+ . replace ( / ^ & / , "?" )
185+ . replace ( / \? $ / , "" ) ;
186+ window . history . replaceState ( { } , "" , newUrl ) ;
187+ }
176188 return ;
177189 }
178190
@@ -240,7 +252,8 @@ export function AuthProvider({
240252 isInitialCheckDone . current &&
241253 ! user &&
242254 requireAuth &&
243- ! isLoggingOut
255+ ! isLoggingOut &&
256+ ! isRefreshing . current
244257 ) {
245258 if ( typeof window !== "undefined" ) {
246259 if ( window . location . pathname . startsWith ( "/login" ) ) return ;
0 commit comments