pleroma-like client for Bluesky
pl.hexmani.ac
bluesky
pleroma
social-media
1import { Did, isHandle } from "@atcute/lexicons/syntax";
2import {
3 configureOAuth,
4 createAuthorizationUrl,
5 deleteStoredSession,
6 finalizeAuthorization,
7 getSession,
8 OAuthUserAgent,
9 resolveFromIdentity,
10 resolveFromService,
11 Session,
12} from "@atcute/oauth-browser-client";
13import { Component, createSignal } from "solid-js";
14import Container from "./container";
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});
22
23export const [loginState, setLoginState] = createSignal(false);
24let agent: OAuthUserAgent;
25
26const Login: Component = () => {
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 document.querySelector(".submitInfo")!.removeAttribute("hidden");
35 document
36 .querySelector('button[type="submit"]')!
37 .setAttribute("disabled", "");
38 if (!isHandle(handle)) {
39 setNotice(`Resolving your service...`);
40 resolved = await resolveFromService(handle);
41 } else {
42 setNotice(`Resolving your identity...`);
43 resolved = await resolveFromIdentity(handle);
44 }
45
46 setNotice(`Contacting your data server...`);
47 const authUrl = await createAuthorizationUrl({
48 scope: import.meta.env.VITE_OAUTH_SCOPE,
49 ...resolved,
50 });
51
52 setNotice(`Redirecting...`);
53 await new Promise((resolve) => setTimeout(resolve, 500));
54
55 location.assign(authUrl);
56 } catch (e: unknown) {
57 if (e instanceof Error) {
58 console.error(e);
59 setNotice(`${e.message}`);
60 } else {
61 console.error(e);
62 setNotice(`Unknown error, check console ¯\\_(ツ)_/¯`);
63 }
64 } finally {
65 document
66 .querySelector('button[type="submit"]')!
67 .removeAttribute("disabled");
68 }
69 };
70
71 return (
72 <>
73 <Container
74 title="Log in"
75 children={
76 <>
77 <div class="login">
78 <form name="login" id="login" onclick={(e) => e.preventDefault()}>
79 <label for="handle">Handle</label>
80 <br />
81 <input
82 type="text"
83 id="handle"
84 name="handle"
85 maxlength="255"
86 placeholder="soykaf.com"
87 onInput={(e) => setLoginInput(e.currentTarget.value)}
88 required
89 />
90 <br />
91 <button type="submit" onclick={() => login(loginInput())}>
92 Login
93 </button>
94 </form>
95 <p class="submitInfo" hidden>
96 {notice()}
97 </p>
98 </div>
99 </>
100 }
101 />
102 </>
103 );
104};
105
106const retrieveSession = async (): Promise<void> => {
107 const init = async (): Promise<Session | undefined> => {
108 const params = new URLSearchParams(location.hash.slice(1));
109
110 if (params.has("state") && (params.has("code") || params.has("error"))) {
111 history.replaceState(null, "", location.pathname + location.search);
112
113 const session = await finalizeAuthorization(params);
114 console.log("Finalizing authorization...", session);
115 const agent = new OAuthUserAgent(session);
116 console.log(await agent.getSession());
117 const did = session.info.sub;
118
119 localStorage.setItem("currentUser", did);
120 return session;
121 } else {
122 const currentUser = localStorage.getItem("currentUser");
123
124 if (currentUser) {
125 try {
126 console.log("Retrieving session...");
127 return await getSession(currentUser as Did);
128 } catch (err) {
129 deleteStoredSession(currentUser as Did);
130 localStorage.removeItem("currentUser");
131 throw err;
132 }
133 }
134 }
135 };
136
137 const session = await init().catch(() => {});
138
139 if (session) {
140 console.log("Retrieved session!", session);
141 agent = new OAuthUserAgent(session);
142 setLoginState(true);
143 }
144};
145
146const killSession = async (): Promise<void> => {
147 await agent.signOut();
148 setLoginState(false);
149 localStorage.removeItem("currentUser");
150 location.href = "/";
151};
152
153export { agent, killSession, Login, retrieveSession };