Files
dukang/apps/h5-user/src/contexts/UserSessionContext.tsx
T

121 lines
3.0 KiB
TypeScript

import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from 'react';
import {
bootstrapSession,
clearAuth,
ensureSession,
getDeviceKey,
request,
saveSession,
type SessionPayload,
type UserProfile,
} from '../lib/api';
import { touchPromoIfNeeded } from '../lib/promo';
type UserSessionContextValue = {
ready: boolean;
profile: UserProfile | null;
phoneVerified: boolean;
applySession: (session: SessionPayload) => void;
refreshProfile: () => Promise<void>;
resetSession: () => Promise<void>;
};
const UserSessionContext = createContext<UserSessionContextValue | null>(null);
export function UserSessionProvider({ children }: { children: ReactNode }) {
const [ready, setReady] = useState(false);
const [profile, setProfile] = useState<UserProfile | null>(null);
const [phoneVerified, setPhoneVerified] = useState(false);
const applySession = useCallback((session: SessionPayload) => {
saveSession(session);
if (session.user) setProfile(session.user);
setPhoneVerified(!!session.phoneVerified || !!session.user?.phoneVerified);
}, []);
const refreshProfile = useCallback(async () => {
const me = await request<UserProfile>('USER_H5', '/auth/me');
setProfile(me);
setPhoneVerified(!!me.phoneVerified);
}, []);
const resetSession = useCallback(async () => {
clearAuth();
const session = await bootstrapSession();
applySession(session);
}, [applySession]);
useEffect(() => {
let cancelled = false;
(async () => {
try {
const session = await ensureSession();
if (cancelled) return;
applySession(session);
if (!session.user) {
await refreshProfile();
}
await touchPromoIfNeeded();
} catch {
if (!cancelled) {
try {
const session = await bootstrapSession();
applySession(session);
await refreshProfile();
} catch {
/* ignore */
}
}
} finally {
if (!cancelled) setReady(true);
}
})();
return () => {
cancelled = true;
};
}, [applySession, refreshProfile]);
const value = useMemo(
() => ({
ready,
profile,
phoneVerified,
applySession,
refreshProfile,
resetSession,
}),
[ready, profile, phoneVerified, applySession, refreshProfile, resetSession],
);
if (!ready) {
return (
<div className="session-boot">
<p className="session-boot-text">加载中...</p>
</div>
);
}
return <UserSessionContext.Provider value={value}>{children}</UserSessionContext.Provider>;
}
export function useUserSession() {
const ctx = useContext(UserSessionContext);
if (!ctx) throw new Error('useUserSession must be used within UserSessionProvider');
return ctx;
}
export function useOptionalUserSession() {
return useContext(UserSessionContext);
}
/** @deprecated use profile from useUserSession */
export { getDeviceKey };