Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions krillnotes-core/src/core/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,11 @@ pub enum KrillnotesError {
RelayAuthExpired(String),

/// Relay rate limit exceeded (HTTP 429).
#[error("Relay rate limited: {0}")]
RelayRateLimited(String),
#[error("Relay rate limited: {message}")]
RelayRateLimited {
message: String,
retry_after_secs: Option<u64>,
},

/// Relay resource not found or expired (HTTP 404/410).
#[error("Relay not found: {0}")]
Expand Down Expand Up @@ -240,7 +243,13 @@ impl KrillnotesError {
Self::Zip(e) => format!("Bundle archive error: {e}"),
Self::NotOwner => "Only the workspace owner can modify scripts".to_string(),
Self::RelayAuthExpired(_) => "Relay session expired. Please log in again.".to_string(),
Self::RelayRateLimited(_) => "Relay is rate limiting requests. Please try again later.".to_string(),
Self::RelayRateLimited { retry_after_secs, .. } => {
if let Some(secs) = retry_after_secs {
format!("Relay is rate limiting requests. Please try again in {secs} seconds.")
} else {
"Relay is rate limiting requests. Please try again later.".to_string()
}
}
Self::RelayNotFound(_) => "The requested relay resource was not found or has expired.".to_string(),
Self::RelayUnavailable(msg) => format!("Relay server unavailable: {msg}"),
Self::Permission(e) => format!("Permission denied: {e}"),
Expand Down
4 changes: 2 additions & 2 deletions krillnotes-core/src/core/swarm/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ pub fn generate_delta(
attachment_sizes.insert(attachment_id.clone(), size);
}
Err(e) => {
log::warn!(target: "krillnotes::sync",
"Could not query size for attachment {}, will skip blob: {e}",
log::debug!(target: "krillnotes::sync",
"attachment {} blob unavailable (note likely deleted), skipping sidecar: {e}",
attachment_id);
}
}
Expand Down
11 changes: 10 additions & 1 deletion krillnotes-core/src/core/sync/relay/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ impl RelayClient {

fn map_error(resp: reqwest::blocking::Response) -> KrillnotesError {
let status = resp.status().as_u16();
// Parse Retry-After header before consuming the response body.
let retry_after_secs = resp
.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok());
let body = resp.text().unwrap_or_default();
// Extract human-readable message from {"error":{"message":"..."}} envelope.
let message = Self::extract_error_message(&body).unwrap_or_else(|| {
Expand All @@ -273,7 +279,10 @@ impl RelayClient {
401 => KrillnotesError::RelayAuthExpired(message),
404 | 410 => KrillnotesError::RelayNotFound(message),
409 => KrillnotesError::RelayUnavailable(format!("HTTP 409: {message}")),
429 => KrillnotesError::RelayRateLimited(message),
429 => KrillnotesError::RelayRateLimited {
message,
retry_after_secs,
},
_ => KrillnotesError::RelayUnavailable(format!("HTTP {status}: {message}")),
}
}
Expand Down
17 changes: 11 additions & 6 deletions krillnotes-desktop/src-tauri/src/commands/receive_poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use crate::AppState;
use krillnotes_core::core::sync::relay::RelayClient;
use krillnotes_core::KrillnotesError;
use krillnotes_core::core::sync::SyncChannel;
use serde::Serialize;
use std::sync::Arc;
Expand Down Expand Up @@ -332,8 +333,15 @@ pub async fn poll_receive_workspace(
// List all pending relay bundles (single API call).
let all_bundles = match client.list_bundles(&device_id) {
Ok(b) => b,
Err(ref e @ KrillnotesError::RelayRateLimited { retry_after_secs, .. }) => {
log::debug!(
"poll_receive_workspace: list_bundles rate-limited, retry_after={:?}: {e}",
retry_after_secs
);
vec![]
}
Err(e) => {
log::warn!("poll_receive_workspace: list_bundles failed: {e}");
log::debug!("poll_receive_workspace: list_bundles skipped: {e}");
vec![]
}
};
Expand Down Expand Up @@ -863,7 +871,7 @@ pub async fn poll_all_identity_snapshots(
};

for uuid in identity_uuids {
// 1. Check relay accounts FIRST (needed for both discovery and polling).
// 1. Check relay accounts (needed for both discovery and invite polling).
let relay_accounts = {
let rams = state
.relay_account_managers
Expand All @@ -882,7 +890,6 @@ pub async fn poll_all_identity_snapshots(
// "Send to My Device" puts a snapshot bundle on the relay but the receiving
// device has no AcceptedInvite for it, so normal polling would never find it.
{
// Collect workspace_ids from ALL existing invites (any status).
let all_invite_ws_ids: std::collections::HashSet<String> = {
let aim = state
.accepted_invite_managers
Expand Down Expand Up @@ -940,7 +947,7 @@ pub async fn poll_all_identity_snapshots(
}
}
Err(e) => {
log::warn!("list_bundles for self-transfer discovery failed: {e}")
log::debug!("list_bundles for self-transfer discovery: {e}")
}
}
}
Expand All @@ -949,7 +956,6 @@ pub async fn poll_all_identity_snapshots(
.await
.map_err(|e| e.to_string())?;

// Create synthetic accepted invites for discovered self-transfers.
for (meta, bytes, relay_url) in self_transfers {
let snapshot_path =
std::env::temp_dir().join(format!("snapshot-{}.bin", Uuid::new_v4()));
Expand Down Expand Up @@ -1046,7 +1052,6 @@ pub async fn poll_all_identity_snapshots(
.await
.map_err(|e| e.to_string())??;

// Update accepted invites with snapshot paths and emit events.
for snapshot in &result.received_snapshots {
{
let mut aim = state
Expand Down
Loading