Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c7a834bc1 | |||
|
78e15f6218 |
|||
|
042141c781 |
42 changed files with 782 additions and 711 deletions
182
apps/tauri/src-tauri/Cargo.lock
generated
182
apps/tauri/src-tauri/Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/tauri": "workspace:*",
|
||||
"@tensamin/tauth": "workspace:*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"@tensamin/identity": "workspace:*",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"eventemitter3": "^5.0.4",
|
||||
"lucide-react": "^1.29.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { User } from "@tensamin/user/context";
|
||||
import type { User } from "@tensamin/identity/context";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarImage,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { User } from "@tensamin/user/context";
|
||||
import type { User } from "@tensamin/identity/context";
|
||||
import { Avatar, AvatarFallback, AvatarImage, Button } from "@methanium/ui";
|
||||
import { Text } from "@methanium/ui/markdown";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from "lucide-react";
|
||||
import { useLocation, useNavigate, useSearch } from "@tanstack/react-router";
|
||||
import { joinCall, useCall } from "@tensamin/call/store";
|
||||
import Wrapper from "@tensamin/user/wrapper";
|
||||
import Wrapper from "@tensamin/identity/wrapper";
|
||||
import { Skeleton } from "@methanium/ui";
|
||||
import {
|
||||
Select,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import Wrapper from "@tensamin/user/wrapper";
|
||||
import Wrapper from "@tensamin/identity/wrapper";
|
||||
import { Basic, Loading } from "./modals/basic";
|
||||
import List from "@/features/conversation/list/body";
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ import SidebarBox from "@tensamin/call/sidebarBox";
|
|||
import { useShowMobileNavbar } from "@/routes/app/useShowMobileNavbar";
|
||||
import { Ellipsis, Check } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useUser, type User } from "@tensamin/user/context";
|
||||
import { useUser, type User } from "@tensamin/identity/context";
|
||||
import { mtp, userPresencePreferenceSchema } from "@tensamin/shared/data";
|
||||
import { useMTP } from "@tensamin/mtp";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Basic, Loading } from "@/components/modals/basic";
|
||||
import Wrapper from "@tensamin/user/wrapper";
|
||||
import Wrapper from "@tensamin/identity/wrapper";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import ChatContext from "@tensamin/chat/context";
|
|||
import { useCall, useInitializeCall } from "@tensamin/call/store";
|
||||
import { useIsSpeaking } from "@tensamin/call/speakingState";
|
||||
import { Provider as MTPProvider } from "@tensamin/mtp";
|
||||
import UserProvider from "@tensamin/user/context";
|
||||
import UserProvider from "@tensamin/identity/context";
|
||||
import DeeplinkContext, { useDeeplinks } from "@tensamin/tauri/deeplinkHandler";
|
||||
import NotificationsProvider from "@tensamin/notifications/context";
|
||||
import PwaRuntime from "@tensamin/pwa/runtime";
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { isTauri } from "@tauri-apps/api/core";
|
|||
import { useSession } from "@tensamin/storage/session";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
import { ShieldAlert } from "lucide-react";
|
||||
import { useUser } from "@tensamin/identity/context";
|
||||
|
||||
// The page
|
||||
export default function Page() {
|
||||
|
|
@ -56,6 +57,7 @@ function AddConversationButton() {
|
|||
const { send, sendSealedRelay } = useMTP();
|
||||
const { contacts, insertContact } = useSession();
|
||||
const { load } = useStorage();
|
||||
const { getIota } = useUser();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
|
@ -110,23 +112,15 @@ function AddConversationButton() {
|
|||
|
||||
try {
|
||||
const userId = await load("user_id");
|
||||
const iota = await send("GetIotaData", { UserId: userId });
|
||||
if (iota.type !== "GetIotaData") {
|
||||
setError(`Iota lookup failed: ${iota.type}`);
|
||||
return;
|
||||
}
|
||||
const iota = await getIota(userId);
|
||||
const response = await sendSealedRelay(
|
||||
"AddConversation",
|
||||
{ ChatPartnerId: user.data.UserId },
|
||||
{
|
||||
nextHop: { kind: "iota", id: iota.data.IotaId },
|
||||
nextHop: { kind: "iota", id: iota.IotaId },
|
||||
finalRecipientId: userId,
|
||||
metadataRecipients: [
|
||||
{ value: iota.data.PublicKey, encoding: "base64" },
|
||||
],
|
||||
contentRecipients: [
|
||||
{ value: iota.data.PublicKey, encoding: "base64" },
|
||||
],
|
||||
metadataRecipients: [{ value: iota.PublicKey, encoding: "base64" }],
|
||||
contentRecipients: [{ value: iota.PublicKey, encoding: "base64" }],
|
||||
},
|
||||
);
|
||||
requireRelaySuccess(response);
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export default defineConfig({
|
|||
"@tensamin/settings",
|
||||
"@tensamin/storage",
|
||||
"@tensamin/mtp",
|
||||
"@tensamin/user",
|
||||
"@tensamin/identity",
|
||||
"@tensamin/tauri",
|
||||
"@tensamin/chat",
|
||||
],
|
||||
|
|
@ -118,7 +118,7 @@ export default defineConfig({
|
|||
"@tensamin/storage/context",
|
||||
"@tensamin/tauri",
|
||||
"@tensamin/tauth",
|
||||
"@tensamin/user",
|
||||
"@tensamin/identity",
|
||||
],
|
||||
},
|
||||
build: {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz",
|
||||
"mtp": "https://git.methanium.net/methanium/mtp/releases/download/0.3.0-b331b9f6a3/mtp-0.3.0.tgz",
|
||||
"mtp": "https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz",
|
||||
"sonner": "^2.0.8"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"@tensamin/mtp": "workspace:*",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"@tensamin/identity": "workspace:*",
|
||||
"deepfilternet3-noise-filter": "1.3.0",
|
||||
"livekit-client": "^2.21.0",
|
||||
"lucide-react": "^1.29.0",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@methanium/ui";
|
||||
import Wrapper from "@tensamin/user/wrapper";
|
||||
import Wrapper from "@tensamin/identity/wrapper";
|
||||
import { Mail } from "lucide-react";
|
||||
import { sendCallInvite, useCall } from "../../store";
|
||||
import { log, toast } from "@tensamin/shared/log";
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
Dialog,
|
||||
DialogContent,
|
||||
} from "@methanium/ui";
|
||||
import Wrapper from "@tensamin/user/wrapper";
|
||||
import Wrapper from "@tensamin/identity/wrapper";
|
||||
import { PhoneIncoming, X } from "lucide-react";
|
||||
|
||||
export default function InvitePopup({
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from "../../store";
|
||||
import { Track, type Participant } from "livekit-client";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { type SelectedUser, useUserFields } from "@tensamin/user/context";
|
||||
import { type SelectedUser, useUserFields } from "@tensamin/identity/context";
|
||||
import { useIsSpeaking } from "../../speakingState";
|
||||
import VideoViewer from "../videoViewer";
|
||||
import { HeadphoneOff, MicOff, Monitor, Plus, Shield } from "lucide-react";
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
useSidebar,
|
||||
} from "@methanium/ui";
|
||||
import { ScreenShareOff } from "lucide-react";
|
||||
import { useUserFields } from "@tensamin/user/context";
|
||||
import { useUserFields } from "@tensamin/identity/context";
|
||||
import { useIsSpeaking, useLastSpeakingParticipantId } from "../speakingState";
|
||||
import { getAverageImageColor } from "./modals/base";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useUserFields } from "@tensamin/user/context";
|
||||
import { useUserFields } from "@tensamin/identity/context";
|
||||
import { useCall, getRoom } from "../store";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from "@tensamin/crypto/callSecret";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
import { useSession } from "@tensamin/storage/session";
|
||||
import { useUser } from "@tensamin/user/context";
|
||||
import { useUser } from "@tensamin/identity/context";
|
||||
import { DeepFilterNoiseFilterProcessor } from "deepfilternet3-noise-filter";
|
||||
import {
|
||||
ExternalE2EEKeyProvider,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useCall } from "../store";
|
||||
import { useUserFields } from "@tensamin/user/context";
|
||||
import { useUserFields } from "@tensamin/identity/context";
|
||||
|
||||
const USER_FIELDS = ["Display"] as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
"@tensamin/mtp": "workspace:*",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"@tensamin/identity": "workspace:*",
|
||||
"lucide-react": "^1.29.0",
|
||||
"motion": "^13.0.0",
|
||||
"react": "^19.2.8",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import GifPicker from "./media/gifPicker";
|
|||
import ReplyBox from "./replyBox";
|
||||
import { useHotkey } from "@tensamin/hotkeys";
|
||||
import { editLastMessageHotkey } from "../hotkeys";
|
||||
import { useUser } from "@tensamin/identity/context";
|
||||
|
||||
export default function InputComponent({
|
||||
value,
|
||||
|
|
@ -39,7 +40,8 @@ export default function InputComponent({
|
|||
}) {
|
||||
const [invertEnterBehavior, setInvertEnterBehavior] = useState(false);
|
||||
|
||||
const { send, sendSealedRelay } = useMTP();
|
||||
const { sendSealedRelay } = useMTP();
|
||||
const { getIota } = useUser();
|
||||
const {
|
||||
addLiveMessage,
|
||||
chatSecret,
|
||||
|
|
@ -176,12 +178,9 @@ export default function InputComponent({
|
|||
|
||||
try {
|
||||
const [ownIota, peerIota] = await Promise.all([
|
||||
send("GetIotaData", { UserId: ownId }),
|
||||
send("GetIotaData", { UserId: userId }),
|
||||
getIota(ownId),
|
||||
getIota(userId),
|
||||
]);
|
||||
if (ownIota.type !== "GetIotaData" || peerIota.type !== "GetIotaData") {
|
||||
throw new Error("Could not resolve an Iota for this message");
|
||||
}
|
||||
const response = await sendSealedRelay(
|
||||
"MessageSend",
|
||||
{
|
||||
|
|
@ -191,15 +190,15 @@ export default function InputComponent({
|
|||
...(replyTo && { ReplyId: replyTo }),
|
||||
},
|
||||
{
|
||||
nextHop: { kind: "iota", id: ownIota.data.IotaId },
|
||||
nextHop: { kind: "iota", id: ownIota.IotaId },
|
||||
finalRecipientId: userId,
|
||||
metadataRecipients: [
|
||||
{ value: ownIota.data.PublicKey, encoding: "base64" },
|
||||
{ value: peerIota.data.PublicKey, encoding: "base64" },
|
||||
{ value: ownIota.PublicKey, encoding: "base64" },
|
||||
{ value: peerIota.PublicKey, encoding: "base64" },
|
||||
],
|
||||
contentRecipients: [
|
||||
{ value: ownIota.data.PublicKey, encoding: "base64" },
|
||||
{ value: peerIota.data.PublicKey, encoding: "base64" },
|
||||
{ value: ownIota.PublicKey, encoding: "base64" },
|
||||
{ value: peerIota.PublicKey, encoding: "base64" },
|
||||
],
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
} from "lucide-react";
|
||||
import { useState, useMemo, useEffect, useRef, type WheelEvent } from "react";
|
||||
import MediaSaveButton from "./mediaSaveButton";
|
||||
import { useUserFields } from "@tensamin/user/context";
|
||||
import { useUserFields } from "@tensamin/identity/context";
|
||||
|
||||
const zoomLevels = [1, 1.5, 2, 3];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { RawMessage } from "../values";
|
||||
import { AlertTriangle, Check, CheckLine, RefreshCw } from "lucide-react";
|
||||
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { type SelectedUser, useUserFields } from "@tensamin/user/context";
|
||||
import { type SelectedUser, useUserFields } from "@tensamin/identity/context";
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import {
|
|||
Skeleton,
|
||||
} from "@methanium/ui";
|
||||
import { Text } from "@methanium/ui/markdown";
|
||||
import type { SelectedUser } from "@tensamin/user/context";
|
||||
import Wrapper from "@tensamin/user/wrapper";
|
||||
import type { SelectedUser } from "@tensamin/identity/context";
|
||||
import Wrapper from "@tensamin/identity/wrapper";
|
||||
import { Forward, X } from "lucide-react";
|
||||
|
||||
type ReplyUserData = SelectedUser<readonly ["Avatar", "Display"]>;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import { useStorage } from "@tensamin/storage/context";
|
|||
import { requireRelaySuccess, useMTP } from "@tensamin/mtp";
|
||||
import { log, toast } from "@tensamin/shared/log";
|
||||
import { useSession } from "@tensamin/storage/session";
|
||||
import { useUser } from "@tensamin/user/context";
|
||||
import { useUser } from "@tensamin/identity/context";
|
||||
import { createCache, type ChatDraft } from "@tensamin/cache";
|
||||
import { secureValueCodec } from "@tensamin/storage/secure";
|
||||
|
||||
|
|
@ -227,7 +227,7 @@ export async function fetchReplyMessage({
|
|||
export default function Provider({ children }: { children: ReactNode }) {
|
||||
const { load } = useStorage();
|
||||
const { send, sendSealedRelay, subscribe } = useMTP();
|
||||
const { get: getUser } = useUser();
|
||||
const { get: getUser, getIota } = useUser();
|
||||
const { moveUserIdToTop } = useSession();
|
||||
|
||||
const [error, setError] = useState("");
|
||||
|
|
@ -469,14 +469,8 @@ export default function Provider({ children }: { children: ReactNode }) {
|
|||
version: CHAT_SECRET_VERSION,
|
||||
});
|
||||
|
||||
const ownIota = await send("GetIotaData", { UserId: ownUserId });
|
||||
if (ownIota.type !== "GetIotaData") {
|
||||
throw new Error(`Own Iota lookup failed: ${ownIota.type}`);
|
||||
}
|
||||
const peerIota = await send("GetIotaData", { UserId: userIdValue });
|
||||
if (peerIota.type !== "GetIotaData") {
|
||||
throw new Error(`Peer Iota lookup failed: ${peerIota.type}`);
|
||||
}
|
||||
const ownIota = await getIota(ownUserId);
|
||||
const peerIota = await getIota(userIdValue);
|
||||
const response = await sendSealedRelay(
|
||||
"SetChatSecret",
|
||||
{
|
||||
|
|
@ -499,15 +493,15 @@ export default function Provider({ children }: { children: ReactNode }) {
|
|||
],
|
||||
},
|
||||
{
|
||||
nextHop: { kind: "iota", id: ownIota.data.IotaId },
|
||||
nextHop: { kind: "iota", id: ownIota.IotaId },
|
||||
finalRecipientId: userIdValue,
|
||||
metadataRecipients: [
|
||||
{ value: ownIota.data.PublicKey, encoding: "base64" },
|
||||
{ value: peerIota.data.PublicKey, encoding: "base64" },
|
||||
{ value: ownIota.PublicKey, encoding: "base64" },
|
||||
{ value: peerIota.PublicKey, encoding: "base64" },
|
||||
],
|
||||
contentRecipients: [
|
||||
{ value: ownIota.data.PublicKey, encoding: "base64" },
|
||||
{ value: peerIota.data.PublicKey, encoding: "base64" },
|
||||
{ value: ownIota.PublicKey, encoding: "base64" },
|
||||
{ value: peerIota.PublicKey, encoding: "base64" },
|
||||
],
|
||||
},
|
||||
);
|
||||
|
|
@ -529,7 +523,7 @@ export default function Provider({ children }: { children: ReactNode }) {
|
|||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [getUser, load, send, sendSealedRelay, userIdValue]);
|
||||
}, [getIota, getUser, load, send, sendSealedRelay, userIdValue]);
|
||||
|
||||
const getChatSecret = useCallback(
|
||||
async (userId: number): Promise<Uint8Array | null> => {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
type LiveMessage,
|
||||
type RawMessage,
|
||||
} from "./values";
|
||||
import Wrapper from "@tensamin/user/wrapper";
|
||||
import Wrapper from "@tensamin/identity/wrapper";
|
||||
|
||||
function shouldFetchPreviousPage({
|
||||
entry,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@tensamin/user",
|
||||
"name": "@tensamin/identity",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
|
|
@ -25,6 +25,7 @@ import { getChangedUserFields, selectUserFields } from "./selection";
|
|||
export { getChangedUserFields, selectUserFields } from "./selection";
|
||||
|
||||
export type User = z.infer<typeof schemas.GetUserData.response>;
|
||||
export type Iota = z.infer<typeof schemas.GetIotaData.response>;
|
||||
type ClientUserState = z.infer<typeof clientUserStateSchema>;
|
||||
export type UserField = keyof User;
|
||||
export type UserFields = readonly [UserField, ...UserField[]];
|
||||
|
|
@ -44,7 +45,7 @@ export function mergeTransientPresence<T extends { UserId: number }>(
|
|||
return state === undefined ? user : ({ ...user, OnlineStatus: state } as T);
|
||||
}
|
||||
|
||||
const USER_CACHE_MAX_AGE = 5 * 60 * 1000;
|
||||
const DATA_CACHE_MAX_AGE = 5 * 60 * 1000;
|
||||
|
||||
interface contextValue {
|
||||
get<const Fields extends UserFields>(
|
||||
|
|
@ -61,6 +62,7 @@ interface contextValue {
|
|||
listener: () => void,
|
||||
): () => void;
|
||||
getVersion(userId: number, fields: readonly UserField[]): string;
|
||||
getIota(userId: number): Promise<Iota>;
|
||||
updateProfile(userId: number, patch: UserProfilePatch): Promise<void>;
|
||||
updateState(userId: number, state: ClientUserState): void;
|
||||
}
|
||||
|
|
@ -75,6 +77,9 @@ const UserContext = createContext<contextValue | undefined>(undefined);
|
|||
export default function UserProvider(props: { children: ReactNode }) {
|
||||
const storageRef = useRef<Record<number, User>>({});
|
||||
const pendingRef = useRef<Record<number, Promise<User> | undefined>>({});
|
||||
const iotaStorageRef = useRef<Record<number, Iota>>({});
|
||||
const pendingIotaRef = useRef<Record<number, Promise<Iota> | undefined>>({});
|
||||
const iotaCheckedAtRef = useRef<Record<number, number>>({});
|
||||
const profileUpdateQueuesRef = useRef(new Map<number, Promise<void>>());
|
||||
const profileGenerationsRef = useRef(new Map<number, number>());
|
||||
const checkedAtRef = useRef<Record<number, number>>({});
|
||||
|
|
@ -230,7 +235,7 @@ export default function UserProvider(props: { children: ReactNode }) {
|
|||
if (cached) {
|
||||
installProfile(cached);
|
||||
const checkedAt = checkedAtRef.current[userId];
|
||||
if (!checkedAt || Date.now() - checkedAt < USER_CACHE_MAX_AGE) {
|
||||
if (!checkedAt || Date.now() - checkedAt < DATA_CACHE_MAX_AGE) {
|
||||
checkedAtRef.current[userId] = Date.now();
|
||||
return storageRef.current[userId];
|
||||
}
|
||||
|
|
@ -289,6 +294,52 @@ export default function UserProvider(props: { children: ReactNode }) {
|
|||
[loadUser],
|
||||
);
|
||||
|
||||
const getIota = useCallback(
|
||||
async (userId: number): Promise<Iota> => {
|
||||
if (userId == null) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
|
||||
const pendingIota = pendingIotaRef.current[userId];
|
||||
if (pendingIota !== undefined) {
|
||||
return pendingIota;
|
||||
}
|
||||
|
||||
const cached = iotaStorageRef.current[userId];
|
||||
const checkedAt = iotaCheckedAtRef.current[userId];
|
||||
if (cached && checkedAt && Date.now() - checkedAt < DATA_CACHE_MAX_AGE) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const request = (async () => {
|
||||
try {
|
||||
const response = await send("GetIotaData", { UserId: userId });
|
||||
if (response.type !== "GetIotaData") {
|
||||
throw new Error(`GetIotaData failed: ${response.type}`);
|
||||
}
|
||||
const iota = schemas.GetIotaData.response.parse(response.data);
|
||||
iotaStorageRef.current[userId] = iota;
|
||||
iotaCheckedAtRef.current[userId] = Date.now();
|
||||
return iota;
|
||||
} catch (error) {
|
||||
if (cached) {
|
||||
iotaCheckedAtRef.current[userId] = Date.now();
|
||||
return cached;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
|
||||
pendingIotaRef.current[userId] = request;
|
||||
try {
|
||||
return await request;
|
||||
} finally {
|
||||
delete pendingIotaRef.current[userId];
|
||||
}
|
||||
},
|
||||
[send],
|
||||
);
|
||||
|
||||
const peek = useCallback(
|
||||
<const Fields extends UserFields>(userId: number, fields: Fields) => {
|
||||
const user = storageRef.current[userId];
|
||||
|
|
@ -387,13 +438,14 @@ export default function UserProvider(props: { children: ReactNode }) {
|
|||
const value = useMemo(
|
||||
() => ({
|
||||
get,
|
||||
getIota,
|
||||
peek,
|
||||
subscribe,
|
||||
getVersion,
|
||||
updateProfile,
|
||||
updateState,
|
||||
}),
|
||||
[get, getVersion, peek, subscribe, updateProfile, updateState],
|
||||
[get, getIota, getVersion, peek, subscribe, updateProfile, updateState],
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -4,7 +4,6 @@ import {
|
|||
base64ToBytes,
|
||||
ConnectionState,
|
||||
MTPClient,
|
||||
MTPProtocolError,
|
||||
} from "mtp";
|
||||
import createAsyncQueue from "@tensamin/shared/asyncQueue";
|
||||
import {
|
||||
|
|
@ -22,7 +21,6 @@ import {
|
|||
type MTPContextType,
|
||||
type ProtocolMessage,
|
||||
removeMissingContacts,
|
||||
sealedRelayResultFromFrame,
|
||||
type SealedRelaySend,
|
||||
useMessageHandlers,
|
||||
} from "./mtpContext";
|
||||
|
|
@ -212,16 +210,13 @@ export function BrowserProvider(props: {
|
|||
() => async (type, data, options) => {
|
||||
const client = clientRef.current;
|
||||
if (!client) throw new Error("mtp is not connected");
|
||||
try {
|
||||
return sealedRelayResultFromFrame(
|
||||
await client.requestSealedRelay(type, data, options),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof MTPProtocolError) {
|
||||
return sealedRelayResultFromFrame(error.frame);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
await client.sendSealedRelay(type, data, {
|
||||
nextHopId: options.nextHop.id,
|
||||
finalRecipientId: options.finalRecipientId,
|
||||
metadataRecipients: options.metadataRecipients,
|
||||
contentRecipients: options.contentRecipients,
|
||||
});
|
||||
return { type: "Success", data: {} };
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
"@tensamin/mtp": "workspace:*",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"@tensamin/identity": "workspace:*",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"sonner": "^2.0.7"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useStorage } from "@tensamin/storage/context";
|
||||
import { useUser } from "@tensamin/user/context";
|
||||
import { useUser } from "@tensamin/identity/context";
|
||||
import { useChat } from "@tensamin/chat/context";
|
||||
import { useMTP } from "@tensamin/mtp";
|
||||
import { createContext, useEffect, useContext } from "react";
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"@tensamin/mtp": "workspace:*",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"@tensamin/identity": "workspace:*",
|
||||
"lucide-react": "^1.29.0",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
useUser,
|
||||
useUserFields,
|
||||
type SelectedUser,
|
||||
} from "@tensamin/user/context";
|
||||
} from "@tensamin/identity/context";
|
||||
import { Check } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/tauri": "workspace:*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"@tensamin/identity": "workspace:*",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { type ReactNode } from "react";
|
||||
/**
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import { useUser } from "@tensamin/user/context";
|
||||
import { useUser } from "@tensamin/identity/context";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
import {
|
||||
Button,
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { User } from "./context";
|
||||
import { getChangedUserFields, selectUserFields } from "./selection";
|
||||
|
||||
const user = {
|
||||
Avatar: undefined,
|
||||
Display: "Alice",
|
||||
IotaId: 1,
|
||||
OmikronConnections: [],
|
||||
OnlineStatus: "user_online",
|
||||
PublicKey: "AA==",
|
||||
SubEnd: 0,
|
||||
SubLevel: 0,
|
||||
UserId: 1,
|
||||
Username: "alice",
|
||||
} satisfies User;
|
||||
|
||||
describe("presence selection", () => {
|
||||
it("notifies OnlineStatus selections when presence changes", () => {
|
||||
const next = { ...user, OnlineStatus: "user_dnd" as const };
|
||||
|
||||
expect(getChangedUserFields(user, next)).toEqual(["OnlineStatus"]);
|
||||
expect(selectUserFields(next, ["OnlineStatus"])).toEqual({
|
||||
OnlineStatus: "user_dnd",
|
||||
});
|
||||
});
|
||||
});
|
||||
1071
pnpm-lock.yaml
generated
1071
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -5,6 +5,8 @@ allowBuilds:
|
|||
electron: true
|
||||
electron-winstaller: true
|
||||
esbuild: true
|
||||
ffi: false
|
||||
ref: false
|
||||
overrides:
|
||||
"@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz"
|
||||
mtp: "link:../../../mtp"
|
||||
mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz"
|
||||
|
|
|
|||
Loading…
Reference in a new issue