From ee5955fc753648198df62ef8fd0e4b71498be91c Mon Sep 17 00:00:00 2001 From: Alois Date: Fri, 3 Jul 2026 10:56:42 +0200 Subject: [PATCH 1/2] (wip): migrate ttp to mtp (feat): migrate tauth to mtp (qol): update todo (qol): add direnv --- .envrc | 1 + .gitignore | 3 +- bun.lock | 4 +-- flake.nix | 3 +- package.json | 2 +- packages/mtp/src/context.tsx | 63 ++++++++++++++++++++-------------- packages/tauth/src/context.tsx | 58 +++++++++++++++++++++++++++---- todo.md | 1 + 8 files changed, 97 insertions(+), 38 deletions(-) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 87d0fc3..c0e6a9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules releases -.fallow/ +.fallow +.direnv diff --git a/bun.lock b/bun.lock index 0af5854..376d438 100644 --- a/bun.lock +++ b/bun.lock @@ -347,7 +347,7 @@ }, "overrides": { "@tensamin/ui": "https://git.methanium.net/tensamin/ui/releases/download/latest/tensamin-ui.tgz", - "mtp": "https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-8ae8377/mtp-0.1.0.tgz", + "mtp": "https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-c3fbeb2/mtp-0.1.0.tgz", }, "packages": { "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], @@ -1540,7 +1540,7 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "mtp": ["mtp@https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-8ae8377/mtp-0.1.0.tgz", {}, "sha512-W0l7Jc+1XI8BdrmRP9rlY/di6kZ5a/srv4mQvzwyJd+Tfg7BWZ9IUYUXIPze6ayx9dLY1f9Y71OgbhjstMzYjA=="], + "mtp": ["mtp@https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-c3fbeb2/mtp-0.1.0.tgz", {}, "sha512-j/RGQY9/3NXzZptXbY44Kv7YnGr38CQQOehRAzPPFvnEVj+kxCwzVLgSImT765+vfgl2L+bkAE9aI2PWwOBTow=="], "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], diff --git a/flake.nix b/flake.nix index 8cd7777..55990bb 100644 --- a/flake.nix +++ b/flake.nix @@ -206,7 +206,7 @@ ]; }; - commonDeps = [pkgs.wasm-pack pkgs.lld]; + commonDeps = [rustToolchain pkgs.wasm-pack pkgs.lld]; in rec { default = electron; @@ -256,7 +256,6 @@ buildInputs = with pkgs; [ jdk17 - rustToolchain gradle bun nodejs diff --git a/package.json b/package.json index c673b1a..4b90957 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "overrides": { "@tensamin/ui": "https://git.methanium.net/tensamin/ui/releases/download/latest/tensamin-ui.tgz", - "mtp": "https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-8ae8377/mtp-0.1.0.tgz" + "mtp": "https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-c3fbeb2/mtp-0.1.0.tgz" }, "dependencies": { "@tensamin/ui": "*", diff --git a/packages/mtp/src/context.tsx b/packages/mtp/src/context.tsx index b6da558..bcdcbaa 100644 --- a/packages/mtp/src/context.tsx +++ b/packages/mtp/src/context.tsx @@ -10,7 +10,7 @@ import { } from "react"; import { isTauri } from "@tauri-apps/api/core"; import { onResume } from "tauri-plugin-app-events-api"; -import { MTPClient } from "mtp"; +import { ConnectionState, MTPClient } from "mtp"; import type { z } from "zod"; import { @@ -31,12 +31,6 @@ import { RETRY_INTERVAL, } from "./values"; -const READY_STATE = { - CLOSED: 0, - CONNECTING: 1, - OPEN: 2, -} as const; - const PUSH_TYPES = [ "message_live", "message_state", @@ -153,6 +147,7 @@ function mapDataKeys(value: unknown, mapKey: (key: string) => string): unknown { ); } +// Zod schema validation function validateResponse( type: T, message: { id?: number; type: string; data: unknown }, @@ -189,8 +184,9 @@ export function Provider(props: { }) { const { load } = useStorage(); - const [readyState, setReadyState] = useState(READY_STATE.CLOSED); - const [connected, setConnected] = useState(false); + const [readyState, setReadyState] = useState( + ConnectionState.Disconnected, + ); const [identified, setIdentified] = useState(false); const [identifying, setIdentifying] = useState(false); @@ -208,11 +204,15 @@ export function Provider(props: { null, ); + const connected = readyState === ConnectionState.Connected; + + // MTP url const [mtpUrl, setMtpUrl] = useState(null); useEffect(() => { load("mtp_url").then(setMtpUrl); }, [load]); + // Validation override functions const send: BoundSendFn = useMemo( () => async (type, data, options) => { const client = clientRef.current; @@ -259,6 +259,7 @@ export function Provider(props: { }; }, []); + // No Iota check useEffect(() => { if (!connected) return; @@ -272,6 +273,7 @@ export function Provider(props: { }); }, [connected, subscribe]); + // Custom Pings useEffect(() => { if (!connected || !identified) { return; @@ -297,11 +299,9 @@ export function Provider(props: { }; }, [connected, identified, send]); + // Reconnect stuff useEffect(() => { - if (!mtpUrl) { - return; - } - const url = mtpUrl; + if (!mtpUrl) return; let attempts = 0; let reconnectTimer: ReturnType | null = null; @@ -358,17 +358,28 @@ export function Provider(props: { } try { - setReadyState(READY_STATE.CONNECTING); - setConnected(false); setIdentified(false); setIdentifying(false); await MTPClient.init(); const client = await MTPClient.create({ - url, + url: mtpUrl ?? "", + descriptor: "client", pings: true, logger: (event) => { - log(2, "mtp", event.type === "state" ? "cyan" : "blue", event.type === "state" ? event.data : event.type, event); + if (event.type === "state") { + setReadyState( + clientRef.current?.state ?? ConnectionState.Disconnected, + ); + } + + log( + 2, + "mtp", + event.type === "state" ? "cyan" : "blue", + event.type === "state" ? event.data : event.type, + event, + ); }, }); @@ -378,6 +389,7 @@ export function Provider(props: { } clientRef.current = client; + setReadyState(client.state); await client.connect(); if (disposed) { @@ -401,8 +413,7 @@ export function Provider(props: { clearReconnectTimer(); scheduleReconnectReset(); - setReadyState(READY_STATE.OPEN); - setConnected(true); + setReadyState(client.state); setIdentifying(true); setError(""); setErrorDescription(""); @@ -425,8 +436,7 @@ export function Provider(props: { clientRef.current?.disconnect(); clientRef.current = null; clearReconnectResetTimer(); - setReadyState(READY_STATE.CLOSED); - setConnected(false); + setReadyState(ConnectionState.Disconnected); setIdentified(false); setIdentifying(false); log( @@ -475,16 +485,16 @@ export function Provider(props: { clientRef.current?.disconnect(); clientRef.current = null; - setReadyState(READY_STATE.CLOSED); - setConnected(false); + setReadyState(ConnectionState.Disconnected); setIdentified(false); setIdentifying(false); }; }, [mtpUrl, props.blockConnection]); + // Loading bar const progress = useMemo(() => { if (!mtpUrl) return 10; - if (readyState === READY_STATE.CONNECTING) return 30; + if (readyState === ConnectionState.Connecting) return 30; if (!connected) return 45; if (identifying) return 75; if (!identified) return 90; @@ -493,7 +503,7 @@ export function Provider(props: { const loadingTitle = useMemo(() => { if (!mtpUrl) return "Looking up configuration"; - if (readyState === READY_STATE.CONNECTING || !connected) + if (readyState === ConnectionState.Connecting || !connected) return "Connecting to Tensamin"; if (identifying || !identified) return "Identifying secure session"; return "Loading"; @@ -501,13 +511,14 @@ export function Provider(props: { const loadingDescription = useMemo(() => { if (!mtpUrl) return "Loading connection details"; - if (readyState === READY_STATE.CONNECTING || !connected) { + if (readyState === ConnectionState.Connecting || !connected) { return "Establishing transport channel"; } if (identifying || !identified) return "Waiting for authenticated session"; return undefined; }, [connected, identified, identifying, readyState, mtpUrl]); + // Return if (error !== "" && errorDescription !== "") { return ; } diff --git a/packages/tauth/src/context.tsx b/packages/tauth/src/context.tsx index 641a278..31ca22a 100644 --- a/packages/tauth/src/context.tsx +++ b/packages/tauth/src/context.tsx @@ -11,7 +11,7 @@ import { DialogHeader, DialogTitle, } from "@tensamin/ui"; -import { useLocation } from "@tanstack/react-router"; +import { useLocation, useNavigate } from "@tanstack/react-router"; import { useDeeplinks } from "@tensamin/tauri/deeplinkHandler"; import { useMTP } from "@tensamin/mtp"; import { log, toast } from "@tensamin/shared/log"; @@ -25,28 +25,40 @@ export default function Wrapper({ children }: { children: ReactNode }) { const { send } = useMTP(); const { getSharedSecret } = useCrypto(); const { searchStr } = useLocation(); + const navigate = useNavigate(); const [dialogOpen, setDialogOpen] = useState(false); const [loading, setLoading] = useState(false); const [identifier, setIdentifier] = useState(null); const [redirect, setRedirect] = useState(null); const [challenge, setChallenge] = useState(null); + const [appPublicKey, setAppPublicKey] = useState(null); const [allowChildern, setAllowChildern] = useState(false); const { deeplinks } = useDeeplinks(); const authorizeApp = async () => { - if (!identifier || !redirect || !challenge) return; + if (!identifier || !redirect || !challenge || !appPublicKey) return; try { // Get Data const user = await get(await load("user_id")); const { - data: { content: appPublicKey }, + data: { content: appPublicKeyHash }, } = await send("load_txt_record", { path: "tauth." + identifier, }); + const verifiedAppPublicKeyHash = await sha256Hex(appPublicKey); + if (normalizeHash(appPublicKeyHash) !== verifiedAppPublicKeyHash) { + log(1, "tauth", "red", "App public key hash mismatch", undefined, { + appPublicKey, + appPublicKeyHash, + verifiedAppPublicKeyHash, + }); + throw new Error("App public key hash mismatch"); + } + // Get Shared Secret const sharedSecret = await getSharedSecret( await load("private_key"), @@ -75,7 +87,7 @@ export default function Wrapper({ children }: { children: ReactNode }) { finalUrl.searchParams.set("challenge", solvedChallenge); finalUrl.searchParams.set("originalChallenge", challenge); - const session = Date.now(); + const session = new Date().getTime(); finalUrl.searchParams.set("sessionId", String(session)); // Save Session @@ -92,11 +104,14 @@ export default function Wrapper({ children }: { children: ReactNode }) { setIdentifier(null); setRedirect(null); setChallenge(null); + setAppPublicKey(null); toast("success", "App authorized successfully"); return; } else { - window.location.href = finalUrl.toString(); + navigate({ + to: finalUrl.toString(), + }); return; } } catch (err) { @@ -108,9 +123,27 @@ export default function Wrapper({ children }: { children: ReactNode }) { setIdentifier(null); setRedirect(null); setChallenge(null); + setAppPublicKey(null); } }; + async function sha256Hex(value: string): Promise { + const hash = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(value), + ); + return [...new Uint8Array(hash)] + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); + } + + function normalizeHash(value: string): string { + return value + .trim() + .toLowerCase() + .replace(/^sha256[:=]/, ""); + } + function hexToBase64(hex: string): string { const bytes = hex.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) ?? []; const binaryString = String.fromCharCode(...bytes); @@ -122,6 +155,7 @@ export default function Wrapper({ children }: { children: ReactNode }) { const identifier = params.get("identifier"); const redirect = params.get("redirect"); const challenge = params.get("challenge"); + const appPublicKey = params.get("public_key"); if (!identifier || !redirect) { setAllowChildern(true); return; @@ -135,10 +169,18 @@ export default function Wrapper({ children }: { children: ReactNode }) { return; } + if (!appPublicKey) { + toast("error", "Missing app public key"); + log(1, "tauth", "red", "Missing app public key"); + setAllowChildern(true); + return; + } + setAllowChildern(true); setIdentifier(identifier); setRedirect(redirect); setChallenge(hexToBase64(challenge || "")); + setAppPublicKey(appPublicKey); setDialogOpen(true); }); }, [searchStr, load]); @@ -149,11 +191,13 @@ export default function Wrapper({ children }: { children: ReactNode }) { const url = new URL(link); const identifier = url.searchParams.get("identifier"); const redirect = url.searchParams.get("redirect"); + const appPublicKey = url.searchParams.get("public_key"); - if (identifier && redirect) { + if (identifier && redirect && appPublicKey) { setIdentifier(identifier); setRedirect(redirect); setChallenge(hexToBase64(url.searchParams.get("challenge") || "")); + setAppPublicKey(appPublicKey); setDialogOpen(true); } } @@ -170,6 +214,7 @@ export default function Wrapper({ children }: { children: ReactNode }) { setIdentifier(null); setRedirect(null); setChallenge(null); + setAppPublicKey(null); } }} > @@ -205,6 +250,7 @@ export default function Wrapper({ children }: { children: ReactNode }) { setIdentifier(null); setRedirect(null); setChallenge(null); + setAppPublicKey(null); }} > Deny diff --git a/todo.md b/todo.md index 837b847..d3665c2 100644 --- a/todo.md +++ b/todo.md @@ -1 +1,2 @@ - Move legal to extra onboarding package +- Add a bunch of tests From 7f75a36d73159ca6e2e96ecf612c1505ec32461f Mon Sep 17 00:00:00 2001 From: Alois Date: Fri, 3 Jul 2026 11:08:38 +0200 Subject: [PATCH 2/2] (wip): migrate ttp to mtp -> snake case to pascal case --- apps/web/src/components/modals/basic.tsx | 5 +- apps/web/src/components/modals/profile.tsx | 12 +- apps/web/src/components/navbar.tsx | 14 +-- apps/web/src/components/sidebar.tsx | 10 +- .../src/features/conversation/list/body.tsx | 2 +- apps/web/src/routes/app/home.tsx | 8 +- .../call/src/components/buttons/invite.tsx | 6 +- packages/call/src/components/modals/base.tsx | 19 ++- .../src/components/modals/contextMenu.tsx | 4 +- packages/call/src/components/top.tsx | 2 +- packages/call/src/store.tsx | 42 +++---- packages/call/src/views/preview.tsx | 4 +- packages/chat/src/behaviour/conversation.ts | 6 +- packages/chat/src/components/input.tsx | 16 +-- packages/chat/src/components/message.tsx | 46 +++---- packages/chat/src/context.tsx | 50 ++++---- packages/chat/src/screen.tsx | 18 +-- packages/mtp/src/context.tsx | 42 +------ packages/notifications/src/context.tsx | 22 ++-- packages/shared/src/data.ts | 112 +++++++++--------- packages/storage/src/session.tsx | 14 +-- packages/tauth/src/context.tsx | 6 +- packages/user/src/context.tsx | 2 +- 23 files changed, 214 insertions(+), 248 deletions(-) diff --git a/apps/web/src/components/modals/basic.tsx b/apps/web/src/components/modals/basic.tsx index 2d5a3d8..eb9a27b 100644 --- a/apps/web/src/components/modals/basic.tsx +++ b/apps/web/src/components/modals/basic.tsx @@ -34,7 +34,7 @@ export function Basic({
@@ -42,8 +42,7 @@ export function Basic({ } /> - {user.online_status - .split("_") + {user.OnlineStatus.split("_") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" ")} diff --git a/apps/web/src/components/modals/profile.tsx b/apps/web/src/components/modals/profile.tsx index c46e4ef..e34a9ac 100644 --- a/apps/web/src/components/modals/profile.tsx +++ b/apps/web/src/components/modals/profile.tsx @@ -34,8 +34,8 @@ export default function Profile({ user }: { user: User }) { const ownData = await get(ownId); const secret = await getSharedSecret( privateKey, - ownData.public_key, - user.public_key, + ownData.PublicKey, + user.PublicKey, ); if (active) { @@ -51,7 +51,7 @@ export default function Profile({ user }: { user: User }) { return () => { active = false; }; - }, [get, getSharedSecret, load, user.public_key]); + }, [get, getSharedSecret, load, user.PublicKey]); const sharedSecretCode = sharedSecret ? toLossySixDigitCode(sharedSecret) @@ -98,13 +98,13 @@ export default function Profile({ user }: { user: User }) {

- Iota ID: {user.iota_id} + Iota ID: {user.IotaId}

- User ID: {user.user_id} + User ID: {user.UserId}

- Public Key: {user.public_key} + Public Key: {user.PublicKey}

)} diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx index 7550b28..0d396c6 100644 --- a/apps/web/src/components/navbar.tsx +++ b/apps/web/src/components/navbar.tsx @@ -28,7 +28,7 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { const { id } = useSearch({ strict: false }); const currentCalls = calls.filter((call) => - call.call_members.some((member) => member === id), + call.CallMembers.some((member) => member === id), ); const isMobile = useIsMobile(); @@ -121,8 +121,8 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { onClick={() => { void joinCall( id, - currentCalls[0].call_secret, - currentCalls[0].call_id, + currentCalls[0].CallSecret, + currentCalls[0].CallId, ); }} className="w-9 h-9 aspect-square rounded-lg" @@ -147,13 +147,13 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { {currentCalls.map((call) => ( { - void joinCall(id, call.call_secret, call.call_id); + void joinCall(id, call.CallSecret, call.CallId); }} > - {displayCallId(call.call_id)} + {displayCallId(call.CallId)} ))} diff --git a/apps/web/src/components/sidebar.tsx b/apps/web/src/components/sidebar.tsx index 4dc94d4..95fcd51 100644 --- a/apps/web/src/components/sidebar.tsx +++ b/apps/web/src/components/sidebar.tsx @@ -41,7 +41,7 @@ import { mtp } from "@tensamin/shared/data"; import { useMTP } from "@tensamin/mtp"; type OnlineStatus = z.infer< - typeof mtp.get_user_data.response.shape.online_status + typeof mtp.get_user_data.response.shape.OnlineStatus >; const onlineStatusLabels: Record = { @@ -89,7 +89,7 @@ function StatusDialog({ onOpenChange={(nextOpen) => { if (!nextOpen) { setDraftStatus(user.status ?? ""); - setDraftOnlineStatus(user.online_status); + setDraftOnlineStatus(user.OnlineStatus); setErrorMessage(""); setSaveSucceeded(false); } @@ -144,7 +144,7 @@ function StatusDialog({ onClick={async () => { const payload = { ...(draftStatus && { status: draftStatus }), - online_status: draftOnlineStatus, + OnlineStatus: draftOnlineStatus, }; const validation = @@ -230,7 +230,7 @@ export default function Sidebar() { { setDraftStatus(user.status ?? ""); - setDraftOnlineStatus(user.online_status); + setDraftOnlineStatus(user.OnlineStatus); setStatusErrorMessage(""); setStatusSaveSucceeded(false); setDialogOpen(true); @@ -249,7 +249,7 @@ export default function Sidebar() { onOpenChange={(nextOpen) => { if (!nextOpen) { setDraftStatus(user.status ?? ""); - setDraftOnlineStatus(user.online_status); + setDraftOnlineStatus(user.OnlineStatus); setStatusErrorMessage(""); setStatusSaveSucceeded(false); } diff --git a/apps/web/src/features/conversation/list/body.tsx b/apps/web/src/features/conversation/list/body.tsx index d240655..36399e9 100644 --- a/apps/web/src/features/conversation/list/body.tsx +++ b/apps/web/src/features/conversation/list/body.tsx @@ -65,7 +65,7 @@ export default function List() { {category === "conversations" ? ( contacts?.[virtualItem.index] ? ( ) : null ) : communities?.[virtualItem.index] ? ( diff --git a/apps/web/src/routes/app/home.tsx b/apps/web/src/routes/app/home.tsx index 0add511..832d498 100644 --- a/apps/web/src/routes/app/home.tsx +++ b/apps/web/src/routes/app/home.tsx @@ -59,7 +59,7 @@ function AddConversationButton() { username: result.data, }) .then((data) => { - if (data.data.user_id === 0) { + if (data.data.UserId === 0) { throw new Error(); } @@ -72,7 +72,7 @@ function AddConversationButton() { if (!user) return; // alrady added check - if (contacts.some((contact) => contact.user_id === user.data.user_id)) { + if (contacts.some((contact) => contact.UserId === user.data.UserId)) { setError("Conversation already exists"); return; } @@ -81,10 +81,10 @@ function AddConversationButton() { const timeout = setTimeout(() => setLoading(true), 500); send("add_conversation", { - chat_partner_name: result.data, + ChatPartnerName: result.data, }) .then(() => { - insertContact(user.data.user_id); + insertContact(user.data.UserId); setOpen(false); }) .catch((error) => { diff --git a/packages/call/src/components/buttons/invite.tsx b/packages/call/src/components/buttons/invite.tsx index d3a6d8e..d7ccf82 100644 --- a/packages/call/src/components/buttons/invite.tsx +++ b/packages/call/src/components/buttons/invite.tsx @@ -61,15 +61,15 @@ export default function InviteButton({ > {contacts.map((contact) => ( Loading...
} component={(user) => (