forked from
tangled.org/core
Monorepo for Tangled
8.7 kB
219 lines
1diff --git a/bun.lock b/bun.lock
2index 12bc3ba..3d6395a 100644
3--- a/bun.lock
4+++ b/bun.lock
5@@ -37,14 +37,14 @@
6 "@takumi-rs/wasm": "^1.8.7",
7 "@tanstack/solid-virtual": "^3.13.31",
8 "codemirror": "^6.0.2",
9- "solid-js": "^1.9.13",
10+ "solid-js": "^1.9.14",
11 "valibot": "^1.4.2",
12 },
13 "devDependencies": {
14 "@iconify-json/lucide": "^1.2.115",
15 "@iconify/tailwind4": "^1.2.3",
16 "@tailwindcss/vite": "^4.3.2",
17- "@types/node": "^26.0.1",
18+ "@types/node": "^26.1.0",
19 "esbuild": "^0.28.1",
20 "oxfmt": "^0.57.0",
21 "tailwindcss": "^4.3.2",
22diff --git a/src/auth/account.tsx b/src/auth/account.tsx
23index e660c1a..7ae64b1 100644
24--- a/src/auth/account.tsx
25+++ b/src/auth/account.tsx
26@@ -43,6 +43,7 @@ const AccountDropdown = (props: { did: Did; onEditPermissions: (did: Did) => voi
27 } catch {
28 deleteStoredSession(did);
29 }
30+ localStorage.removeItem(`pass_session:${did}`);
31 setSessions(
32 produce((accs) => {
33 delete accs[did];
34diff --git a/src/auth/session-manager.ts b/src/auth/session-manager.ts
35index 6ac8570..bca6de4 100644
36--- a/src/auth/session-manager.ts
37+++ b/src/auth/session-manager.ts
38@@ -42,8 +42,79 @@ export const loadHandleForSession = async (did: Did, storedSessions: Sessions) =
39 }
40 };
41
42+export class PasswordUserAgent {
43+ sub: Did;
44+ service: string;
45+ accessJwt: string;
46+ constructor(sub: Did, service: string, accessJwt: string) {
47+ this.sub = sub;
48+ this.service = service;
49+ this.accessJwt = accessJwt;
50+ }
51+ async signOut() {}
52+ async handle(pathname: string, init?: RequestInit): Promise<Response> {
53+ const url = new URL(pathname, this.service);
54+ const headers = new Headers(init?.headers);
55+ headers.set("Authorization", `Bearer ${this.accessJwt}`);
56+ return await fetch(url.href, { ...init, headers });
57+ }
58+}
59+
60+export const autoLoginLocalinfra = async (): Promise<void> => {
61+ try {
62+ const pdsUrl = "https://pds.tngl.boltless.dev";
63+
64+ const healthRes = await fetch(`${pdsUrl}/xrpc/_health`, { signal: AbortSignal.timeout(2000) });
65+ if (!healthRes.ok) return;
66+
67+ const resolveAndLogin = async (username: string) => {
68+ const handle = `${username}.pds.tngl.boltless.dev`;
69+ const resolveRes = await fetch(`${pdsUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`);
70+ if (!resolveRes.ok) return null;
71+ const { did } = await resolveRes.json();
72+
73+ const loginRes = await fetch(`${pdsUrl}/xrpc/com.atproto.server.createSession`, {
74+ method: "POST",
75+ headers: { "Content-Type": "application/json" },
76+ body: JSON.stringify({ identifier: handle, password: "password" }),
77+ });
78+ if (!loginRes.ok) return null;
79+ const { accessJwt } = await loginRes.json();
80+ return { did, handle, accessJwt };
81+ };
82+
83+ const alice = await resolveAndLogin("alice");
84+ const bob = await resolveAndLogin("bob");
85+
86+ const sessions = loadSessionsFromStorage() || {};
87+ let lastSignedIn = localStorage.getItem("lastSignedIn");
88+
89+ if (alice) {
90+ sessions[alice.did] = { signedIn: true, handle: alice.handle, grantedScopes: "atproto,create,update,delete,blob" };
91+ localStorage.setItem(`pass_session:${alice.did}`, JSON.stringify({ service: pdsUrl, accessJwt: alice.accessJwt }));
92+ if (!lastSignedIn) lastSignedIn = alice.did;
93+ }
94+ if (bob) {
95+ sessions[bob.did] = { signedIn: true, handle: bob.handle, grantedScopes: "atproto,create,update,delete,blob" };
96+ localStorage.setItem(`pass_session:${bob.did}`, JSON.stringify({ service: pdsUrl, accessJwt: bob.accessJwt }));
97+ if (!lastSignedIn) lastSignedIn = lastSignedIn || bob.did;
98+ }
99+
100+ if (alice || bob) {
101+ saveSessionToStorage(sessions);
102+ if (lastSignedIn) {
103+ localStorage.setItem("lastSignedIn", lastSignedIn);
104+ }
105+ }
106+ } catch (e) {
107+ console.error("Auto login failed:", e);
108+ }
109+};
110+
111 export const retrieveSession = async (): Promise<void> => {
112- const init = async (): Promise<Session | undefined> => {
113+ await autoLoginLocalinfra();
114+
115+ const init = async (): Promise<any> => {
116 const params = new URLSearchParams(decodeURIComponent(location.hash.slice(1)));
117
118 if (params.has("state") && (params.has("code") || params.has("error"))) {
119@@ -61,21 +133,34 @@ export const retrieveSession = async (): Promise<void> => {
120 const newSessions: Sessions = sessions || {};
121 newSessions[did] = { signedIn: true, grantedScopes };
122 saveSessionToStorage(newSessions);
123- return auth.session;
124+ return new OAuthUserAgent(auth.session);
125 } else {
126 const lastSignedIn = localStorage.getItem("lastSignedIn");
127
128 if (lastSignedIn) {
129 const sessions = loadSessionsFromStorage();
130 const newSessions: Sessions = sessions || {};
131+
132+ const passSession = localStorage.getItem(`pass_session:${lastSignedIn}`);
133+ if (passSession) {
134+ try {
135+ const { service, accessJwt } = JSON.parse(passSession);
136+ const agent = new PasswordUserAgent(lastSignedIn as Did, service, accessJwt);
137+ newSessions[lastSignedIn].signedIn = true;
138+ saveSessionToStorage(newSessions);
139+ return agent;
140+ } catch {}
141+ }
142+
143 try {
144 const session = await getSession(lastSignedIn as Did);
145- const rpc = new Client({ handler: new OAuthUserAgent(session) });
146+ const agent = new OAuthUserAgent(session);
147+ const rpc = new Client({ handler: agent });
148 const res = await rpc.get("com.atproto.server.getSession");
149 newSessions[lastSignedIn].signedIn = true;
150 saveSessionToStorage(newSessions);
151 if (!res.ok) throw res.data.error;
152- return session;
153+ return agent;
154 } catch (err) {
155 newSessions[lastSignedIn].signedIn = false;
156 saveSessionToStorage(newSessions);
157@@ -85,9 +170,9 @@ export const retrieveSession = async (): Promise<void> => {
158 }
159 };
160
161- const session = await init();
162+ const agentInstance = await init();
163
164- if (session) setAgent(new OAuthUserAgent(session));
165+ if (agentInstance) setAgent(agentInstance);
166 };
167
168 export const resumeSession = async (did: Did): Promise<void> => {
169diff --git a/src/layout.tsx b/src/layout.tsx
170index 51757fa..b76e556 100644
171--- a/src/layout.tsx
172+++ b/src/layout.tsx
173@@ -195,7 +195,7 @@ const Layout = (props: RouteSectionProps<unknown>) => {
174 </div>
175 <NotificationContainer />
176 <PermissionPromptContainer />
177- <Show when={plcDirectory() !== "https://plc.directory"}>
178+ <Show when={plcDirectory() !== "https://plc.tngl.boltless.dev"}>
179 <div class="dark:bg-dark-500 fixed right-0 bottom-0 left-0 z-10 flex items-center justify-center bg-neutral-100 px-3 py-1 text-xs">
180 <span>
181 PLC directory: <span class="font-medium">{plcDirectory()}</span>
182diff --git a/src/views/settings.tsx b/src/views/settings.tsx
183index 5f16783..7621cf8 100644
184--- a/src/views/settings.tsx
185+++ b/src/views/settings.tsx
186@@ -5,7 +5,7 @@ import { ThemeSelection } from "../components/theme.jsx";
187
188 export const [hideMedia, setHideMedia] = createSignal(localStorage.hideMedia === "true");
189 export const [plcDirectory, setPlcDirectory] = createSignal(
190- localStorage.plcDirectory || "https://plc.directory",
191+ localStorage.plcDirectory || "https://plc.tngl.boltless.dev",
192 );
193
194 const Settings = () => {
195@@ -18,13 +18,13 @@ const Settings = () => {
196 <label for="plcDirectory" class="font-medium select-none">
197 PLC Directory
198 </label>
199- {plcDirectory() !== "https://plc.directory" && (
200+ {plcDirectory() !== "https://plc.tngl.boltless.dev" && (
201 <button
202 type="button"
203 class="rounded px-2 py-1 text-xs text-neutral-500 hover:bg-neutral-200 hover:text-neutral-700 active:bg-neutral-300 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-200 dark:active:bg-neutral-600"
204 onClick={() => {
205 localStorage.removeItem("plcDirectory");
206- setPlcDirectory("https://plc.directory");
207+ setPlcDirectory("https://plc.tngl.boltless.dev");
208 }}
209 >
210 Reset
211@@ -41,7 +41,7 @@ const Settings = () => {
212 setPlcDirectory(value);
213 } else {
214 localStorage.removeItem("plcDirectory");
215- setPlcDirectory("https://plc.directory");
216+ setPlcDirectory("https://plc.tngl.boltless.dev");
217 }
218 }}
219 />