iris-chat 0.1.54

Iris Chat command line client and shared encrypted chat core
Documentation
use crate::actions::AppAction;
use crate::state::{AppState, MutualGroupsSnapshot, PeerProfileDebugSnapshot};
use crate::DesktopNearbySnapshot;
use flume::Sender;
use nostr_sdk::prelude::{Event, RelayStatus};

#[derive(uniffi::Enum, Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum AppUpdate {
    CallMedia {
        call_id: String,
        kind: u8,
        sequence: u32,
        timestamp_us: u64,
        key_frame: bool,
        data: Vec<u8>,
    },
    FullState(AppState),
    PersistAccountBundle {
        rev: u64,
        owner_nsec: Option<String>,
        owner_pubkey_hex: String,
        device_nsec: String,
    },
    PersistPendingDeviceLink {
        device_nsec: String,
        approval_bootstrap_json: String,
    },
    ClearPendingDeviceLink,
    SignerLoginSignEvent {
        request_id: String,
        owner_pubkey_hex: String,
        unsigned_event_json: String,
    },
    NearbyPublishedEvent {
        event_id: String,
        kind: u32,
        created_at_secs: u64,
        event_json: String,
    },
    NearbyPeersChanged {
        snapshot: DesktopNearbySnapshot,
        bluetooth_peer_ids: Vec<String>,
        lan_peer_ids: Vec<String>,
    },
}

