Files
2026-03-26 01:59:04 -05:00

167 lines
4.3 KiB
TypeScript

import React, {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { Alert } from 'react-native'
import { clearPbSession, loadPbSession, savePbSession, type StoredPbSession } from '@/lib/authStorage'
import {
isLiveAllowed,
pbAuthRefresh,
pbAuthWithPassword,
type PbUserRecord,
} from '@/lib/pocketbase'
type Ctx = {
session: StoredPbSession | null
ready: boolean
loginVisible: boolean
openLogin: () => void
closeLogin: () => void
login: (email: string, password: string) => Promise<void>
logout: () => Promise<void>
refreshSession: () => Promise<boolean>
/** 用于远程 API 请求头 */
pbToken: string | null
/** 开始/重启直播前调用;未登录则弹出登录;无权限则 false */
ensureCanControlLive: () => Promise<boolean>
}
const AuthContext = createContext<Ctx | null>(null)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [session, setSession] = useState<StoredPbSession | null>(null)
const [ready, setReady] = useState(false)
const [loginVisible, setLoginVisible] = useState(false)
const pendingGate = useRef<((ok: boolean) => void) | null>(null)
useEffect(() => {
void (async () => {
const s = await loadPbSession()
if (s) {
try {
const r = await pbAuthRefresh(s.token)
const next: StoredPbSession = { token: r.token, record: r.record }
setSession(next)
await savePbSession(next)
} catch {
await clearPbSession()
setSession(null)
}
}
setReady(true)
})()
}, [])
const openLogin = useCallback(() => setLoginVisible(true), [])
const closeLogin = useCallback(() => {
setLoginVisible(false)
const p = pendingGate.current
pendingGate.current = null
p?.(false)
}, [])
const login = useCallback(async (email: string, password: string) => {
const r = await pbAuthWithPassword(email, password)
if (!isLiveAllowed(r.record)) {
throw new Error('该账号暂无直播权限,请联系管理员')
}
const next: StoredPbSession = { token: r.token, record: r.record }
setSession(next)
await savePbSession(next)
setLoginVisible(false)
const p = pendingGate.current
pendingGate.current = null
p?.(true)
}, [])
const logout = useCallback(async () => {
await clearPbSession()
setSession(null)
}, [])
const refreshSession = useCallback(async () => {
if (!session?.token) return false
try {
const r = await pbAuthRefresh(session.token)
const next: StoredPbSession = { token: r.token, record: r.record }
setSession(next)
await savePbSession(next)
return isLiveAllowed(r.record)
} catch {
await clearPbSession()
setSession(null)
return false
}
}, [session?.token])
const ensureCanControlLive = useCallback(async (): Promise<boolean> => {
if (session?.token) {
try {
const r = await pbAuthRefresh(session.token)
const next: StoredPbSession = { token: r.token, record: r.record }
setSession(next)
await savePbSession(next)
if (!isLiveAllowed(r.record)) {
Alert.alert('无权限', '账号已被管理员停用远程直播权限。')
return false
}
return true
} catch {
await clearPbSession()
setSession(null)
}
}
return new Promise<boolean>((resolve) => {
pendingGate.current = (ok) => resolve(ok)
setLoginVisible(true)
})
}, [session?.token])
const pbToken = session?.token ?? null
const value = useMemo(
() => ({
session,
ready,
loginVisible,
openLogin,
closeLogin,
login,
logout,
refreshSession,
pbToken,
ensureCanControlLive,
}),
[
session,
ready,
loginVisible,
openLogin,
closeLogin,
login,
logout,
refreshSession,
pbToken,
ensureCanControlLive,
],
)
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
export function useAuth(): Ctx {
const c = useContext(AuthContext)
if (!c) throw new Error('useAuth outside AuthProvider')
return c
}
export function useAuthOptional(): Ctx | null {
return useContext(AuthContext)
}