👁️
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

at dev 131 lines 3.2 kB view raw
1import type { ActorIdentifier, Did } from "@atcute/lexicons"; 2import { 3 createAuthorizationUrl, 4 getSession, 5 OAuthUserAgent, 6 type Session, 7} from "@atcute/oauth-browser-client"; 8import { useNavigate } from "@tanstack/react-router"; 9import { 10 createContext, 11 type ReactNode, 12 useContext, 13 useEffect, 14 useState, 15} from "react"; 16import { toast } from "sonner"; 17 18const STORAGE_KEY = "deckbelcher:last-did"; 19export const RETURN_TO_KEY = "deckbelcher:return-to"; 20 21interface AuthContextValue { 22 session: Session | null; 23 agent: OAuthUserAgent | null; 24 signIn: (handle: string) => Promise<void>; 25 signUp: (pdsUrl: string) => Promise<void>; 26 signOut: () => Promise<void>; 27 setAuthSession: (session: Session) => void; 28 isLoading: boolean; 29} 30 31const AuthContext = createContext<AuthContextValue | null>(null); 32 33export function AuthProvider({ children }: { children: ReactNode }) { 34 const [session, setSession] = useState<Session | null>(null); 35 const [agent, setAgent] = useState<OAuthUserAgent | null>(null); 36 const [isLoading, setIsLoading] = useState(true); 37 const navigate = useNavigate(); 38 39 useEffect(() => { 40 const restoreSession = async () => { 41 try { 42 const lastDid = localStorage.getItem(STORAGE_KEY); 43 if (lastDid) { 44 const stored = await getSession(lastDid as Did); 45 if (stored) { 46 setSession(stored); 47 setAgent(new OAuthUserAgent(stored)); 48 } 49 } 50 } catch (error) { 51 console.warn("Session restoration failed:", error); 52 const lastDid = localStorage.getItem(STORAGE_KEY); 53 if (lastDid) { 54 localStorage.removeItem(STORAGE_KEY); 55 toast.error("Your session expired. Please sign in again.", { 56 duration: 5000, 57 action: { 58 label: "Sign In", 59 onClick: () => { 60 navigate({ to: "/signin" }); 61 }, 62 }, 63 }); 64 } 65 } finally { 66 setIsLoading(false); 67 } 68 }; 69 70 restoreSession(); 71 }, [navigate]); 72 73 const signIn = async (handle: string) => { 74 const authUrl = await createAuthorizationUrl({ 75 target: { type: "account", identifier: handle as ActorIdentifier }, 76 scope: import.meta.env.VITE_OAUTH_SCOPE, 77 }); 78 79 window.location.assign(authUrl); 80 }; 81 82 const signUp = async (pdsUrl: string) => { 83 // TODO: add `prompt: "create"` when more PDS implementations support it 84 // to hint that signup UI should be shown instead of login 85 const authUrl = await createAuthorizationUrl({ 86 target: { type: "pds", serviceUrl: pdsUrl }, 87 scope: import.meta.env.VITE_OAUTH_SCOPE, 88 }); 89 90 window.location.assign(authUrl); 91 }; 92 93 const signOut = async () => { 94 if (agent) { 95 await agent.signOut(); 96 } 97 localStorage.removeItem(STORAGE_KEY); 98 setSession(null); 99 setAgent(null); 100 }; 101 102 const setAuthSession = (newSession: Session) => { 103 localStorage.setItem(STORAGE_KEY, newSession.info.sub); 104 setSession(newSession); 105 setAgent(new OAuthUserAgent(newSession)); 106 }; 107 108 return ( 109 <AuthContext.Provider 110 value={{ 111 session, 112 agent, 113 signIn, 114 signUp, 115 signOut, 116 isLoading, 117 setAuthSession, 118 }} 119 > 120 {children} 121 </AuthContext.Provider> 122 ); 123} 124 125export function useAuth() { 126 const context = useContext(AuthContext); 127 if (!context) { 128 throw new Error("useAuth must be used within an AuthProvider"); 129 } 130 return context; 131}