This document describes the NostrRelayManager subsystem responsible for managing WebSocket connections to Nostr relays, handling subscriptions, routing messages, and implementing connection reliability strategies. This manager serves as the network layer for the Nostr transport, coordinating with the broader transport architecture.
The manager handles complex networking requirements including Tor integration via TorManager, geographic relay selection for location-based channels, and NIP-17 private message delivery via gift wrapping.
Sources: bitchat/Nostr/NostrRelayManager.swift105-108
The NostrRelayManager is a @MainActor singleton that manages the entire lifecycle of Nostr relay connections. It maintains a collection of relay states, active WebSocket tasks, subscription mappings, and message handlers.
Sources: bitchat/Nostr/NostrRelayManager.swift115-190 bitchat/Nostr/NostrRelayManager.swift55-72 bitchat/Nostr/NostrRelayManager.swift81-83
The NostrRelayManager implements a connection lifecycle with exponential backoff and permanent failure detection.
Backoff Configuration:
The manager uses parameters defined in TransportConfig:
initialBackoffInterval: bitchat/Nostr/NostrRelayManager.swift196maxBackoffInterval: bitchat/Nostr/NostrRelayManager.swift197backoffMultiplier: bitchat/Nostr/NostrRelayManager.swift198maxReconnectAttempts: bitchat/Nostr/NostrRelayManager.swift199Permanent failure logic: DNS errors (NSURLErrorCannotFindHost) or NSURLErrorBadServerResponse trigger immediate exhaustion of retry attempts to prevent hammering unreachable hosts. bitchat/Nostr/NostrRelayManager.swift766-783
Sources: bitchat/Nostr/NostrRelayManager.swift196-199 bitchat/Nostr/NostrRelayManager.swift531-598 bitchat/Nostr/NostrRelayManager.swift753-818
For geohash-based communication, BitChat prioritizes relays physically close to the user's geohash center to minimize latency and localize traffic.
The system uses a curated list of relays with GPS coordinates, often updated via CI workflows that fetch data from external repositories.
Implementation Details:
relays/online_relays_gps.csv. relays/online_relays_gps.csv1-183Fetch GeoRelays Data runs weekly to synchronize the local relay directory with the upstream permissionlesstech/georelays repository. .github/workflows/fetch_georelays.yml1-42wss://relay.damus.io, wss://nos.lol) specifically for NIP-17 gift-wrap delivery. bitchat/Nostr/NostrRelayManager.swift128-135Sources: relays/online_relays_gps.csv1-183 .github/workflows/fetch_georelays.yml1-42 bitchat/Nostr/NostrRelayManager.swift128-135
Subscriptions are managed via REQ messages. The manager tracks EOSE (End of Stored Events) to notify the application when historical data synchronization is complete.
subscribe(filter:id:relayUrls:handler:onEOSE:) is called. bitchat/Nostr/NostrRelayManager.swift364EOSETracker is initialized with the set of target relay URLs and a fallback timer (defined in TransportConfig.nostrSubscriptionEOSEFallbackSeconds) to ensure the UI isn't blocked by a slow relay. bitchat/Nostr/NostrRelayManager.swift423-441EOSE messages arrive, relays are removed from pendingRelays. When empty, the onEOSE callback fires. bitchat/Nostr/NostrRelayManager.swift674-684Sources: bitchat/Nostr/NostrRelayManager.swift364-454 bitchat/Nostr/NostrRelayManager.swift674-684
Messages sent to relays that are currently disconnected are stored in messageQueue. bitchat/Nostr/NostrRelayManager.swift190
sendEvent(_:to:) attempts immediate delivery and queues failures for later retry. bitchat/Nostr/NostrRelayManager.swift293-324flushMessageQueue(for:) is triggered upon successful connection to a relay. bitchat/Nostr/NostrRelayManager.swift327-362Private messages use NIP-17 gift wrapping (Kind 1059) to ensure metadata privacy.
registerPendingGiftWrap(id:) tracks outgoing wraps to handle relay OK acknowledgments. bitchat/Nostr/NostrRelayManager.swift111-113NostrProtocol.createPrivateMessage creates the rumor (Kind 14), seals it (Kind 13), and finally gift-wraps it (Kind 1059). bitchat/Nostr/NostrProtocol.swift30-69NostrEmbeddedBitChat encodes BitChat-specific packets (like private messages or read receipts) into the Nostr event content using base64url encoding. bitchat/Nostr/NostrEmbeddedBitChat.swift8-98Sources: bitchat/Nostr/NostrRelayManager.swift111-113 bitchat/Nostr/NostrProtocol.swift30-124 bitchat/Nostr/NostrEmbeddedBitChat.swift8-98 bitchatTests/NostrProtocolTests.swift16-53
The manager implements "fail-closed" security regarding Tor and network activation:
activationAllowed from NetworkActivationService. bitchat/Nostr/NostrRelayManager.swift78 bitchat/Nostr/NostrRelayManager.swift230TorManager.torEnforced. bitchat/Nostr/NostrRelayManager.swift84userTorEnabled is true, the manager uses TorURLSession.shared.session for all WebSocket connections. bitchat/Nostr/NostrRelayManager.swift95 bitchat/Nostr/NostrRelayManager.swift181awaitTorReady before initiating any WebSocket tasks. If Tor readiness fails, the manager retries up to TransportConfig.nostrTorReadyMaxWaitAttempts. bitchat/Nostr/NostrRelayManager.swift87-94 bitchat/Nostr/NostrRelayManager.swift548-559Sources: bitchat/Nostr/NostrRelayManager.swift78-95 bitchat/Nostr/NostrRelayManager.swift181-182 bitchat/Nostr/NostrRelayManager.swift548-559 bitchatTests/Services/NostrRelayManagerTests.swift47-61
Refresh this wiki
This wiki was recently refreshed. Please wait 6 days to refresh again.