fix(federation,cloud): dedup trusted nodes + chat contacts by onion; guard cloud my-folders (B1,B2,B4)

B1/B2: the same physical node can linger in the federation list under two
dids (e.g. after a did/key change). An onion is a node's unique stable
identity, so two entries with the same onion are one node. This showed the
node twice in the trusted-node list (B1) and as two mesh chat contacts —
one by name+logo, one by raw did (B2).
- storage::load_nodes now collapses same-onion entries (keep first, merge
  fips_npub/name/last_state) so every consumer (list + chat seed + sync)
  sees one entry per node.
- federation::sync merge_transitive_peers also matches by onion (not just
  did) so new transitive hints don't re-add a known node under a new did.
- mesh::seed_federation_peers_into_mesh skips already-seeded onions (belt
  and suspenders).
- Unit tests for dedup_nodes_by_onion (collapse + onion-suffix handling).

B4: filebrowser-client.listDirectory only checked res.ok before res.json(),
so when File Browser is absent (nginx serves the SPA index.html, 200) or
down (502) the JSON parse threw the opaque "Unexpected token '<'". Now it
checks the content-type and throws a friendly "File Browser is not
available" the Cloud view already renders as an empty state.

Verified: dedup unit tests 2/2; live .198 (15 entries→13 distinct onions)
restarted healthy on new binary; B4 guard present in built bundle + deployed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-15 12:29:12 -04:00
co-authored by Claude Opus 4.8
parent 1db720af13
commit ed4931064b
5 changed files with 135 additions and 8 deletions
+21
View File
@@ -145,6 +145,27 @@ async fn merge_transitive_peers(
}
continue;
}
// Same physical node advertised under a DIFFERENT did? Match on the
// onion (its stable network identity). Without this, a node that
// appears under two dids (e.g. after a key/did change) gets added
// twice — showing up duplicated in the trusted-node list (B1) and as
// two separate mesh chat contacts (B2). Merge into the existing entry.
let hint_onion = hint.onion.trim_end_matches(".onion");
if !hint_onion.is_empty() {
if let Some(existing) = nodes
.iter_mut()
.find(|n| n.onion.trim_end_matches(".onion") == hint_onion)
{
if existing.fips_npub.is_none() && hint.fips_npub.is_some() {
existing.fips_npub = hint.fips_npub.clone();
}
if existing.name.is_none() && hint.name.is_some() {
existing.name = hint.name.clone();
}
refreshed += 1;
continue;
}
}
nodes.push(FederatedNode {
did: hint.did.clone(),
pubkey: hint.pubkey.clone(),