forked from
pds.ls/pdsls
atproto explorer
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 };