"use client"; import { createContext, useContext, useEffect, useState, useCallback, ReactNode, } from "react"; import { BrowserOAuthClient, AtprotoDohHandleResolver, type OAuthSession, } from "@atproto/oauth-client-browser"; import { Agent } from "@atproto/api"; import { th } from "zod/v4/locales"; type AuthContextType = { session: OAuthSession | null; agent: Agent; loading: boolean; login: (handle: string) => Promise; logout: () => void; }; const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const defaultAgent = new Agent({ service: "https://bsky.social" }); const [session, setSession] = useState(null); const [agent, setAgent] = useState(defaultAgent); const [loading, setLoading] = useState(true); const [client, setClient] = useState(null); useEffect(() => { const initClient = async () => { const isDev = process.env.NODE_ENV === "development"; const c = isDev ? new BrowserOAuthClient({ handleResolver: "https://bsky.social", clientMetadata: { client_name: "Statusphere React App", client_id: `http://localhost?scope=${encodeURI( "atproto transition:generic transition:chat.bsky" )}`, client_uri: "http://127.0.0.1:3000", redirect_uris: ["http://127.0.0.1:3000"], scope: "atproto transition:generic", grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], application_type: "web", token_endpoint_auth_method: "none", dpop_bound_access_tokens: true, }, // loopback client }) : await BrowserOAuthClient.load({ handleResolver: "https://bsky.social", clientId: "https://pin.to.it/client-metadata.json", }); setClient(c); try { const result = await c.init(); if (result?.session) { console.log("session found", result); localStorage.setItem("did", result.session.did); const ag = new Agent(result.session); setSession(result.session); setAgent(ag); const prefs = await ag.getPreferences(); if (!prefs) return; } else { const did = localStorage.getItem("did"); console.log("restoring", did); if (did != null) { const result = await c.restore(did); const ag = new Agent(result); setSession(result); setAgent(ag); } } } catch (err) { console.error("OAuth init failed", err); } finally { setLoading(false); } c.addEventListener("deleted", (event: CustomEvent) => { console.warn("Session invalidated", event.detail); setSession(null); setAgent(defaultAgent); }); }; initClient(); }, []); const login = useCallback( async (handle: string) => { if (!client) return; try { await client.signIn(handle, { scope: "atproto transition:generic", ui_locales: "en", // Only supported by some OAuth servers (requires OpenID Connect support + i18n support) signal: new AbortController().signal, }); } catch (e) { console.warn("Login aborted or failed", e); throw new Error( `Login failed: ${e instanceof Error ? e.message : "Unknown error"}` ); } }, [client] ); const logout = useCallback(() => { if (client && session) { client.revoke(session.sub); setSession(null); setAgent(defaultAgent); // refresh page window.location.reload(); } }, [client, session]); return ( {children} ); } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used inside "); return ctx; }