pub(crate) fn enqueue_update_for_delivery(
    update: AppUpdate,
    latest_full_state: &mut Option<AppUpdate>,
    before_full_state: &mut Vec<AppUpdate>,
    after_full_state: &mut Vec<AppUpdate>,
) {
    match update {
        full @ AppUpdate::FullState(_) => *latest_full_state = Some(full),
        nearby @ (AppUpdate::CallMedia { .. }
        | AppUpdate::NearbyPublishedEvent { .. }
        | AppUpdate::NearbyPeersChanged { .. }) => after_full_state.push(nearby),
        other => before_full_state.push(other),
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct FipsNearbyLinkSnapshot {
    pub(crate) device_pubkey_hex: String,
    pub(crate) transport_type: String,
    pub(crate) transport_addr: Option<String>,
}

#[derive(Debug)]
pub(crate) enum CoreMsg {
    Action(AppAction),
    AcceptDirectFiles {
        chat_id: String,
        transfer_id: String,
        destination: std::sync::Arc<dyn crate::DirectFileDestination>,
    },
    Internal(Box<InternalEvent>),
    ExportSupportBundle(Sender<String>),
    PeerProfileDebug {
        owner_input: String,
        reply_tx: Sender<Option<PeerProfileDebugSnapshot>>,
    },
    MutualGroups {
        owner_input: String,
        reply_tx: Sender<MutualGroupsSnapshot>,
    },
    AttachHostBle {
        attachment: crate::core::HostBleAttachment,
        reply_tx: Sender<Result<(), String>>,
    },
    DetachHostBle {
        ownership: crate::core::HostBleOwnership,
        reply_tx: Sender<()>,
    },
    HostBleActivity {
        ownership: crate::core::HostBleOwnership,
        generation: u64,
        active: bool,
        reply_tx: Sender<bool>,
    },
    PrepareForSuspend(Sender<()>),
    /// Snapshot of core-internal perf counters (debug-snapshot
    /// rebuild count etc.) — read by `FfiApp::core_perf_counters()`
    /// so the release-gate budget tests can assert on internal hot
    /// loops, not just FFI surface traffic.
    CorePerfCounters(Sender<CorePerfCountersSnapshot>),
    Shutdown(Option<Sender<()>>),
    #[cfg(test)]
    PanicForTest,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct CorePerfCountersSnapshot {
    /// `build_runtime_debug_snapshot` calls since core start.
    pub debug_snapshot_builds: u64,
}

#[derive(Debug)]
pub(crate) enum InternalEvent {
    PrivateInviteUploaded {
        request_id: String,
        owner: String,
        device: String,
        result: Result<String, String>,
    },
    PrivateInviteResolved {
        request_id: String,
        owner: String,
        result: Result<crate::invite_record::InviteRecord, String>,
    },
    DeviceLinkSignerRepair {
        token: String,
        heads: Vec<Event>,
        reply: tokio::sync::oneshot::Sender<Result<Event, String>>,
    },
    DeviceLinkSignerRequest {
        token: String,
        unsigned_event_json: String,
        previous: Option<Event>,
        reply: tokio::sync::oneshot::Sender<
            Result<(Event, crate::core::device_link_signer::DeviceLinkInfo), String>,
        >,
    },
    DeviceLinkSignerFinished {
        token: String,
        success: bool,
        error: Option<String>,
    },
    PrivateContactSyncTick {
        generation: u64,
    },
    FollowUpdateReady {
        request_id: String,
        result: Result<Vec<Event>, String>,
    },
    RemoteSignerProgress {
        token: String,
        phase: crate::RemoteSignerPhase,
        auth_url: Option<String>,
    },
    RemoteSignerConnected {
        token: String,
        owner_pubkey_hex: String,
    },
    RemoteSignerSigned {
        token: String,
        request_id: String,
        signed_event_json: String,
        link_info: Option<crate::core::device_link_signer::DeviceLinkInfo>,
    },
    RemoteSignerFailed {
        token: String,
        message: String,
    },
    DirectFilesPrepared {
        generation: u64,
        device: String,
        result: Result<crate::core::direct_files::Record, String>,
    },
    DirectFile {
        generation: u64,
        event: crate::core::direct_file_tcp::DirectFileEvent,
    },
    CallPacket {
        source_pubkey_hex: String,
        source_port: u16,
        data: Vec<u8>,
    },
    CallMediaBatch {
        packets: Vec<(String, u16, Vec<u8>)>,
        received_at: std::time::Instant,
        // Released on consumption or discard, including during shutdown.
        _permit: tokio::sync::OwnedSemaphorePermit,
    },
    CallRecoveryTick {
        call_id: String,
    },
    CallTick {
        call_id: String,
    },
    SignerLoginFetched {
        request_id: String,
        result: Result<Option<Event>, String>,
    },
    SignerLoginPublished {
        request_id: String,
        result: Result<Event, String>,
    },
    SignerLoginTimedOut {
        request_id: String,
    },
    RelayEvent(Event),
    MeshEvent(Event),
    FipsNearbyPacket {
        source_pubkey_hex: String,
        source_port: u16,
        data: Vec<u8>,
    },
    FipsNearbyPeersChanged {
        generation: u64,
        peers: Vec<FipsNearbyLinkSnapshot>,
    },
    FipsPeerDiscoveryNeeded {
        generation: u64,
        author: nostr::PublicKey,
        requires_pending_work: bool,
    },
    FetchTrackedPeerCatchUp {
        token: u64,
    },
    ProtocolSubscriptionLivenessCheck {
        token: u64,
    },
    RetryReadyProtocolWork {
        due_at: tokio::time::Instant,
    },
    PollPendingDeviceInvites {
        token: u64,
    },
    PollPendingDeviceLink {
        token: u64,
    },
    PendingDeviceLinkRefreshFinished {
        token: u64,
        events: Vec<Event>,
    },
    ExpireChatMutes {
        token: u64,
    },
    PruneExpiredMessages {
        token: u64,
    },
    FetchCatchUpEvents(Vec<Event>),
    ProfileMetadataFetchFinished {
        owner_pubkey_hex: String,
        events: Vec<Event>,
        error: Option<String>,
    },
    DirectChatCapabilityFetchFinished {
        generation: u64,
        token: u64,
        owner_pubkey_hex: String,
        result: Result<Vec<Event>, String>,
    },
    UserDiscoveryFetchFinished {
        token: u64,
        result: crate::core::UserDiscoveryFetchResult,
    },
    ProfileSearchRequested {
        query: String,
    },
    ProfileSearchDebounceElapsed {
        token: u64,
        query: String,
    },
    ProfileSearchFetchFinished {
        token: u64,
        query: String,
        result: Result<crate::core::ProfileSearchFetchResult, String>,
    },
    ProfileSearchCapabilitiesReady {
        token: u64,
        query: String,
        events: Vec<Event>,
    },
    RelayStatusChanged {
        relay_url: String,
        status: RelayStatus,
        generation: u64,
    },
    ProtocolSubscriptionReconcileCompleted {
        generation: u64,
        token: u64,
        reason: String,
        plan: Option<crate::core::ProtocolSubscriptionPlan>,
        success: bool,
        error: Option<String>,
        relay_statuses: Vec<(String, RelayStatus)>,
        connected_before: u64,
        connected_after: u64,
        filter_count: u64,
    },
    RelayTransportConnectionFinished {
        token: u64,
        reason: String,
        relay_statuses: Vec<(String, RelayStatus)>,
        connected_count: u64,
    },
    #[cfg(not(target_os = "ios"))]
    DebugSnapshotWriteFinished {
        generation: u64,
    },
    DebugLog {
        category: String,
        detail: String,
    },
    TypingIndicatorExpired {
        chat_id: String,
        author: String,
    },
    FlushPendingDeliveredReceipts {
        token: u64,
    },
    RelayPublishDrainFinished {
        token: u64,
        results: Vec<RelayPublishDrainResult>,
    },
    RelayPublishDrainProgress {
        token: u64,
        result: RelayPublishDrainResult,
    },
    SessionStartupFollowUp,
    RetryPendingRelayPublishes {
        reason: String,
    },
    AttachmentReceiptsUpdated,
    AttachmentUploadFinished {
        store_id: u64,
        chat_id: String,
        result: Result<String, String>,
    },
    AttachmentUploadProgress {
        store_id: u64,
        bytes_uploaded: u64,
        total_bytes: u64,
    },
    ProfilePictureUploadFinished {
        result: Result<String, String>,
    },
    GroupPictureUploadFinished {
        group_id: String,
        result: Result<String, String>,
    },
    SyncComplete,
    OwnerRegistrationLookupFinished {
        generation: u64,
        owner: nostr::PublicKey,
        device: nostr::PublicKey,
        completed: usize,
        queried: usize,
        events: Vec<Event>,
    },
    ProtocolAuthorBackfillComplete {
        reason: String,
    },
    // Heavy tail of `open_chat` — DB page load, identity republish,
    // persist, protocol refresh. Runs on the same event loop as a
    // queued follow-up so subsequent UI actions (back, switch chat)
    // can interleave between the screen flip and the load.
    OpenChatFinalize {
        chat_id: String,
    },
    DeviceSyncPacket {
        source_pubkey_hex: String,
        source_port: u16,
        data: Vec<u8>,
    },
}

#[derive(Debug)]
pub(crate) struct RelayPublishDrainResult {
    pub(crate) event_id: String,
    pub(crate) success: bool,
    pub(crate) relay_urls: Vec<String>,
    pub(crate) detail: String,
}