atproto explorer

new auth logic

handle.invalid 895e336f 4e4b7dfc

verified
+73 -28
+50 -23
src/components/account.tsx
··· 1 import { Client, CredentialManager } from "@atcute/client"; 2 import { Did } from "@atcute/lexicons"; 3 - import { deleteStoredSession, getSession, OAuthUserAgent } from "@atcute/oauth-browser-client"; 4 import { A } from "@solidjs/router"; 5 import { createSignal, For, onMount, Show } from "solid-js"; 6 - import { createStore } from "solid-js/store"; 7 import { resolveDidDoc } from "../utils/api.js"; 8 - import { agent, Login, retrieveSession, setAgent } from "./login.jsx"; 9 import { Modal } from "./modal.jsx"; 10 11 const AccountManager = () => { 12 const [openManager, setOpenManager] = createSignal(false); 13 - const [sessions, setSessions] = createStore<Record<string, string | undefined>>(); 14 const [avatars, setAvatars] = createStore<Record<Did, string>>(); 15 16 onMount(async () => { 17 - await retrieveSession(); 18 19 - const storedSessions = localStorage.getItem("atcute-oauth:sessions"); 20 - if (storedSessions) { 21 - const sessionDids = Object.keys(JSON.parse(storedSessions)) as Did[]; 22 - sessionDids.forEach((did) => setSessions(did, "")); 23 sessionDids.forEach(async (did) => { 24 const doc = await resolveDidDoc(did); 25 doc.alsoKnownAs?.forEach((alias) => { 26 if (alias.startsWith("at://")) { 27 - setSessions(did, alias.replace("at://", "")); 28 return; 29 } 30 }); 31 }); 32 sessionDids.forEach(async (did) => { 33 - try { 34 - await getSession(did, { allowStale: true }); 35 - const avatar = await getAvatar(did); 36 - if (avatar) setAvatars(did, avatar); 37 - } catch { 38 - deleteStoredSession(did); 39 - setSessions(did, undefined); 40 - } 41 }); 42 } 43 }); 44 45 const resumeSession = async (did: Did) => { 46 - localStorage.setItem("lastSignedIn", did); 47 - retrieveSession(); 48 }; 49 50 const removeSession = async (did: Did) => { ··· 56 } catch { 57 deleteStoredSession(did); 58 } 59 - setSessions(did, undefined); 60 if (currentSession === did) setAgent(undefined); 61 }; 62 ··· 93 class="size-6 rounded-full" 94 /> 95 </Show> 96 - <span class="truncate">{sessions[did]?.length ? sessions[did] : did}</span> 97 </span> 98 - <Show when={did === agent()?.sub}> 99 <span class="iconify lucide--check shrink-0 text-green-500 dark:text-green-400"></span> 100 </Show> 101 </button> 102 <A
··· 1 import { Client, CredentialManager } from "@atcute/client"; 2 import { Did } from "@atcute/lexicons"; 3 + import { 4 + createAuthorizationUrl, 5 + deleteStoredSession, 6 + getSession, 7 + OAuthUserAgent, 8 + resolveFromIdentity, 9 + } from "@atcute/oauth-browser-client"; 10 import { A } from "@solidjs/router"; 11 import { createSignal, For, onMount, Show } from "solid-js"; 12 + import { createStore, produce } from "solid-js/store"; 13 import { resolveDidDoc } from "../utils/api.js"; 14 + import { agent, Login, retrieveSession, Sessions, setAgent } from "./login.jsx"; 15 import { Modal } from "./modal.jsx"; 16 17 const AccountManager = () => { 18 const [openManager, setOpenManager] = createSignal(false); 19 + const [sessions, setSessions] = createStore<Sessions>(); 20 const [avatars, setAvatars] = createStore<Record<Did, string>>(); 21 22 onMount(async () => { 23 + try { 24 + await retrieveSession(); 25 + } catch {} 26 27 + const localSessions = localStorage.getItem("sessions"); 28 + if (localSessions) { 29 + const storedSessions: Sessions = JSON.parse(localSessions); 30 + const sessionDids = Object.keys(storedSessions) as Did[]; 31 sessionDids.forEach(async (did) => { 32 const doc = await resolveDidDoc(did); 33 doc.alsoKnownAs?.forEach((alias) => { 34 if (alias.startsWith("at://")) { 35 + setSessions(did, { 36 + signedIn: storedSessions[did].signedIn, 37 + handle: alias.replace("at://", ""), 38 + }); 39 return; 40 } 41 }); 42 }); 43 sessionDids.forEach(async (did) => { 44 + const avatar = await getAvatar(did); 45 + if (avatar) setAvatars(did, avatar); 46 }); 47 } 48 }); 49 50 const resumeSession = async (did: Did) => { 51 + try { 52 + localStorage.setItem("lastSignedIn", did); 53 + await retrieveSession(); 54 + } catch { 55 + const resolved = await resolveFromIdentity(did); 56 + const authUrl = await createAuthorizationUrl({ 57 + scope: import.meta.env.VITE_OAUTH_SCOPE, 58 + ...resolved, 59 + }); 60 + 61 + await new Promise((resolve) => setTimeout(resolve, 250)); 62 + 63 + location.assign(authUrl); 64 + } 65 }; 66 67 const removeSession = async (did: Did) => { ··· 73 } catch { 74 deleteStoredSession(did); 75 } 76 + setSessions( 77 + produce((accs) => { 78 + delete accs[did]; 79 + }), 80 + ); 81 + localStorage.setItem("sessions", JSON.stringify(sessions)); 82 if (currentSession === did) setAgent(undefined); 83 }; 84 ··· 115 class="size-6 rounded-full" 116 /> 117 </Show> 118 + <span class="truncate"> 119 + {sessions[did]?.handle ? sessions[did].handle : did} 120 + </span> 121 </span> 122 + <Show when={did === agent()?.sub && sessions[did].signedIn}> 123 <span class="iconify lucide--check shrink-0 text-green-500 dark:text-green-400"></span> 124 + </Show> 125 + <Show when={!sessions[did].signedIn}> 126 + <span class="iconify lucide--circle-alert shrink-0 text-red-500 dark:text-red-400"></span> 127 </Show> 128 </button> 129 <A
+23 -5
src/components/login.tsx
··· 1 import { Did } from "@atcute/lexicons"; 2 import { isHandle } from "@atcute/lexicons/syntax"; 3 import { 4 configureOAuth, 5 createAuthorizationUrl, 6 - deleteStoredSession, 7 finalizeAuthorization, 8 getSession, 9 OAuthUserAgent, ··· 21 }); 22 23 export const [agent, setAgent] = createSignal<OAuthUserAgent | undefined>(); 24 25 const Login = () => { 26 const [notice, setNotice] = createSignal(""); ··· 100 const did = session.info.sub; 101 102 localStorage.setItem("lastSignedIn", did); 103 return session; 104 } else { 105 const lastSignedIn = localStorage.getItem("lastSignedIn"); 106 107 if (lastSignedIn) { 108 try { 109 - return await getSession(lastSignedIn as Did); 110 } catch (err) { 111 - deleteStoredSession(lastSignedIn as Did); 112 - localStorage.removeItem("lastSignedIn"); 113 throw err; 114 } 115 } 116 } 117 }; 118 119 - const session = await init().catch(() => {}); 120 121 if (session) setAgent(new OAuthUserAgent(session)); 122 };
··· 1 + import { Client } from "@atcute/client"; 2 import { Did } from "@atcute/lexicons"; 3 import { isHandle } from "@atcute/lexicons/syntax"; 4 import { 5 configureOAuth, 6 createAuthorizationUrl, 7 finalizeAuthorization, 8 getSession, 9 OAuthUserAgent, ··· 21 }); 22 23 export const [agent, setAgent] = createSignal<OAuthUserAgent | undefined>(); 24 + 25 + type Account = { 26 + signedIn: boolean; 27 + handle?: string; 28 + }; 29 + 30 + export type Sessions = Record<string, Account>; 31 32 const Login = () => { 33 const [notice, setNotice] = createSignal(""); ··· 107 const did = session.info.sub; 108 109 localStorage.setItem("lastSignedIn", did); 110 + 111 + const sessions = localStorage.getItem("sessions"); 112 + const newSessions: Sessions = sessions ? JSON.parse(sessions) : { [did]: {} }; 113 + newSessions[did] = { signedIn: true }; 114 + localStorage.setItem("sessions", JSON.stringify(newSessions)); 115 return session; 116 } else { 117 const lastSignedIn = localStorage.getItem("lastSignedIn"); 118 119 if (lastSignedIn) { 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 + if (!res.ok) throw res.data.error; 125 + return session; 126 } catch (err) { 127 + const sessions = localStorage.getItem("sessions"); 128 + const newSessions: Sessions = sessions ? JSON.parse(sessions) : {}; 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 };