bluesky client without react native baggage written in sveltekit
at main 104 lines 2.9 kB view raw
1import { Client, ok, simpleFetchHandler } from '@atcute/client'; 2import { AppBskyActorDefs } from '@atcute/bluesky'; 3import { 4 configureOAuth, 5 createAuthorizationUrl, 6 finalizeAuthorization, 7 getSession, 8 listStoredSessions, 9 OAuthUserAgent, 10 type Session 11} from '@atcute/oauth-browser-client'; 12import { 13 CompositeDidDocumentResolver, 14 LocalActorResolver, 15 PlcDidDocumentResolver, 16 WebDidDocumentResolver, 17 XrpcHandleResolver 18} from '@atcute/identity-resolver'; 19 20configureOAuth({ 21 metadata: { 22 client_id: import.meta.env.VITE_OAUTH_CLIENT_ID, 23 redirect_uri: import.meta.env.VITE_OAUTH_REDIRECT_URI 24 }, 25 identityResolver: new LocalActorResolver({ 26 handleResolver: new XrpcHandleResolver({ 27 serviceUrl: 'https://public.api.bsky.app' 28 }), 29 didDocumentResolver: new CompositeDidDocumentResolver({ 30 methods: { 31 plc: new PlcDidDocumentResolver(), 32 web: new WebDidDocumentResolver() 33 } 34 }) 35 }) 36}); 37 38/** Public (unauthenticated) RPC client — always available */ 39export const publicClient = new Client({ 40 handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' }) 41}); 42 43/** Authenticated RPC client — set after login, null otherwise */ 44export let rpc: Client | null = null; 45 46/** Current OAuth session */ 47let currentSession: Session | null = null; 48 49export async function getClient(): Promise<Client> { 50 if (rpc === null) { 51 await resumeSession(); 52 } 53 return rpc ?? publicClient; 54} 55 56/** Try to resume an existing session from localStorage */ 57export async function resumeSession(): Promise<boolean> { 58 const dids = listStoredSessions(); 59 if (dids.length === 0) return false; 60 61 try { 62 const session = await getSession(dids[0], { allowStale: true }); 63 setSession(session); 64 return true; 65 } catch { 66 return false; 67 } 68} 69 70/** Handle the OAuth callback — call this from the callback route */ 71export async function handleCallback(params: URLSearchParams): Promise<void> { 72 const { session } = await finalizeAuthorization(params); 73 setSession(session); 74} 75 76/** Start the login flow */ 77export async function login(handle: string): Promise<void> { 78 const authUrl = await createAuthorizationUrl({ 79 target: { type: 'account', identifier: handle }, 80 scope: import.meta.env.VITE_OAUTH_SCOPE 81 }); 82 await new Promise((r) => setTimeout(r, 200)); 83 window.location.assign(authUrl); 84} 85 86/** Check if a session is active */ 87export function isLoggedIn(): boolean { 88 return rpc !== null; 89} 90 91/** Fetch the logged-in user's profile */ 92export async function getProfile(): Promise<AppBskyActorDefs.ProfileViewDetailed | null> { 93 if (!rpc || !currentSession) return null; 94 const data = await ok(rpc.get('app.bsky.actor.getProfile', { 95 params: { actor: currentSession.info.sub } 96 })); 97 return data; 98} 99 100function setSession(session: Session): void { 101 currentSession = session; 102 const agent = new OAuthUserAgent(session); 103 rpc = new Client({ handler: agent }); 104}