Skip to main content

iris_chat_core/
updates.rs

1use crate::actions::AppAction;
2use crate::state::{AppState, MutualGroupsSnapshot, PeerProfileDebugSnapshot};
3use crate::DesktopNearbySnapshot;
4use flume::Sender;
5use nostr_sdk::prelude::{Event, RelayStatus};
6
7#[derive(uniffi::Enum, Clone, Debug)]
8#[allow(clippy::large_enum_variant)]
9pub enum AppUpdate {
10    CallMedia {
11        call_id: String,
12        kind: u8,
13        sequence: u32,
14        timestamp_us: u64,
15        key_frame: bool,
16        data: Vec<u8>,
17    },
18    FullState(AppState),
19    PersistAccountBundle {
20        rev: u64,
21        owner_nsec: Option<String>,
22        owner_pubkey_hex: String,
23        device_nsec: String,
24    },
25    PersistPendingDeviceLink {
26        device_nsec: String,
27        approval_bootstrap_json: String,
28    },
29    ClearPendingDeviceLink,
30    SignerLoginSignEvent {
31        request_id: String,
32        owner_pubkey_hex: String,
33        unsigned_event_json: String,
34    },
35    NearbyPublishedEvent {
36        event_id: String,
37        kind: u32,
38        created_at_secs: u64,
39        event_json: String,
40    },
41    NearbyPeersChanged {
42        snapshot: DesktopNearbySnapshot,
43        bluetooth_peer_ids: Vec<String>,
44        lan_peer_ids: Vec<String>,
45    },
46}
47
48pub(crate) fn enqueue_update_for_delivery(
49    update: AppUpdate,
50    latest_full_state: &mut Option<AppUpdate>,
51    before_full_state: &mut Vec<AppUpdate>,
52    after_full_state: &mut Vec<AppUpdate>,
53) {
54    match update {
55        full @ AppUpdate::FullState(_) => *latest_full_state = Some(full),
56        nearby @ (AppUpdate::CallMedia { .. }
57        | AppUpdate::NearbyPublishedEvent { .. }
58        | AppUpdate::NearbyPeersChanged { .. }) => after_full_state.push(nearby),
59        other => before_full_state.push(other),
60    }
61}
62
63#[derive(Clone, Debug, PartialEq, Eq)]
64pub(crate) struct FipsNearbyLinkSnapshot {
65    pub(crate) device_pubkey_hex: String,
66    pub(crate) transport_type: String,
67    pub(crate) transport_addr: Option<String>,
68}
69
70#[derive(Debug)]
71pub(crate) enum CoreMsg {
72    Action(AppAction),
73    AcceptDirectFiles {
74        chat_id: String,
75        transfer_id: String,
76        destination: std::sync::Arc<dyn crate::DirectFileDestination>,
77    },
78    Internal(Box<InternalEvent>),
79    ExportSupportBundle(Sender<String>),
80    PeerProfileDebug {
81        owner_input: String,
82        reply_tx: Sender<Option<PeerProfileDebugSnapshot>>,
83    },
84    MutualGroups {
85        owner_input: String,
86        reply_tx: Sender<MutualGroupsSnapshot>,
87    },
88    AttachHostBle {
89        attachment: crate::core::HostBleAttachment,
90        reply_tx: Sender<Result<(), String>>,
91    },
92    DetachHostBle {
93        ownership: crate::core::HostBleOwnership,
94        reply_tx: Sender<()>,
95    },
96    HostBleActivity {
97        ownership: crate::core::HostBleOwnership,
98        generation: u64,
99        active: bool,
100        reply_tx: Sender<bool>,
101    },
102    PrepareForSuspend(Sender<()>),
103    /// Snapshot of core-internal perf counters (debug-snapshot
104    /// rebuild count etc.) — read by `FfiApp::core_perf_counters()`
105    /// so the release-gate budget tests can assert on internal hot
106    /// loops, not just FFI surface traffic.
107    CorePerfCounters(Sender<CorePerfCountersSnapshot>),
108    Shutdown(Option<Sender<()>>),
109    #[cfg(test)]
110    PanicForTest,
111}
112
113#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
114pub(crate) struct CorePerfCountersSnapshot {
115    /// `build_runtime_debug_snapshot` calls since core start.
116    pub debug_snapshot_builds: u64,
117}
118
119#[derive(Debug)]
120pub(crate) enum InternalEvent {
121    PrivateInviteUploaded {
122        request_id: String,
123        owner: String,
124        device: String,
125        result: Result<String, String>,
126    },
127    PrivateInviteResolved {
128        request_id: String,
129        owner: String,
130        result: Result<crate::invite_record::InviteRecord, String>,
131    },
132    DeviceLinkSignerRepair {
133        token: String,
134        heads: Vec<Event>,
135        reply: tokio::sync::oneshot::Sender<Result<Event, String>>,
136    },
137    DeviceLinkSignerRequest {
138        token: String,
139        unsigned_event_json: String,
140        previous: Option<Event>,
141        reply: tokio::sync::oneshot::Sender<
142            Result<(Event, crate::core::device_link_signer::DeviceLinkInfo), String>,
143        >,
144    },
145    DeviceLinkSignerFinished {
146        token: String,
147        success: bool,
148        error: Option<String>,
149    },
150    PrivateContactSyncTick {
151        generation: u64,
152    },
153    FollowUpdateReady {
154        request_id: String,
155        result: Result<Vec<Event>, String>,
156    },
157    RemoteSignerProgress {
158        token: String,
159        phase: crate::RemoteSignerPhase,
160        auth_url: Option<String>,
161    },
162    RemoteSignerConnected {
163        token: String,
164        owner_pubkey_hex: String,
165    },
166    RemoteSignerSigned {
167        token: String,
168        request_id: String,
169        signed_event_json: String,
170        link_info: Option<crate::core::device_link_signer::DeviceLinkInfo>,
171    },
172    RemoteSignerFailed {
173        token: String,
174        message: String,
175    },
176    DirectFilesPrepared {
177        generation: u64,
178        device: String,
179        result: Result<crate::core::direct_files::Record, String>,
180    },
181    DirectFile {
182        generation: u64,
183        event: crate::core::direct_file_tcp::DirectFileEvent,
184    },
185    CallPacket {
186        source_pubkey_hex: String,
187        source_port: u16,
188        data: Vec<u8>,
189    },
190    CallMediaBatch {
191        packets: Vec<(String, u16, Vec<u8>)>,
192        received_at: std::time::Instant,
193        // Released on consumption or discard, including during shutdown.
194        _permit: tokio::sync::OwnedSemaphorePermit,
195    },
196    CallRecoveryTick {
197        call_id: String,
198    },
199    CallTick {
200        call_id: String,
201    },
202    SignerLoginFetched {
203        request_id: String,
204        result: Result<Option<Event>, String>,
205    },
206    SignerLoginPublished {
207        request_id: String,
208        result: Result<Event, String>,
209    },
210    SignerLoginTimedOut {
211        request_id: String,
212    },
213    RelayEvent(Event),
214    MeshEvent(Event),
215    FipsNearbyPacket {
216        source_pubkey_hex: String,
217        source_port: u16,
218        data: Vec<u8>,
219    },
220    FipsNearbyPeersChanged {
221        generation: u64,
222        peers: Vec<FipsNearbyLinkSnapshot>,
223    },
224    FipsPeerDiscoveryNeeded {
225        generation: u64,
226        author: nostr::PublicKey,
227        requires_pending_work: bool,
228    },
229    FetchTrackedPeerCatchUp {
230        token: u64,
231    },
232    ProtocolSubscriptionLivenessCheck {
233        token: u64,
234    },
235    RetryReadyProtocolWork {
236        due_at: tokio::time::Instant,
237    },
238    PollPendingDeviceInvites {
239        token: u64,
240    },
241    PollPendingDeviceLink {
242        token: u64,
243    },
244    PendingDeviceLinkRefreshFinished {
245        token: u64,
246        events: Vec<Event>,
247    },
248    ExpireChatMutes {
249        token: u64,
250    },
251    PruneExpiredMessages {
252        token: u64,
253    },
254    FetchCatchUpEvents(Vec<Event>),
255    ProfileMetadataFetchFinished {
256        owner_pubkey_hex: String,
257        events: Vec<Event>,
258        error: Option<String>,
259    },
260    DirectChatCapabilityFetchFinished {
261        generation: u64,
262        token: u64,
263        owner_pubkey_hex: String,
264        result: Result<Vec<Event>, String>,
265    },
266    UserDiscoveryFetchFinished {
267        token: u64,
268        result: crate::core::UserDiscoveryFetchResult,
269    },
270    ProfileSearchRequested {
271        query: String,
272    },
273    ProfileSearchDebounceElapsed {
274        token: u64,
275        query: String,
276    },
277    ProfileSearchFetchFinished {
278        token: u64,
279        query: String,
280        result: Result<crate::core::ProfileSearchFetchResult, String>,
281    },
282    ProfileSearchCapabilitiesReady {
283        token: u64,
284        query: String,
285        events: Vec<Event>,
286    },
287    RelayStatusChanged {
288        relay_url: String,
289        status: RelayStatus,
290        generation: u64,
291    },
292    ProtocolSubscriptionReconcileCompleted {
293        generation: u64,
294        token: u64,
295        reason: String,
296        plan: Option<crate::core::ProtocolSubscriptionPlan>,
297        success: bool,
298        error: Option<String>,
299        relay_statuses: Vec<(String, RelayStatus)>,
300        connected_before: u64,
301        connected_after: u64,
302        filter_count: u64,
303    },
304    RelayTransportConnectionFinished {
305        token: u64,
306        reason: String,
307        relay_statuses: Vec<(String, RelayStatus)>,
308        connected_count: u64,
309    },
310    #[cfg(not(target_os = "ios"))]
311    DebugSnapshotWriteFinished {
312        generation: u64,
313    },
314    DebugLog {
315        category: String,
316        detail: String,
317    },
318    TypingIndicatorExpired {
319        chat_id: String,
320        author: String,
321    },
322    FlushPendingDeliveredReceipts {
323        token: u64,
324    },
325    RelayPublishDrainFinished {
326        token: u64,
327        results: Vec<RelayPublishDrainResult>,
328    },
329    RelayPublishDrainProgress {
330        token: u64,
331        result: RelayPublishDrainResult,
332    },
333    SessionStartupFollowUp,
334    RetryPendingRelayPublishes {
335        reason: String,
336    },
337    AttachmentReceiptsUpdated,
338    AttachmentUploadFinished {
339        store_id: u64,
340        chat_id: String,
341        result: Result<String, String>,
342    },
343    AttachmentUploadProgress {
344        store_id: u64,
345        bytes_uploaded: u64,
346        total_bytes: u64,
347    },
348    ProfilePictureUploadFinished {
349        result: Result<String, String>,
350    },
351    GroupPictureUploadFinished {
352        group_id: String,
353        result: Result<String, String>,
354    },
355    SyncComplete,
356    OwnerRegistrationLookupFinished {
357        generation: u64,
358        owner: nostr::PublicKey,
359        device: nostr::PublicKey,
360        completed: usize,
361        queried: usize,
362        events: Vec<Event>,
363    },
364    ProtocolAuthorBackfillComplete {
365        reason: String,
366    },
367    // Heavy tail of `open_chat` — DB page load, identity republish,
368    // persist, protocol refresh. Runs on the same event loop as a
369    // queued follow-up so subsequent UI actions (back, switch chat)
370    // can interleave between the screen flip and the load.
371    OpenChatFinalize {
372        chat_id: String,
373    },
374    DeviceSyncPacket {
375        source_pubkey_hex: String,
376        source_port: u16,
377        data: Vec<u8>,
378    },
379}
380
381#[derive(Debug)]
382pub(crate) struct RelayPublishDrainResult {
383    pub(crate) event_id: String,
384    pub(crate) success: bool,
385    pub(crate) relay_urls: Vec<String>,
386    pub(crate) detail: String,
387}