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="username" 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="username" 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="username" 78 name="username" 79 autocomplete="username" 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 auth = await finalizeAuthorization(params); 105 const did = auth.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 auth.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 };