From 36a485ea2625e4f35f7cff85feb6ba8f5590c4d1 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Mon, 25 May 2026 01:03:31 +0200 Subject: [PATCH] feat: chat E2EE MVP - rooms list + timeline + composer + sync (issues 0148+0149+0150) Backend extends MatrixService with Start()/Stop()/ListRooms()/LoadTimeline()/ SendText()/SendMarkdown(). On login the service initialises the crypto store (cryptohelper, Olm/Megolm via goolm build tag) and a sync loop that fans events out through Wails events ("matrix:event", "matrix:error"). Pickle key is 32 random bytes hex-encoded in the OS keyring alongside the access token, so the crypto SQLite store survives restarts. Vendors 4 fresh helpers from fn_registry/functions/infra/: matrix_crypto_init.go (//go:build goolm || libolm) matrix_sync_service.go matrix_message_send.go matrix_room_list.go Plus the existing 3 (mas_oidc_loopback, keyring_token_store, matrix_client_init). go-sqlite3 driver pulled explicitly via sqlite_driver.go. Frontend rewires HomeScreen as a 3-zone AppShell (sidebar / timeline / composer). useMatrixRooms polls + reacts to the sync stream; useMatrixTimeline loads the last 50 events of the selected room and appends live ones. New components: RoomList, Timeline, EventBubble, Composer. Composer supports plain text (default) and a markdown toggle; Enter sends, Shift+Enter newline. wails.json now passes "build:tags": "goolm" by default. Tested with wails build -tags goolm on linux/amd64 and windows/amd64. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/HomeScreen.tsx | 248 +++++++++++---- frontend/src/components/Composer.tsx | 88 ++++++ frontend/src/components/EventBubble.tsx | 80 +++++ frontend/src/components/RoomList.tsx | 88 ++++++ frontend/src/components/Timeline.tsx | 64 ++++ frontend/src/hooks/useMatrixRooms.ts | 54 ++++ frontend/src/hooks/useMatrixTimeline.ts | 66 ++++ frontend/src/types.ts | 19 ++ go.mod | 22 +- go.sum | 40 +++ helpers.go | 4 - internal/infra/keyring_token_store.go | 84 ++++++ internal/infra/mas_oidc_loopback.go | 382 ++++++++++++++++++++++++ internal/infra/matrix_client_init.go | 153 ++++++++++ internal/infra/matrix_crypto_init.go | 107 +++++++ internal/infra/matrix_message_send.go | 121 ++++++++ internal/infra/matrix_room_list.go | 300 +++++++++++++++++++ internal/infra/matrix_sync_service.go | 366 +++++++++++++++++++++++ matrix_service.go | 320 ++++++++++++++++++-- sqlite_driver.go | 15 + wails.json | 1 + 21 files changed, 2529 insertions(+), 93 deletions(-) create mode 100644 frontend/src/components/Composer.tsx create mode 100644 frontend/src/components/EventBubble.tsx create mode 100644 frontend/src/components/RoomList.tsx create mode 100644 frontend/src/components/Timeline.tsx create mode 100644 frontend/src/hooks/useMatrixRooms.ts create mode 100644 frontend/src/hooks/useMatrixTimeline.ts create mode 100644 frontend/src/types.ts create mode 100644 internal/infra/keyring_token_store.go create mode 100644 internal/infra/mas_oidc_loopback.go create mode 100644 internal/infra/matrix_client_init.go create mode 100644 internal/infra/matrix_crypto_init.go create mode 100644 internal/infra/matrix_message_send.go create mode 100644 internal/infra/matrix_room_list.go create mode 100644 internal/infra/matrix_sync_service.go create mode 100644 sqlite_driver.go diff --git a/frontend/src/HomeScreen.tsx b/frontend/src/HomeScreen.tsx index e147ba5..e5d60ae 100644 --- a/frontend/src/HomeScreen.tsx +++ b/frontend/src/HomeScreen.tsx @@ -1,27 +1,39 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { AppShell, - Avatar, Badge, Box, + Burger, Button, - Code, + Center, Group, - Paper, + Loader, Stack, Text, Title, } from "@mantine/core"; -import { IconLogout, IconUserCircle } from "@tabler/icons-react"; -import { GetSession, Logout } from "../wailsjs/go/main/MatrixService"; +import { useDisclosure } from "@mantine/hooks"; +import { notifications } from "@mantine/notifications"; +import { + IconLock, + IconLogout, + IconUserCircle, +} from "@tabler/icons-react"; +import { + Logout, + SendMarkdown, + SendText, + Start, + Stop, +} from "../wailsjs/go/main/MatrixService"; +import { EventsOn } from "../wailsjs/runtime/runtime"; +import RoomList from "./components/RoomList"; +import Timeline from "./components/Timeline"; +import Composer from "./components/Composer"; +import { useMatrixRooms } from "./hooks/useMatrixRooms"; +import { useMatrixTimeline } from "./hooks/useMatrixTimeline"; -interface Session { - user_id: string; - device_id: string; - homeserver_url: string; - has_token: boolean; - expires_at?: string; -} +const NAVBAR_WIDTH = 300; export default function HomeScreen({ userID, @@ -30,12 +42,63 @@ export default function HomeScreen({ userID: string; onLogout: () => void; }) { - const [session, setSession] = useState(null); + const [navOpen, navHandlers] = useDisclosure(true); + const [activeRoomID, setActiveRoomID] = useState(null); + const [started, setStarted] = useState(false); + const [startError, setStartError] = useState(null); + const { rooms } = useMatrixRooms(started); + const { events, loading: timelineLoading, error: timelineError } = useMatrixTimeline( + activeRoomID, + 50, + ); + + // Boot sync on mount; tear down on unmount. useEffect(() => { - GetSession(userID).then((s) => setSession(s as Session | null)); + let cancelled = false; + (async () => { + try { + await Start(userID); + if (!cancelled) setStarted(true); + } catch (e: any) { + if (!cancelled) { + const msg = String(e?.message ?? e); + setStartError(msg); + notifications.show({ + title: "Sync error", + message: msg, + color: "red", + autoClose: false, + }); + } + } + })(); + return () => { + cancelled = true; + Stop().catch(() => {}); + }; }, [userID]); + // Show transient Matrix errors as toast notifications. + useEffect(() => { + const off = EventsOn("matrix:error", (msg: string) => { + notifications.show({ + title: "Matrix", + message: String(msg), + color: "orange", + autoClose: 6000, + }); + }); + return () => { + if (typeof off === "function") off(); + }; + }, []); + + const activeRoom = useMemo( + () => rooms.find((r) => r.room_id === activeRoomID) || null, + [rooms, activeRoomID], + ); + async function handleLogout() { try { await Logout(userID); @@ -44,22 +107,43 @@ export default function HomeScreen({ } } - const initials = userID - .replace("@", "") - .split(":")[0] - .slice(0, 2) - .toUpperCase(); + async function handleSendText(body: string) { + if (!activeRoomID) return; + await SendText(activeRoomID, body); + } + + async function handleSendMarkdown(md: string) { + if (!activeRoomID) return; + await SendMarkdown(activeRoomID, md); + } return ( - + - - + + + matrix_client_pc - v0.1.0 + v0.2.0 + + {userID} +