From ac1bdf5ac2f6ef3f591ad3194a8cb2643f58abd5 Mon Sep 17 00:00:00 2001 From: forgejo-actions Date: Thu, 6 Aug 2026 14:47:23 +0000 Subject: [PATCH 1/9] (qol): update release flake hash --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 1ed0189..2b8781f 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ systems = [ "x86_64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs systems; version = "0.0.11"; - x86_64DebHash = "sha256-GtPVJ+WK7np0iGA3OXTWtc7or5biFt2AkchqPRIR9Zg="; + x86_64DebHash = "sha256-E4FGMpCt2ByaN6d+YzglqeA6LbhMOQmPf3UzAj7uZXE="; forgejoBaseUrl = "https://git.methanium.net/tensamin/client/releases/download/${version}"; in { From 73db7e6b2b7541563a81f25a44fa1b2489c01860 Mon Sep 17 00:00:00 2001 From: alois Date: Wed, 12 Aug 2026 15:12:05 +0200 Subject: [PATCH 2/9] Update flake.nix --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 2b8781f..1ed0189 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ systems = [ "x86_64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs systems; version = "0.0.11"; - x86_64DebHash = "sha256-E4FGMpCt2ByaN6d+YzglqeA6LbhMOQmPf3UzAj7uZXE="; + x86_64DebHash = "sha256-GtPVJ+WK7np0iGA3OXTWtc7or5biFt2AkchqPRIR9Zg="; forgejoBaseUrl = "https://git.methanium.net/tensamin/client/releases/download/${version}"; in { From d0918ff384a356cc50fa5a4bf6db24ae6d7f41c2 Mon Sep 17 00:00:00 2001 From: forgejo-actions Date: Tue, 18 Aug 2026 20:46:44 +0000 Subject: [PATCH 3/9] (qol): update release flake hash --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 2b8781f..d91a817 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ systems = [ "x86_64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs systems; version = "0.0.11"; - x86_64DebHash = "sha256-E4FGMpCt2ByaN6d+YzglqeA6LbhMOQmPf3UzAj7uZXE="; + x86_64DebHash = "sha256-p/6boNv9uWTHsPTVa3EhGzr41EwRzCY/fJWlj854DVA="; forgejoBaseUrl = "https://git.methanium.net/tensamin/client/releases/download/${version}"; in { From 96b81d30b1408d3a94f1cf191eedbdc705f192b4 Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 27 Aug 2026 14:38:53 +0200 Subject: [PATCH 4/9] feat(pwa): keep service workers from getting registerd outside the pwa --- apps/pwa/src/runtime.tsx | 66 ++++++---------------------------------- apps/pwa/src/vite.ts | 1 + 2 files changed, 10 insertions(+), 57 deletions(-) diff --git a/apps/pwa/src/runtime.tsx b/apps/pwa/src/runtime.tsx index d7917cc..a1947eb 100644 --- a/apps/pwa/src/runtime.tsx +++ b/apps/pwa/src/runtime.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { toast } from "sonner"; import { setDatabaseEntry } from "@tensamin/shared/indexedDb"; @@ -6,11 +6,6 @@ import { isTauri } from "@tauri-apps/api/core"; import "./style.css"; -type BeforeInstallPromptEvent = Event & { - prompt: () => Promise; - userChoice: Promise<{ outcome: "accepted" | "dismissed" }>; -}; - const launchedFiles: File[] = []; const fileListeners = new Set<(file: File) => void>(); @@ -27,9 +22,10 @@ export function subscribeTuFileLaunch(listener: (file: File) => void) { }; } -function isStandalone() { +function isInstalledPwa() { return ( window.matchMedia("(display-mode: standalone)").matches || + window.matchMedia("(display-mode: window-controls-overlay)").matches || (navigator as Navigator & { standalone?: boolean }).standalone === true ); } @@ -65,13 +61,9 @@ async function enablePush() { await setDatabaseEntry("keys", "push-subscription", subscription.toJSON()); } -export default function PwaRuntime() { - const [installPrompt, setInstallPrompt] = - useState(null); - +function InstalledPwaRuntime() { useEffect(() => { if ( - isTauri() || !("serviceWorker" in navigator) || !["http:", "https:"].includes(window.location.protocol) ) { @@ -129,16 +121,6 @@ export default function PwaRuntime() { }; }, []); - useEffect(() => { - const handleInstallPrompt = (event: Event) => { - event.preventDefault(); - setInstallPrompt(event as BeforeInstallPromptEvent); - }; - window.addEventListener("beforeinstallprompt", handleInstallPrompt); - return () => - window.removeEventListener("beforeinstallprompt", handleInstallPrompt); - }, []); - useEffect(() => { const launchQueue = ( window as Window & { @@ -159,41 +141,7 @@ export default function PwaRuntime() { }, []); useEffect(() => { - if (!installPrompt) return; - toast("Install Tensamin for a native app experience", { - duration: Infinity, - action: { - label: "Install", - onClick: () => { - void installPrompt.prompt().then(() => installPrompt.userChoice); - setInstallPrompt(null); - }, - }, - }); - }, [installPrompt]); - - useEffect(() => { - const isAppleMobile = /iPad|iPhone|iPod/.test(navigator.userAgent); if ( - isTauri() || - !isAppleMobile || - isStandalone() || - localStorage.getItem("pwa-ios-install-hint") - ) { - return; - } - localStorage.setItem("pwa-ios-install-hint", "shown"); - toast( - "Install Tensamin from Safari's Share menu to enable background notifications.", - { - duration: 12_000, - }, - ); - }, []); - - useEffect(() => { - if ( - !isStandalone() || !("Notification" in window) || Notification.permission !== "default" || localStorage.getItem("pwa-push-hint") @@ -222,7 +170,6 @@ export default function PwaRuntime() { useEffect(() => { if ( - isStandalone() && "Notification" in window && Notification.permission === "granted" && import.meta.env.VITE_WEB_PUSH_PUBLIC_KEY @@ -235,3 +182,8 @@ export default function PwaRuntime() { return null; } + +export default function PwaRuntime() { + if (isTauri() || !isInstalledPwa()) return null; + return ; +} diff --git a/apps/pwa/src/vite.ts b/apps/pwa/src/vite.ts index 908a3d2..962dce1 100644 --- a/apps/pwa/src/vite.ts +++ b/apps/pwa/src/vite.ts @@ -128,6 +128,7 @@ export function tensaminPwa(): Plugin[] { filename: "serviceWorker.ts", injectRegister: null, registerType: "prompt", + buildBase: "/", manifestFilename: "manifest.json", includeAssets: ["favicon.ico", "icons/*.png"], manifest: { From 0fbedf6733710d08b5cedc28bd3e4043012198b0 Mon Sep 17 00:00:00 2001 From: forgejo-actions Date: Thu, 27 Aug 2026 12:59:08 +0000 Subject: [PATCH 5/9] (qol): update release flake hash --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index d91a817..e02a001 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ systems = [ "x86_64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs systems; version = "0.0.11"; - x86_64DebHash = "sha256-p/6boNv9uWTHsPTVa3EhGzr41EwRzCY/fJWlj854DVA="; + x86_64DebHash = "sha256-iMEgdjY8Ghlx6K/YzKC5JAZbeHNJDIFHm8MKmM4efaI="; forgejoBaseUrl = "https://git.methanium.net/tensamin/client/releases/download/${version}"; in { From fb0ec1e8ecf793a3e0547a155f0c2440817973eb Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Thu, 27 Aug 2026 18:00:33 +0300 Subject: [PATCH 6/9] chore(deps): update mtp digest to c30315a --- apps/tauri/src-tauri/Cargo.lock | 19 +++++++++---------- apps/tauri/src-tauri/Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/tauri/src-tauri/Cargo.lock b/apps/tauri/src-tauri/Cargo.lock index d404d61..e387136 100644 --- a/apps/tauri/src-tauri/Cargo.lock +++ b/apps/tauri/src-tauri/Cargo.lock @@ -2745,7 +2745,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=c30315af944bda05ecdb9c4e9cc350cb99cf7b2e#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "mtp-client", "mtp-codec", @@ -2760,7 +2760,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=c30315af944bda05ecdb9c4e9cc350cb99cf7b2e#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "mtp-codec", "mtp-common", @@ -2773,7 +2773,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=c30315af944bda05ecdb9c4e9cc350cb99cf7b2e#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "base64 0.23.1", "byteorder", @@ -2787,10 +2787,9 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=c30315af944bda05ecdb9c4e9cc350cb99cf7b2e#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "quinn", - "rustls", "thiserror 2.0.19", "wtransport", ] @@ -2798,7 +2797,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=c30315af944bda05ecdb9c4e9cc350cb99cf7b2e#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "base64 0.22.1", "chacha20poly1305", @@ -2820,7 +2819,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=c30315af944bda05ecdb9c4e9cc350cb99cf7b2e#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "mtp-codec", "mtp-common", @@ -2836,7 +2835,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=c30315af944bda05ecdb9c4e9cc350cb99cf7b2e#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "async-trait", "mtp-codec", @@ -2856,7 +2855,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=c30315af944bda05ecdb9c4e9cc350cb99cf7b2e#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "serde", "serde_yaml", @@ -2865,7 +2864,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=c30315af944bda05ecdb9c4e9cc350cb99cf7b2e#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "async-trait", "bytes", diff --git a/apps/tauri/src-tauri/Cargo.toml b/apps/tauri/src-tauri/Cargo.toml index 2a68e95..615b894 100644 --- a/apps/tauri/src-tauri/Cargo.toml +++ b/apps/tauri/src-tauri/Cargo.toml @@ -24,7 +24,7 @@ serde_json = "1" base64 = "0.22" reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] } tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] } -mtp = { git = "https://git.methanium.net/Methanium/mtp.git", rev = "a5c8d4f0c898c78351e9d54124886c86e789a22a", features = ["client", "crypto"] } +mtp = { git = "https://git.methanium.net/Methanium/mtp.git", rev = "c30315af944bda05ecdb9c4e9cc350cb99cf7b2e", features = ["client", "crypto"] } webpki-root-certs = "1" tauri-plugin-deep-link = "2" tauri-plugin-notification = "2" From 1d1b304bcf8e056de20360d08ff5be028e947778 Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 27 Aug 2026 19:32:48 +0200 Subject: [PATCH 7/9] feat(workflows): remove env thing --- .forgejo/workflows/dependency-builds.yml | 1 - .forgejo/workflows/deploy-dev.yml | 3 --- .forgejo/workflows/deploy-prod.yml | 3 --- 3 files changed, 7 deletions(-) diff --git a/.forgejo/workflows/dependency-builds.yml b/.forgejo/workflows/dependency-builds.yml index 709c75b..f8ec1cd 100644 --- a/.forgejo/workflows/dependency-builds.yml +++ b/.forgejo/workflows/dependency-builds.yml @@ -4,7 +4,6 @@ on: pull_request: env: - NIX_CONFIG: experimental-features = nix-command flakes FORGEJO_TOKEN: "" GITHUB_TOKEN: "" diff --git a/.forgejo/workflows/deploy-dev.yml b/.forgejo/workflows/deploy-dev.yml index 0d5be50..a09f69b 100644 --- a/.forgejo/workflows/deploy-dev.yml +++ b/.forgejo/workflows/deploy-dev.yml @@ -5,9 +5,6 @@ on: paths-ignore: - flake.nix -env: - NIX_CONFIG: experimental-features = nix-command flakes - jobs: build-web: runs-on: nixos diff --git a/.forgejo/workflows/deploy-prod.yml b/.forgejo/workflows/deploy-prod.yml index d85ce27..5fe2ba9 100644 --- a/.forgejo/workflows/deploy-prod.yml +++ b/.forgejo/workflows/deploy-prod.yml @@ -6,9 +6,6 @@ on: paths-ignore: - flake.nix -env: - NIX_CONFIG: experimental-features = nix-command flakes - jobs: build-web: runs-on: nixos From aa34bd962b6539289755f28e8cd7b938bd19f7e9 Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 27 Aug 2026 19:35:17 +0200 Subject: [PATCH 8/9] temp(mtp): temp commit stuff that needs to get a rework --- packages/mtp/package.json | 5 +- packages/mtp/src/context.test.tsx | 21 -- packages/mtp/src/context.tsx | 308 ++++++++++++------------------ pnpm-lock.yaml | 9 - 4 files changed, 124 insertions(+), 219 deletions(-) delete mode 100644 packages/mtp/src/context.test.tsx diff --git a/packages/mtp/package.json b/packages/mtp/package.json index 982593a..dfd973c 100644 --- a/packages/mtp/package.json +++ b/packages/mtp/package.json @@ -14,13 +14,10 @@ "dependencies": { "@methanium/ui": "*", "@tauri-apps/api": "^2.11.1", - "@tensamin/crypto": "workspace:*", "@tensamin/shared": "workspace:*", "@tensamin/storage": "workspace:*", "mtp": "*", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "zod": "^4.4.3" + "react": "^19.2.8" }, "devDependencies": { "eslint": "^10.8.0" diff --git a/packages/mtp/src/context.test.tsx b/packages/mtp/src/context.test.tsx deleted file mode 100644 index 7f50aba..0000000 --- a/packages/mtp/src/context.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { isPushType, validateResponse } from "./context"; - -describe("MTP protocol dispatch", () => { - it("preserves protocol errors for the request layer", () => { - const error = validateResponse("GetStates", { - id: 12, - type: "ErrorInternal", - data: { ErrorType: "temporary" }, - }); - expect(error.type).toBe("ErrorInternal"); - expect(error.id).toBe(12); - }); - - it("recognizes initial and live presence pushes", () => { - expect(isPushType("GetStates")).toBe(true); - expect(isPushType("ClientChanged")).toBe(true); - expect(isPushType("UnknownMessage")).toBe(false); - }); -}); diff --git a/packages/mtp/src/context.tsx b/packages/mtp/src/context.tsx index af06ee0..288acbf 100644 --- a/packages/mtp/src/context.tsx +++ b/packages/mtp/src/context.tsx @@ -10,9 +10,18 @@ import { } from "react"; import { invoke, isTauri } from "@tauri-apps/api/core"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; -import { MTPClient } from "mtp"; -import { type z } from "zod"; -import { ConnectionState } from "mtp"; +import { + base64ToBytes, + ConnectionState, + MTPClient, + MTPProxyConnection, + type MTPFrame, + type MTPKeyMaterialInput, + type MTPProxyAdapter, + type MTPRequestOptions, + type MTPRequestFunction, + type MTPResponseFrame, +} from "mtp"; import createAsyncQueue from "@tensamin/shared/asyncQueue"; import { toast as sonnerToast } from "@methanium/ui"; @@ -24,35 +33,15 @@ import { type MTP as Schemas, } from "@tensamin/shared/data"; import { log } from "@tensamin/shared/log"; -import { ProtocolError } from "@tensamin/shared/errors"; import { useStorage } from "@tensamin/storage/context"; import { RECONNECT_RESET, RECONNECT_TRIES, RETRY_INTERVAL } from "./values"; -function base64ToUint8Array(b64: string) { - const bin = atob(b64); - const out = new Uint8Array(bin.length); - - for (let i = 0; i < bin.length; i++) { - out[i] = bin.charCodeAt(i); - } - - return out; -} - export type ProtocolMessage< T extends keyof Schemas & string = keyof Schemas & string, -> = { - id?: number; - type: T | string; - data: z.infer; -}; +> = MTPResponseFrame; -export type BoundSendFn = ( - type: T, - data?: z.infer, - options?: { id?: number }, -) => Promise>; +export type BoundSendFn = MTPRequestFunction; export type PushHandler = (message: ProtocolMessage) => void | Promise; @@ -68,6 +57,15 @@ const PUSH_TYPES = [ "ErrorNoIota", ] as const; +function encodedPublicKey(value: string): MTPKeyMaterialInput { + const trimmed = value.trim(); + const hex = trimmed.replace(/^0x/i, ""); + return { + value: /^[0-9a-f]+$/i.test(hex) ? hex : trimmed, + encoding: /^[0-9a-f]+$/i.test(hex) ? "hex" : "base64", + }; +} + export function isPushType(type: string): boolean { return (PUSH_TYPES as readonly string[]).includes(type); } @@ -114,53 +112,6 @@ type ContextType = { const MTPContext = createContext(undefined); -function getProtocolErrorDetails(error: unknown) { - if (typeof error !== "object" || error === null || !("type" in error)) { - return null; - } - - const protocolError = error as { - id?: unknown; - type?: unknown; - data?: unknown; - }; - return { - id: protocolError.id, - type: protocolError.type, - data: protocolError.data, - }; -} - -// Zod schema validation -export function validateResponse( - type: T, - message: { id?: number; type: string; data: unknown }, -): ProtocolMessage { - if (message.type.startsWith("Error")) { - return message as ProtocolMessage; - } - - const schema = - schemas[message.type as keyof Schemas & string]?.response ?? - schemas[type]?.response; - if (!schema) { - return message as ProtocolMessage; - } - - const parsed = schema.safeParse(message.data); - if (!parsed.success) { - throw new Error( - `Response validation failed for ${type}: ${parsed.error.message}`, - ); - } - - return { - id: message.id, - type: message.type, - data: parsed.data, - } as ProtocolMessage; -} - function useMessageHandlers() { const interceptorsRef = useRef(new Set()); const pushHandlersRef = useRef(new Set()); @@ -202,9 +153,7 @@ function BrowserProvider(props: { const [freshContacts, setFreshContacts] = useState([]); const [freshCalls, setFreshCalls] = useState([]); - const clientRef = useRef> | null>( - null, - ); + const clientRef = useRef | null>(null); const { addInterceptor, interceptorsRef, @@ -221,7 +170,6 @@ function BrowserProvider(props: { load("omega_url").then(setMtpUrl); }, [load]); - // Validation override functions const send: BoundSendFn = useMemo( () => async (type, data, options) => { const client = clientRef.current; @@ -230,24 +178,8 @@ function BrowserProvider(props: { throw new Error("mtp is not connected"); } - const message = await client.request( - type, - (data ?? {}) as Record, - options, - ); - const response = validateResponse(type, message); + const response = await client.request(type, data, options); setFreshContacts((contacts) => removeMissingContacts(contacts, response)); - if (response.type.startsWith("Error")) { - const errorData = response.data as Record; - throw new ProtocolError({ - type: response.type, - requestId: response.id, - errorType: - typeof errorData.ErrorType === "string" - ? errorData.ErrorType - : undefined, - }); - } return response; }, [], @@ -259,9 +191,7 @@ function BrowserProvider(props: { return () => {}; } - return client.subscribe(type, (message) => { - handler(validateResponse(type, message)); - }); + return client.subscribe(type, handler); }, []); // Reconnect stuff @@ -324,7 +254,7 @@ function BrowserProvider(props: { if (disposed || props.blockConnection) return; const generation = ++connectionGeneration; - let client: Awaited> | null = null; + let client: MTPClient | null = null; let failed = false; const cleanup = () => { client?.disconnect(); @@ -398,15 +328,20 @@ function BrowserProvider(props: { log(2, "mtp", "green", "Connecting to: " + url); - client = await MTPClient.create({ + client = await MTPClient.create({ url, credentials: { clientId: userId, - keyring: base64ToUint8Array(keyring), + keyring: base64ToBytes(keyring), }, - hostPublicKey: omikronPublicKey, + hostPublicKey: encodedPublicKey(omikronPublicKey), descriptor: "client", pings: true, + schemas, + throwProtocolErrors: true, + onValidationError: (error) => { + log(1, "mtp", "red", "Failed to validate MTP message", error); + }, logger: (event) => { if (event.type === "state") { if (generation !== connectionGeneration) return; @@ -458,16 +393,7 @@ function BrowserProvider(props: { clientRef.current = activeClient; for (const type of PUSH_TYPES) { activeClient.subscribe(type, (message) => { - let validated: ProtocolMessage; - try { - validated = validateResponse(type, message); - } catch (error) { - log(1, "mtp", "red", "Failed to validate push message", error, { - type, - data: message.data, - }); - return; - } + const validated = message as ProtocolMessage; setFreshContacts((contacts) => removeMissingContacts(contacts, validated), @@ -515,11 +441,7 @@ function BrowserProvider(props: { "ClientStateSync", (message) => { cleanupStateSync(); - try { - resolve(validateResponse("ClientStateSync", message)); - } catch (error) { - reject(error); - } + resolve(message); }, ); unsubscribeNoIota = activeClient.subscribe("ErrorNoIota", () => { @@ -539,15 +461,10 @@ function BrowserProvider(props: { ); } - const acknowledgement = await activeClient.request("ClientStateAck", { + await activeClient.request("ClientStateAck", { SessionId: finalResponse.data.SessionId, VersionNumber: finalResponse.data.VersionNumber, }); - if (acknowledgement.type.startsWith("Error")) { - throw new Error( - `State acknowledgement failed: ${acknowledgement.type}`, - ); - } if (disposed || clientRef.current !== activeClient) return; @@ -573,7 +490,7 @@ function BrowserProvider(props: { "mtp", "red", `Connection/authentication attempt failed: ${connectErrorMessage}`, - getProtocolErrorDetails(connectError) ?? connectError, + connectError, ); scheduleReconnect(connectError); @@ -694,6 +611,39 @@ type NativeSnapshot = { error?: string; }; +class TauriMTPAdapter implements MTPProxyAdapter { + readonly #subscriptions = new Map void>>(); + + async request( + type: string, + data: Record, + options?: MTPRequestOptions, + ) { + return await invoke("mtp_request", { + typeName: type, + data, + id: options?.id, + }); + } + + subscribe(type: string, handler: (message: MTPFrame) => void) { + const handlers = + this.#subscriptions.get(type) ?? new Set<(message: MTPFrame) => void>(); + handlers.add(handler); + this.#subscriptions.set(type, handlers); + return () => { + handlers.delete(handler); + if (handlers.size === 0) this.#subscriptions.delete(type); + }; + } + + dispatch(message: MTPFrame) { + for (const handler of this.#subscriptions.get(message.type) ?? []) { + handler(message); + } + } +} + function TauriProvider(props: { children: ReactNode; blockConnection?: boolean; @@ -714,8 +664,16 @@ function TauriProvider(props: { pushHandlersRef, subscribePush, } = useMessageHandlers(); - const subscriptionsRef = useRef( - new Map void>>(), + const [adapter] = useState(() => new TauriMTPAdapter()); + const [connection] = useState( + () => + new MTPProxyConnection(adapter, { + schemas, + throwProtocolErrors: true, + onValidationError: (error) => { + log(1, "mtp", "red", "Failed to validate native MTP message", error); + }, + }), ); const applySnapshot = useCallback((next: NativeSnapshot) => { @@ -739,39 +697,35 @@ function TauriProvider(props: { const dispatchMessage = useCallback( (raw: unknown) => { if (!raw || typeof raw !== "object" || !("type" in raw)) return; - const message = raw as { id?: number; type: string; data: unknown }; - let validated: ProtocolMessage; - try { - validated = validateResponse( - message.type as keyof Schemas & string, - message, - ); - } catch (error) { - log(1, "mtp", "red", "Failed to validate native MTP message", error); - return; - } - for (const handler of subscriptionsRef.current.get(validated.type) ?? - []) { - handler(validated); - } - if (!isPushType(validated.type)) return; - setFreshContacts((contacts) => - removeMissingContacts(contacts, validated), - ); - for (const handler of [...pushHandlersRef.current]) { - void Promise.resolve(handler(validated)).catch((error) => { - log(1, "mtp", "red", "Native MTP push handler failed", error, { - type: validated.type, - }); - }); - } - if (validated.type === "GetStates") { - lastInitialStateRef.current = validated; - } + adapter.dispatch(raw as MTPFrame); }, - [lastInitialStateRef, pushHandlersRef], + [adapter], ); + useEffect(() => { + const unsubscribers = PUSH_TYPES.map((type) => + connection.subscribe(type, (message) => { + const validated = message as ProtocolMessage; + setFreshContacts((contacts) => + removeMissingContacts(contacts, validated), + ); + for (const handler of [...pushHandlersRef.current]) { + void Promise.resolve(handler(validated)).catch((error) => { + log(1, "mtp", "red", "Native MTP push handler failed", error, { + type, + }); + }); + } + if (validated.type === "GetStates") { + lastInitialStateRef.current = validated; + } + }), + ); + return () => { + for (const unsubscribe of unsubscribers) unsubscribe(); + }; + }, [connection, lastInitialStateRef, pushHandlersRef]); + useEffect(() => { if (props.blockConnection) return; let disposed = false; @@ -847,26 +801,10 @@ function TauriProvider(props: { const send = useCallback( async (type, data, options) => { - const response = await invoke("mtp_request", { - typeName: type, - data: data ?? {}, - id: options?.id, - }); - const validated = validateResponse(type, response); + const validated = await connection.request(type, data, options); setFreshContacts((contacts) => removeMissingContacts(contacts, validated), ); - if (validated.type.startsWith("Error")) { - const errorData = validated.data as Record; - throw new ProtocolError({ - type: validated.type, - requestId: validated.id, - errorType: - typeof errorData.ErrorType === "string" - ? errorData.ErrorType - : undefined, - }); - } for (const interceptor of interceptorsRef.current) { void Promise.resolve( interceptor({ type, data, response: validated as ProtocolMessage }), @@ -876,20 +814,13 @@ function TauriProvider(props: { } return validated; }, - [interceptorsRef], + [connection, interceptorsRef], ); - const subscribe = useCallback((type, handler) => { - const handlers = - subscriptionsRef.current.get(type) ?? - new Set<(message: ProtocolMessage) => void>(); - handlers.add(handler as (message: ProtocolMessage) => void); - subscriptionsRef.current.set(type, handlers); - return () => { - handlers.delete(handler as (message: ProtocolMessage) => void); - if (handlers.size === 0) subscriptionsRef.current.delete(type); - }; - }, []); + const subscribe = useCallback( + (type, handler) => connection.subscribe(type, handler), + [connection], + ); const connected = snapshot.readyState === ConnectionState.Connected; const contextReady = connected && snapshot.identified; @@ -916,7 +847,7 @@ function TauriProvider(props: { ); } -export function Provider(props: { +function BrowserProviderLoader(props: { children: ReactNode; blockConnection?: boolean; }) { @@ -941,10 +872,17 @@ export function Provider(props: { if (wasmError) throw wasmError; if (!wasmReady) return null; + return ; +} + +export function Provider(props: { + children: ReactNode; + blockConnection?: boolean; +}) { return isTauri() ? ( ) : ( - + ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index faff301..721102f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -458,9 +458,6 @@ importers: '@tauri-apps/api': specifier: ^2.11.1 version: 2.11.1 - '@tensamin/crypto': - specifier: workspace:* - version: link:../crypto '@tensamin/shared': specifier: workspace:* version: link:../shared @@ -473,12 +470,6 @@ importers: react: specifier: ^19.2.8 version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - zod: - specifier: ^4.4.3 - version: 4.4.3 devDependencies: eslint: specifier: ^10.8.0 From b083855477736ba16e06df184c886812f37a99a3 Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Thu, 27 Aug 2026 21:00:37 +0300 Subject: [PATCH 9/9] chore(deps): update mtp digest to 7ef6ec9 --- apps/tauri/src-tauri/Cargo.lock | 19 +++++++++---------- apps/tauri/src-tauri/Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/tauri/src-tauri/Cargo.lock b/apps/tauri/src-tauri/Cargo.lock index d404d61..1c30d1f 100644 --- a/apps/tauri/src-tauri/Cargo.lock +++ b/apps/tauri/src-tauri/Cargo.lock @@ -2745,7 +2745,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=7ef6ec9e8817121ce8d7938b810f13dd979555f1#7ef6ec9e8817121ce8d7938b810f13dd979555f1" dependencies = [ "mtp-client", "mtp-codec", @@ -2760,7 +2760,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=7ef6ec9e8817121ce8d7938b810f13dd979555f1#7ef6ec9e8817121ce8d7938b810f13dd979555f1" dependencies = [ "mtp-codec", "mtp-common", @@ -2773,7 +2773,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=7ef6ec9e8817121ce8d7938b810f13dd979555f1#7ef6ec9e8817121ce8d7938b810f13dd979555f1" dependencies = [ "base64 0.23.1", "byteorder", @@ -2787,10 +2787,9 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=7ef6ec9e8817121ce8d7938b810f13dd979555f1#7ef6ec9e8817121ce8d7938b810f13dd979555f1" dependencies = [ "quinn", - "rustls", "thiserror 2.0.19", "wtransport", ] @@ -2798,7 +2797,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=7ef6ec9e8817121ce8d7938b810f13dd979555f1#7ef6ec9e8817121ce8d7938b810f13dd979555f1" dependencies = [ "base64 0.22.1", "chacha20poly1305", @@ -2820,7 +2819,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=7ef6ec9e8817121ce8d7938b810f13dd979555f1#7ef6ec9e8817121ce8d7938b810f13dd979555f1" dependencies = [ "mtp-codec", "mtp-common", @@ -2836,7 +2835,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=7ef6ec9e8817121ce8d7938b810f13dd979555f1#7ef6ec9e8817121ce8d7938b810f13dd979555f1" dependencies = [ "async-trait", "mtp-codec", @@ -2856,7 +2855,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=7ef6ec9e8817121ce8d7938b810f13dd979555f1#7ef6ec9e8817121ce8d7938b810f13dd979555f1" dependencies = [ "serde", "serde_yaml", @@ -2865,7 +2864,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git?rev=7ef6ec9e8817121ce8d7938b810f13dd979555f1#7ef6ec9e8817121ce8d7938b810f13dd979555f1" dependencies = [ "async-trait", "bytes", diff --git a/apps/tauri/src-tauri/Cargo.toml b/apps/tauri/src-tauri/Cargo.toml index 2a68e95..ea65a95 100644 --- a/apps/tauri/src-tauri/Cargo.toml +++ b/apps/tauri/src-tauri/Cargo.toml @@ -24,7 +24,7 @@ serde_json = "1" base64 = "0.22" reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] } tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] } -mtp = { git = "https://git.methanium.net/Methanium/mtp.git", rev = "a5c8d4f0c898c78351e9d54124886c86e789a22a", features = ["client", "crypto"] } +mtp = { git = "https://git.methanium.net/Methanium/mtp.git", rev = "7ef6ec9e8817121ce8d7938b810f13dd979555f1", features = ["client", "crypto"] } webpki-root-certs = "1" tauri-plugin-deep-link = "2" tauri-plugin-notification = "2"