63 lines
1.9 KiB
TypeScript
63 lines
1.9 KiB
TypeScript
/**
|
|
* #31 세션 자동 만료 훅
|
|
* 마지막 활동 시간을 기록하고, 15분 비활성 시 토큰을 삭제하고 로그인 화면으로 보낸다.
|
|
*
|
|
* 저장소: expo-secure-store (AsyncStorage 미설치 → EAS 빌드 안전을 위해 SecureStore 사용)
|
|
* 키: grd_last_activity
|
|
*/
|
|
import { useCallback } from 'react'
|
|
import { useRouter } from 'expo-router'
|
|
import * as SecureStore from 'expo-secure-store'
|
|
|
|
export const SESSION_TIMEOUT = 15 * 60 * 1000 // 15분
|
|
const LAST_ACTIVITY_KEY = 'grd_last_activity'
|
|
const TOKEN_KEY = 'grd_token'
|
|
const USER_KEY = 'grd_user'
|
|
|
|
export async function recordActivity(): Promise<void> {
|
|
try {
|
|
await SecureStore.setItemAsync(LAST_ACTIVITY_KEY, String(Date.now()))
|
|
} catch {}
|
|
}
|
|
|
|
/** 만료 여부만 판정 (라우터 의존 없이 _layout 등에서 사용) */
|
|
export async function isSessionExpired(): Promise<boolean> {
|
|
try {
|
|
const token = await SecureStore.getItemAsync(TOKEN_KEY)
|
|
if (!token) return false // 로그인 전이면 만료 개념 없음
|
|
const last = await SecureStore.getItemAsync(LAST_ACTIVITY_KEY)
|
|
if (!last) return false
|
|
return Date.now() - parseInt(last, 10) > SESSION_TIMEOUT
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
export async function clearSession(): Promise<void> {
|
|
try {
|
|
await SecureStore.deleteItemAsync(TOKEN_KEY)
|
|
await SecureStore.deleteItemAsync(USER_KEY)
|
|
await SecureStore.deleteItemAsync(LAST_ACTIVITY_KEY)
|
|
} catch {}
|
|
}
|
|
|
|
export function useSessionExpiry() {
|
|
const router = useRouter()
|
|
|
|
const updateActivity = useCallback(async () => {
|
|
await recordActivity()
|
|
}, [])
|
|
|
|
const checkExpiry = useCallback(async (): Promise<boolean> => {
|
|
const expired = await isSessionExpired()
|
|
if (expired) {
|
|
await clearSession()
|
|
router.replace('/(auth)/login')
|
|
return true
|
|
}
|
|
return false
|
|
}, [router])
|
|
|
|
return { updateActivity, checkExpiry, isSessionExpired, clearSession }
|
|
}
|