Skip to main content

huddle_core/app/
events.rs

1use libp2p::PeerId;
2
3#[derive(Debug, Clone)]
4pub struct DiscoveredRoom {
5    pub room_id: String,
6    pub name: String,
7    pub encrypted: bool,
8    pub member_count: u32,
9    pub creator_fingerprint: String,
10    pub last_seen: i64,
11    /// True for rooms loaded from local storage that we haven't rejoined
12    /// yet this session (encrypted rooms whose passphrase key isn't in
13    /// memory). The lobby renders these with a "saved" hint; pressing
14    /// Enter goes through the join flow with passphrase prompt.
15    pub restorable: bool,
16    /// huddle 0.5.1: cached host multiaddrs from the announcing peer's
17    /// `RoomAnnouncement.host_addrs`. Used by `dial_by_id_or_username`
18    /// to resolve an HD- ID or username back to a dialable address
19    /// when the target is on our gossipsub mesh.
20    pub host_addrs: Vec<String>,
21}
22
23#[derive(Debug, Clone)]
24pub enum AppEvent {
25    /// A room was discovered (announced on the global topic).
26    RoomDiscovered(DiscoveredRoom),
27    /// A previously-discovered room hasn't been re-announced — TTL expired.
28    RoomLost { room_id: String },
29    /// We successfully joined a room (subscribed to its topic).
30    RoomJoined { room_id: String },
31    /// We left a room.
32    RoomLeft { room_id: String },
33    /// A new member appeared in a room we're in.
34    MemberJoined {
35        room_id: String,
36        fingerprint: String,
37    },
38    /// A member left a room we're in.
39    MemberLeft {
40        room_id: String,
41        fingerprint: String,
42    },
43    /// A message arrived in a room.
44    MessageReceived {
45        room_id: String,
46        sender_fingerprint: String,
47        body: String,
48        sent_at: i64,
49    },
50    /// Our own message was sent successfully.
51    MessageSent {
52        room_id: String,
53        body: String,
54        message_id: i64,
55    },
56    /// Listening on a network address.
57    ListeningOn { address: String },
58    /// A peer was discovered on the LAN.
59    PeerDiscovered { peer_id: PeerId },
60    /// A peer's mDNS presence expired — they left the LAN or stopped
61    /// announcing. The lobby refreshes its online/offline indicators.
62    PeerExpired { peer_id: PeerId },
63    /// We've fired a dial command — useful for the UI to show "dialing...".
64    Dialing { address: String },
65    /// A user-initiated dial completed successfully.
66    DialSucceeded { address: String, peer_id: PeerId },
67    /// A user-initiated dial failed.
68    DialFailed { address: String, error: String },
69    /// Non-fatal error.
70    Error { description: String },
71    /// Someone (us or a peer) offered a file in a room.
72    FileOffered {
73        room_id: String,
74        file_id: String,
75        name: String,
76        size_bytes: u64,
77        sender_fingerprint: String,
78    },
79    /// A chunk of an incoming transfer arrived. `total_bytes` is the
80    /// announced size from the offer.
81    FileProgress {
82        file_id: String,
83        bytes_received: u64,
84        total_bytes: u64,
85    },
86    /// All chunks of a transfer received and SHA-256 verified.
87    FileReady { file_id: String },
88    /// User saved a ready file to Downloads.
89    FileSaved { file_id: String, path: String },
90    /// A transfer failed (hash mismatch, decrypt error, IO error).
91    FileFailed { file_id: String, reason: String },
92    /// A peer initiated a key rotation in a room we're in. The UI
93    /// surfaces a modal asking the user to enter the new passphrase.
94    RotationRequested {
95        room_id: String,
96        rotator_fingerprint: String,
97        new_salt: Vec<u8>,
98    },
99    /// Someone in a room started typing. The UI re-reads typing peers
100    /// from `AppHandle::typers_in_room` on each render; the event is
101    /// just a nudge.
102    TypingChanged { room_id: String },
103    /// A received message included our fingerprint (full or short
104    /// form). The TUI uses this to ring the terminal bell, even in
105    /// muted rooms.
106    MentionReceived { room_id: String, body: String },
107    /// Phase A: an unknown peer has dialed us and Identify has
108    /// completed. The TUI shows an accept/reject/trust modal with the
109    /// peer's short fingerprint. Routed through `replace_modal_if_idle`
110    /// so it doesn't clobber whatever the user is typing.
111    InboundDial {
112        peer_id: PeerId,
113        /// 24-char fingerprint, freshly derived from the peer's Ed25519
114        /// pubkey via Identify — proves they hold the matching key.
115        fingerprint: String,
116        /// String form of the listener-side multiaddr (the address as
117        /// seen from our side of the connection). Mostly informational
118        /// for the user; we persist it on accept so the lobby online
119        /// dot tracks the peer.
120        address: String,
121    },
122    /// Phase G: SAS code is ready on both sides — both ephemeral
123    /// X25519 pubkeys exchanged + ECDH derived. The TUI shows the
124    /// `code` (emoji + decimal) and the Match/Cancel buttons.
125    SasCodeReady {
126        room_id: String,
127        partner_fingerprint: String,
128        tx_id: String,
129        emoji_string: String,
130        emoji_labels: String,
131        decimal: String,
132    },
133    /// Phase G: SAS completed — both sides confirmed the match. The
134    /// partner's fingerprint is now verified (per-room + global).
135    SasVerified {
136        room_id: String,
137        partner_fingerprint: String,
138    },
139    /// Phase F follow-up: 30 seconds passed since we broadcast a
140    /// `CodeJoinRequest` and no `CodeJoinResponse` ever came back. The
141    /// owner either ignored us (bad/expired code), wasn't online, or
142    /// the network dropped our packet. Fired by the timeout task
143    /// spawned in `join_room_with_code` once it confirms our pending
144    /// secret is still sitting in the map.
145    CodeJoinTimedOut { room_id: String, reason: String },
146    /// Phase C follow-up: we dialed a peer via an invite link, the
147    /// peer identified, and the fingerprint they cryptographically
148    /// asserted doesn't match the one the invite claimed. The
149    /// connection has already been dropped. The TUI shows an error
150    /// modal so the user knows the link is forged or stale.
151    InviteFingerprintMismatch {
152        address: String,
153        claimed: String,
154        actual: String,
155    },
156    /// Phase D follow-up: aggregated NAT reachability state derived
157    /// from the AutoNAT probe stream. The app layer maintains a small
158    /// "do any probes say reachable?" tally; this event fires when
159    /// that aggregate changes. The TUI renders it as a badge in the
160    /// lobby header ('reachable' / 'private' / 'detecting').
161    NatStatusChanged { label: String, reachable: bool },
162    /// Phase D follow-up: a successful DCUtR upgrade — a relay-hopped
163    /// connection became direct. The TUI shows a transient status
164    /// line ("direct connection to <peer>"). Fires only on success;
165    /// failures stay in the debug log.
166    DcutrSucceeded { peer_label: String },
167    /// huddle 0.5: a peer announced or cleared their self-declared
168    /// username via a signed `ProfileUpdate`. `username = None` means
169    /// the peer is now `[anonymous]`. TUI consumers redraw the chat
170    /// + member list so the new label flows through.
171    PeerProfileUpdated {
172        fingerprint: String,
173        username: Option<String>,
174    },
175    /// huddle 0.5: the local user's `go_dark` call succeeded — every
176    /// joined room got a best-effort `MemberLeave`, the network task
177    /// shut down, and the data dir was wiped. TUI shows a final
178    /// "Goodbye" modal and exits the process.
179    WentDark,
180}