diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj index be216ccb..87958a08 100644 --- a/bitchat.xcodeproj/project.pbxproj +++ b/bitchat.xcodeproj/project.pbxproj @@ -1182,7 +1182,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.3.3; + MARKETING_VERSION = 1.3.4; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_NAME = bitchat; REGISTER_APP_GROUPS = YES; @@ -1271,7 +1271,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.3.3; + MARKETING_VERSION = 1.3.4; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_NAME = bitchat; REGISTER_APP_GROUPS = YES; diff --git a/bitchat/Nostr/NostrRelayManager.swift b/bitchat/Nostr/NostrRelayManager.swift index 86689cef..37517a9a 100644 --- a/bitchat/Nostr/NostrRelayManager.swift +++ b/bitchat/Nostr/NostrRelayManager.swift @@ -194,8 +194,9 @@ class NostrRelayManager: ObservableObject { // SecureLogger.log("📋 Subscription filter JSON: \(messageString.prefix(200))...", // category: SecureLogger.session, level: .debug) - // Target specific relays if provided; else all connections - let urls = relayUrls ?? Self.defaultRelays + // Target specific relays if provided; else default. Filter permanently failed relays. + let baseUrls = relayUrls ?? Self.defaultRelays + let urls = baseUrls.filter { !isPermanentlyFailed($0) } ensureConnections(to: urls) let targets: [(String, URLSessionWebSocketTask)] = urls.compactMap { url in connections[url].map { (url, $0) } @@ -278,6 +279,9 @@ class NostrRelayManager: ObservableObject { if connections[urlString] != nil { return } + if isPermanentlyFailed(urlString) { + return + } // Attempting to connect to Nostr relay via the proxied session @@ -483,18 +487,21 @@ class NostrRelayManager: ObservableObject { subscriptions.removeValue(forKey: relayUrl) updateRelayStatus(relayUrl, isConnected: false, error: error) - // Check if this is a DNS error + // Check if this is a DNS or handshake error; treat as permanent let errorDescription = error.localizedDescription.lowercased() + let ns = error as NSError if errorDescription.contains("hostname could not be found") || - errorDescription.contains("dns") { - // Only log once for DNS failures + errorDescription.contains("dns") || + (ns.domain == NSURLErrorDomain && ns.code == NSURLErrorBadServerResponse) { if relays.first(where: { $0.url == relayUrl })?.lastError == nil { - SecureLogger.log("Nostr relay DNS failure for \(relayUrl) - not retrying", category: SecureLogger.session, level: .warning) + SecureLogger.log("Nostr relay permanent failure for \(relayUrl) - not retrying (code=\(ns.code))", category: SecureLogger.session, level: .warning) } - // Mark relay as permanently failed if let index = relays.firstIndex(where: { $0.url == relayUrl }) { relays[index].lastError = error + relays[index].reconnectAttempts = maxReconnectAttempts + relays[index].nextReconnectTime = nil } + pendingSubscriptions[relayUrl] = nil return } @@ -575,6 +582,18 @@ class NostrRelayManager: ObservableObject { // Reconnect connect() } + + // MARK: - Failure classification + private func isPermanentlyFailed(_ url: String) -> Bool { + guard let r = relays.first(where: { $0.url == url }) else { return false } + if r.reconnectAttempts >= maxReconnectAttempts { return true } + if let ns = r.lastError as NSError?, ns.domain == NSURLErrorDomain { + if ns.code == NSURLErrorBadServerResponse || ns.code == NSURLErrorCannotFindHost { + return true + } + } + return false + } } // MARK: - Off-main inbound parsing helpers (file scope, non-isolated)