atproto explorer
at main 114 lines 3.3 kB view raw
1import { Did } from "@atcute/lexicons"; 2import { isHandle } from "@atcute/lexicons/syntax"; 3import { 4 configureOAuth, 5 createAuthorizationUrl, 6 deleteStoredSession, 7 finalizeAuthorization, 8 getSession, 9 OAuthUserAgent, 10 resolveFromIdentity, 11 resolveFromService, 12 type Session, 13} from "@atcute/oauth-browser-client"; 14import { createSignal } from "solid-js"; 15import { TextInput } from "./text-input"; 16 17configureOAuth({ 18 metadata: { 19 client_id: import.meta.env.VITE_OAUTH_CLIENT_ID, 20 redirect_uri: import.meta.env.VITE_OAUTH_REDIRECT_URL, 21 }, 22}); 23 24export const [agent, setAgent] = createSignal<OAuthUserAgent | undefined>(); 25 26const Login = () => { 27 const [notice, setNotice] = createSignal(""); 28 const [loginInput, setLoginInput] = createSignal(""); 29 30 const login = async (handle: string) => { 31 try { 32 if (!handle) return; 33 let resolved; 34 if (!isHandle(handle)) { 35 setNotice(`Resolving your service...`); 36 resolved = await resolveFromService(handle); 37 } else { 38 setNotice(`Resolving your identity...`); 39 resolved = await resolveFromIdentity(handle); 40 } 41 42 setNotice(`Contacting your data server...`); 43 const authUrl = await createAuthorizationUrl({ 44 scope: import.meta.env.VITE_OAUTH_SCOPE, 45 ...resolved, 46 }); 47 48 setNotice(`Redirecting...`); 49 await new Promise((resolve) => setTimeout(resolve, 250)); 50 51 location.assign(authUrl); 52 } catch (e) { 53 console.error(e); 54 setNotice(`${e}`); 55 } 56 }; 57 58 return ( 59 <form class="flex flex-col gap-y-2" onsubmit={(e) => e.preventDefault()}> 60 <div class="flex items-center gap-1"> 61 <label for="handle" class="mr-1 flex items-center"> 62 <span class="iconify lucide--user-round-plus text-lg"></span> 63 </label> 64 <TextInput 65 id="handle" 66 placeholder="user.bsky.social" 67 onInput={(e) => setLoginInput(e.currentTarget.value)} 68 class="grow" 69 /> 70 <button 71 onclick={() => login(loginInput())} 72 class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 73 > 74 <span class="iconify lucide--log-in text-lg"></span> 75 </button> 76 </div> 77 <div>{notice()}</div> 78 </form> 79 ); 80}; 81 82const retrieveSession = async () => { 83 const init = async (): Promise<Session | undefined> => { 84 const params = new URLSearchParams(location.hash.slice(1)); 85 86 if (params.has("state") && (params.has("code") || params.has("error"))) { 87 history.replaceState(null, "", location.pathname + location.search); 88 89 const session = await finalizeAuthorization(params); 90 const did = session.info.sub; 91 92 localStorage.setItem("lastSignedIn", did); 93 return session; 94 } else { 95 const lastSignedIn = localStorage.getItem("lastSignedIn"); 96 97 if (lastSignedIn) { 98 try { 99 return await getSession(lastSignedIn as Did); 100 } catch (err) { 101 deleteStoredSession(lastSignedIn as Did); 102 localStorage.removeItem("lastSignedIn"); 103 throw err; 104 } 105 } 106 } 107 }; 108 109 const session = await init().catch(() => {}); 110 111 if (session) setAgent(new OAuthUserAgent(session)); 112}; 113 114export { Login, retrieveSession };