summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjack <212554440+jackjackbits@users.noreply.github.com>2025-08-12 11:33:08 +0200
committerGitHub <noreply@github.com>2025-08-12 11:33:08 +0200
commit275f0ebaaf015db4d87e05f34cd53c5e7f3f3ab9 (patch)
tree6e08f96cdd5d12e60a9687479d299ad750e1623c
parent7a7c89e68993511510e693334313a027206c694a (diff)
Remove dead code and simplify codebase (#436)
* refactor: remove dead code and consolidate system messages - Delete 3 unused functions in ShareViewController (36 lines) - Extract addSystemMessage() helper to eliminate duplication (120+ lines) - Remove 22 'let _ =' wasteful computations across multiple files - Net reduction of 215 lines of dead/duplicate code - Improves maintainability and reduces technical debt * fix: remove remaining unused variables to eliminate compiler warnings - Remove unused senderNoiseKey in ChatViewModel - Remove unused lastSuccess variable in BluetoothMeshService - Eliminates all compiler warnings related to unused values * refactor: remove all dead legacy and migration code - Remove unused migrateSession() functions (never called in production) - NoiseSession.migrateSession() - 13 lines - NoiseEncryptionService.migratePeerSession() - 18 lines - Test for migration functionality - 17 lines - Remove unnecessary keychain cleanup code (no legacy data existed) - cleanupLegacyKeychainItems() - 62 lines - aggressiveCleanupLegacyItems() - 72 lines - resetCleanupFlag() - 4 lines - Simplified panic mode to just use deleteAllKeychainData() Total removed: 194 lines of dead/unnecessary code Analysis revealed: - KeychainManager introduced July 5, 2025 - Cleanup code added July 15, 2025 (10 days later) - Legacy service names were never used in production - Migration functions were incomplete implementation never called - Peer ID rotation remains active (not legacy) * Remove dead code and simplify codebase - Remove unused BinaryEncodable protocol and BinaryMessageType enum - Delete MockNoiseSession.swift (never used in tests) - Remove all relay detection code (hardcoded to false) - Removed isRelayConnected property from BitchatPeer - Removed relayConnected case from ConnectionState enum - Cleaned up relay-related UI indicators in ContentView - Removed relay status checks from ChatViewModel - Simplified peer connection logic by removing relay layer Total: 169 lines removed across 5 files --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
-rw-r--r--bitchat/Models/BitchatPeer.swift48
-rw-r--r--bitchat/Noise/NoiseSession.swift13
-rw-r--r--bitchat/Nostr/NostrProtocol.swift7
-rw-r--r--bitchat/Nostr/NostrRelayManager.swift7
-rw-r--r--bitchat/Protocols/BinaryEncodingUtils.swift15
-rw-r--r--bitchat/Services/BluetoothMeshService.swift13
-rw-r--r--bitchat/Services/KeychainManager.swift145
-rw-r--r--bitchat/Services/NoiseEncryptionService.swift18
-rw-r--r--bitchat/ViewModels/ChatViewModel.swift246
-rw-r--r--bitchat/Views/ContentView.swift15
-rw-r--r--bitchatShareExtension/ShareViewController.swift38
-rw-r--r--bitchatTests/Mocks/MockNoiseSession.swift101
-rw-r--r--bitchatTests/Noise/NoiseProtocolTests.swift17
13 files changed, 57 insertions, 626 deletions
diff --git a/bitchat/Models/BitchatPeer.swift b/bitchat/Models/BitchatPeer.swift
index b8b9f21..566bc73 100644
--- a/bitchat/Models/BitchatPeer.swift
+++ b/bitchat/Models/BitchatPeer.swift
@@ -18,7 +18,6 @@ struct BitchatPeer: Identifiable, Equatable {
// Connection state
enum ConnectionState {
case bluetoothConnected
- case relayConnected // Connected via mesh relay (another peer)
case nostrAvailable // Mutual favorite, reachable via Nostr
case offline // Not connected via any transport
}
@@ -26,8 +25,6 @@ struct BitchatPeer: Identifiable, Equatable {
var connectionState: ConnectionState {
if isConnected {
return .bluetoothConnected
- } else if isRelayConnected {
- return .relayConnected
} else if favoriteStatus?.isMutual == true {
// Mutual favorites can communicate via Nostr when offline
return .nostrAvailable
@@ -36,8 +33,6 @@ struct BitchatPeer: Identifiable, Equatable {
}
}
- var isRelayConnected: Bool = false // Set by PeerManager based on session state
-
var isFavorite: Bool {
favoriteStatus?.isFavorite ?? false
}
@@ -59,8 +54,6 @@ struct BitchatPeer: Identifiable, Equatable {
switch connectionState {
case .bluetoothConnected:
return "📻" // Radio icon for mesh connection
- case .relayConnected:
- return "🔗" // Chain link for relay connection
case .nostrAvailable:
return "🌐" // Purple globe for Nostr
case .offline:
@@ -78,15 +71,13 @@ struct BitchatPeer: Identifiable, Equatable {
noisePublicKey: Data,
nickname: String,
lastSeen: Date = Date(),
- isConnected: Bool = false,
- isRelayConnected: Bool = false
+ isConnected: Bool = false
) {
self.id = id
self.noisePublicKey = noisePublicKey
self.nickname = nickname
self.lastSeen = lastSeen
self.isConnected = isConnected
- self.isRelayConnected = isRelayConnected
// Load favorite status - will be set later by the manager
self.favoriteStatus = nil
@@ -156,28 +147,15 @@ class PeerManager: ObservableObject {
continue
}
- // Check if this peer is actually connected (not just known via relay)
+ // Check if this peer is actually connected
let isConnected = meshService.isPeerConnected(peerID)
- let isKnown = meshService.isPeerKnown(peerID)
- // In a mesh network, a peer can only be relay-connected if:
- // 1. We know about them (have received announce)
- // 2. We're not directly connected
- // 3. There are other peers that could relay (mesh peer count > 2)
- // For now, disable relay detection until we have proper relay tracking
- let isRelayConnected = false
-
- // Debug logging for relay connection detection
- if isKnown && !isConnected {
- SecureLogger.log("Peer \(nickname) (\(peerID)): isConnected=\(isConnected), isKnown=\(isKnown), isRelayConnected=\(isRelayConnected)",
- category: SecureLogger.session, level: .debug)
- }
// Skip disconnected peers unless they're favorites (handled later)
- if !isConnected && !isRelayConnected {
+ if !isConnected {
continue
}
- if isConnected || isRelayConnected {
+ if isConnected {
connectedNicknames.insert(nickname)
}
@@ -188,8 +166,7 @@ class PeerManager: ObservableObject {
id: peerID,
noisePublicKey: noiseKey,
nickname: nickname,
- isConnected: isConnected,
- isRelayConnected: isRelayConnected
+ isConnected: isConnected
)
// Set favorite status - check both by current noise key and by nickname
if let favoriteStatus = favoritesService.getFavoriteStatus(for: noiseKey) {
@@ -211,14 +188,14 @@ class PeerManager: ObservableObject {
allPeers.append(peer)
}
- // Add offline favorites (only those not currently connected/relay-connected AND that we actively favorite)
+ // Add offline favorites (only those not currently connected AND that we actively favorite)
for (favoriteKey, favorite) in favoritesService.favorites {
let favoriteID = favorite.peerNoisePublicKey.hexEncodedString()
- // Skip if this peer is already connected or relay-connected (by nickname)
+ // Skip if this peer is already connected (by nickname)
if connectedNicknames.contains(favorite.peerNickname) {
- SecureLogger.log(" - Skipping '\(favorite.peerNickname)' (key: \(favoriteKey.hexEncodedString())) - already connected/relay-connected",
+ SecureLogger.log(" - Skipping '\(favorite.peerNickname)' (key: \(favoriteKey.hexEncodedString())) - already connected",
category: SecureLogger.session, level: .debug)
continue
}
@@ -259,16 +236,12 @@ class PeerManager: ObservableObject {
!(peer.displayName == "Unknown" && peer.favoriteStatus == nil)
}
- // Sort: Connected first (direct then relay), then favorites, then alphabetical
+ // Sort: Connected first, then favorites, then alphabetical
allPeers.sort { lhs, rhs in
// Direct connections first
if lhs.isConnected != rhs.isConnected {
return lhs.isConnected
}
- // Then relay connections
- if lhs.isRelayConnected != rhs.isRelayConnected {
- return lhs.isRelayConnected
- }
// Then favorites
if lhs.isFavorite != rhs.isFavorite {
return lhs.isFavorite
@@ -321,13 +294,10 @@ class PeerManager: ObservableObject {
// Log each peer's status
for peer in allPeers {
- // Use the actual statusIcon from the peer which accounts for relay connections
let statusIcon: String
switch peer.connectionState {
case .bluetoothConnected:
statusIcon = "🟢"
- case .relayConnected:
- statusIcon = "🔗"
case .nostrAvailable:
statusIcon = "🌐"
case .offline:
diff --git a/bitchat/Noise/NoiseSession.swift b/bitchat/Noise/NoiseSession.swift
index bc327b5..005710a 100644
--- a/bitchat/Noise/NoiseSession.swift
+++ b/bitchat/Noise/NoiseSession.swift
@@ -296,19 +296,6 @@ class NoiseSessionManager {
}
}
- func migrateSession(from oldPeerID: String, to newPeerID: String) {
- managerQueue.sync(flags: .barrier) {
- // Check if we have a session for the old peer ID
- if let session = sessions[oldPeerID] {
- // Move the session to the new peer ID
- sessions[newPeerID] = session
- _ = sessions.removeValue(forKey: oldPeerID)
-
- SecureLogger.log("Migrated Noise session from \(oldPeerID) to \(newPeerID)", category: SecureLogger.noise, level: .info)
- }
- }
- }
-
func getEstablishedSessions() -> [String: NoiseSession] {
return managerQueue.sync {
return sessions.filter { $0.value.isEstablished() }
diff --git a/bitchat/Nostr/NostrProtocol.swift b/bitchat/Nostr/NostrProtocol.swift
index 068ce64..55880c5 100644
--- a/bitchat/Nostr/NostrProtocol.swift
+++ b/bitchat/Nostr/NostrProtocol.swift
@@ -37,7 +37,6 @@ struct NostrProtocol {
// 2. Create ephemeral key for this message
let ephemeralKey = try P256K.Schnorr.PrivateKey()
- let _ = Data(ephemeralKey.xonly.bytes).hexEncodedString()
// Created ephemeral key for seal
// 3. Seal the rumor (encrypt to recipient)
@@ -213,7 +212,6 @@ struct NostrProtocol {
throw NostrError.invalidPublicKey
}
- let _ = Data(senderKey.xonly.bytes).hexEncodedString()
// Encrypting message
// Derive shared secret
@@ -447,14 +445,11 @@ struct NostrProtocol {
// Log with explicit UTC and local time for debugging
let formatter = DateFormatter()
+ // Removed unnecessary date formatting operations
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.timeZone = TimeZone(abbreviation: "UTC")
- let _ = formatter.string(from: now)
- let _ = formatter.string(from: randomized)
formatter.timeZone = TimeZone.current
- let _ = formatter.string(from: now)
- let _ = formatter.string(from: randomized)
// Timestamp randomized for privacy
diff --git a/bitchat/Nostr/NostrRelayManager.swift b/bitchat/Nostr/NostrRelayManager.swift
index 351b95b..fa7c673 100644
--- a/bitchat/Nostr/NostrRelayManager.swift
+++ b/bitchat/Nostr/NostrRelayManager.swift
@@ -273,8 +273,7 @@ class NostrRelayManager: ObservableObject {
}
case "EOSE":
- if array.count >= 2,
- let _ = array[1] as? String {
+ if array.count >= 2 {
// End of stored events
}
@@ -290,8 +289,8 @@ class NostrRelayManager: ObservableObject {
}
case "NOTICE":
- if array.count >= 2,
- let _ = array[1] as? String {
+ if array.count >= 2 {
+ // Server notice received
}
default:
diff --git a/bitchat/Protocols/BinaryEncodingUtils.swift b/bitchat/Protocols/BinaryEncodingUtils.swift
index f6d4ca9..575d207 100644
--- a/bitchat/Protocols/BinaryEncodingUtils.swift
+++ b/bitchat/Protocols/BinaryEncodingUtils.swift
@@ -221,18 +221,3 @@ extension Data {
}
}
-// MARK: - Binary Message Protocol
-
-protocol BinaryEncodable {
- func toBinaryData() -> Data
- static func fromBinaryData(_ data: Data) -> Self?
-}
-
-// MARK: - Message Type Registry
-
-enum BinaryMessageType: UInt8 {
- case deliveryAck = 0x01
- case readReceipt = 0x02
- case noiseIdentityAnnouncement = 0x09
- case noiseMessage = 0x0A
-} \ No newline at end of file
diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift
index 409a16e..3ae0f6f 100644
--- a/bitchat/Services/BluetoothMeshService.swift
+++ b/bitchat/Services/BluetoothMeshService.swift
@@ -705,7 +705,6 @@ class BluetoothMeshService: NSObject {
// Update PeerSession
if let session = peerSessions[peerID] {
- let _ = session.isConnected
session.updateBluetoothConnection(peripheral: mapping.peripheral, characteristic: nil)
} else {
// This is a truly new peer session
@@ -2804,7 +2803,6 @@ class BluetoothMeshService: NSObject {
// Check if we have a recent entry
if let entry = protocolMessageDedup[key], !isExpired(entry) {
- let _ = Date().timeIntervalSince(entry.sentAt)
return true
}
@@ -3466,7 +3464,6 @@ class BluetoothMeshService: NSObject {
// Bloom filter will be reset by timer, processedMessages is now bounded
- // let _ = packet.senderID.hexEncodedString()
// Note: We'll decode messages in the switch statement below, not here
@@ -4548,7 +4545,6 @@ class BluetoothMeshService: NSObject {
if !isPeerIDOurs(senderID) {
// Check our current handshake state
- let _ = handshakeCoordinator.getHandshakeState(for: senderID)
// Processing handshake response
// Process the response - this could be message 2 or message 3 in the XX pattern
@@ -4734,7 +4730,6 @@ class BluetoothMeshService: NSObject {
}
}
- let _ = Double(fragments.count - 1) * delayBetweenFragments
}
private func handleFragment(_ packet: BitchatPacket, from peerID: String) {
@@ -5093,8 +5088,6 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
category: SecureLogger.session, level: .info)
// Log current peripheral mappings
- let _ = collectionsQueue.sync { peripheralMappings.count }
- let _ = connectionPool.count
peripheral.delegate = self
peripheral.discoverServices([BluetoothMeshService.serviceUUID])
@@ -5282,8 +5275,6 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
// Time tracking removed - now in PeerSession
// Time tracking removed - now in PeerSession
// Keep lastSuccessfulMessageTime to validate session on reconnect
- let lastSuccess = self.peerSessions[peerID]?.lastSuccessfulMessageTime ?? Date.distantPast
- let _ = Date().timeIntervalSince(lastSuccess)
// Keeping Noise session on disconnect
}
@@ -5500,7 +5491,6 @@ extension BluetoothMeshService: CBPeripheralDelegate {
// Use the sender ID from the packet, not our local mapping which might still be a temp ID
- let _ = connectedPeripherals.first(where: { $0.value == peripheral })?.key ?? "unknown"
let packetSenderID = packet.senderID.hexEncodedString()
// Log the packet type we received
@@ -6611,8 +6601,6 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
SecureLogger.logHandshake("processing \(isInitiation ? "init" : "response")", peerID: peerID, success: true)
// Get current handshake state before processing
- let _ = handshakeCoordinator.getHandshakeState(for: peerID)
- let _ = noiseService.hasEstablishedSession(with: peerID)
// Current handshake state check
// Check for duplicate handshake messages
@@ -7535,7 +7523,6 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
self.pendingPrivateMessages[recipientPeerID] = []
}
self.pendingPrivateMessages[recipientPeerID]?.append((content, recipientNickname, messageID ?? UUID().uuidString))
- let _ = self.pendingPrivateMessages[recipientPeerID]?.count ?? 0
// Queued private message
}
diff --git a/bitchat/Services/KeychainManager.swift b/bitchat/Services/KeychainManager.swift
index 5eee249..e863392 100644
--- a/bitchat/Services/KeychainManager.swift
+++ b/bitchat/Services/KeychainManager.swift
@@ -17,73 +17,7 @@ class KeychainManager {
private let service = "chat.bitchat"
private let appGroup = "group.chat.bitchat"
- private init() {
- // Clean up legacy keychain items on first run
- cleanupLegacyKeychainItems()
- }
-
- private func cleanupLegacyKeychainItems() {
- // Check if we've already done cleanup
- let cleanupKey = "bitchat.keychain.cleanup.v2"
- if UserDefaults.standard.bool(forKey: cleanupKey) {
- return
- }
-
-
- // List of old service names to migrate from
- let legacyServices = [
- "com.bitchat.passwords",
- "com.bitchat.deviceidentity",
- "com.bitchat.noise.identity",
- "chat.bitchat.passwords",
- "bitchat.keychain"
- ]
-
- var migratedItems = 0
-
- // Try to migrate identity keys
- for oldService in legacyServices {
- // Check for noise identity key
- let query: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecAttrService as String: oldService,
- kSecAttrAccount as String: "identity_noiseStaticKey",
- kSecReturnData as String: true
- ]
-
- var result: AnyObject?
- let status = SecItemCopyMatching(query as CFDictionary, &result)
-
- if status == errSecSuccess, let data = result as? Data {
- // Save to new service
- if saveIdentityKey(data, forKey: "noiseStaticKey") {
- migratedItems += 1
- SecureLogger.logKeyOperation("migrate", keyType: "noiseStaticKey", success: true)
- }
- // Delete from old service
- let deleteQuery: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecAttrService as String: oldService,
- kSecAttrAccount as String: "identity_noiseStaticKey"
- ]
- SecItemDelete(deleteQuery as CFDictionary)
- }
- }
-
- // Clean up all other legacy items
- for oldService in legacyServices {
- let deleteQuery: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecAttrService as String: oldService
- ]
-
- SecItemDelete(deleteQuery as CFDictionary)
- }
-
-
- // Mark cleanup as done
- UserDefaults.standard.set(true, forKey: cleanupKey)
- }
+ private init() {}
private func isSandboxed() -> Bool {
@@ -240,11 +174,6 @@ class KeychainManager {
return status == errSecSuccess || status == errSecItemNotFound
}
- // Force cleanup to run again (for development/testing)
- func resetCleanupFlag() {
- UserDefaults.standard.removeObject(forKey: "bitchat.keychain.cleanup.v2")
- }
-
// Delete ALL keychain data for panic mode
func deleteAllKeychainData() -> Bool {
@@ -384,76 +313,4 @@ class KeychainManager {
let key = "identity_noiseStaticKey"
return retrieveData(forKey: key) != nil
}
-
- // Aggressive cleanup for legacy items - can be called manually
- func aggressiveCleanupLegacyItems() -> Int {
- var deletedCount = 0
-
- // List of KNOWN bitchat service names from our development history
- let knownBitchatServices = [
- "com.bitchat.passwords",
- "com.bitchat.deviceidentity",
- "com.bitchat.noise.identity",
- "chat.bitchat.passwords",
- "bitchat.keychain",
- "Bitchat",
- "BitChat"
- ]
-
- // First, delete all items from known legacy services
- for legacyService in knownBitchatServices {
- let deleteQuery: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecAttrService as String: legacyService
- ]
-
- let status = SecItemDelete(deleteQuery as CFDictionary)
- if status == errSecSuccess {
- deletedCount += 1
- }
- }
-
- // Now search for items that have our specific account patterns with bitchat service names
- let searchQuery: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecMatchLimit as String: kSecMatchLimitAll,
- kSecReturnAttributes as String: true
- ]
-
- var result: AnyObject?
- let status = SecItemCopyMatching(searchQuery as CFDictionary, &result)
-
- if status == errSecSuccess, let items = result as? [[String: Any]] {
- for item in items {
- let account = item[kSecAttrAccount as String] as? String ?? ""
- let service = item[kSecAttrService as String] as? String ?? ""
-
- // ONLY delete if service name contains "bitchat" somewhere
- // This ensures we never touch other apps' keychain items
- var shouldDelete = false
-
- // Check if service contains "bitchat" (case insensitive) but NOT our current service
- let serviceLower = service.lowercased()
- if service != self.service && serviceLower.contains("bitchat") {
- shouldDelete = true
- }
-
- if shouldDelete {
- // Build precise delete query
- let deleteQuery: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecAttrService as String: service,
- kSecAttrAccount as String: account
- ]
-
- let deleteStatus = SecItemDelete(deleteQuery as CFDictionary)
- if deleteStatus == errSecSuccess {
- deletedCount += 1
- }
- }
- }
- }
-
- return deletedCount
- }
} \ No newline at end of file
diff --git a/bitchat/Services/NoiseEncryptionService.swift b/bitchat/Services/NoiseEncryptionService.swift
index 09c9fe7..5cd6ae3 100644
--- a/bitchat/Services/NoiseEncryptionService.swift
+++ b/bitchat/Services/NoiseEncryptionService.swift
@@ -419,24 +419,6 @@ class NoiseEncryptionService {
SecureLogger.logSecurityEvent(.sessionExpired(peerID: peerID))
}
- /// Migrate session when peer ID changes
- func migratePeerSession(from oldPeerID: String, to newPeerID: String, fingerprint: String) {
- // First update the fingerprint mappings
- serviceQueue.sync(flags: .barrier) {
- // Remove old mapping
- if let oldFingerprint = peerFingerprints[oldPeerID], oldFingerprint == fingerprint {
- peerFingerprints.removeValue(forKey: oldPeerID)
- }
-
- // Add new mapping
- peerFingerprints[newPeerID] = fingerprint
- fingerprintToPeerID[fingerprint] = newPeerID
- }
-
- // Migrate the session in session manager
- sessionManager.migrateSession(from: oldPeerID, to: newPeerID)
- }
-
// MARK: - Private Helpers
private func handleSessionEstablished(peerID: String, remoteStaticKey: Curve25519.KeyAgreement.PublicKey) {
diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift
index b8312a2..0a216dc 100644
--- a/bitchat/ViewModels/ChatViewModel.swift
+++ b/bitchat/ViewModels/ChatViewModel.swift
@@ -737,13 +737,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
// Check if the recipient is blocked
if isPeerBlocked(peerID) {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "cannot send message to \(recipientNickname): user is blocked.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("cannot send message to \(recipientNickname): user is blocked.")
return
}
@@ -803,13 +797,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
category: SecureLogger.session, level: .warning)
// Add system message to inform user
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "Cannot send message to \(recipientNickname) - peer is not reachable via mesh or Nostr.",
- timestamp: Date(),
- isRelay: false
- )
- addMessage(systemMessage)
+ addSystemMessage("Cannot send message to \(recipientNickname) - peer is not reachable via mesh or Nostr.")
}
}
@@ -864,27 +852,15 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
// Check if the peer is blocked
if isPeerBlocked(peerID) {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "cannot start chat with \(peerNickname): user is blocked.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("cannot start chat with \(peerNickname): user is blocked.")
return
}
// Check if this is a moon peer (we favorite them but they don't favorite us) AND they're offline
// Only require mutual favorites for offline Nostr messaging
if let peer = peerIndex[peerID],
- peer.isFavorite && !peer.theyFavoritedUs && !peer.isConnected && !peer.isRelayConnected {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "cannot start chat with \(peerNickname): mutual favorite required for offline messaging.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ peer.isFavorite && !peer.theyFavoritedUs && !peer.isConnected {
+ addSystemMessage("cannot start chat with \(peerNickname): mutual favorite required for offline messaging.")
return
}
@@ -1035,10 +1011,8 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
}
@objc private func handleDeliveryAcknowledgment(_ notification: Notification) {
- guard let messageId = notification.userInfo?["messageId"] as? String,
- let senderNoiseKey = notification.userInfo?["senderNoiseKey"] as? Data else { return }
+ guard let messageId = notification.userInfo?["messageId"] as? String else { return }
- let _ = senderNoiseKey.hexEncodedString()
// Update the delivery status for the message
@@ -1500,10 +1474,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
privateChats.removeAll()
unreadPrivateMessages.removeAll()
- // First run aggressive cleanup to get rid of all legacy items
- _ = KeychainManager.shared.aggressiveCleanupLegacyItems()
-
- // Then delete all current keychain data
+ // Delete all keychain data
_ = KeychainManager.shared.deleteAllKeychainData()
// Clear UserDefaults identity fallbacks
@@ -2323,54 +2294,24 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
let messageContent = parts[2...].joined(separator: " ")
sendPrivateMessage(messageContent, to: peerID)
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "started private chat with \(nickname)",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("started private chat with \(nickname)")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "user '\(nickname)' not found. they may be offline or using a different nickname.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("user '\(nickname)' not found. they may be offline or using a different nickname.")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "usage: /m @nickname [message] or /m nickname [message]",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("usage: /m @nickname [message] or /m nickname [message]")
}
case "/w":
let peerNicknames = meshService.getPeerNicknames()
if connectedPeers.isEmpty {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "no one else is online right now.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("no one else is online right now.")
} else {
let onlineList = connectedPeers.compactMap { peerID in
peerNicknames[peerID]
}.sorted().joined(separator: ", ")
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "online users: \(onlineList)",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("online users: \(onlineList)")
}
case "/clear":
// Clear messages based on current context
@@ -2493,13 +2434,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
if let fingerprintStr = meshService.getPeerFingerprint(peerID) {
if SecureIdentityStateManager.shared.isBlocked(fingerprint: fingerprintStr) {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "\(nickname) is already blocked.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("\(nickname) is already blocked.")
} else {
// Update or create social identity with blocked status
if var identity = SecureIdentityStateManager.shared.getSocialIdentity(for: fingerprintStr) {
@@ -2523,42 +2458,18 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
blockedUsers.insert(fingerprintStr)
favoritePeers.remove(fingerprintStr)
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "blocked \(nickname). you will no longer receive messages from them.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("blocked \(nickname). you will no longer receive messages from them.")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "cannot block \(nickname): unable to verify identity.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("cannot block \(nickname): unable to verify identity.")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "cannot block \(nickname): user not found.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("cannot block \(nickname): user not found.")
}
} else {
// List blocked users
if blockedUsers.isEmpty {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "no blocked peers.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("no blocked peers.")
} else {
// Find nicknames for blocked users
var blockedNicknames: [String] = []
@@ -2573,13 +2484,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
}
let blockedList = blockedNicknames.isEmpty ? "blocked peers (not currently online)" : blockedNicknames.sorted().joined(separator: ", ")
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "blocked peers: \(blockedList)",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("blocked peers: \(blockedList)")
}
}
@@ -2601,48 +2506,18 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
// Update local set for UI
blockedUsers.remove(fingerprintStr)
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "unblocked \(nickname).",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("unblocked \(nickname).")
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "\(nickname) is not blocked.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("\(nickname) is not blocked.")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "cannot unblock \(nickname): unable to verify identity.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("cannot unblock \(nickname): unable to verify identity.")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "cannot unblock \(nickname): user not found.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("cannot unblock \(nickname): user not found.")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "usage: /unblock <nickname>",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("usage: /unblock <nickname>")
}
case "/fav":
@@ -2671,31 +2546,13 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
try? await self?.messageRouter?.sendFavoriteNotification(to: noisePublicKey, isFavorite: true)
}
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "added \(nickname) to favorites.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("added \(nickname) to favorites.")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "can't find peer: \(nickname)",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("can't find peer: \(nickname)")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "usage: /fav <nickname>",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("usage: /fav <nickname>")
}
case "/unfav":
@@ -2718,41 +2575,17 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
try? await self?.messageRouter?.sendFavoriteNotification(to: noisePublicKey, isFavorite: false)
}
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "removed \(nickname) from favorites.",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("removed \(nickname) from favorites.")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "can't find peer: \(nickname)",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("can't find peer: \(nickname)")
}
} else {
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "usage: /unfav <nickname>",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("usage: /unfav <nickname>")
}
case "/testnostr":
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "testing nostr relay connectivity...",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("testing nostr relay connectivity...")
Task { @MainActor in
if let relayManager = self.nostrRelayManager {
@@ -2788,13 +2621,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
default:
// Unknown command
- let systemMessage = BitchatMessage(
- sender: "system",
- content: "unknown command: \(cmd).",
- timestamp: Date(),
- isRelay: false
- )
- messages.append(systemMessage)
+ addSystemMessage("unknown command: \(cmd).")
}
}
@@ -3367,4 +3194,15 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
}
+ // MARK: - Helper for System Messages
+ private func addSystemMessage(_ content: String, timestamp: Date = Date()) {
+ let systemMessage = BitchatMessage(
+ sender: "system",
+ content: content,
+ timestamp: timestamp,
+ isRelay: false
+ )
+ messages.append(systemMessage)
+ }
+
} // End of ChatViewModel class
diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift
index f70090f..abe3309 100644
--- a/bitchat/Views/ContentView.swift
+++ b/bitchat/Views/ContentView.swift
@@ -271,7 +271,6 @@ struct ContentView: View {
ForEach(windowedMessages, id: \.id) { message in
VStack(alignment: .leading, spacing: 0) {
// Check if current user is mentioned
- let _ = message.mentions?.contains(viewModel.nickname) ?? false
if message.sender == "system" {
// System messages
@@ -681,12 +680,6 @@ struct ContentView: View {
.font(.system(size: 10))
.foregroundColor(textColor)
.accessibilityLabel("Connected via mesh")
- case .relayConnected:
- // Chain link for relay connection
- Image(systemName: "link")
- .font(.system(size: 10))
- .foregroundColor(Color.blue)
- .accessibilityLabel("Connected via relay")
case .nostrAvailable:
// Purple globe for mutual favorites reachable via Nostr
Image(systemName: "globe")
@@ -902,7 +895,7 @@ struct ContentView: View {
let peerCounts = viewModel.allPeers.reduce(into: (others: 0, mesh: 0)) { counts, peer in
guard peer.id != viewModel.meshService.myPeerID else { return }
- let isMeshConnected = peer.isConnected || peer.isRelayConnected
+ let isMeshConnected = peer.isConnected
if isMeshConnected {
counts.mesh += 1
counts.others += 1
@@ -994,12 +987,6 @@ struct ContentView: View {
.font(.system(size: 14))
.foregroundColor(textColor)
.accessibilityLabel("Connected via mesh")
- case .relayConnected:
- // Chain link for relay connection
- Image(systemName: "link")
- .font(.system(size: 14))
- .foregroundColor(Color.blue)
- .accessibilityLabel("Connected via relay")
case .nostrAvailable:
// Purple globe for Nostr
Image(systemName: "globe")
diff --git a/bitchatShareExtension/ShareViewController.swift b/bitchatShareExtension/ShareViewController.swift
index 451f82c..65668cd 100644
--- a/bitchatShareExtension/ShareViewController.swift
+++ b/bitchatShareExtension/ShareViewController.swift
@@ -138,44 +138,6 @@ class ShareViewController: SLComposeServiceViewController {
// MARK: - Helper Methods
- private func handleSharedText(_ text: String) {
- // Save to shared user defaults to pass to main app
- saveToSharedDefaults(content: text, type: "text")
- openMainApp()
- }
-
- private func handleSharedURL(_ url: URL) {
- // Get the page title if available from the extension context
- var pageTitle: String? = nil
- if let item = extensionContext?.inputItems.first as? NSExtensionItem {
- pageTitle = item.attributedContentText?.string ?? item.attributedTitle?.string
- }
-
- // Create a structured format for URL sharing
- let urlData: [String: String] = [
- "url": url.absoluteString,
- "title": pageTitle ?? url.host ?? "Shared Link"
- ]
-
- // Convert to JSON string
- if let jsonData = try? JSONSerialization.data(withJSONObject: urlData),
- let jsonString = String(data: jsonData, encoding: .utf8) {
- saveToSharedDefaults(content: jsonString, type: "url")
- } else {
- // Fallback to simple URL
- saveToSharedDefaults(content: url.absoluteString, type: "url")
- }
-
- openMainApp()
- }
-
- private func handleSharedImage(_ image: UIImage) {
- // For now, we'll just notify that image sharing isn't supported
- // In the future, we could implement image sharing via the mesh
- saveToSharedDefaults(content: "Image sharing coming soon!", type: "image")
- openMainApp()
- }
-
private func saveToSharedDefaults(content: String, type: String) {
// Use app groups to share data between extension and main app
guard let userDefaults = UserDefaults(suiteName: "group.chat.bitchat") else {
diff --git a/bitchatTests/Mocks/MockNoiseSession.swift b/bitchatTests/Mocks/MockNoiseSession.swift
deleted file mode 100644
index 1dc2811..0000000
--- a/bitchatTests/Mocks/MockNoiseSession.swift
+++ /dev/null
@@ -1,101 +0,0 @@
-//
-// MockNoiseSession.swift
-// bitchatTests
-//
-// This is free and unencumbered software released into the public domain.
-// For more information, see <https://unlicense.org>
-//
-
-import Foundation
-import CryptoKit
-@testable import bitchat
-
-class MockNoiseSession: NoiseSession {
- var mockState: NoiseSessionState = .uninitialized
- var shouldFailHandshake = false
- var shouldFailEncryption = false
- var handshakeMessages: [Data] = []
- var encryptedData: [Data] = []
- var decryptedData: [Data] = []
-
- override func getState() -> NoiseSessionState {
- return mockState
- }
-
- override func isEstablished() -> Bool {
- return mockState == .established
- }
-
- override func startHandshake() throws -> Data {
- if shouldFailHandshake {
- mockState = .failed(NoiseSessionError.handshakeFailed(TestError.testFailure("Mock handshake failure")))
- throw NoiseSessionError.handshakeFailed(TestError.testFailure("Mock handshake failure"))
- }
-
- mockState = .handshaking
- let handshakeData = TestHelpers.generateRandomData(length: 32)
- handshakeMessages.append(handshakeData)
- return handshakeData
- }
-
- override func processHandshakeMessage(_ message: Data) throws -> Data? {
- if shouldFailHandshake {
- mockState = .failed(NoiseSessionError.handshakeFailed(TestError.testFailure("Mock handshake failure")))
- throw NoiseSessionError.handshakeFailed(TestError.testFailure("Mock handshake failure"))
- }
-
- handshakeMessages.append(message)
-
- // Simulate handshake completion after 2 messages
- if handshakeMessages.count >= 2 {
- mockState = .established
- return nil
- } else {
- let response = TestHelpers.generateRandomData(length: 48)
- handshakeMessages.append(response)
- return response
- }
- }
-
- override func encrypt(_ plaintext: Data) throws -> Data {
- if shouldFailEncryption {
- throw NoiseSessionError.notEstablished
- }
-
- guard mockState == .established else {
- throw NoiseSessionError.notEstablished
- }
-
- // Simple mock encryption: prepend magic bytes and append the data
- var encrypted = Data([0xDE, 0xAD, 0xBE, 0xEF])
- encrypted.append(plaintext)
- encryptedData.append(encrypted)
- return encrypted
- }
-
- override func decrypt(_ ciphertext: Data) throws -> Data {
- if shouldFailEncryption {
- throw NoiseSessionError.notEstablished
- }
-
- guard mockState == .established else {
- throw NoiseSessionError.notEstablished
- }
-
- // Simple mock decryption: remove magic bytes
- guard ciphertext.count > 4 else {
- throw TestError.testFailure("Invalid ciphertext")
- }
-
- let plaintext = ciphertext.dropFirst(4)
- decryptedData.append(plaintext)
- return plaintext
- }
-
- override func reset() {
- mockState = .uninitialized
- handshakeMessages.removeAll()
- encryptedData.removeAll()
- decryptedData.removeAll()
- }
-} \ No newline at end of file
diff --git a/bitchatTests/Noise/NoiseProtocolTests.swift b/bitchatTests/Noise/NoiseProtocolTests.swift
index f54275d..be82ac7 100644
--- a/bitchatTests/Noise/NoiseProtocolTests.swift
+++ b/bitchatTests/Noise/NoiseProtocolTests.swift
@@ -226,23 +226,6 @@ final class NoiseProtocolTests: XCTestCase {
XCTAssertEqual(decrypted, plaintext)
}
- func testSessionMigration() throws {
- let manager = NoiseSessionManager(localStaticKey: aliceKey)
-
- // Create and establish a session
- _ = try manager.initiateHandshake(with: TestConstants.testPeerID2)
-
- // Migrate to new peer ID
- let newPeerID = TestConstants.testPeerID3
- manager.migrateSession(from: TestConstants.testPeerID2, to: newPeerID)
-
- // Old peer ID should not have session
- XCTAssertNil(manager.getSession(for: TestConstants.testPeerID2))
-
- // New peer ID should have the session
- XCTAssertNotNil(manager.getSession(for: newPeerID))
- }
-
// MARK: - Security Tests
func testTamperedCiphertextDetection() throws {