guardia-messenger/hooks/useSessionExpiry.ts

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 }
}