Download src/lib/auth/AuthProvider.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 6.62 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/auth/AuthProvider.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/lib/auth/AuthProvider.tsx
-
curl -L -o AuthProvider.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/auth/AuthProvider.tsx
6.62 kB
| "use client"; | |
| import { createContext, useCallback, useEffect, useMemo, useRef, useState } from "react"; | |
| import type { | |
| ApiUser, | |
| AuthLoginRequest, | |
| AuthSignupResponse, | |
| AuthSignupRequest, | |
| UserUpdateRequest, | |
| } from "@/lib/api/types"; | |
| import { | |
| fetchCurrentUser, | |
| AUTH_SESSION_UPDATED_EVENT, | |
| getAuthToken, | |
| getStoredUser, | |
| loginWithEmailPassword, | |
| signupWithEmailPassword, | |
| logoutCurrentSession, | |
| updateCurrentUser, | |
| verifyLoginOtp, | |
| verifySignupOtp, | |
| } from "@/lib/auth/auth-client"; | |
| import { getPublicApiBaseUrl, getPublicDemoMode } from "@/lib/env"; | |
| type AuthLoginOptions = { | |
| staySignedIn?: boolean; | |
| rememberEmail?: boolean; | |
| }; | |
| type AuthContextValue = { | |
| user: ApiUser | null; | |
| isLoading: boolean; | |
| isDevMode: boolean; | |
| error: string | null; | |
| refreshUser: () => Promise<void>; | |
| login: (payload: AuthLoginRequest, options?: AuthLoginOptions) => Promise<void>; | |
| loginWithOtp: ( | |
| payload: { email: string; code: string }, | |
| options?: AuthLoginOptions, | |
| ) => Promise<void>; | |
| signup: (payload: AuthSignupRequest) => Promise<AuthSignupResponse>; | |
| verifySignup: (payload: { email: string; code: string }) => Promise<void>; | |
| logout: () => void; | |
| updateProfile: (payload: UserUpdateRequest) => Promise<void>; | |
| }; | |
| export const AuthContext = createContext<AuthContextValue | null>(null); | |
| export function AuthProvider({ children }: { children: React.ReactNode }) { | |
| const [user, setUser] = useState<ApiUser | null>(null); | |
| const [isLoading, setIsLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| const [hasToken, setHasToken] = useState(false); | |
| const refreshSequence = useRef(0); | |
| const refreshUser = useCallback(async () => { | |
| const sequence = ++refreshSequence.current; | |
| setError(null); | |
| try { | |
| const nextUser = await fetchCurrentUser(); | |
| if (sequence !== refreshSequence.current) return; | |
| setUser(nextUser); | |
| setHasToken(Boolean(getAuthToken())); | |
| } catch (caughtError) { | |
| if (sequence !== refreshSequence.current) return; | |
| const message = caughtError instanceof Error ? caughtError.message : "Could not load user session."; | |
| // Distinguish auth errors (401/403) from network/server errors | |
| const isAuthError = | |
| message.includes("401") || | |
| message.includes("403") || | |
| message.toLowerCase().includes("unauthorized") || | |
| message.toLowerCase().includes("session expired"); | |
| if (isAuthError) { | |
| // Real auth failure β clear session, show guest state | |
| setUser(null); | |
| setHasToken(false); | |
| setError("Your session expired. Please sign in again."); | |
| } else { | |
| // Network or server error β preserve existing user to avoid data loss | |
| const stored = getStoredUser(); | |
| setUser(stored); | |
| setHasToken(Boolean(getAuthToken())); | |
| if (!stored) { | |
| setError("Could not connect to server. Please check your connection."); | |
| } | |
| // Don't set error if we have a stored user β silent retry is better UX | |
| } | |
| } finally { | |
| if (sequence === refreshSequence.current) setIsLoading(false); | |
| } | |
| }, []); | |
| useEffect(() => { | |
| const timeoutId = window.setTimeout(() => void refreshUser(), 0); | |
| return () => window.clearTimeout(timeoutId); | |
| }, [refreshUser]); | |
| // Background wake-up ping: HF Space sleeps on inactivity and takes ~60s to | |
| // restart. Fire a silent /health request immediately so the Space is awake | |
| // by the time the user tries to send a message. | |
| useEffect(() => { | |
| if (typeof window === "undefined") return; | |
| const base = getPublicApiBaseUrl(); | |
| const controller = new AbortController(); | |
| const timer = window.setTimeout(() => controller.abort(), 90_000); | |
| void fetch(`${base}/health`, { signal: controller.signal }).catch(() => {/* silent */}).finally(() => window.clearTimeout(timer)); | |
| return () => { controller.abort(); window.clearTimeout(timer); }; | |
| }, []); | |
| useEffect(() => { | |
| function handleSessionUpdated() { | |
| const storedUser = getStoredUser(); | |
| const token = getAuthToken(); | |
| setUser(storedUser); | |
| setHasToken(Boolean(token)); | |
| setError(null); | |
| setIsLoading(false); | |
| // Confirm the just-persisted session with the backend without delaying | |
| // navigation to onboarding or the workspace. | |
| void refreshUser(); | |
| } | |
| function handleSessionCleared() { | |
| // Session was explicitly cleared (401 received) β show sign-in prompt, not demo data | |
| refreshSequence.current += 1; | |
| setUser(null); | |
| setHasToken(false); | |
| setIsLoading(false); | |
| setError("Your session expired. Please sign in again."); | |
| } | |
| window.addEventListener(AUTH_SESSION_UPDATED_EVENT, handleSessionUpdated); | |
| window.addEventListener("docdoe-auth-session-cleared", handleSessionCleared); | |
| return () => { | |
| window.removeEventListener(AUTH_SESSION_UPDATED_EVENT, handleSessionUpdated); | |
| window.removeEventListener("docdoe-auth-session-cleared", handleSessionCleared); | |
| }; | |
| }, [refreshUser]); | |
| const value = useMemo<AuthContextValue>( | |
| () => ({ | |
| user, | |
| isLoading, | |
| isDevMode: getPublicDemoMode() !== "live" && Boolean(user) && !hasToken, | |
| error, | |
| refreshUser, | |
| login: async (payload, options) => { | |
| setError(null); | |
| const response = await loginWithEmailPassword(payload, options); | |
| setUser(response.user); | |
| setHasToken(true); | |
| }, | |
| loginWithOtp: async (payload, options) => { | |
| setError(null); | |
| const response = await verifyLoginOtp(payload, options); | |
| setUser(response.user); | |
| setHasToken(true); | |
| }, | |
| signup: async (payload) => { | |
| setError(null); | |
| const response = await signupWithEmailPassword(payload); | |
| if ("access_token" in response) { | |
| setUser(response.user); | |
| setHasToken(true); | |
| } | |
| return response; | |
| }, | |
| verifySignup: async (payload) => { | |
| setError(null); | |
| const response = await verifySignupOtp(payload); | |
| setUser(response.user); | |
| setHasToken(true); | |
| }, | |
| logout: () => { | |
| refreshSequence.current += 1; | |
| logoutCurrentSession(); | |
| setUser(null); | |
| setHasToken(false); | |
| setIsLoading(false); | |
| }, | |
| updateProfile: async (payload) => { | |
| setError(null); | |
| const nextUser = await updateCurrentUser(payload); | |
| setUser(nextUser); | |
| }, | |
| }), | |
| [error, hasToken, isLoading, refreshUser, user], | |
| ); | |
| return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; | |
| } | |