Files
Barry Deen 28cb2dad42 feat(tor): embedded Tor with fail-closed routing for all connections
Add an embedded Tor daemon (kmp-tor) with an onion toggle in the sidebar
drawer and on the pre-login Splash/Auth screens. When enabled, every
connection — relay WebSockets, Coil images, ExoPlayer media, NIP-05/NIP-11,
LNURL/zaps, Blossom uploads, link previews, exchange rates, DM media, live
metrics — routes through Tor's SOCKS proxy.

Routing is fail-closed: from the moment the toggle flips, new clients point
at a dead loopback port until bootstrap completes, and existing clients have
their in-flight requests cancelled and keep-alive pools evicted so no
pre-toggle socket survives. OkHttp passes unresolved hostnames to SOCKS
proxies, so DNS also resolves inside Tor.

- TorManager: kmp-tor runtime in a foreground service; state machine
  (Off/Starting %/On/Stopping/Error) observed by all toggles; start retries
  around kmp-tor's fixed 1s service-bind timeout, which a busy main thread
  misses during cold start
- TorPreferences: device-level pref so the toggle works pre-login and
  survives logout
- HttpClientFactory: single proxy choke point; client registry +
  setTorSocks() invalidation; new getRelayClient/getLoopbackClient/
  getDmMediaClient
- Relay/RelayPool: clients resolved through providers on every (re)connect;
  suspendForTorSwitch/resumeAfterTorSwitch tear down sockets and suppress
  reconnects during bootstrap so the 5-min relay cooldown can't trip; local
  relay stays direct (Tor refuses loopback/LAN targets)
- Captured-client fixes so a mid-session toggle can't leak: Coil delegating
  Call.Factory (it caches its lambda forever), ZapSender, DmBubble,
  NwcRepository, SplashViewModel raw client, ExchangeRateRepository refetch
- Cold start with Tor on: relay init gated on bootstrap
- kmp-tor pinned to 2.4.x: 2.5+ needs Kotlin 2.2 metadata, unreadable by
  this project's Kotlin 2.0.21

Verified on-device by socket-inode audit: app process held 43 sockets, all
to 127.0.0.1:<socks>; only the tor child process held external connections.

Known gaps: Breez/Spark SDK (own Rust networking) and ML Kit model
downloads bypass OkHttp and are not routed.
2026-06-10 13:00:23 -04:00
..