import React, { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react'; import { BrowserOAuthClient } from '@atproto/oauth-client-browser'; export const AuthContext = createContext(null); export const AuthProvider = ({ children }) => { const [client, setClient] = useState(null); const [session, setSession] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const initializing = useRef(false); useEffect(() => { const initializeAuth = async () => { if (initializing.current || client) return; initializing.current = true; setLoading(true); setError(null); try { // P6: single source of truth — load client metadata at startup const metadataResponse = await fetch('/client-metadata.json'); if (!metadataResponse.ok) { throw new Error(`Failed to load client metadata: ${metadataResponse.status}`); } const clientMetadata = await metadataResponse.json(); const oauthClient = new BrowserOAuthClient({ clientMetadata, handleResolver: 'https://public.api.bsky.app', plcDirectoryUrl: 'https://plc.directory', }); setClient(oauthClient); const initResult = await oauthClient.init(); if (initResult?.session) { setSession(initResult.session); } else { setSession(null); } } catch (err) { console.error('Auth initialization failed:', err); setError('Authentication initialization failed. Please try refreshing.'); setSession(null); } finally { setLoading(false); initializing.current = false; } }; initializeAuth(); }, [client]); const login = useCallback(async (handle, returnUrl = '/') => { if (!client) { setError("Client not initialized."); return; } try { const stateData = JSON.stringify({ returnUrl }); await client.signIn(handle, { state: stateData }); } catch (err) { console.error('Login failed:', err); setError('Login initiation failed or was cancelled.'); } }, [client]); const logout = useCallback(async () => { if (!session) return; try { await session.signOut(); setSession(null); window.location.href = '/'; } catch (err) { console.error('Logout failed:', err); setSession(null); window.location.href = '/'; } }, [session]); return ( {children} ); }; export const useAuth = () => { const context = useContext(AuthContext); if (context === null) { throw new Error('useAuth must be used within an AuthProvider'); } return context; };