feat(01-01): expose last-seen, transport and display_name on PeerView

- Extend PeerView additively with last_seen_ms/transport/display_name
- Join EntitySnapshot peer rows onto stats.peer_meta by NodeAddr
- Default to 0/empty when no entity row exists yet for an address
- Reword a pre-existing doc comment to remove embedder-app naming
This commit is contained in:
Arjen
2026-08-04 18:14:40 +01:00
parent 9121925d4b
commit e68d69cae2
+50 -9
View File
@@ -32,7 +32,7 @@ use crate::node::context::NodeContext;
use crate::node::metrics::MetricsRegistry;
use super::protocol::{Request, Response};
use super::snapshot::{EntitySnapshot, RoutingSnapshot, StatsSnapshot};
use super::snapshot::{EntitySnapshot, PeerRow, RoutingSnapshot, StatsSnapshot};
/// Cloneable read-only view of node state for off-loop control serving.
///
@@ -110,6 +110,11 @@ impl ControlReadHandle {
/// A minimal, public view of one peer for embedders (e.g. an app UI), read
/// lock-free from the tick-published snapshot. See [`ControlReadHandle::peer_views`].
///
/// `last_seen_ms`, `transport` and `display_name` are additive fields, joined
/// in from the tick-published `EntitySnapshot`'s peer rows by `node_addr`;
/// they default to `0` / `""` / `""` when no entity row exists yet for a given
/// address (e.g. a peer known only from stats, not yet in the live table).
#[derive(Debug, Clone)]
pub struct PeerView {
/// The peer's `node_addr`, hex-encoded.
@@ -118,25 +123,61 @@ pub struct PeerView {
pub npub: String,
/// Whether the peer is currently in the live authenticated-peer table.
pub connected: bool,
/// Milliseconds-since-epoch this peer was last heard from, as captured in
/// the entity snapshot at publish time. `0` when no entity row exists yet.
pub last_seen_ms: u64,
/// Transport type currently carrying this peer's resolved link (e.g.
/// `"ble"`, `"udp"`, `"tcp"`). Empty when no entity row or no resolved
/// link exists yet.
pub transport: String,
/// Display name resolved for this peer at publish time. Empty when no
/// entity row exists yet.
pub display_name: String,
}
impl ControlReadHandle {
/// A lock-free snapshot of known peers (node_addr / npub / connected),
/// read from the tick-published stats snapshot.
/// A lock-free snapshot of known peers (node_addr / npub / connected /
/// last_seen_ms / transport / display_name), read from the
/// tick-published stats and entity snapshots.
///
/// Intended for embedders that run [`crate::Node::run_rx_loop`] on a
/// background task (so the node is exclusively borrowed there) and poll peer
/// state from a clone of this handle — the read touches only an `ArcSwap`
/// load, never the `Node`. See the Myco app for the reference embedding.
/// state from a clone of this handle — the read touches only `ArcSwap`
/// loads, never the `Node`. See the reference Android app embedding for
/// the intended usage.
pub fn peer_views(&self) -> Vec<PeerView> {
let entities = self.entities.load();
let entity_by_addr: std::collections::HashMap<crate::identity::NodeAddr, &PeerRow> =
entities
.peers
.iter()
.map(|row| (row.node_addr, row.as_ref()))
.collect();
self.stats
.load()
.peer_meta
.iter()
.map(|(addr, meta)| PeerView {
node_addr_hex: addr.to_string(),
npub: meta.npub.clone(),
connected: meta.is_active,
.map(|(addr, meta)| {
let (last_seen_ms, transport, display_name) = match entity_by_addr.get(addr) {
Some(row) => (
row.last_seen_ms,
row.link_info
.as_ref()
.and_then(|link| link.transport_type.clone())
.unwrap_or_default(),
row.display_name.clone(),
),
None => (0, String::new(), String::new()),
};
PeerView {
node_addr_hex: addr.to_string(),
npub: meta.npub.clone(),
connected: meta.is_active,
last_seen_ms,
transport,
display_name,
}
})
.collect()
}