This repository has no description
0

Configure Feed

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

at main 6.7 kB View raw
1import React, { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react'; 2import { BrowserOAuthClient } from '@atproto/oauth-client-browser'; 3 4// Create auth context 5export const AuthContext = createContext(null); 6 7 8// Client metadata for Bluesky OAuth 9const clientMetadata = { 10 client_id: `https://cred.blue/client-metadata.json`, 11 client_name: "cred.blue", 12 client_uri: `https://cred.blue`, 13 redirect_uris: [`https://cred.blue/login/callback`], 14 logo_uri: `https://cred.blue/favicon.ico`, 15 scope: "atproto transition:generic", 16 grant_types: ["authorization_code", "refresh_token"], 17 response_types: ["code"], 18 token_endpoint_auth_method: "none", 19 application_type: "web", 20 dpop_bound_access_tokens: true 21}; 22 23export const AuthProvider = ({ children }) => { 24 const [client, setClient] = useState(null); 25 const [session, setSession] = useState(null); 26 const [loading, setLoading] = useState(true); 27 const [error, setError] = useState(null); 28 const initializing = useRef(false); 29 30 // Updated initializeAuth for BrowserOAuthClient 31 useEffect(() => { 32 const initializeAuth = async () => { 33 if (initializing.current || client) return; // Prevent multiple initializations 34 initializing.current = true; 35 setLoading(true); 36 setError(null); 37 console.log('(AuthProvider) Initializing BrowserOAuthClient...'); 38 39 try { 40 // Create the client instance 41 const oauthClient = new BrowserOAuthClient({ 42 clientMetadata: clientMetadata, // Reverted to original clientMetadata 43 handleResolver: 'https://public.api.bsky.app', 44 plcDirectoryUrl: 'https://plc.directory', 45 }); 46 47 setClient(oauthClient); // Store the client instance 48 49 // Initialize the client - this handles callbacks and session restoration 50 console.log('(AuthProvider) Initializing OAuth client...'); 51 const initResult = await oauthClient.init(); 52 console.log('(AuthProvider) Init result:', { 53 hasSession: !!initResult?.session, 54 hasState: !!initResult?.state, 55 did: initResult?.session?.did 56 }); 57 58 if (initResult?.session) { 59 setSession(initResult.session); 60 console.log(`(AuthProvider) Session ${initResult.state ? 'established via callback' : 'restored'}:`, initResult.session.did); 61 if (initResult.state) { 62 console.log('(AuthProvider) Original state from callback:', initResult.state); 63 // Optionally, redirect based on state if needed, e.g., using navigate 64 // const returnUrl = initResult.state.returnUrl || '/'; // Example state usage 65 // window.location.href = returnUrl; // Or use React Router navigate 66 } 67 } else { 68 setSession(null); 69 console.log('(AuthProvider) No active session found or callback processed.'); 70 } 71 } catch (err) { 72 console.error('(AuthProvider) Error initializing client or handling callback:', err); 73 setError('Authentication initialization failed. Please try refreshing.'); 74 setSession(null); 75 } finally { 76 setLoading(false); 77 initializing.current = false; 78 console.log('(AuthProvider) Initialization complete.'); 79 } 80 }; 81 82 initializeAuth(); 83 }, [client]); // Dependency on client ensures it runs only once after client is potentially set 84 85 // Updated login function - uses client.signIn() 86 const login = useCallback(async (handle, returnUrl = '/') => { 87 if (!client) { 88 setError("Client not initialized."); 89 return; 90 } 91 console.log(`(AuthProvider) Initiating client-side login for handle: ${handle || 'none specified'}, returnUrl: ${returnUrl}`); 92 try { 93 // The state can be used to pass information through the redirect, like the return URL 94 const stateData = JSON.stringify({ returnUrl }); 95 // signIn redirects the browser, so code execution stops here if successful 96 await client.signIn(handle, { // Pass handle directly for resolution 97 state: stateData, 98 // prompt: 'none', // Uncomment for silent sign-in attempt 99 }); 100 } catch (err) { 101 // This catch might run if the user navigates back or cancels 102 console.error('(AuthProvider) Error during signIn initiation or cancellation:', err); 103 setError('Login initiation failed or was cancelled.'); 104 } 105 }, [client]); 106 107 // Updated Logout function - uses session.signOut() 108 const logout = useCallback(async () => { 109 if (!session) { 110 console.log('(AuthProvider) No active session to log out'); 111 return; 112 } 113 114 console.log('(AuthProvider) Logging out...'); 115 try { 116 await session.signOut(); // Use session's signOut method 117 setSession(null); 118 // Optionally clear other app state here 119 console.log('(AuthProvider) Logout complete.'); 120 // Redirect to home or login page 121 window.location.href = '/'; // Force reload to ensure clean state 122 } catch (err) { 123 console.error('(AuthProvider) Error during logout:', err); 124 // Still attempt to clear local session on error 125 setSession(null); 126 window.location.href = '/'; // Force reload 127 } 128 }, [session]); 129 130 // Debug effect to log auth state changes 131 useEffect(() => { 132 console.log('(AuthProvider) Auth state updated:', { 133 isAuthenticated: !!session, 134 did: session?.did || null, 135 loading, 136 hasError: !!error 137 }); 138 139 // Force a page refresh if session state changes and we're on a protected path 140 // This ensures the latest auth state is always used for route protection 141 // REMOVED: This can cause unwanted flashing/reloads 142 /* 143 if (!loading && window.location.pathname === '/verifier' && !session) { 144 console.log('(AuthProvider) No session on protected page, forcing refresh'); 145 window.location.reload(); 146 } 147 */ 148 }, [session, loading, error]); 149 150 return ( 151 <AuthContext.Provider 152 value={{ 153 client, // Export the client instance if needed by components (e.g., LoginCallback) 154 session, 155 loading, // Renamed from authLoading for clarity 156 error, 157 isAuthenticated: !!session, 158 login, 159 logout, 160 // Remove checkAuthStatus export 161 }} 162 > 163 {children} 164 </AuthContext.Provider> 165 ); 166}; 167 168// Custom hook to use the auth context 169export const useAuth = () => { 170 const context = useContext(AuthContext); 171 if (context === null) { 172 throw new Error('useAuth must be used within an AuthProvider'); 173 } 174 // Ensure components using the hook get the updated context value 175 // (React handles this, but good to be mindful) 176 return context; 177};