From 3225b4e652915094588b03d1db1a6d50d7936bbb Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 11 May 2026 14:36:12 +0200 Subject: [PATCH] (feat): add custom ttp urls (qol): update todo --- .../web/src/components/screens/login/form.tsx | 59 +++++++++++++++---- apps/web/todo.md | 1 + packages/shared/src/data.ts | 2 + packages/ttp/src/context.tsx | 40 ++++++++++--- packages/ttp/src/values.ts | 1 - 5 files changed, 82 insertions(+), 21 deletions(-) diff --git a/apps/web/src/components/screens/login/form.tsx b/apps/web/src/components/screens/login/form.tsx index da5726c..0f297d6 100644 --- a/apps/web/src/components/screens/login/form.tsx +++ b/apps/web/src/components/screens/login/form.tsx @@ -23,7 +23,7 @@ const fetchedUser = z.object({ }); const formSchema = z.object({ - username: z.string().min(1).max(15), + username: z.string().min(1).max(255), private_key: z.string().min(1).max(92), }); @@ -35,6 +35,7 @@ const formSchema = z.object({ function parseTuFileContent(rawFileContent: string): { userId: number; privateKey: string; + domain: string | null; } { if (rawFileContent.trim().length === 0) { throw new Error("File is empty"); @@ -42,21 +43,40 @@ function parseTuFileContent(rawFileContent: string): { throw new Error("Invalid file"); } else if (rawFileContent.split("::").length !== 2) { throw new Error("Invalid file"); - } else if (rawFileContent.split("::")[0].length === 0) { + } + + const left = rawFileContent.split("::")[0]; + const right = rawFileContent.split("::")[1]; + + if (left.length === 0) { throw new Error("Invalid file"); - } else if (rawFileContent.split("::")[1].length === 0) { + } else if (right.length === 0) { throw new Error("Invalid file"); - } else if (isNaN(Number(rawFileContent.split("::")[0]))) { + } else if (isNaN(Number(left)) && !left.includes("@")) { throw new Error("Invalid file"); } const [userIdString, privateKey] = rawFileContent.split("::"); - const userId = Number(userIdString); + const userId = isNaN(Number(userIdString)) + ? Number(userIdString.split("@")[0]) + : Number(userIdString); + + const rawDomain = userIdString.includes("@") + ? userIdString.split("@")[1] + : null; + const domain = rawDomain ? (rawDomain.includes(":") ? rawDomain : rawDomain + ":1984") : null; + if (!userId || !privateKey) { throw new Error("Invalid file"); } - return { userId, privateKey }; + console.log({ + domain, + rawDomain, + userId, + }); + + return { userId, privateKey, domain }; } /** @@ -86,6 +106,9 @@ export default function Form() { await save("session_id", Date.now()); await save("user_id", parsed.userId); await save("private_key", parsed.privateKey); + if (parsed.domain) { + await save("ttp_url", `https://${parsed.domain}/`); + } location.href = "/"; } catch (error) { @@ -114,9 +137,14 @@ export default function Form() { return; } + const inputUsername = inputParse.data.username; + const actualUsername = inputUsername.includes("@") ? inputUsername.split("@")[0] : inputUsername; + const rawDomain = inputUsername.includes("@") ? inputUsername.split("@")[1] : null; + const domain = rawDomain ? (rawDomain.includes(":") ? rawDomain : rawDomain + ":1984") : null; + try { const response = await fetch( - `https://omega.tensamin.net/api/get/id/${inputParse.data.username}`, + `https://omega.tensamin.net/api/get/id/${actualUsername}`, ); const rawData = await response.arrayBuffer(); @@ -137,6 +165,9 @@ export default function Form() { await save("session_id", Date.now()); await save("user_id", user.user_id); await save("private_key", inputParse.data.private_key); + if (domain) { + await save("ttp_url", `https://${domain}/`); + } location.href = "/"; } catch (error) { @@ -154,7 +185,7 @@ export default function Form() { {isTauriEnv ? ( <> { + onData={async (data) => { if (!data.startsWith("tensamin://tu::")) { toast("error", "Invalid QR code"); return; @@ -162,10 +193,14 @@ export default function Form() { const decoded = data.replace("tensamin://tu::", ""); try { - const { userId, privateKey } = parseTuFileContent(decoded); - save("session_id", Date.now()); - save("user_id", userId); - save("private_key", privateKey); + const { userId, privateKey, domain } = + parseTuFileContent(decoded); + await save("session_id", Date.now()); + await save("user_id", userId); + await save("private_key", privateKey); + if (domain) { + await save("ttp_url", `https://${domain}/`); + } location.href = "/"; } catch (error) { log(0, "login", "red", error); diff --git a/apps/web/todo.md b/apps/web/todo.md index 790b489..dd80800 100644 --- a/apps/web/todo.md +++ b/apps/web/todo.md @@ -1 +1,2 @@ - Make the sidebar use to make it feel less slow on mobile +- Add logout button diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts index a059e9c..e03ca3d 100644 --- a/packages/shared/src/data.ts +++ b/packages/shared/src/data.ts @@ -244,6 +244,7 @@ export interface Storage extends SettingsStorageDefaults { legal_docs: z.infer; cached_contacts: Contacts; cached_communities: Communities; + ttp_url: string; } export const storageDefaults: Storage = { @@ -276,4 +277,5 @@ export const storageDefaults: Storage = { }, cached_contacts: [], cached_communities: [], + ttp_url: "https://tensamin.net:959", }; diff --git a/packages/ttp/src/context.tsx b/packages/ttp/src/context.tsx index f1c30aa..0a2ea2c 100644 --- a/packages/ttp/src/context.tsx +++ b/packages/ttp/src/context.tsx @@ -24,7 +24,6 @@ import { RECONNECT_RESET, RECONNECT_TRIES, RETRY_INTERVAL, - TRANSPORT_URL, } from "./values"; import { type Calls, @@ -193,6 +192,14 @@ export function Provider(props: { > | null>(null); const identificationStartedRef = useRef(false); + // Load ttp url + const [ttpUrl, setTtpUrl] = useState(null); + useEffect(() => { + load("ttp_url").then((url) => { + setTtpUrl(url); + }); + }, [load]); + /** * Sends typed protocol messages through the active transport client. * @param type Protocol message type. @@ -266,6 +273,10 @@ export function Provider(props: { }, [connected, identified, send]); useEffect(() => { + if (!ttpUrl) { + return; + } + let attempts = 0; let reconnectTimer: ReturnType | null = null; let reconnectResetTimer: ReturnType | null = null; @@ -343,7 +354,7 @@ export function Provider(props: { const transportClient = !props.blockConnection ? createTransportClient(schemas, { - url: TRANSPORT_URL, + url: ttpUrl, onReadyStateChange: (state) => { currentReadyState = state; setReadyState(state); @@ -392,8 +403,12 @@ export function Provider(props: { return; } + if (!ttpUrl) { + return; + } + try { - await transportClient?.connect(TRANSPORT_URL); + await transportClient?.connect(ttpUrl); } catch (connectError) { if (disposed) { return; @@ -460,7 +475,7 @@ export function Provider(props: { setIdentifying(false); identificationStartedRef.current = false; }; - }, [props.blockConnection]); + }, [props.blockConnection, ttpUrl]); useEffect(() => { if (!connected) { @@ -603,14 +618,19 @@ export function Provider(props: { }, [connected, decrypt, getSharedSecret, load, send]); const progress = useMemo(() => { + if (!ttpUrl) return 10; if (readyState === READY_STATE.CONNECTING) return 30; if (!connected) return 45; if (identifying) return 75; if (!identified) return 90; return 100; - }, [connected, identified, identifying, readyState]); + }, [connected, identified, identifying, readyState, ttpUrl]); const loadingTitle = useMemo(() => { + if (!ttpUrl) { + return "Looking up configuration"; + } + if (readyState === READY_STATE.CONNECTING || !connected) { return "Connecting to Tensamin"; } @@ -620,9 +640,13 @@ export function Provider(props: { } return "Loading"; - }, [connected, identified, identifying, readyState]); + }, [connected, identified, identifying, readyState, ttpUrl]); const loadingDescription = useMemo(() => { + if (!ttpUrl) { + return "Loading connection details"; + } + if (readyState === READY_STATE.CONNECTING || !connected) { return "Establishing transport channel"; } @@ -632,13 +656,13 @@ export function Provider(props: { } return undefined; - }, [connected, identified, identifying, readyState]); + }, [connected, identified, identifying, readyState, ttpUrl]); if (error !== "" && errorDescription !== "") { return ; } - if (!connected || !identified) { + if (!connected || !identified || !ttpUrl) { return (