TokenContext.tsx (2749B)
1 import { getAccessTokenCookieName } from '@/lib/env' 2 import { AccessTokenPayload } from '@/lib/security/token' 3 import { decode } from 'jsonwebtoken' 4 import { createContext, ReactNode, useCallback, useContext, useEffect, useState } from 'react' 5 6 const regexCookie = /^([^=]*)=([^;]*)(?:;\s*|$)/ 7 8 const TOKEN_REFRESH_BEFORE = 60 * 1000 // 만료 1분 전 갱신 9 10 const TokenContext = createContext<AccessTokenPayload | null>(null) 11 const TokenFlushContext = createContext<() => void>(() => { 12 }) 13 14 export function TokenProvider ({ children }: { children: ReactNode }) { 15 const [accessToken, setAccessToken] = useState<AccessTokenPayload | null>(null) 16 const [checkCounter, setCheckCounter] = useState(0) 17 18 // 최초 로딩시 토큰 있는지 체크 19 useEffect(() => { 20 // 토큰 체크 21 const cookie = getCookie(getAccessTokenCookieName()) 22 const accessToken = cookie != null 23 ? decode(cookie) as AccessTokenPayload 24 : null 25 26 setAccessToken(accessToken ?? {}) 27 }, [checkCounter]) 28 29 // 토큰 갱신 30 useEffect(() => { 31 // 아직 토큰이 존재하는지 체크하지 않았으면 생략 32 if (accessToken == null) { 33 return 34 } 35 36 const refresh = () => { 37 console.log('TokenContext: refresh token') 38 39 fetch('/api/auth/refresh', { 40 method: 'POST', 41 }).then(res => { 42 // 갱신하지 못했을 때에는 무시 43 if (res.status !== 200) { 44 return 45 } 46 47 // 토큰 갱신 48 setCheckCounter(v => v + 1) 49 }).catch(err => { 50 console.error('TokenContext: fetch failed:', err) 51 }) 52 } 53 54 // 토큰이 없거나 만료되었을 때 갱신 55 const exp = (accessToken.exp ?? 0) * 1000 - (Date.now() + TOKEN_REFRESH_BEFORE) 56 if (exp <= 0) { 57 refresh() 58 } else { 59 const timer = setTimeout(refresh, exp) 60 return () => clearTimeout(timer) 61 } 62 }, [accessToken, accessToken?.exp]) 63 64 const flushTrigger = useCallback( 65 () => setCheckCounter(v => v + 1), 66 [], 67 ) 68 69 return ( 70 <TokenContext.Provider value={accessToken}> 71 <TokenFlushContext.Provider value={flushTrigger}> 72 {children} 73 </TokenFlushContext.Provider> 74 </TokenContext.Provider> 75 ) 76 } 77 78 export function useToken () { 79 const token = useContext(TokenContext) 80 if (token?.uid == null) { 81 return null 82 } 83 return token 84 } 85 86 export function useTokenFlushTrigger () { 87 return useContext(TokenFlushContext) 88 } 89 90 function getCookie (name: string) { 91 name = name.toLowerCase() 92 93 let cookie = document.cookie 94 for (; ;) { 95 let caps = regexCookie.exec(cookie) 96 if (caps == null) { 97 return null 98 } 99 if (caps[1].toLowerCase() === name) { 100 return caps[2] 101 } 102 cookie = cookie.substring(caps[0].length) 103 } 104 }