import { AuthSession, type AuthState } from '@pipu/core'; import Constants from 'expo-constants'; import { createContext, useCallback, useContext, useEffect, useMemo, useState, useSyncExternalStore, type ReactNode } from 'react'; import { currentServer, refreshStoreFor, rememberServer, setCurrentServer } from './storage'; interface SessionCtx { /** Адрес сервера; null — сервер не выбран (экран подключения). */ server: string | null; session: AuthSession | null; ready: boolean; connect: (url: string, version: string) => Promise; disconnect: () => Promise; } const Ctx = createContext(null); function deviceName(): string { return Constants.deviceName ?? 'Android'; } function makeSession(url: string) { return new AuthSession({ baseUrl: url, clientType: 'android', deviceName: deviceName(), refreshStore: refreshStoreFor(url) }); } /** Текущий сервер и сессия на нём; при запуске восстанавливает вход по сохранённому refresh-токену. */ export function SessionProvider({ children }: { children: ReactNode }) { const [server, setServer] = useState(null); const [session, setSession] = useState(null); const [ready, setReady] = useState(false); useEffect(() => { void (async () => { const url = await currentServer(); if (url) { const s = makeSession(url); await s.restore(); setServer(url); setSession(s); } setReady(true); })(); }, []); const connect = useCallback(async (url: string, version: string) => { await rememberServer({ url, version }); await setCurrentServer(url); const s = makeSession(url); await s.restore(); // на этом сервере уже могли войти раньше setServer(url); setSession(s); }, []); const disconnect = useCallback(async () => { await setCurrentServer(null); setServer(null); setSession(null); }, []); const value = useMemo(() => ({ server, session, ready, connect, disconnect }), [server, session, ready, connect, disconnect]); return {children}; } export function useSessionCtx(): SessionCtx { const c = useContext(Ctx); if (!c) throw new Error('useSessionCtx outside SessionProvider'); return c; } /** Сессия вошедшего пользователя (экраны внутри приложения). */ export function useSession(): AuthSession { const { session } = useSessionCtx(); if (!session) throw new Error('no session'); return session; } const UNKNOWN: AuthState = { status: 'unknown' }; export function useAuthState(): AuthState { const { session } = useSessionCtx(); return useSyncExternalStore( (fn) => session?.subscribe(fn) ?? (() => undefined), () => session?.state ?? UNKNOWN, ); } export function useUser() { const state = useAuthState(); if (state.status !== 'signed_in') throw new Error('not signed in'); return state.user; }