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