Monorepo for Tangled tangled.org
3

Configure Feed

Select the types of activity you want to include in your feed.

core / localinfra / pdsls.patch
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 />