167 lines
4.3 KiB
TypeScript
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)
|
|
}
|