iris-chat 0.1.53

Iris Chat command line client and shared encrypted chat core
Documentation
fn handle_invite_device_command(cli: &CliApp, command: InviteDeviceTopCommands) -> Result<Value> {
    match command {
        InviteDeviceTopCommands::Invite(InviteCommands::Create { full }) => {
            cli.dispatch_and_wait(
                if full {
                    AppAction::CreateFullInvite
                } else {
                    AppAction::CreatePublicInvite
                },
                Duration::from_secs(40),
            )?;
            let state = cli.app.state();
            fail_on_toast(&state)?;
            let invite = state.public_invite.context("No invite was created.")?;
            Ok(json!({ "url": invite.url }))
        }
        InviteDeviceTopCommands::Invite(InviteCommands::Group {
            group,
            max_uses,
            expires_in,
        }) => {
            let group_id = group.strip_prefix("group:").unwrap_or(&group).to_string();
            cli.dispatch_and_wait(
                AppAction::PushScreen {
                    screen: iris_chat_core::Screen::GroupDetails {
                        group_id: group_id.clone(),
                    },
                },
                Duration::from_secs(3),
            )?;
            cli.dispatch_and_wait(
                AppAction::CreateGroupInvite {
                    group_id,
                    max_uses,
                    expires_in_seconds: (expires_in > 0).then_some(expires_in),
                },
                Duration::from_secs(40),
            )?;
            let state = cli.app.state();
            fail_on_toast(&state)?;
            let links = state.group_details.context("Group not found")?.invite_links;
            Ok(
                json!({"invites": links.iter().map(|i| json!({"id": i.id, "url": i.url, "uses": i.use_count, "max_uses": i.max_uses, "expires_at": i.expires_at})).collect::<Vec<_>>() }),
            )
        }
        InviteDeviceTopCommands::Invite(InviteCommands::Revoke { group, invite_id }) => {
            cli.dispatch_and_wait(
                AppAction::RevokeGroupInvite {
                    group_id: group.trim_start_matches("group:").to_string(),
                    invite_id,
                },
                Duration::from_secs(3),
            )?;
            fail_on_toast(&cli.app.state())?;
            Ok(json!({"revoked": true}))
        }
        InviteDeviceTopCommands::Invite(InviteCommands::Accept { invite }) => {
            cli.dispatch_and_wait(
                AppAction::AcceptInvite {
                    invite_input: invite,
                },
                Duration::from_secs(30),
            )?;
            let state = cli.app.state();
            let pending_group = matches!(
                state.toast.as_deref(),
                Some("Waiting for the group admin to connect…" | "Joining group…")
            );
            if !pending_group {
                fail_on_toast(&state)?;
            }
            Ok(json!({
                "pending_group_join": pending_group,
                "chats": chat_list_json(&state),
                "current_chat": state.current_chat.as_ref().map(chat_summary_json),
            }))
        }
        InviteDeviceTopCommands::Link(LinkCommands::Create) => {
            cli.dispatch_and_wait(
                AppAction::StartLinkedDevice {
                    owner_input: String::new(),
                },
                Duration::from_secs(3),
            )?;
            let state = cli.app.state();
            fail_on_toast(&state)?;
            let link = state.link_device.context("No link code was created.")?;
            Ok(json!({
                "url": link.url,
                "device_input": link.device_input,
            }))
        }
        InviteDeviceTopCommands::Link(LinkCommands::Accept {
            invite,
            include_message_history,
        }) => {
            let is_signer_link = invite.trim().starts_with("nostrconnect://");
            let previous_count = cli
                .app
                .state()
                .device_roster
                .map_or(0, |roster| roster.devices.len());
            cli.dispatch_and_wait(
                if include_message_history {
                    AppAction::AddAuthorizedDeviceWithHistory {
                        device_input: invite,
                        include_message_history: true,
                    }
                } else {
                    AppAction::AddAuthorizedDevice {
                        device_input: invite,
                    }
                },
                Duration::from_secs(4),
            )?;
            if is_signer_link {
                let deadline = Instant::now() + Duration::from_secs(180);
                loop {
                    let state = cli.app.state();
                    fail_on_toast_except(&state, &["Device added"])?;
                    if state
                        .device_roster
                        .is_some_and(|roster| roster.devices.len() > previous_count)
                    {
                        break;
                    }
                    anyhow::ensure!(
                        Instant::now() < deadline,
                        "Device linking timed out. Try again."
                    );
                    thread::sleep(Duration::from_millis(50));
                }
            }
            let state = cli.app.state();
            fail_on_toast_except(&state, &["Device added"])?;
            Ok(json!({
                "accepted": true,
                "device_roster": state.device_roster.map(|roster| {
                    json!({
                        "device_count": roster.devices.len(),
                        "devices": roster.devices.iter().map(|device| {
                            json!({
                                "device_id": device.device_pubkey_hex,
                                "device_npub": device.device_npub,
                                "current": device.is_current_device,
                                "authorized": device.is_authorized,
                                "stale": device.is_stale,
                            })
                        }).collect::<Vec<_>>(),
                    })
                }),
            }))
        }
    }
}