import { Agent } from "@atproto/api"; import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; import { OAuthClient, type OAuthSession } from "@atproto/oauth-client"; import { BrowserOAuthClient } from "@atproto/oauth-client-browser"; import { Store } from "@tauri-apps/plugin-store"; import { createContext, ReactNode, useContext, useEffect, useState, } from "react"; interface AuthContextType { isLoading: boolean; isAuthenticated: boolean; profile: ProfileViewDetailed | null; client: OAuthClient | null; login: (session: OAuthSession) => Promise; logout: () => Promise; agent: Agent | null; } const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const [isLoading, setIsLoading] = useState(true); const [profile, setProfile] = useState(null); const [client, setClient] = useState(null); const [agent, setAgent] = useState(null); // Initialize OAuth client useEffect(() => { const init = async () => { setIsLoading(true); try { const client = await BrowserOAuthClient.load({ clientId: "https://atproto-backup.pages.dev/client_metadata.json", handleResolver: "https://bsky.social", }); setClient(client); // Try to restore existing session from storage try { //@ts-expect-error const result: undefined | { session: OAuthSession; state?: string } = await client.init(); if (result) { const { session, state } = result; if (state != null) { console.log( `${session.sub} was successfully authenticated (state: ${state})` ); } else { console.log(`${session.sub} was restored (last active session)`); } await login(session); } else { const store = await Store.load("store.json", { autoSave: true }); const sub = (await store.get("session")) as string; if (sub) { const session = await client.restore(sub); await login(session); return; } } } catch (error) { console.error("Failed to restore session:", error); } } catch (error) { console.error("Failed to initialize auth:", error); } finally { setIsLoading(false); } }; init(); }, []); const login = async (session: OAuthSession) => { try { const store = await Store.load("store.json", { autoSave: true }); store.set("session", session.did); const agent = new Agent(session); setAgent(agent); setIsLoading(true); const actor = await agent.getProfile({ actor: session.did }); setProfile(actor.data); } catch (error) { console.error("Login failed:", error); throw error; } finally { setIsLoading(false); } }; const logout = async () => { try { if (client && profile) { // Revoke the session and clear storage await client.revoke(profile.did); const store = await Store.load("store.json", { autoSave: true }); // probably unnecessary await store.clear(); setProfile(null); } } catch (error) { console.error("Logout failed:", error); throw error; } }; return ( {children} ); } export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error("useAuth must be used within an AuthProvider"); } return context; }