Backend
- api/messages.rs covers send/pop/reply/status with an externally
tagged MessageDestination enum that matches the daemon's
{ip|pk: ...} body shape; pop_message uses an inflated request
timeout to outlast the long-poll window
- api/topics.rs implements default action, topic CRUD, sources
whitelist, and forward-socket get/set/remove. POST /topics ships
the raw base64 string as the body (not JSON); path segments are
percent-encoded inline (topics contain '/' and '+')
- api/pubkey.rs resolves an overlay IPv6 to a hex public key
- poller spawns a third long-poll loop on /messages?peek=false
that fans every inbound message into a 200-deep ring buffer and
emits messages://incoming for the UI
Frontend
- messages store: live inbox via the event, persisted outbox via
tauri-plugin-store keyed under outbox.json
- ComposeMessage form: ip/pk toggle, optional UTF-8 topic and
payload that get base64-encoded with a TextEncoder-based helper
- MessageList renders printable payloads decoded; binary payloads
fall back to a "(N bytes binary)" hint
- Topics view: split layout with whitelist on the left, per-topic
sources/forward editor on the right; default-action toggle is
surfaced at the top
73 lines
1.8 KiB
Rust
73 lines
1.8 KiB
Rust
pub mod admin;
|
|
pub mod messages;
|
|
pub mod peers;
|
|
pub mod pubkey;
|
|
pub mod routes;
|
|
pub mod topics;
|
|
|
|
use crate::error::{AppError, AppResult};
|
|
use reqwest::{Client, Response};
|
|
use serde::de::DeserializeOwned;
|
|
use std::time::Duration;
|
|
|
|
/// Thin REST client for the mycelium daemon's HTTP API.
|
|
///
|
|
/// The base URL is set after the sidecar reports ready; clients are cheap
|
|
/// to clone (the inner `reqwest::Client` keeps a shared connection pool).
|
|
#[derive(Debug, Clone)]
|
|
pub struct MyceliumClient {
|
|
base: String,
|
|
http: Client,
|
|
}
|
|
|
|
impl MyceliumClient {
|
|
pub fn new(base: impl Into<String>) -> Self {
|
|
let http = Client::builder()
|
|
.timeout(Duration::from_secs(10))
|
|
.build()
|
|
.expect("reqwest client build");
|
|
Self {
|
|
base: base.into(),
|
|
http,
|
|
}
|
|
}
|
|
|
|
pub fn base_url(&self) -> &str {
|
|
&self.base
|
|
}
|
|
|
|
pub(crate) fn url(&self, path: &str) -> String {
|
|
format!("{}/api/v1{}", self.base, path)
|
|
}
|
|
|
|
pub(crate) async fn parse<T: DeserializeOwned>(resp: Response) -> AppResult<T> {
|
|
let status = resp.status();
|
|
if status.is_success() {
|
|
resp.json::<T>().await.map_err(AppError::from)
|
|
} else {
|
|
let body = resp.text().await.unwrap_or_default();
|
|
Err(AppError::DaemonStatus {
|
|
status: status.as_u16(),
|
|
body,
|
|
})
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn check_status(resp: Response) -> AppResult<()> {
|
|
let status = resp.status();
|
|
if status.is_success() {
|
|
Ok(())
|
|
} else {
|
|
let body = resp.text().await.unwrap_or_default();
|
|
Err(AppError::DaemonStatus {
|
|
status: status.as_u16(),
|
|
body,
|
|
})
|
|
}
|
|
}
|
|
|
|
pub(crate) fn http(&self) -> &Client {
|
|
&self.http
|
|
}
|
|
}
|