From c155bbb5340dc7dae8f32f44d5795ced010c7e97 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 30 Aug 2026 21:28:36 +0200 Subject: [PATCH 1/2] [Fix] Durability --- packages/chat/src/components/message.tsx | 51 ++++++++++++++++-------- packages/chat/src/context.tsx | 19 +++++++-- packages/notifications/src/context.tsx | 8 +--- packages/shared/src/data.ts | 21 +++++----- 4 files changed, 61 insertions(+), 38 deletions(-) diff --git a/packages/chat/src/components/message.tsx b/packages/chat/src/components/message.tsx index cbcd37e..a2b153c 100644 --- a/packages/chat/src/components/message.tsx +++ b/packages/chat/src/components/message.tsx @@ -15,7 +15,7 @@ import { import MessageContextMenu from "./messageContextMenu"; import Media from "./media/media"; import { useStorage } from "@tensamin/storage/context"; -import { useMTP } from "@tensamin/mtp"; +import { requireRelaySuccess, useMTP } from "@tensamin/mtp"; import { getMessage, useChat } from "../context"; import { decryptChatText, encryptChatText } from "@tensamin/crypto/chatSecret"; import { log, toast } from "@tensamin/shared/log"; @@ -42,6 +42,7 @@ function MessageComponent({ user: SelectedUser | null; }) { const actuallyFailed = + Boolean(message.ErrorType) || (message.failed && message.MessageState === "awaiting") || message.decryptionFailed; @@ -65,7 +66,7 @@ function MessageComponent({ // Message states const { load } = useStorage(); - const { send } = useMTP(); + const { send, sendSealedRelay } = useMTP(); const [ownId, setOwnId] = useState(0); useEffect(() => { load("user_id").then(setOwnId); @@ -73,22 +74,38 @@ function MessageComponent({ const messageStateReadUpdate = useCallback(async () => { const readConfirmations = await load("settings.read_confirmations"); - if (readConfirmations) { - if (!user?.UserId) return; - - await send("MessageState", { - ChatPartnerId: user?.UserId, - SendTime: message.SendTime, - MessageState: "read", - }); - } else { - await send("MessageState", { - ChatPartnerId: user?.UserId, - SendTime: message.SendTime, - MessageState: "received", - }); + if (!user?.UserId || !message.RelayMessageId || ownId === 0) return; + const [ownIota, peerIota] = await Promise.all([ + send("GetIotaData", { UserId: ownId }), + send("GetIotaData", { UserId: user.UserId }), + ]); + if (ownIota.type !== "GetIotaData" || peerIota.type !== "GetIotaData") { + throw new Error("Could not resolve an Iota for this receipt"); } - }, [load, message.SendTime, user?.UserId, send]); + const response = await sendSealedRelay( + "MessageState", + { + ChatPartnerId: user.UserId, + RelayMessageId: message.RelayMessageId, + EventAt: Date.now(), + MessageState: readConfirmations ? "read" : "received", + ReceiverId: user.UserId, + }, + { + nextHop: { kind: "iota", id: ownIota.data.IotaId }, + finalRecipientId: user.UserId, + metadataRecipients: [ + { value: ownIota.data.PublicKey, encoding: "base64" }, + { value: peerIota.data.PublicKey, encoding: "base64" }, + ], + contentRecipients: [ + { value: ownIota.data.PublicKey, encoding: "base64" }, + { value: peerIota.data.PublicKey, encoding: "base64" }, + ], + }, + ); + requireRelaySuccess(response); + }, [load, message.RelayMessageId, ownId, send, sendSealedRelay, user]); useEffect(() => { if (message.SenderId === ownId || ownId === 0) return; diff --git a/packages/chat/src/context.tsx b/packages/chat/src/context.tsx index 5cf3535..d307a12 100644 --- a/packages/chat/src/context.tsx +++ b/packages/chat/src/context.tsx @@ -69,6 +69,14 @@ type MessageEdit = Partial< > >; +const messageStateRank: Record = { + awaiting: 0, + sending: 0, + sent: 1, + received: 2, + read: 3, +}; + function updateMessagesBySendTime( messages: T[], sendTime: number, @@ -81,6 +89,13 @@ function updateMessagesBySendTime( return item; } + if ( + edit.MessageState !== undefined && + messageStateRank[edit.MessageState] < messageStateRank[item.MessageState] + ) { + return item; + } + const entries = Object.entries(edit) as Array< [keyof MessageEdit, MessageEdit[keyof MessageEdit]] >; @@ -989,9 +1004,7 @@ export default function Provider({ children }: { children: ReactNode }) { ); return; } - editMessage(data.SendTime, { - MessageState: data.MessageState, - }); + editMessage(data.SendTime, { MessageState: data.MessageState }); }); return () => { unsubscribeEdit(); diff --git a/packages/notifications/src/context.tsx b/packages/notifications/src/context.tsx index e0da277..ef80f33 100644 --- a/packages/notifications/src/context.tsx +++ b/packages/notifications/src/context.tsx @@ -29,7 +29,7 @@ async function requestNotificationPermission() { } export default function Provider(props: { children: React.ReactNode }) { - const { subscribe, send } = useMTP(); + const { subscribe } = useMTP(); const { load } = useStorage(); const { get } = useUser(); const { addLiveMessage, chatSecret, getChatSecret, userId } = useChat(); @@ -79,11 +79,6 @@ export default function Provider(props: { children: React.ReactNode }) { // todo: add notification symbol to conversation cards (incl. message start) moveUserIdToTop(data.SenderId); - if (await load("settings.receive_confirmations")) { - void send("MessageState", { - MessageState: "received", - }); - } } const user = await get(data.SenderId, [ @@ -180,7 +175,6 @@ export default function Provider(props: { children: React.ReactNode }) { load, location.pathname, navigate, - send, moveUserIdToTop, subscribe, getChatSecret, diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts index 4a64c80..e7d2c33 100644 --- a/packages/shared/src/data.ts +++ b/packages/shared/src/data.ts @@ -70,6 +70,7 @@ const callSecretEnvelopeRequest = z.object({ export const Reaction = z.object({ Reaction: z.string(), SenderId: z.number(), + RelayMessageId: z.string().optional(), }); export const Message = z.object({ @@ -82,6 +83,8 @@ export const Message = z.object({ Avatar: z.boolean().optional(), Display: z.boolean().optional(), ReplyId: z.number().optional(), + UpdatedAt: z.number().optional(), + ErrorType: z.string().optional(), MessageState: z .enum(["read", "received", "sent", "sending", "awaiting"]) // awaiting for 'internal' use .default("received"), @@ -107,6 +110,7 @@ const authPayload = z.object({ .array( z.object({ LastMessageAt: z.number().default(0), + CreatedAt: z.number().optional(), UserId: z.number(), LastMessage: z .object({ @@ -397,17 +401,12 @@ export const mtp = { response: z.object({}), }, MessageState: { - request: z - .object({ - ChatPartnerId: z.number(), - SendTime: z.number(), - MessageState: Message.shape.MessageState, - }) - .or( - z.object({ - MessageState: Message.shape.MessageState, - }), - ), + request: z.object({ + ChatPartnerId: z.number(), + RelayMessageId: z.string(), + EventAt: z.number(), + MessageState: z.enum(["received", "read"]), + }), response: z.object({ ChatPartnerId: z.number(), MessageState: Message.shape.MessageState, From 5b8d0d85f23b1315bbca21f6f3a3c75691811809 Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Sun, 30 Aug 2026 23:00:37 +0300 Subject: [PATCH 2/2] chore(deps): update tauri-build digest to dd6befd --- apps/tauri/src-tauri/Cargo.lock | 182 +++++++++++++++++++++++++++++--- apps/tauri/src-tauri/Cargo.toml | 2 +- 2 files changed, 169 insertions(+), 15 deletions(-) diff --git a/apps/tauri/src-tauri/Cargo.lock b/apps/tauri/src-tauri/Cargo.lock index d404d61..c1398bb 100644 --- a/apps/tauri/src-tauri/Cargo.lock +++ b/apps/tauri/src-tauri/Cargo.lock @@ -560,6 +560,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "cfb" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3" +dependencies = [ + "fnv", + "uuid", + "web-time", +] + [[package]] name = "cfg-expr" version = "0.15.8" @@ -826,7 +837,20 @@ version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" dependencies = [ - "cssparser-macros", + "cssparser-macros 0.6.1", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" +dependencies = [ + "cssparser-macros 0.7.0", "dtoa-short", "itoa", "phf", @@ -843,6 +867,16 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "cssparser-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a2a99df6e410a8ff4245aa2006499ea662245f967cc7c0a38c83ef8eb44dbf" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "ctor" version = "0.8.0" @@ -1127,11 +1161,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" dependencies = [ "bit-set", - "cssparser", + "cssparser 0.36.0", "foldhash", - "html5ever", + "html5ever 0.38.0", "precomputed-hash", - "selectors", + "selectors 0.36.1", + "tendril", +] + +[[package]] +name = "dom_query" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc" +dependencies = [ + "bit-set", + "cssparser 0.37.0", + "foldhash", + "html5ever 0.39.0", + "precomputed-hash", + "selectors 0.38.0", "tendril", ] @@ -1982,7 +2031,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" dependencies = [ "log", - "markup5ever", + "markup5ever 0.38.0", +] + +[[package]] +name = "html5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" +dependencies = [ + "log", + "markup5ever 0.39.0", ] [[package]] @@ -2278,7 +2337,16 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" dependencies = [ - "cfb", + "cfb 0.7.3", +] + +[[package]] +name = "infer" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4" +dependencies = [ + "cfb 0.14.0", ] [[package]] @@ -2641,6 +2709,17 @@ dependencies = [ "web_atoms", ] +[[package]] +name = "markup5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + [[package]] name = "memchr" version = "2.8.3" @@ -4200,7 +4279,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ "bitflags 2.13.1", - "cssparser", + "cssparser 0.36.0", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "selectors" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d" +dependencies = [ + "bitflags 2.13.1", + "cssparser 0.37.0", "derive_more", "log", "new_debug_unreachable", @@ -4798,7 +4896,7 @@ dependencies = [ "serde_repr", "serialize-to-javascript", "swift-rs", - "tauri-build", + "tauri-build 2.6.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", "tauri-macros", "tauri-runtime", "tauri-runtime-wry", @@ -4833,6 +4931,26 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-build" +version = "2.6.3" +source = "git+https://github.com/tauri-apps/tauri?rev=dd6befda925845fc74ca595d6d42de6c49580dd8#dd6befda925845fc74ca595d6d42de6c49580dd8" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch 4.2.0", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=dd6befda925845fc74ca595d6d42de6c49580dd8)", + "tauri-winres", + "walkdir", +] + [[package]] name = "tauri-codegen" version = "2.6.3" @@ -5027,11 +5145,11 @@ dependencies = [ "anyhow", "cargo_metadata", "ctor 0.8.0", - "dom_query", + "dom_query 0.27.0", "dunce", "glob", "http", - "infer", + "infer 0.19.0", "json-patch 3.0.1", "log", "memchr", @@ -5063,11 +5181,11 @@ dependencies = [ "brotli", "cargo_metadata", "ctor 1.0.12", - "dom_query", + "dom_query 0.27.0", "dunce", "glob", "http", - "infer", + "infer 0.19.0", "json-patch 4.2.0", "log", "memchr", @@ -5091,6 +5209,42 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "git+https://github.com/tauri-apps/tauri?rev=dd6befda925845fc74ca595d6d42de6c49580dd8#dd6befda925845fc74ca595d6d42de6c49580dd8" +dependencies = [ + "anyhow", + "cargo_metadata", + "ctor 1.0.12", + "dom_query 0.28.0", + "dunce", + "glob", + "http", + "infer 0.22.0", + "json-patch 4.2.0", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.19", + "toml 1.1.4+spec-1.1.0", + "url", + "urlpattern 0.6.0", + "uuid", + "walkdir", +] + [[package]] name = "tauri-winres" version = "0.3.6" @@ -5146,7 +5300,7 @@ dependencies = [ "serde", "serde_json", "tauri", - "tauri-build", + "tauri-build 2.6.3 (git+https://github.com/tauri-apps/tauri?rev=dd6befda925845fc74ca595d6d42de6c49580dd8)", "tauri-plugin-deep-link", "tauri-plugin-log", "tauri-plugin-notification", @@ -6330,7 +6484,7 @@ dependencies = [ "cookie", "crossbeam-channel", "dirs", - "dom_query", + "dom_query 0.27.0", "dpi", "dunce", "gdkx11", diff --git a/apps/tauri/src-tauri/Cargo.toml b/apps/tauri/src-tauri/Cargo.toml index 2a68e95..269032f 100644 --- a/apps/tauri/src-tauri/Cargo.toml +++ b/apps/tauri/src-tauri/Cargo.toml @@ -15,7 +15,7 @@ name = "mobile_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { git = "https://github.com/tauri-apps/tauri", rev = "4af26a3f7f8b692d62cca549bbacd93f5ce90b41", features = [] } +tauri-build = { git = "https://github.com/tauri-apps/tauri", rev = "dd6befda925845fc74ca595d6d42de6c49580dd8", features = [] } [dependencies] tauri-plugin-opener = "2"