Gate Tor/Nostr start behind permissions or mutual favorites; add clean shutdown + robust gating (#619)

- Add NetworkActivationService to permit Tor/Nostr only when location is authorized OR at least one mutual favorite exists.
- Gate TorManager.startIfNeeded/ensureRunningOnForeground behind a global allowAutoStart flag.
- Always stop Tor on background for deterministic restarts; rebuild sessions on foreground when allowed.
- NostrRelayManager respects the gate in connect/ensureConnections/subscribe/send/connectToRelay and skips reconnection when disallowed.
- Symmetric shutdown when conditions become disallowed: disconnect relays and stop Tor.
- Fix double-start by avoiding restart if Tor is already ready; prevent background thrash.
- Improve UX: post "starting tor…" via TorWillStart, and "tor started…" on initial ready; keep existing restart messages.

Rationale: Avoid starting Tor/relays when the user has no location permission and no mutual favorites, and ensure a clean, predictable lifecycle (no stale sockets, no double starts).

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
jack
2025-09-15 16:46:49 +02:00
committed by GitHub
co-authored by jack
parent d1682db79b
commit 9cbdb0a764
6 changed files with 183 additions and 76 deletions
+17
View File
@@ -86,6 +86,8 @@ final class NostrRelayManager: ObservableObject {
/// Connect to all configured relays
func connect() {
// Global network policy gate
if !TorManager.shared.isAutoStartAllowed() { return }
// Ensure Tor is started early and wait for readiness off-main; then hop back to connect.
Task.detached {
let ready = await TorManager.shared.awaitReady()
@@ -117,6 +119,8 @@ final class NostrRelayManager: ObservableObject {
/// Ensure connections exist to the given relay URLs (idempotent).
func ensureConnections(to relayUrls: [String]) {
// Global network policy gate
if !TorManager.shared.isAutoStartAllowed() { return }
if TorManager.shared.torEnforced && !TorManager.shared.isReady {
// Defer until Tor is fully ready; avoid queuing connection attempts early
Task.detached { [weak self] in
@@ -139,6 +143,8 @@ final class NostrRelayManager: ObservableObject {
/// Send an event to specified relays (or all if none specified)
func sendEvent(_ event: NostrEvent, to relayUrls: [String]? = nil) {
// Global network policy gate
if !TorManager.shared.isAutoStartAllowed() { return }
if TorManager.shared.torEnforced && !TorManager.shared.isReady {
// Defer sends until Tor is ready to avoid premature queueing
Task.detached { [weak self] in
@@ -213,6 +219,8 @@ final class NostrRelayManager: ObservableObject {
handler: @escaping (NostrEvent) -> Void,
onEOSE: (() -> Void)? = nil
) {
// Global network policy gate
if !TorManager.shared.isAutoStartAllowed() { return }
// Coalesce rapid duplicate subscribe requests only if a handler already exists
let now = Date()
if messageHandlers[id] != nil {
@@ -317,6 +325,8 @@ final class NostrRelayManager: ObservableObject {
// MARK: - Private Methods
private func connectToRelay(_ urlString: String) {
// Global network policy gate
if !TorManager.shared.isAutoStartAllowed() { return }
guard let url = URL(string: urlString) else {
SecureLogger.warning("Invalid relay URL: \(urlString)", category: .session)
return
@@ -523,6 +533,13 @@ final class NostrRelayManager: ObservableObject {
}
private func handleDisconnection(relayUrl: String, error: Error) {
// If networking is disallowed, do not schedule reconnection
if !TorManager.shared.isAutoStartAllowed() {
connections.removeValue(forKey: relayUrl)
subscriptions.removeValue(forKey: relayUrl)
updateRelayStatus(relayUrl, isConnected: false, error: error)
return
}
connections.removeValue(forKey: relayUrl)
subscriptions.removeValue(forKey: relayUrl)
updateRelayStatus(relayUrl, isConnected: false, error: error)