The 1st decentralized social network for sharing when you're on the toilet. Post a "flush" today! Powered by the AT Protocol.
at main 159 lines 4.6 kB view raw
1'use client'; 2 3import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; 4import { OAuthSession } from '@atproto/oauth-client-browser'; 5 6interface AuthContextType { 7 session: OAuthSession | null; 8 isAuthenticated: boolean; 9 isLoading: boolean; 10 signIn: (handle: string) => Promise<void>; 11 signOut: () => Promise<void>; 12 restoreSession: (did: string) => Promise<OAuthSession>; 13 // Legacy compatibility properties for existing code 14 accessToken: string | null; 15 refreshToken: string | null; 16 did: string | null; 17 handle: string | null; 18 pdsEndpoint: string | null; 19} 20 21const AuthContext = createContext<AuthContextType | undefined>(undefined); 22 23interface AuthProviderProps { 24 children: ReactNode; 25} 26 27export function AuthProvider({ children }: AuthProviderProps) { 28 const [session, setSession] = useState<OAuthSession | null>(null); 29 const [isLoading, setIsLoading] = useState(true); 30 const [isClient, setIsClient] = useState(false); 31 32 // Track if we're on the client side 33 useEffect(() => { 34 setIsClient(true); 35 }, []); 36 37 // Initialize the OAuth client on mount (client-side only) 38 useEffect(() => { 39 if (!isClient) return; 40 41 async function initialize() { 42 try { 43 setIsLoading(true); 44 45 // Dynamic import to ensure client-side only execution 46 const { initializeOAuthClient } = await import('./oauth-client'); 47 const result = await initializeOAuthClient(); 48 49 if (result) { 50 console.log('Initialized with existing session:', result.session.sub); 51 setSession(result.session); 52 } 53 } catch (error) { 54 console.error('Failed to initialize OAuth client:', error); 55 } finally { 56 setIsLoading(false); 57 } 58 } 59 60 initialize(); 61 }, [isClient]); 62 63 // Set up session deletion listener (client-side only) 64 useEffect(() => { 65 if (!isClient) return; 66 67 async function setupListener() { 68 try { 69 const { onSessionDeleted } = await import('./oauth-client'); 70 71 const handleSessionDeleted = ({ sub, cause }: { sub: string; cause: any }) => { 72 console.error(`Session for ${sub} was invalidated:`, cause); 73 setSession(null); 74 }; 75 76 onSessionDeleted(handleSessionDeleted); 77 } catch (error) { 78 console.error('Failed to set up session listener:', error); 79 } 80 } 81 82 setupListener(); 83 }, [isClient]); 84 85 const handleSignIn = async (handle: string) => { 86 if (!isClient) { 87 throw new Error('Sign in can only be called on the client side'); 88 } 89 90 try { 91 const { signIn } = await import('./oauth-client'); 92 await signIn(handle); 93 // Note: This will redirect, so we won't reach this point 94 } catch (error) { 95 console.error('Sign in failed:', error); 96 throw error; 97 } 98 }; 99 100 const handleSignOut = async () => { 101 if (!isClient) { 102 throw new Error('Sign out can only be called on the client side'); 103 } 104 105 try { 106 const { signOut } = await import('./oauth-client'); 107 await signOut(); 108 setSession(null); 109 } catch (error) { 110 console.error('Sign out failed:', error); 111 throw error; 112 } 113 }; 114 115 const handleRestoreSession = async (did: string) => { 116 if (!isClient) { 117 throw new Error('Restore session can only be called on the client side'); 118 } 119 120 try { 121 const { restoreSession } = await import('./oauth-client'); 122 const restoredSession = await restoreSession(did); 123 setSession(restoredSession); 124 return restoredSession; 125 } catch (error) { 126 console.error('Failed to restore session:', error); 127 throw error; 128 } 129 }; 130 131 const contextValue: AuthContextType = { 132 session, 133 isAuthenticated: !!session, 134 isLoading: isLoading || !isClient, // Keep loading until client-side hydration 135 signIn: handleSignIn, 136 signOut: handleSignOut, 137 restoreSession: handleRestoreSession, 138 // Legacy compatibility - provide basic properties 139 accessToken: session ? 'available' : null, // Session manages tokens internally 140 refreshToken: session ? 'available' : null, // Session manages tokens internally 141 did: session?.sub || null, 142 handle: null, // Will be fetched by components when needed 143 pdsEndpoint: null // Not exposed by the new OAuth client 144 }; 145 146 return ( 147 <AuthContext.Provider value={contextValue}> 148 {children} 149 </AuthContext.Provider> 150 ); 151} 152 153export function useAuth() { 154 const context = useContext(AuthContext); 155 if (context === undefined) { 156 throw new Error('useAuth must be used within an AuthProvider'); 157 } 158 return context; 159}