fix: stop the device picker hanging on an offer

Two causes. The connect step had no timeout, so an unreachable device was
retried indefinitely instead of falling through to hold-for-later; it now
gives up after 15s and holds the offer as designed.

The picker also waited on the whole exchange, which includes a person on the
other device deciding — up to two minutes. It now closes on tap and reports
the outcome as a message, and a decline or an unanswered offer is shown as
information rather than an error, since the offer did arrive.
This commit is contained in:
2026-08-07 10:49:32 +02:00
parent 677fc3c6d5
commit 225ff9ad22
15 changed files with 139 additions and 8 deletions

View File

@@ -4,7 +4,7 @@
//! [`crate::pairing`]; this is where those meet the endpoint and the UniFFI
//! surface.
use std::sync::Arc;
use std::{sync::Arc, time::Duration};
use anyhow::{Context, Result};
use iroh::{EndpointAddr, EndpointId};
@@ -27,6 +27,12 @@ use crate::{
util::now_ms,
};
/// How long to wait for a device to answer before treating it as not running.
///
/// Without this an offline peer never fails, it just keeps being retried, and
/// the offer is never handed to the hold-for-later path.
const OFFER_CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
/// Whether a device may be polled again yet.
///
/// Split out because the surrounding call needs two live nodes to exercise,
@@ -419,9 +425,9 @@ impl CoreInner {
) -> Result<OfferResponse> {
let addr = self.contact_addr(endpoint_id).await?;
let client = OfferService::client(self.endpoint.clone(), addr);
let challenge = client
.request_challenge()
let challenge = tokio::time::timeout(OFFER_CONNECT_TIMEOUT, client.request_challenge())
.await
.map_err(|_| VnidropError::transfer(anyhow::anyhow!("device did not answer in time")))?
.context("device is not reachable")
.map_err(VnidropError::transfer)?;