atproto explorer pdsls.dev
atproto tool
1import { Client } from "@atcute/client"; 2import { Did } from "@atcute/lexicons"; 3import { isHandle } from "@atcute/lexicons/syntax"; 4import { 5 configureOAuth, 6 createAuthorizationUrl, 7 finalizeAuthorization, 8 getSession, 9 OAuthUserAgent, 10 resolveFromIdentity, 11 resolveFromService, 12 type Session, 13} from "@atcute/oauth-browser-client"; 14import { createSignal, Show } from "solid-js"; 15 16configureOAuth({ 17 metadata: { 18 client_id: import.meta.env.VITE_OAUTH_CLIENT_ID, 19 redirect_uri: import.meta.env.VITE_OAUTH_REDIRECT_URL, 20 }, 21}); 22 23export const [agent, setAgent] = createSignal<OAuthUserAgent | undefined>(); 24 25type Account = { 26 signedIn: boolean; 27 handle?: string; 28}; 29 30export type Sessions = Record<string, Account>; 31 32const Login = () => { 33 const [notice, setNotice] = createSignal(""); 34 const [loginInput, setLoginInput] = createSignal(""); 35 36 const login = async (handle: string) => { 37 try { 38 setNotice(""); 39 if (!handle) return; 40 let resolved; 41 if (!isHandle(handle)) { 42 setNotice(`Resolving your service...`); 43 resolved = await resolveFromService(handle); 44 } else { 45 setNotice(`Resolving your identity...`); 46 resolved = await resolveFromIdentity(handle); 47 } 48 49 setNotice(`Contacting your data server...`); 50 const authUrl = await createAuthorizationUrl({ 51 scope: import.meta.env.VITE_OAUTH_SCOPE, 52 ...resolved, 53 }); 54 55 setNotice(`Redirecting...`); 56 await new Promise((resolve) => setTimeout(resolve, 250)); 57 58 location.assign(authUrl); 59 } catch (e) { 60 console.error(e); 61 setNotice(`${e}`); 62 } 63 }; 64 65 return ( 66 <form class="flex flex-col gap-y-2 px-1" onsubmit={(e) => e.preventDefault()}> 67 <label for="handle" class="hidden"> 68 Add account 69 </label> 70 <div class="dark:bg-dark-100 dark:shadow-dark-700 flex grow items-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 shadow-xs focus-within:outline-[1px] focus-within:outline-neutral-600 dark:border-neutral-600 dark:focus-within:outline-neutral-400"> 71 <label 72 for="handle" 73 class="iconify lucide--user-round-plus shrink-0 text-neutral-500 dark:text-neutral-400" 74 ></label> 75 <input 76 type="text" 77 spellcheck={false} 78 placeholder="user.bsky.social" 79 id="handle" 80 class="grow py-1 select-none placeholder:text-sm focus:outline-none" 81 onInput={(e) => setLoginInput(e.currentTarget.value)} 82 /> 83 <button 84 onclick={() => login(loginInput())} 85 class="flex items-center rounded-lg p-1 hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-600 dark:active:bg-neutral-500" 86 > 87 <span class="iconify lucide--log-in"></span> 88 </button> 89 </div> 90 <Show when={notice()}> 91 <div class="text-sm">{notice()}</div> 92 </Show> 93 </form> 94 ); 95}; 96 97const retrieveSession = async () => { 98 const init = async (): Promise<Session | undefined> => { 99 const params = new URLSearchParams(location.hash.slice(1)); 100 101 if (params.has("state") && (params.has("code") || params.has("error"))) { 102 history.replaceState(null, "", location.pathname + location.search); 103 104 const session = await finalizeAuthorization(params); 105 const did = session.info.sub; 106 107 localStorage.setItem("lastSignedIn", did); 108 109 const sessions = localStorage.getItem("sessions"); 110 const newSessions: Sessions = sessions ? JSON.parse(sessions) : { [did]: {} }; 111 newSessions[did] = { signedIn: true }; 112 localStorage.setItem("sessions", JSON.stringify(newSessions)); 113 return session; 114 } else { 115 const lastSignedIn = localStorage.getItem("lastSignedIn"); 116 117 if (lastSignedIn) { 118 const sessions = localStorage.getItem("sessions"); 119 const newSessions: Sessions = sessions ? JSON.parse(sessions) : {}; 120 try { 121 const session = await getSession(lastSignedIn as Did); 122 const rpc = new Client({ handler: new OAuthUserAgent(session) }); 123 const res = await rpc.get("com.atproto.server.getSession"); 124 newSessions[lastSignedIn].signedIn = true; 125 localStorage.setItem("sessions", JSON.stringify(newSessions)); 126 if (!res.ok) throw res.data.error; 127 return session; 128 } catch (err) { 129 newSessions[lastSignedIn].signedIn = false; 130 localStorage.setItem("sessions", JSON.stringify(newSessions)); 131 throw err; 132 } 133 } 134 } 135 }; 136 137 const session = await init(); 138 139 if (session) setAgent(new OAuthUserAgent(session)); 140}; 141 142export { Login, retrieveSession };