Something went wrong. Try again.
An atproto Tilemap editor
Something went wrong. Try again.
3.5 kB · 99 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100// ---------------------------------------------------------------------------// Authenticated atproto session for the editor.//// Wraps the single OAuth client (src/lib/oauth.ts) in a small zustand store the// UI can subscribe to. The client owns session storage + refresh; this store// just tracks the *current* session for the app: who's signed in (did/handle)// and an authenticated `@atproto/api` Agent for writes (uploadBlob, putRecord).//// init() restore an existing session / finish a popup callback// login(handle) open the PDS login in a popup, then adopt the session// logout() sign the current session out// ---------------------------------------------------------------------------
import { create } from 'zustand'import { Agent } from '@atproto/api'import type { OAuthSession } from '@atproto/oauth-client-browser'import { OAUTH_SCOPE, getOAuthClient } from '@/lib/oauth'
type AuthStatus = 'loading' | 'out' | 'in'
interface AuthState { status: AuthStatus did?: string handle?: string /** authenticated agent for repo writes; present iff status === 'in'. */ agent?: Agent /** the live session backing `agent`, kept for sign-out. */ session?: OAuthSession /** last login/restore error, surfaced by the login UI. */ error?: string
/** Restore a stored session (and process any OAuth popup callback). Idempotent. */ init: () => Promise<void> /** Open the user's PDS login in a popup and adopt the resulting session. */ login: (handle: string) => Promise<void> /** Sign the current session out. */ logout: () => Promise<void>}
/** Adopt an OAuth session: build the agent, resolve the handle for display. */async function adopt(session: OAuthSession): Promise<Partial<AuthState>> { const agent = new Agent(session) let handle: string | undefined try { const res = await agent.com.atproto.repo.describeRepo({ repo: session.did }) handle = res.data.handle } catch { // handle is cosmetic — fall back to the DID if describeRepo is unreachable } return { status: 'in', did: session.did, handle, agent, session, error: undefined }}
// StrictMode mounts effects twice in dev; init() must run its callback handling// only once.let initStarted = false
export const useAuthStore = create<AuthState>((set) => ({ status: 'loading',
init: async () => { if (initStarted) return initStarted = true try { const client = await getOAuthClient() const result = await client.init() if (result?.session) set(await adopt(result.session)) else set({ status: 'out' }) } catch (err) { set({ status: 'out', error: err instanceof Error ? err.message : String(err) }) } },
login: async (handle: string) => { set({ error: undefined }) try { const client = await getOAuthClient() const session = await client.signInPopup(handle.trim(), { scope: OAUTH_SCOPE }) set(await adopt(session)) } catch (err) { // popup closed / blocked / auth declined set({ error: err instanceof Error ? err.message : 'Login failed.' }) throw err } },
logout: async () => { const { session } = useAuthStore.getState() try { await session?.signOut() } finally { set({ status: 'out', did: undefined, handle: undefined, agent: undefined, session: undefined }) } },}))
/** Non-hook accessor for the write path. */export function getAuth(): AuthState { return useAuthStore.getState()}