1import { createSignal } from "solid-js";
2import {
3 configureOAuth,
4 createAuthorizationUrl,
5 deleteStoredSession,
6 finalizeAuthorization,
7 getSession,
8 OAuthUserAgent,
9 resolveFromIdentity,
10 resolveFromService,
11 type Session,
12} from "@atcute/oauth-browser-client";
13import { Did } from "@atcute/lexicons";
14import { isHandle } from "@atcute/lexicons/syntax";
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-1" onsubmit={(e) => e.preventDefault()}>
60 <div class="flex items-center gap-2">
61 <label for="handle">
62 <div class="i-lucide-user-round-plus text-lg" />
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 onclick={() => login(loginInput())}>
71 <div class="i-lucide-log-in text-lg" />
72 </button>
73 </div>
74 <div>{notice()}</div>
75 </form>
76 );
77};
78
79const retrieveSession = async () => {
80 const init = async (): Promise<Session | undefined> => {
81 const params = new URLSearchParams(location.hash.slice(1));
82
83 if (params.has("state") && (params.has("code") || params.has("error"))) {
84 history.replaceState(null, "", location.pathname + location.search);
85
86 const session = await finalizeAuthorization(params);
87 const did = session.info.sub;
88
89 localStorage.setItem("lastSignedIn", did);
90 return session;
91 } else {
92 const lastSignedIn = localStorage.getItem("lastSignedIn");
93
94 if (lastSignedIn) {
95 try {
96 return await getSession(lastSignedIn as Did);
97 } catch (err) {
98 deleteStoredSession(lastSignedIn as Did);
99 localStorage.removeItem("lastSignedIn");
100 throw err;
101 }
102 }
103 }
104 };
105
106 const session = await init().catch(() => {});
107
108 if (session) setAgent(new OAuthUserAgent(session));
109};
110
111export { Login, retrieveSession };