atproto explorer pdsls.dev
atproto tool
at v1.1.3 4.9 kB view raw
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:inset-shadow-dark-200 flex grow items-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 inset-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 aria-label="Your AT Protocol handle" 81 class="grow py-1 select-none placeholder:text-sm focus:outline-none" 82 onInput={(e) => setLoginInput(e.currentTarget.value)} 83 /> 84 <button 85 onclick={() => login(loginInput())} 86 class="flex items-center rounded-md p-1 hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-600 dark:active:bg-neutral-500" 87 > 88 <span class="iconify lucide--log-in"></span> 89 </button> 90 </div> 91 <Show when={notice()}> 92 <div class="text-sm">{notice()}</div> 93 </Show> 94 </form> 95 ); 96}; 97 98const retrieveSession = async () => { 99 const init = async (): Promise<Session | undefined> => { 100 const params = new URLSearchParams(location.hash.slice(1)); 101 102 if (params.has("state") && (params.has("code") || params.has("error"))) { 103 history.replaceState(null, "", location.pathname + location.search); 104 105 const auth = await finalizeAuthorization(params); 106 const did = auth.session.info.sub; 107 108 localStorage.setItem("lastSignedIn", did); 109 110 const sessions = localStorage.getItem("sessions"); 111 const newSessions: Sessions = sessions ? JSON.parse(sessions) : { [did]: {} }; 112 newSessions[did] = { signedIn: true }; 113 localStorage.setItem("sessions", JSON.stringify(newSessions)); 114 return auth.session; 115 } else { 116 const lastSignedIn = localStorage.getItem("lastSignedIn"); 117 118 if (lastSignedIn) { 119 const sessions = localStorage.getItem("sessions"); 120 const newSessions: Sessions = sessions ? JSON.parse(sessions) : {}; 121 try { 122 const session = await getSession(lastSignedIn as Did); 123 const rpc = new Client({ handler: new OAuthUserAgent(session) }); 124 const res = await rpc.get("com.atproto.server.getSession"); 125 newSessions[lastSignedIn].signedIn = true; 126 localStorage.setItem("sessions", JSON.stringify(newSessions)); 127 if (!res.ok) throw res.data.error; 128 return session; 129 } catch (err) { 130 newSessions[lastSignedIn].signedIn = false; 131 localStorage.setItem("sessions", JSON.stringify(newSessions)); 132 throw err; 133 } 134 } 135 } 136 }; 137 138 const session = await init(); 139 140 if (session) setAgent(new OAuthUserAgent(session)); 141}; 142 143export { Login, retrieveSession };