/** * useAnomalyAlert (#22) — 이상 탐지 푸시 훅 * * 1분마다 GET /api/metrics/anomalies?severity=high 폴링. * 이전에 보지 못한 새 이상 감지 시 Alert.alert로 알림. * (expo-notifications 플러그인 등록 없이 인앱 Alert 사용 → EAS 빌드 안전) */ import { useEffect, useRef, useState } from 'react' import { Alert, AppState } from 'react-native' import { API_BASE } from '../constants/Config' import { authFetch } from '../utils/auth' interface Anomaly { id: number | string title?: string metric?: string server?: string severity?: string detected_at?: string } const POLL_MS = 60_000 export function useAnomalyAlert(enabled = true) { const seen = useRef>(new Set()) const [latest, setLatest] = useState([]) useEffect(() => { if (!enabled) return let alive = true async function poll() { if (AppState.currentState !== 'active') return try { const res = await authFetch(`${API_BASE}/api/metrics/anomalies?severity=high`) if (!res.ok) return const d = await res.json() const list: Anomaly[] = Array.isArray(d) ? d : d.items ?? d.anomalies ?? [] if (!alive) return setLatest(list) const fresh = list.filter(a => !seen.current.has(String(a.id))) fresh.forEach(a => seen.current.add(String(a.id))) if (fresh.length > 0) { const first = fresh[0] const more = fresh.length > 1 ? ` 외 ${fresh.length - 1}건` : '' Alert.alert( '⚠️ 이상 징후 감지', `${first.title ?? first.metric ?? '메트릭 이상'}${first.server ? ` (${first.server})` : ''}${more}`, ) } } catch { /* 폴링 실패 무시 */ } } poll() const id = setInterval(poll, POLL_MS) return () => { alive = false clearInterval(id) } }, [enabled]) return { latest } } export default useAnomalyAlert